diff --git a/src/app/clusters/ota-provider/ota-provider.cpp b/src/app/clusters/ota-provider/ota-provider.cpp index bc5b364512fa7d..cbee7aa4ac283b 100644 --- a/src/app/clusters/ota-provider/ota-provider.cpp +++ b/src/app/clusters/ota-provider/ota-provider.cpp @@ -121,7 +121,7 @@ bool emberAfOtaSoftwareUpdateProviderClusterNotifyUpdateAppliedCallback( EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; OTAProviderDelegate * delegate = GetDelegate(endpoint); - ChipLogDetail(Zcl, "OTA Provider received NotifyUpdateUpplied"); + ChipLogDetail(Zcl, "OTA Provider received NotifyUpdateApplied"); if (SendStatusIfDelegateNull(endpoint)) { diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 1e23db274e6fb6..616e259069ecfc 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -129,7 +129,7 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse if (err != CHIP_NO_ERROR) { - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kQuerying, err); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, err); return; } @@ -153,7 +153,7 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); break; default: - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kQuerying, CHIP_ERROR_BAD_REQUEST); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); break; } @@ -165,7 +165,7 @@ void OTARequestor::OnQueryImageFailure(void * context, EmberAfStatus status) VerifyOrDie(requestorCore != nullptr); ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kQuerying, CHIP_ERROR_BAD_REQUEST); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); } @@ -198,7 +198,19 @@ void OTARequestor::OnApplyUpdateFailure(void * context, EmberAfStatus status) VerifyOrDie(requestorCore != nullptr); ChipLogDetail(SoftwareUpdate, "ApplyUpdate failure response %" PRIu8, status); - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kApplying, CHIP_ERROR_BAD_REQUEST); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kApplying, CHIP_ERROR_BAD_REQUEST); + SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); +} + +void OTARequestor::OnNotifyUpdateAppliedResponse(void * context, const app::DataModel::NullObjectType & response) {} + +void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, EmberAfStatus status) +{ + OTARequestor * requestorCore = static_cast(context); + VerifyOrDie(requestorCore != nullptr); + + ChipLogDetail(SoftwareUpdate, "NotifyUpdateApplied failure response %" PRIu8, status); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kNotifying, CHIP_ERROR_BAD_REQUEST); SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); } @@ -287,7 +299,7 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr if (err != CHIP_NO_ERROR) { ChipLogError(SoftwareUpdate, "Failed to send QueryImage command: %" CHIP_ERROR_FORMAT, err.Format()); - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kQuerying, err); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, err); return; } @@ -300,7 +312,7 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr if (err != CHIP_NO_ERROR) { ChipLogError(SoftwareUpdate, "Failed to start download: %" CHIP_ERROR_FORMAT, err.Format()); - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kDownloading, err); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kDownloading, err); SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); return; } @@ -314,7 +326,7 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr if (err != CHIP_NO_ERROR) { ChipLogError(SoftwareUpdate, "Failed to send ApplyUpdate command: %" CHIP_ERROR_FORMAT, err.Format()); - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kApplying, err); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kApplying, err); SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); return; } @@ -322,6 +334,20 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr SetUpdateStateAttribute(OTAUpdateStateEnum::kApplying); break; } + case kNotifyUpdateApplied: { + CHIP_ERROR err = requestorCore->SendNotifyUpdateAppliedRequest(*deviceProxy); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Failed to send NotifyUpdateApplied command: %" CHIP_ERROR_FORMAT, err.Format()); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kNotifying, err); + SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + return; + } + + SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + break; + } default: break; } @@ -339,13 +365,13 @@ void OTARequestor::OnConnectionFailure(void * context, PeerId peerId, CHIP_ERROR switch (requestorCore->mOnConnectedAction) { case kQueryImage: - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kQuerying, error); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, error); break; case kStartBDX: - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kDownloading, error); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kDownloading, error); break; case kApplyUpdate: - requestorCore->mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kApplying, error); + requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kApplying, error); break; default: break; @@ -376,6 +402,11 @@ void OTARequestor::ApplyUpdate() ConnectToProvider(kApplyUpdate); } +void OTARequestor::NotifyUpdateApplied() +{ + ConnectToProvider(kNotifyUpdateApplied); +} + void OTARequestor::OnDownloadStateChanged(OTADownloader::State state) { VerifyOrReturn(mOtaRequestorDriver != nullptr); @@ -386,7 +417,7 @@ void OTARequestor::OnDownloadStateChanged(OTADownloader::State state) mOtaRequestorDriver->UpdateDownloaded(); break; case OTADownloader::State::kIdle: - mOtaRequestorDriver->HandleError(OTAUpdateStateEnum::kDownloading, CHIP_ERROR_CONNECTION_ABORTED); + mOtaRequestorDriver->HandleError(UpdateFailureState::kDownloading, CHIP_ERROR_CONNECTION_ABORTED); break; default: break; @@ -398,6 +429,23 @@ void OTARequestor::OnUpdateProgressChanged(uint8_t percent) OtaRequestorServerSetUpdateStateProgress(percent); } +CHIP_ERROR OTARequestor::GenerateUpdateToken() +{ + if (mUpdateToken.empty()) + { + VerifyOrReturnError(mServer != nullptr, CHIP_ERROR_INCORRECT_STATE); + + FabricInfo * fabricInfo = mServer->GetFabricTable().FindFabricWithIndex(mProviderFabricIndex); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); + + static_assert(sizeof(NodeId) == sizeof(uint64_t), "Unexpected NodeId size"); + Encoding::BigEndian::Put64(mUpdateTokenBuffer, fabricInfo->GetPeerId().GetNodeId()); + mUpdateToken = ByteSpan(mUpdateTokenBuffer, sizeof(NodeId)); + } + + return CHIP_NO_ERROR; +} + CHIP_ERROR OTARequestor::SendQueryImageRequest(OperationalDeviceProxy & deviceProxy) { constexpr OTADownloadProtocol kProtocolsSupported[] = { OTADownloadProtocol::kBDXSynchronous }; @@ -488,19 +536,7 @@ CHIP_ERROR OTARequestor::StartDownload(OperationalDeviceProxy & deviceProxy) CHIP_ERROR OTARequestor::SendApplyUpdateRequest(OperationalDeviceProxy & deviceProxy) { - if (mUpdateToken.empty()) - { - // OTA Requestor shall use its node ID as the update token in case the original update - // token, received in QueryImageResponse, got lost. - VerifyOrReturnError(mServer != nullptr, CHIP_ERROR_INCORRECT_STATE); - - FabricInfo * fabricInfo = mServer->GetFabricTable().FindFabricWithIndex(mProviderFabricIndex); - VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); - - static_assert(sizeof(NodeId) == sizeof(uint64_t), "Unexpected NodeId size"); - Encoding::BigEndian::Put64(mUpdateTokenBuffer, fabricInfo->GetPeerId().GetNodeId()); - mUpdateToken = ByteSpan(mUpdateTokenBuffer, sizeof(NodeId)); - } + ReturnErrorOnFailure(GenerateUpdateToken()); ApplyUpdateRequest::Type args; args.updateToken = mUpdateToken; @@ -512,4 +548,18 @@ CHIP_ERROR OTARequestor::SendApplyUpdateRequest(OperationalDeviceProxy & deviceP return cluster.InvokeCommand(args, this, OnApplyUpdateResponse, OnApplyUpdateFailure); } +CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy & deviceProxy) +{ + ReturnErrorOnFailure(GenerateUpdateToken()); + + NotifyUpdateApplied::Type args; + args.updateToken = mUpdateToken; + args.softwareVersion = mUpdateVersion; + + Controller::OtaSoftwareUpdateProviderCluster cluster; + cluster.Associate(&deviceProxy, mProviderEndpointId); + + return cluster.InvokeCommand(args, this, OnNotifyUpdateAppliedResponse, OnNotifyUpdateAppliedFailure); +} + } // namespace chip diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h index ee5184ffaa4d76..fc70e83fab975c 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -43,6 +43,7 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe kQueryImage = 0, kStartBDX, kApplyUpdate, + kNotifyUpdateApplied, }; OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {} @@ -62,6 +63,9 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe // Send ApplyImage void ApplyUpdate() override; + // Send NotifyUpdateApplied + void NotifyUpdateApplied() override; + //////////// BDXDownloader::StateDelegate Implementation /////////////// void OnDownloadStateChanged(OTADownloader::State state) override; void OnUpdateProgressChanged(uint8_t percent) override; @@ -188,6 +192,11 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe chip::BDXDownloader * mDownloader; }; + /** + * Generate an update token using the operational node ID in case of token lost, received in QueryImageResponse + */ + CHIP_ERROR GenerateUpdateToken(); + /** * Send QueryImage request using values matching Basic cluster */ @@ -208,6 +217,11 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe */ CHIP_ERROR SendApplyUpdateRequest(OperationalDeviceProxy & deviceProxy); + /** + * Send NotifyUpdateApplied request + */ + CHIP_ERROR SendNotifyUpdateAppliedRequest(OperationalDeviceProxy & deviceProxy); + /** * Session connection callbacks */ @@ -228,6 +242,12 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe static void OnApplyUpdateResponse(void * context, const ApplyUpdateResponseDecodableType & response); static void OnApplyUpdateFailure(void * context, EmberAfStatus); + /** + * NotifyUpdateApplied callbacks + */ + static void OnNotifyUpdateAppliedResponse(void * context, const app::DataModel::NullObjectType & response); + static void OnNotifyUpdateAppliedFailure(void * context, EmberAfStatus); + OTARequestorDriver * mOtaRequestorDriver = nullptr; NodeId mProviderNodeId = kUndefinedNodeId; FabricIndex mProviderFabricIndex = kUndefinedFabricIndex; diff --git a/src/include/platform/OTARequestorDriver.h b/src/include/platform/OTARequestorDriver.h index 5bfb7be664e645..5a2e2f152c0ead 100644 --- a/src/include/platform/OTARequestorDriver.h +++ b/src/include/platform/OTARequestorDriver.h @@ -40,6 +40,15 @@ struct UpdateDescription ByteSpan metadataForRequestor; }; +enum class UpdateFailureState +{ + kQuerying, + kDownloading, + kApplying, + kNotifying, + kAwaitingNextAction, +}; + enum class UpdateNotFoundReason { Busy, @@ -61,7 +70,7 @@ class OTARequestorDriver virtual uint16_t GetMaxDownloadBlockSize() { return 1024; } /// Called when an error occurs at any OTA requestor operation - virtual void HandleError(app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum state, CHIP_ERROR error) = 0; + virtual void HandleError(UpdateFailureState state, CHIP_ERROR error) = 0; /// Called when the latest query found a software update virtual void UpdateAvailable(const UpdateDescription & update, System::Clock::Seconds32 delay) = 0; diff --git a/src/include/platform/OTARequestorInterface.h b/src/include/platform/OTARequestorInterface.h index b629c67fc85916..7825eb1fde35c8 100644 --- a/src/include/platform/OTARequestorInterface.h +++ b/src/include/platform/OTARequestorInterface.h @@ -64,6 +64,9 @@ class OTARequestorInterface // Send ApplyImage command virtual void ApplyUpdate() = 0; + // Send NotifyUpdateApplied command + virtual void NotifyUpdateApplied() = 0; + // Manually set OTA Provider parameters virtual void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) = 0; }; diff --git a/src/lib/shell/commands/Ota.cpp b/src/lib/shell/commands/Ota.cpp index 3f2067f44e860c..3d0d7ba64338c5 100644 --- a/src/lib/shell/commands/Ota.cpp +++ b/src/lib/shell/commands/Ota.cpp @@ -59,6 +59,20 @@ CHIP_ERROR ApplyImageHandler(int argc, char ** argv) return CHIP_NO_ERROR; } +CHIP_ERROR NotifyImageHandler(int argc, char ** argv) +{ + VerifyOrReturnError(GetRequestorInstance() != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(argc == 3, CHIP_ERROR_INVALID_ARGUMENT); + + const FabricIndex fabricIndex = static_cast(strtoul(argv[0], nullptr, 10)); + const NodeId providerNodeId = static_cast(strtoull(argv[1], nullptr, 10)); + const EndpointId providerEndpointId = static_cast(strtoul(argv[2], nullptr, 10)); + + GetRequestorInstance()->TestModeSetProviderParameters(providerNodeId, fabricIndex, providerEndpointId); + PlatformMgr().ScheduleWork([](intptr_t) { GetRequestorInstance()->NotifyUpdateApplied(); }); + return CHIP_NO_ERROR; +} + CHIP_ERROR OtaHandler(int argc, char ** argv) { if (argc == 0) @@ -85,6 +99,8 @@ void RegisterOtaCommands() { &QueryImageHandler, "query", "Query for a new image. Usage: ota query " }, { &ApplyImageHandler, "apply", "Apply the current update. Usage ota apply " }, + { &NotifyImageHandler, "notify", + "Notify the new image has been applied. Usage: ota notify " }, }; sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); diff --git a/src/platform/GenericOTARequestorDriver.cpp b/src/platform/GenericOTARequestorDriver.cpp index 5fcacdebe4b184..461696a0168299 100644 --- a/src/platform/GenericOTARequestorDriver.cpp +++ b/src/platform/GenericOTARequestorDriver.cpp @@ -44,7 +44,7 @@ uint16_t GenericOTARequestorDriver::GetMaxDownloadBlockSize() return 1024; } -void GenericOTARequestorDriver::HandleError(OTAUpdateStateEnum state, CHIP_ERROR error) +void GenericOTARequestorDriver::HandleError(UpdateFailureState state, CHIP_ERROR error) { // TODO: Schedule the next QueryImage } @@ -52,7 +52,7 @@ void GenericOTARequestorDriver::HandleError(OTAUpdateStateEnum state, CHIP_ERROR void GenericOTARequestorDriver::UpdateAvailable(const UpdateDescription & update, System::Clock::Seconds32 delay) { VerifyOrDie(mRequestor != nullptr); - ScheduleDelayedAction(OTAUpdateStateEnum::kDelayedOnQuery, delay, + ScheduleDelayedAction(UpdateFailureState::kDownloading, delay, [](System::Layer *, void * context) { ToDriver(context)->mRequestor->DownloadUpdate(); }); } @@ -70,14 +70,14 @@ void GenericOTARequestorDriver::UpdateDownloaded() void GenericOTARequestorDriver::UpdateConfirmed(System::Clock::Seconds32 delay) { VerifyOrDie(mImageProcessor != nullptr); - ScheduleDelayedAction(OTAUpdateStateEnum::kDelayedOnApply, delay, + ScheduleDelayedAction(UpdateFailureState::kApplying, delay, [](System::Layer *, void * context) { ToDriver(context)->mImageProcessor->Apply(); }); } void GenericOTARequestorDriver::UpdateSuspended(System::Clock::Seconds32 delay) { VerifyOrDie(mRequestor != nullptr); - ScheduleDelayedAction(OTAUpdateStateEnum::kDelayedOnApply, delay, + ScheduleDelayedAction(UpdateFailureState::kAwaitingNextAction, delay, [](System::Layer *, void * context) { ToDriver(context)->mRequestor->ApplyUpdate(); }); } @@ -87,7 +87,7 @@ void GenericOTARequestorDriver::UpdateDiscontinued() mImageProcessor->Abort(); } -void GenericOTARequestorDriver::ScheduleDelayedAction(OTAUpdateStateEnum state, System::Clock::Seconds32 delay, +void GenericOTARequestorDriver::ScheduleDelayedAction(UpdateFailureState state, System::Clock::Seconds32 delay, System::TimerCompleteCallback action) { CHIP_ERROR error = SystemLayer().StartTimer(std::chrono::duration_cast(delay), action, this); diff --git a/src/platform/GenericOTARequestorDriver.h b/src/platform/GenericOTARequestorDriver.h index a6533d2fe93cbe..7e9ba69abed85c 100644 --- a/src/platform/GenericOTARequestorDriver.h +++ b/src/platform/GenericOTARequestorDriver.h @@ -45,7 +45,7 @@ class GenericOTARequestorDriver : public OTARequestorDriver bool CanConsent() override; uint16_t GetMaxDownloadBlockSize() override; - void HandleError(app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum state, CHIP_ERROR error) override; + void HandleError(UpdateFailureState state, CHIP_ERROR error) override; void UpdateAvailable(const UpdateDescription & update, System::Clock::Seconds32 delay) override; void UpdateNotFound(UpdateNotFoundReason reason, System::Clock::Seconds32 delay) override; void UpdateDownloaded() override; @@ -54,8 +54,7 @@ class GenericOTARequestorDriver : public OTARequestorDriver void UpdateDiscontinued() override; private: - void ScheduleDelayedAction(app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum state, System::Clock::Seconds32 delay, - System::TimerCompleteCallback action); + void ScheduleDelayedAction(UpdateFailureState state, System::Clock::Seconds32 delay, System::TimerCompleteCallback action); OTARequestorInterface * mRequestor = nullptr; OTAImageProcessorInterface * mImageProcessor = nullptr; diff --git a/src/platform/Linux/OTAImageProcessorImpl.cpp b/src/platform/Linux/OTAImageProcessorImpl.cpp index 14a061484b680e..5c2fa16486ff8c 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.cpp +++ b/src/platform/Linux/OTAImageProcessorImpl.cpp @@ -17,6 +17,7 @@ */ #include +#include #include "OTAImageProcessorImpl.h" @@ -42,6 +43,7 @@ CHIP_ERROR OTAImageProcessorImpl::Finalize() CHIP_ERROR OTAImageProcessorImpl::Apply() { + DeviceLayer::PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast(this)); return CHIP_NO_ERROR; } @@ -121,6 +123,15 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) ChipLogProgress(SoftwareUpdate, "OTA image downloaded to %s", imageProcessor->mParams.imageFile.data()); } +void OTAImageProcessorImpl::HandleApply(intptr_t context) +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor != nullptr) + { + requestor->NotifyUpdateApplied(); + } +} + void OTAImageProcessorImpl::HandleAbort(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); diff --git a/src/platform/Linux/OTAImageProcessorImpl.h b/src/platform/Linux/OTAImageProcessorImpl.h index 77a13ca36e890c..c78735485a9a3b 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.h +++ b/src/platform/Linux/OTAImageProcessorImpl.h @@ -42,6 +42,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface //////////// Actual handlers for the OTAImageProcessorInterface /////////////// static void HandlePrepareDownload(intptr_t context); static void HandleFinalize(intptr_t context); + static void HandleApply(intptr_t context); static void HandleAbort(intptr_t context); static void HandleProcessBlock(intptr_t context);