From d71a36364c2c7ba6c6792212b1b3f5ac6f6916d9 Mon Sep 17 00:00:00 2001 From: cdj <45139296+DejinChen@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:54:58 +0800 Subject: [PATCH] Auto-commissioner: support secondary network interface commissioning (#33801) * Auto-commissioner: support secondary network interface commissioning * Fix code-wifi-thread command and reset trying secondary network flag * Revert server-side changes * Added write request cancel function --- .../chip-tool/commands/pairing/Commands.h | 9 +++ .../commands/pairing/PairingCommand.cpp | 4 ++ .../commands/pairing/PairingCommand.h | 6 ++ src/controller/AutoCommissioner.cpp | 59 +++++++++++-------- src/controller/AutoCommissioner.h | 16 +++++ src/controller/CHIPDeviceController.cpp | 47 +++++++++++++++ src/controller/CHIPDeviceController.h | 7 +++ src/controller/CommissioningDelegate.cpp | 6 ++ src/controller/CommissioningDelegate.h | 3 + src/controller/WriteInteraction.h | 18 +++++- 10 files changed, 151 insertions(+), 24 deletions(-) diff --git a/examples/chip-tool/commands/pairing/Commands.h b/examples/chip-tool/commands/pairing/Commands.h index 388dc4741ffe30..41a35bb6bae6ad 100644 --- a/examples/chip-tool/commands/pairing/Commands.h +++ b/examples/chip-tool/commands/pairing/Commands.h @@ -69,6 +69,14 @@ class PairCodeThread : public PairingCommand {} }; +class PairCodeWiFiThread : public PairingCommand +{ +public: + PairCodeWiFiThread(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("code-wifi-thread", PairingMode::Code, PairingNetworkType::WiFiOrThread, credsIssuerConfig) + {} +}; + class PairOnNetwork : public PairingCommand { public: @@ -231,6 +239,7 @@ void registerCommandsPairing(Commands & commands, CredentialIssuerCommands * cre make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index 76c8ac8a58cc19..a0437c53f56a7d 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.cpp +++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp @@ -111,6 +111,10 @@ CommissioningParameters PairingCommand::GetCommissioningParameters() case PairingNetworkType::Thread: params.SetThreadOperationalDataset(mOperationalDataset); break; + case PairingNetworkType::WiFiOrThread: + params.SetWiFiCredentials(Controller::WiFiCredentials(mSSID, mPassword)); + params.SetThreadOperationalDataset(mOperationalDataset); + break; case PairingNetworkType::None: break; } diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index aaa8dc714e1017..9ff63e37878576 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.h +++ b/examples/chip-tool/commands/pairing/PairingCommand.h @@ -44,6 +44,7 @@ enum class PairingNetworkType None, WiFi, Thread, + WiFiOrThread, }; class PairingCommand : public CHIPCommand, @@ -85,6 +86,11 @@ class PairingCommand : public CHIPCommand, case PairingNetworkType::Thread: AddArgument("operationalDataset", &mOperationalDataset); break; + case PairingNetworkType::WiFiOrThread: + AddArgument("ssid", &mSSID); + AddArgument("password", &mPassword); + AddArgument("operationalDataset", &mOperationalDataset); + break; } switch (mode) diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp index a72543853fa2d1..d6bc9f5c79fcac 100644 --- a/src/controller/AutoCommissioner.cpp +++ b/src/controller/AutoCommissioner.cpp @@ -298,6 +298,19 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStage(CommissioningStag CommissioningStage AutoCommissioner::GetNextCommissioningStageNetworkSetup(CommissioningStage currentStage, CHIP_ERROR & lastErr) { + if (IsSecondaryNetworkSupported()) + { + if (TryingSecondaryNetwork()) + { + // Try secondary network interface. + return mDeviceCommissioningInfo.network.wifi.endpoint == kRootEndpointId ? CommissioningStage::kThreadNetworkSetup + : CommissioningStage::kWiFiNetworkSetup; + } + // Try primary network interface + return mDeviceCommissioningInfo.network.wifi.endpoint == kRootEndpointId ? CommissioningStage::kWiFiNetworkSetup + : CommissioningStage::kThreadNetworkSetup; + } + if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId) { return CommissioningStage::kWiFiNetworkSetup; @@ -455,35 +468,15 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio case CommissioningStage::kNeedsNetworkCreds: return GetNextCommissioningStageNetworkSetup(currentStage, lastErr); case CommissioningStage::kWiFiNetworkSetup: - if (mParams.GetThreadOperationalDataset().HasValue() && - mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId) - { - return CommissioningStage::kThreadNetworkSetup; - } - else - { - return CommissioningStage::kFailsafeBeforeWiFiEnable; - } + return CommissioningStage::kFailsafeBeforeWiFiEnable; case CommissioningStage::kThreadNetworkSetup: - if (mParams.GetWiFiCredentials().HasValue() && mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId) - { - return CommissioningStage::kFailsafeBeforeWiFiEnable; - } - else - { - return CommissioningStage::kFailsafeBeforeThreadEnable; - } + return CommissioningStage::kFailsafeBeforeThreadEnable; case CommissioningStage::kFailsafeBeforeWiFiEnable: return CommissioningStage::kWiFiNetworkEnable; case CommissioningStage::kFailsafeBeforeThreadEnable: return CommissioningStage::kThreadNetworkEnable; case CommissioningStage::kWiFiNetworkEnable: - if (mParams.GetThreadOperationalDataset().HasValue() && - mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId) - { - return CommissioningStage::kThreadNetworkEnable; - } - else if (mParams.GetSkipCommissioningComplete().ValueOr(false)) + if (mParams.GetSkipCommissioningComplete().ValueOr(false)) { SetCASEFailsafeTimerIfNeeded(); return CommissioningStage::kCleanup; @@ -502,6 +495,10 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio return CommissioningStage::kEvictPreviousCaseSessions; case CommissioningStage::kEvictPreviousCaseSessions: return CommissioningStage::kFindOperationalForStayActive; + case CommissioningStage::kPrimaryOperationalNetworkFailed: + return CommissioningStage::kDisablePrimaryNetworkInterface; + case CommissioningStage::kDisablePrimaryNetworkInterface: + return GetNextCommissioningStageNetworkSetup(currentStage, lastErr); case CommissioningStage::kFindOperationalForStayActive: return CommissioningStage::kICDSendStayActive; case CommissioningStage::kICDSendStayActive: @@ -564,6 +561,8 @@ EndpointId AutoCommissioner::GetEndpoint(const CommissioningStage & stage) const case CommissioningStage::kThreadNetworkSetup: case CommissioningStage::kThreadNetworkEnable: return mDeviceCommissioningInfo.network.thread.endpoint; + case CommissioningStage::kDisablePrimaryNetworkInterface: + return kRootEndpointId; default: return kRootEndpointId; } @@ -729,6 +728,16 @@ CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, Commissio report.stageCompleted = CommissioningStage::kScanNetworks; } } + + if (err != CHIP_NO_ERROR && IsSecondaryNetworkSupported() && !TryingSecondaryNetwork() && + completionStatus.failedStage.HasValue() && completionStatus.failedStage.Value() >= kWiFiNetworkSetup && + completionStatus.failedStage.Value() <= kICDSendStayActive) + { + // Primary network failed, disable primary network interface and try secondary network interface. + TrySecondaryNetwork(); + err = CHIP_NO_ERROR; + report.stageCompleted = CommissioningStage::kPrimaryOperationalNetworkFailed; + } } else { @@ -847,6 +856,10 @@ CHIP_ERROR AutoCommissioner::CommissioningStepFinished(CHIP_ERROR err, Commissio mOperationalDeviceProxy = report.Get().operationalProxy; break; case CommissioningStage::kCleanup: + if (IsSecondaryNetworkSupported() && TryingSecondaryNetwork()) + { + ResetTryingSecondaryNetwork(); + } ReleasePAI(); ReleaseDAC(); mCommissioneeDeviceProxy = nullptr; diff --git a/src/controller/AutoCommissioner.h b/src/controller/AutoCommissioner.h index 3399e2776946de..deacda6e3cca31 100644 --- a/src/controller/AutoCommissioner.h +++ b/src/controller/AutoCommissioner.h @@ -94,6 +94,22 @@ class AutoCommissioner : public CommissioningDelegate mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId)); }; + // Helper function to Determine whether secondary network interface is supported. + // Only true if information is provided for both networks, and the target has endpoint + // for wifi and thread. + bool IsSecondaryNetworkSupported() const + { + return ((mParams.GetSupportsConcurrentConnection().ValueOr(false) && mParams.GetWiFiCredentials().HasValue() && + mDeviceCommissioningInfo.network.wifi.endpoint != kInvalidEndpointId) && + mParams.GetThreadOperationalDataset().HasValue() && + mDeviceCommissioningInfo.network.thread.endpoint != kInvalidEndpointId); + } + + void TrySecondaryNetwork() { mTryingSecondaryNetwork = true; } + bool TryingSecondaryNetwork() const { return mTryingSecondaryNetwork; } + void ResetTryingSecondaryNetwork() { mTryingSecondaryNetwork = false; } + bool mTryingSecondaryNetwork = false; + bool mStopCommissioning = false; DeviceCommissioner * mCommissioner = nullptr; diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 21cd71e2932b0a..444b48bcf12b49 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1028,6 +1029,12 @@ void DeviceCommissioner::CancelCommissioningInteractions() mInvokeCancelFn(); mInvokeCancelFn = nullptr; } + if (mWriteCancelFn) + { + ChipLogDetail(Controller, "Cancelling write request for step '%s'", StageToString(mCommissioningStage)); + mWriteCancelFn(); + mWriteCancelFn = nullptr; + } if (mOnDeviceConnectedCallback.IsRegistered()) { ChipLogDetail(Controller, "Cancelling CASE setup for step '%s'", StageToString(mCommissioningStage)); @@ -1800,6 +1807,12 @@ void DeviceCommissioner::OnBasicSuccess(void * context, const chip::app::DataMod commissioner->CommissioningStageComplete(CHIP_NO_ERROR); } +void DeviceCommissioner::OnInterfaceEnableWriteSuccessResponse(void * context) +{ + DeviceCommissioner * commissioner = static_cast(context); + commissioner->CommissioningStageComplete(CHIP_NO_ERROR); +} + void DeviceCommissioner::OnBasicFailure(void * context, CHIP_ERROR error) { ChipLogProgress(Controller, "Received failure response %s\n", chip::ErrorStr(error)); @@ -1971,6 +1984,7 @@ void DeviceCommissioner::CommissioningStageComplete(CHIP_ERROR err, Commissionin DeviceProxy * proxy = mDeviceBeingCommissioned; mDeviceBeingCommissioned = nullptr; mInvokeCancelFn = nullptr; + mWriteCancelFn = nullptr; if (mPairingDelegate != nullptr) { @@ -2739,6 +2753,20 @@ DeviceCommissioner::SendCommissioningCommand(DeviceProxy * device, const Request onFailureCb, NullOptional, timeout, (!fireAndForget) ? &mInvokeCancelFn : nullptr); } +template +CHIP_ERROR DeviceCommissioner::SendCommissioningWriteRequest(DeviceProxy * device, EndpointId endpoint, ClusterId cluster, + AttributeId attribute, const AttrType & requestData, + WriteResponseSuccessCallback successCb, + WriteResponseFailureCallback failureCb) +{ + VerifyOrDie(!mWriteCancelFn); // we don't make parallel (cancellable) calls + auto onSuccessCb = [this, successCb](const app::ConcreteAttributePath & aPath) { successCb(this); }; + auto onFailureCb = [this, failureCb](const app::ConcreteAttributePath * aPath, CHIP_ERROR aError) { failureCb(this, aError); }; + return WriteAttribute(device->GetSecureSession().Value(), endpoint, cluster, attribute, requestData, onSuccessCb, onFailureCb, + /* aTimedWriteTimeoutMs = */ NullOptional, /* onDoneCb = */ nullptr, /* aDataVersion = */ NullOptional, + /* outCancelFn = */ &mWriteCancelFn); +} + void DeviceCommissioner::SendCommissioningReadRequest(DeviceProxy * proxy, Optional timeout, app::AttributePathParams * readPaths, size_t readPathsSize) { @@ -3423,6 +3451,25 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio ); } break; + case CommissioningStage::kPrimaryOperationalNetworkFailed: { + // nothing to do. This stage indicates that the primary operational network failed and the network interface should be + // disabled later. + break; + } + case CommissioningStage::kDisablePrimaryNetworkInterface: { + NetworkCommissioning::Attributes::InterfaceEnabled::TypeInfo::Type request = false; + CHIP_ERROR err = SendCommissioningWriteRequest(proxy, endpoint, NetworkCommissioning::Id, + NetworkCommissioning::Attributes::InterfaceEnabled::Id, request, + OnInterfaceEnableWriteSuccessResponse, OnBasicFailure); + if (err != CHIP_NO_ERROR) + { + // We won't get any async callbacks here, so just complete our stage. + ChipLogError(Controller, "Failed to send InterfaceEnabled write request: %" CHIP_ERROR_FORMAT, err.Format()); + CommissioningStageComplete(err); + return; + } + break; + } case CommissioningStage::kICDSendStayActive: { if (!(params.GetICDStayActiveDurationMsec().HasValue())) { diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index e08c6483710ed2..1ab85ca0dc182b 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -830,6 +830,7 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, CommissioningStage mCommissioningStage = CommissioningStage::kSecurePairing; bool mRunCommissioningAfterConnection = false; Internal::InvokeCancelFn mInvokeCancelFn; + Internal::WriteCancelFn mWriteCancelFn; ObjectPool mCommissioneeDevicePool; @@ -973,6 +974,8 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, OnICDManagementStayActiveResponse(void * context, const app::Clusters::IcdManagement::Commands::StayActiveResponse::DecodableType & data); + static void OnInterfaceEnableWriteSuccessResponse(void * context); + /** * @brief * This function processes the CSR sent by the device. @@ -1025,6 +1028,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, Optional timeout = NullOptional, bool fireAndForget = false); void SendCommissioningReadRequest(DeviceProxy * proxy, Optional timeout, app::AttributePathParams * readPaths, size_t readPathsSize); + template + CHIP_ERROR SendCommissioningWriteRequest(DeviceProxy * device, EndpointId endpoint, ClusterId cluster, AttributeId attribute, + const AttrType & requestData, WriteResponseSuccessCallback successCb, + WriteResponseFailureCallback failureCb); void CancelCommissioningInteractions(); void CancelCASECallbacks(); diff --git a/src/controller/CommissioningDelegate.cpp b/src/controller/CommissioningDelegate.cpp index 30f469932a800a..85ea5e86c5e3a6 100644 --- a/src/controller/CommissioningDelegate.cpp +++ b/src/controller/CommissioningDelegate.cpp @@ -136,6 +136,12 @@ const char * StageToString(CommissioningStage stage) case kNeedsNetworkCreds: return "NeedsNetworkCreds"; + case kPrimaryOperationalNetworkFailed: + return "PrimaryOperationalNetworkFailed"; + + case kDisablePrimaryNetworkInterface: + return "DisablePrimaryNetworkInterface"; + default: return "???"; } diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 57d6a0c9a8da53..99f00f109d4ff0 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -79,6 +79,9 @@ enum CommissioningStage : uint8_t /// Call CHIPDeviceController::NetworkCredentialsReady() when CommissioningParameters is populated with /// network credentials to use in kWiFiNetworkSetup or kThreadNetworkSetup steps. kNeedsNetworkCreds, + kPrimaryOperationalNetworkFailed, ///< Indicate that the primary operational network (on root endpoint) failed, should disable + ///< the primary network interface later. + kDisablePrimaryNetworkInterface, ///< Send InterfaceEnabled write request to the device to disable network interface. }; enum class ICDRegistrationStrategy : uint8_t diff --git a/src/controller/WriteInteraction.h b/src/controller/WriteInteraction.h index 19e383d738efe8..de95e45f6c100c 100644 --- a/src/controller/WriteInteraction.h +++ b/src/controller/WriteInteraction.h @@ -23,11 +23,17 @@ #include #include #include +#include #include namespace chip { namespace Controller { +namespace Internal { +// WriteCancelFn functions on WriteAttribute() are for internal use only. +typedef std::function WriteCancelFn; +} // namespace Internal + /* * An adapter callback that permits applications to provide std::function callbacks for success, error and on done. * This permits a slightly more flexible programming model that allows applications to pass in lambdas and bound member functions @@ -130,7 +136,8 @@ CHIP_ERROR WriteAttribute(const SessionHandle & sessionHandle, chip::EndpointId AttributeId attributeId, const AttrType & requestData, WriteCallback::OnSuccessCallbackType onSuccessCb, WriteCallback::OnErrorCallbackType onErrorCb, const Optional & aTimedWriteTimeoutMs, WriteCallback::OnDoneCallbackType onDoneCb = nullptr, - const Optional & aDataVersion = NullOptional) + const Optional & aDataVersion = NullOptional, + Internal::WriteCancelFn * outCancelFn = nullptr) { auto callback = Platform::MakeUnique(onSuccessCb, onErrorCb, onDoneCb, sessionHandle->IsGroupSession()); VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); @@ -151,6 +158,15 @@ CHIP_ERROR WriteAttribute(const SessionHandle & sessionHandle, chip::EndpointId ReturnErrorOnFailure(client->SendWriteRequest(sessionHandle)); + // If requested by the caller, provide a way to cancel the write interaction. + if (outCancelFn != nullptr) + { + *outCancelFn = [rawCallback = callback.get(), rawClient = client.get()]() { + chip::Platform::Delete(rawClient); + chip::Platform::Delete(rawCallback); + }; + } + // At this point the handle will ensure our callback's OnDone is always // called. client.release();