diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 8bc60ce732b015..183b95120fc380 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -1993,7 +1993,7 @@ server cluster OtaSoftwareUpdateRequestor = 42 { info event DownloadError = 2 { INT32U softwareVersion = 0; INT64U bytesDownloaded = 1; - INT8U progressPercent = 2; + nullable INT8U progressPercent = 2; nullable INT64S platformCode = 3; } diff --git a/examples/lighting-app/lighting-common/lighting-app.matter b/examples/lighting-app/lighting-common/lighting-app.matter index 256b639135c43c..57f915c9eb7c70 100644 --- a/examples/lighting-app/lighting-common/lighting-app.matter +++ b/examples/lighting-app/lighting-common/lighting-app.matter @@ -905,7 +905,7 @@ server cluster OtaSoftwareUpdateRequestor = 42 { info event DownloadError = 2 { INT32U softwareVersion = 0; INT64U bytesDownloaded = 1; - INT8U progressPercent = 2; + nullable INT8U progressPercent = 2; nullable INT64S platformCode = 3; } diff --git a/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter b/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter index b7e711ba8ba50c..e3d9f01aad088f 100644 --- a/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter +++ b/examples/ota-requestor-app/ota-requestor-common/ota-requestor-app.matter @@ -337,7 +337,7 @@ server cluster OtaSoftwareUpdateRequestor = 42 { info event DownloadError = 2 { INT32U softwareVersion = 0; INT64U bytesDownloaded = 1; - INT8U progressPercent = 2; + nullable INT8U progressPercent = 2; nullable INT64S platformCode = 3; } diff --git a/src/app/clusters/ota-requestor/BDXDownloader.cpp b/src/app/clusters/ota-requestor/BDXDownloader.cpp index 69297107afdf17..31a77fdfef2c55 100644 --- a/src/app/clusters/ota-requestor/BDXDownloader.cpp +++ b/src/app/clusters/ota-requestor/BDXDownloader.cpp @@ -18,6 +18,7 @@ #include "BDXDownloader.h" +#include #include #include #include @@ -28,6 +29,8 @@ #include using chip::OTADownloader; +using chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum; +using chip::app::DataModel::Nullable; using chip::bdx::TransferSession; namespace chip { @@ -68,7 +71,7 @@ CHIP_ERROR BDXDownloader::BeginPrepareDownload() VerifyOrReturnError(mImageProcessor != nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(mImageProcessor->PrepareDownload()); - SetState(State::kPreparing); + SetState(State::kPreparing, OTAChangeReasonEnum::kSuccess); return CHIP_NO_ERROR; } @@ -79,7 +82,7 @@ CHIP_ERROR BDXDownloader::OnPreparedForDownload(CHIP_ERROR status) if (status == CHIP_NO_ERROR) { - SetState(State::kInProgress); + SetState(State::kInProgress, OTAChangeReasonEnum::kSuccess); // Must call here because StartTransfer() should have prepared a ReceiveInit message, and now we should send it. PollTransferSession(); @@ -88,7 +91,7 @@ CHIP_ERROR BDXDownloader::OnPreparedForDownload(CHIP_ERROR status) { ChipLogError(BDX, "failed to prepare download: %" CHIP_ERROR_FORMAT, status.Format()); mBdxTransfer.Reset(); - SetState(State::kIdle); + SetState(State::kIdle, OTAChangeReasonEnum::kFailure); } return CHIP_NO_ERROR; @@ -113,7 +116,7 @@ void BDXDownloader::OnDownloadTimeout() { mImageProcessor->Abort(); } - SetState(State::kIdle); + SetState(State::kIdle, OTAChangeReasonEnum::kTimeOut); } else { @@ -131,7 +134,7 @@ void BDXDownloader::EndDownload(CHIP_ERROR reason) { mImageProcessor->Abort(); } - SetState(State::kIdle); + SetState(State::kIdle, OTAChangeReasonEnum::kSuccess); // Because AbortTransfer() will generate a StatusReport to send. PollTransferSession(); @@ -174,14 +177,16 @@ CHIP_ERROR BDXDownloader::HandleBdxEvent(const chip::bdx::TransferSession::Outpu if (outEvent.msgTypeData.HasMessageType(chip::bdx::MessageType::BlockAckEOF)) { // BDX transfer is not complete until BlockAckEOF has been sent - SetState(State::kComplete); + SetState(State::kComplete, OTAChangeReasonEnum::kSuccess); } break; } case TransferSession::OutputEventType::kBlockReceived: { chip::ByteSpan blockData(outEvent.blockdata.Data, outEvent.blockdata.Length); ReturnErrorOnFailure(mImageProcessor->ProcessBlock(blockData)); - mStateDelegate->OnUpdateProgressChanged(mImageProcessor->GetPercentComplete()); + Nullable percent; + mImageProcessor->GetPercentComplete(percent); + mStateDelegate->OnUpdateProgressChanged(percent); // TODO: this will cause problems if Finalize() is not guaranteed to do its work after ProcessBlock(). if (outEvent.blockdata.IsEof) @@ -219,13 +224,13 @@ CHIP_ERROR BDXDownloader::HandleBdxEvent(const chip::bdx::TransferSession::Outpu return CHIP_NO_ERROR; } -void BDXDownloader::SetState(State state) +void BDXDownloader::SetState(State state, OTAChangeReasonEnum reason) { mState = state; if (mStateDelegate) { - mStateDelegate->OnDownloadStateChanged(state); + mStateDelegate->OnDownloadStateChanged(state, reason); } } diff --git a/src/app/clusters/ota-requestor/BDXDownloader.h b/src/app/clusters/ota-requestor/BDXDownloader.h index d783662c5a38fc..96c63d941d9180 100644 --- a/src/app/clusters/ota-requestor/BDXDownloader.h +++ b/src/app/clusters/ota-requestor/BDXDownloader.h @@ -26,6 +26,7 @@ #include "OTADownloader.h" +#include #include #include #include @@ -49,10 +50,10 @@ class BDXDownloader : public chip::OTADownloader { public: // Handle download state change - virtual void OnDownloadStateChanged(State state) = 0; + virtual void OnDownloadStateChanged(State state, app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum reason) = 0; // Handle update progress change - virtual void OnUpdateProgressChanged(uint8_t percent) = 0; - virtual ~StateDelegate() = default; + virtual void OnUpdateProgressChanged(app::DataModel::Nullable percent) = 0; + virtual ~StateDelegate() = default; }; // To be called when there is an incoming message to handle (of any protocol type) @@ -77,7 +78,7 @@ class BDXDownloader : public chip::OTADownloader private: void PollTransferSession(); CHIP_ERROR HandleBdxEvent(const chip::bdx::TransferSession::OutputEvent & outEvent); - void SetState(State state); + void SetState(State state, app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum reason); chip::bdx::TransferSession mBdxTransfer; MessagingDelegate * mMsgDelegate = nullptr; diff --git a/src/app/clusters/ota-requestor/OTADownloader.h b/src/app/clusters/ota-requestor/OTADownloader.h index b77514c35b53b2..fa12bbce77a07e 100644 --- a/src/app/clusters/ota-requestor/OTADownloader.h +++ b/src/app/clusters/ota-requestor/OTADownloader.h @@ -75,7 +75,7 @@ class OTADownloader virtual ~OTADownloader() = default; protected: - OTAImageProcessorInterface * mImageProcessor; + OTAImageProcessorInterface * mImageProcessor = nullptr; State mState; }; diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 616e259069ecfc..c44d8a1754fb61 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -36,6 +36,7 @@ using namespace app::Clusters::OtaSoftwareUpdateProvider; using namespace app::Clusters::OtaSoftwareUpdateProvider::Commands; using namespace app::Clusters::OtaSoftwareUpdateRequestor; using namespace app::Clusters::OtaSoftwareUpdateRequestor::Commands; +using app::DataModel::Nullable; using bdx::TransferSession; // Global instance of the OTARequestorInterface. @@ -87,17 +88,6 @@ static void LogApplyUpdateResponse(const ApplyUpdateResponse::DecodableType & re ChipLogDetail(SoftwareUpdate, " delayedActionTime: %" PRIu32 " seconds", response.delayedActionTime); } -static void SetUpdateStateAttribute(OTAUpdateStateEnum state) -{ - OtaRequestorServerSetUpdateState(state); - - // The UpdateStateProgress attribute only applies to the querying state - if (state != OTAUpdateStateEnum::kQuerying) - { - OtaRequestorServerSetUpdateStateProgress(0); - } -} - void StartDelayTimerHandler(System::Layer * systemLayer, void * appState) { VerifyOrReturn(appState != nullptr); @@ -129,13 +119,13 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse if (err != CHIP_NO_ERROR) { - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, err); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kQuerying, err); return; } MutableByteSpan updateToken(requestorCore->mUpdateTokenBuffer); CopySpanToMutableSpan(update.updateToken, updateToken); - requestorCore->mUpdateVersion = update.softwareVersion; + requestorCore->mTargetVersion = update.softwareVersion; requestorCore->mUpdateToken = updateToken; requestorCore->mOtaRequestorDriver->UpdateAvailable(update, @@ -145,16 +135,15 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse case OTAQueryStatus::kBusy: requestorCore->mOtaRequestorDriver->UpdateNotFound(UpdateNotFoundReason::Busy, System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); - SetUpdateStateAttribute(OTAUpdateStateEnum::kDelayedOnQuery); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kDelayedOnQuery, OTAChangeReasonEnum::kDelayByProvider); break; case OTAQueryStatus::kNotAvailable: requestorCore->mOtaRequestorDriver->UpdateNotFound(UpdateNotFoundReason::NotAvailable, System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); break; default: - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); break; } } @@ -165,8 +154,7 @@ void OTARequestor::OnQueryImageFailure(void * context, EmberAfStatus status) VerifyOrDie(requestorCore != nullptr); ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kQuerying, CHIP_ERROR_BAD_REQUEST); } void OTARequestor::OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response) @@ -183,11 +171,11 @@ void OTARequestor::OnApplyUpdateResponse(void * context, const ApplyUpdateRespon break; case OTAApplyUpdateAction::kAwaitNextAction: requestorCore->mOtaRequestorDriver->UpdateSuspended(System::Clock::Seconds32(response.delayedActionTime)); - SetUpdateStateAttribute(OTAUpdateStateEnum::kDelayedOnApply); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kDelayedOnApply, OTAChangeReasonEnum::kDelayByProvider); break; case OTAApplyUpdateAction::kDiscontinue: requestorCore->mOtaRequestorDriver->UpdateDiscontinued(); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); break; } } @@ -198,8 +186,7 @@ void OTARequestor::OnApplyUpdateFailure(void * context, EmberAfStatus status) VerifyOrDie(requestorCore != nullptr); ChipLogDetail(SoftwareUpdate, "ApplyUpdate failure response %" PRIu8, status); - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kApplying, CHIP_ERROR_BAD_REQUEST); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kApplying, CHIP_ERROR_BAD_REQUEST); } void OTARequestor::OnNotifyUpdateAppliedResponse(void * context, const app::DataModel::NullObjectType & response) {} @@ -210,8 +197,7 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, EmberAfStatus st VerifyOrDie(requestorCore != nullptr); ChipLogDetail(SoftwareUpdate, "NotifyUpdateApplied failure response %" PRIu8, status); - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kNotifying, CHIP_ERROR_BAD_REQUEST); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, CHIP_ERROR_BAD_REQUEST); } EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj, @@ -299,11 +285,11 @@ 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(UpdateFailureState::kQuerying, err); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kQuerying, err); return; } - SetUpdateStateAttribute(OTAUpdateStateEnum::kQuerying); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kQuerying, OTAChangeReasonEnum::kSuccess); break; } case kStartBDX: { @@ -312,12 +298,11 @@ 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(UpdateFailureState::kDownloading, err); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kDownloading, err); return; } - SetUpdateStateAttribute(OTAUpdateStateEnum::kDownloading); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kDownloading, OTAChangeReasonEnum::kSuccess); break; } case kApplyUpdate: { @@ -326,12 +311,11 @@ 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(UpdateFailureState::kApplying, err); - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kApplying, err); return; } - SetUpdateStateAttribute(OTAUpdateStateEnum::kApplying); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kApplying, OTAChangeReasonEnum::kSuccess); break; } case kNotifyUpdateApplied: { @@ -340,12 +324,11 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr 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); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, err); return; } - SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle); + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); break; } default: @@ -365,13 +348,13 @@ void OTARequestor::OnConnectionFailure(void * context, PeerId peerId, CHIP_ERROR switch (requestorCore->mOnConnectedAction) { case kQueryImage: - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kQuerying, error); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kQuerying, error); break; case kStartBDX: - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kDownloading, error); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kDownloading, error); break; case kApplyUpdate: - requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kApplying, error); + requestorCore->RecordErrorUpdateState(UpdateFailureState::kApplying, error); break; default: break; @@ -402,12 +385,21 @@ void OTARequestor::ApplyUpdate() ConnectToProvider(kApplyUpdate); } -void OTARequestor::NotifyUpdateApplied() +void OTARequestor::NotifyUpdateApplied(uint32_t version) { + // New version is executing so update where applicable + VerifyOrReturn(Basic::Attributes::SoftwareVersion::Set(kRootEndpointId, version) == EMBER_ZCL_STATUS_SUCCESS); + mCurrentVersion = version; + + // Log the VersionApplied event + uint16_t productId; + VerifyOrReturn(Basic::Attributes::ProductID::Get(kRootEndpointId, &productId) == EMBER_ZCL_STATUS_SUCCESS); + OtaRequestorServerOnVersionApplied(version, productId); + ConnectToProvider(kNotifyUpdateApplied); } -void OTARequestor::OnDownloadStateChanged(OTADownloader::State state) +void OTARequestor::OnDownloadStateChanged(OTADownloader::State state, OTAChangeReasonEnum reason) { VerifyOrReturn(mOtaRequestorDriver != nullptr); @@ -417,18 +409,66 @@ void OTARequestor::OnDownloadStateChanged(OTADownloader::State state) mOtaRequestorDriver->UpdateDownloaded(); break; case OTADownloader::State::kIdle: - mOtaRequestorDriver->HandleError(UpdateFailureState::kDownloading, CHIP_ERROR_CONNECTION_ABORTED); + if (reason != OTAChangeReasonEnum::kSuccess) + { + RecordErrorUpdateState(UpdateFailureState::kDownloading, CHIP_ERROR_CONNECTION_ABORTED, reason); + } + break; default: break; } } -void OTARequestor::OnUpdateProgressChanged(uint8_t percent) +void OTARequestor::OnUpdateProgressChanged(Nullable percent) { OtaRequestorServerSetUpdateStateProgress(percent); } +void OTARequestor::RecordNewUpdateState(OTAUpdateStateEnum newState, OTAChangeReasonEnum reason) +{ + // Set server UpdateState attribute + OtaRequestorServerSetUpdateState(newState); + + // The UpdateStateProgress attribute only applies to the downloading state + if (newState != OTAUpdateStateEnum::kDownloading) + { + app::DataModel::Nullable percent; + percent.SetNull(); + OtaRequestorServerSetUpdateStateProgress(percent); + } + + // Log the StateTransition event + Nullable previousState; + previousState.SetNonNull(mCurrentUpdateState); + Nullable targetSoftwareVersion; + if ((newState == OTAUpdateStateEnum::kDownloading) || (newState == OTAUpdateStateEnum::kApplying) || + (newState == OTAUpdateStateEnum::kRollingBack)) + { + targetSoftwareVersion.SetNonNull(mTargetVersion); + } + OtaRequestorServerOnStateTransition(previousState, newState, reason, targetSoftwareVersion); + + mCurrentUpdateState = newState; +} + +void OTARequestor::RecordErrorUpdateState(UpdateFailureState failureState, CHIP_ERROR error, OTAChangeReasonEnum reason) +{ + // Inform driver of the error + mOtaRequestorDriver->HandleError(failureState, error); + + // Log the DownloadError event + OTAImageProcessorInterface * imageProcessor = mBdxDownloader->GetImageProcessorDelegate(); + VerifyOrReturn(imageProcessor != nullptr); + Nullable progressPercent; + imageProcessor->GetPercentComplete(progressPercent); + Nullable platformCode; + OtaRequestorServerOnDownloadError(mTargetVersion, imageProcessor->GetBytesDownloaded(), progressPercent, platformCode); + + // Whenever an error occurs, always reset to Idle state + RecordNewUpdateState(OTAUpdateStateEnum::kIdle, reason); +} + CHIP_ERROR OTARequestor::GenerateUpdateToken() { if (mUpdateToken.empty()) @@ -540,7 +580,7 @@ CHIP_ERROR OTARequestor::SendApplyUpdateRequest(OperationalDeviceProxy & deviceP ApplyUpdateRequest::Type args; args.updateToken = mUpdateToken; - args.newVersion = mUpdateVersion; + args.newVersion = mTargetVersion; Controller::OtaSoftwareUpdateProviderCluster cluster; cluster.Associate(&deviceProxy, mProviderEndpointId); @@ -554,7 +594,7 @@ CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy & NotifyUpdateApplied::Type args; args.updateToken = mUpdateToken; - args.softwareVersion = mUpdateVersion; + args.softwareVersion = mCurrentVersion; Controller::OtaSoftwareUpdateProviderCluster cluster; cluster.Associate(&deviceProxy, mProviderEndpointId); diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h index fc70e83fab975c..0f650f78ddd618 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -63,12 +63,13 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe // Send ApplyImage void ApplyUpdate() override; - // Send NotifyUpdateApplied - void NotifyUpdateApplied() override; + // Send NotifyUpdateApplied, update Basic cluster SoftwareVersion attribute, log the VersionApplied event + void NotifyUpdateApplied(uint32_t version) override; //////////// BDXDownloader::StateDelegate Implementation /////////////// - void OnDownloadStateChanged(OTADownloader::State state) override; - void OnUpdateProgressChanged(uint8_t percent) override; + void OnDownloadStateChanged(OTADownloader::State state, + app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum reason) override; + void OnUpdateProgressChanged(app::DataModel::Nullable percent) override; /** * Called to perform some initialization including: @@ -84,8 +85,14 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe mOtaRequestorDriver = driver; mBdxDownloader = downloader; - OtaRequestorServerSetUpdateState(app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum::kIdle); - OtaRequestorServerSetUpdateStateProgress(0); + uint32_t version; + VerifyOrDie(app::Clusters::Basic::Attributes::SoftwareVersion::Get(kRootEndpointId, &version) == EMBER_ZCL_STATUS_SUCCESS); + mCurrentVersion = version; + + OtaRequestorServerSetUpdateState(mCurrentUpdateState); + app::DataModel::Nullable percent; + percent.SetNull(); + OtaRequestorServerSetUpdateStateProgress(percent); } /** @@ -122,6 +129,8 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe private: using QueryImageResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType; using ApplyUpdateResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateResponse::DecodableType; + using OTAUpdateStateEnum = app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum; + using OTAChangeReasonEnum = app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum; static constexpr size_t kMaxUpdateTokenLen = 32; @@ -192,6 +201,17 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe chip::BDXDownloader * mDownloader; }; + /** + * Record the new update state by updating the corresponding server attribute and logging a StateTransition event + */ + void RecordNewUpdateState(OTAUpdateStateEnum newState, OTAChangeReasonEnum reason); + + /** + * Record the error update state by informing the driver of the error and calling `RecordNewUpdateState` + */ + void RecordErrorUpdateState(UpdateFailureState failureState, CHIP_ERROR error, + OTAChangeReasonEnum reason = OTAChangeReasonEnum::kFailure); + /** * Generate an update token using the operational node ID in case of token lost, received in QueryImageResponse */ @@ -260,8 +280,10 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe BDXMessenger mBdxMessenger; // TODO: ideally this is held by the application uint8_t mUpdateTokenBuffer[kMaxUpdateTokenLen]; ByteSpan mUpdateToken; - uint32_t mUpdateVersion = 0; - Server * mServer = nullptr; + uint32_t mCurrentVersion = 0; + uint32_t mTargetVersion = 0; + OTAUpdateStateEnum mCurrentUpdateState = OTAUpdateStateEnum::kIdle; + Server * mServer = nullptr; }; } // namespace chip diff --git a/src/app/clusters/ota-requestor/ota-requestor-server.cpp b/src/app/clusters/ota-requestor/ota-requestor-server.cpp index 0fc2bffba7ce45..032a1137d5b6b3 100644 --- a/src/app/clusters/ota-requestor/ota-requestor-server.cpp +++ b/src/app/clusters/ota-requestor/ota-requestor-server.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -102,7 +103,7 @@ EmberAfStatus OtaRequestorServerSetUpdateState(OTAUpdateStateEnum value) return status; } -EmberAfStatus OtaRequestorServerSetUpdateStateProgress(uint8_t value) +EmberAfStatus OtaRequestorServerSetUpdateStateProgress(app::DataModel::Nullable value) { EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; @@ -112,7 +113,9 @@ EmberAfStatus OtaRequestorServerSetUpdateStateProgress(uint8_t value) app::DataModel::Nullable currentValue; status = Attributes::UpdateStateProgress::Get(endpoint, currentValue); VerifyOrDie(EMBER_ZCL_STATUS_SUCCESS == status); - if (currentValue.IsNull() || currentValue.Value() != value) + + if ((currentValue.IsNull() && !value.IsNull()) || (!currentValue.IsNull() && value.IsNull()) || + (!currentValue.IsNull() && !value.IsNull() && (currentValue.Value() != value.Value()))) { status = Attributes::UpdateStateProgress::Set(endpoint, value); VerifyOrDie(EMBER_ZCL_STATUS_SUCCESS == status); @@ -122,6 +125,62 @@ EmberAfStatus OtaRequestorServerSetUpdateStateProgress(uint8_t value) return status; } +void OtaRequestorServerOnStateTransition(DataModel::Nullable previousState, OTAUpdateStateEnum newState, + OTAChangeReasonEnum reason, DataModel::Nullable targetSoftwareVersion) +{ + if (!previousState.IsNull() && previousState.Value() == newState) + { + ChipLogError(Zcl, "Previous state and new state are the same, no event to log"); + return; + } + + // Find all endpoints that has OtaSoftwareUpdateRequestor implemented + for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) + { + Events::StateTransition::Type event{ previousState, newState, reason, targetSoftwareVersion }; + EventNumber eventNumber; + + CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(Zcl, "Failed to record StateTransition event: %" CHIP_ERROR_FORMAT, err.Format()); + } + } +} + +void OtaRequestorServerOnVersionApplied(uint32_t softwareVersion, uint16_t productId) +{ + // Find all endpoints that has OtaSoftwareUpdateRequestor implemented + for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) + { + Events::VersionApplied::Type event{ softwareVersion, productId }; + EventNumber eventNumber; + + CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(Zcl, "Failed to record VersionApplied event: %" CHIP_ERROR_FORMAT, err.Format()); + } + } +} + +void OtaRequestorServerOnDownloadError(uint32_t softwareVersion, uint64_t bytesDownloaded, + DataModel::Nullable progressPercent, DataModel::Nullable platformCode) +{ + // Find all endpoints that has OtaSoftwareUpdateRequestor implemented + for (auto endpoint : EnabledEndpointsWithServerCluster(OtaSoftwareUpdateRequestor::Id)) + { + Events::DownloadError::Type event{ softwareVersion, bytesDownloaded, progressPercent, platformCode }; + EventNumber eventNumber; + + CHIP_ERROR err = LogEvent(event, endpoint, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(Zcl, "Failed to record DownloadError event: %" CHIP_ERROR_FORMAT, err.Format()); + } + } +} + // ----------------------------------------------------------------------------- // Callbacks implementation diff --git a/src/app/clusters/ota-requestor/ota-requestor-server.h b/src/app/clusters/ota-requestor/ota-requestor-server.h index 9a5afcfc193979..6c6fe3756da43c 100644 --- a/src/app/clusters/ota-requestor/ota-requestor-server.h +++ b/src/app/clusters/ota-requestor/ota-requestor-server.h @@ -21,4 +21,14 @@ #include EmberAfStatus OtaRequestorServerSetUpdateState(chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum value); -EmberAfStatus OtaRequestorServerSetUpdateStateProgress(uint8_t value); +EmberAfStatus OtaRequestorServerSetUpdateStateProgress(chip::app::DataModel::Nullable value); + +void OtaRequestorServerOnStateTransition( + chip::app::DataModel::Nullable previousState, + chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum newState, + chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum reason, + chip::app::DataModel::Nullable targetSoftwareVersion); +void OtaRequestorServerOnVersionApplied(uint32_t softwareVersion, uint16_t productId); +void OtaRequestorServerOnDownloadError(uint32_t softwareVersion, uint64_t bytesDownloaded, + chip::app::DataModel::Nullable progressPercent, + chip::app::DataModel::Nullable platformCode); diff --git a/src/app/zap-templates/zcl/data-model/chip/chip-ota.xml b/src/app/zap-templates/zcl/data-model/chip/chip-ota.xml index 906c0c5cd4786c..c83bff37b74dbc 100644 --- a/src/app/zap-templates/zcl/data-model/chip/chip-ota.xml +++ b/src/app/zap-templates/zcl/data-model/chip/chip-ota.xml @@ -150,7 +150,7 @@ limitations under the License. This event SHALL be generated whenever an error occurs during OTA Requestor download operation. - + diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index f37ee214bc9abb..e0419f3837a564 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -2209,7 +2209,7 @@ client cluster OtaSoftwareUpdateRequestor = 42 { info event DownloadError = 2 { INT32U softwareVersion = 0; INT64U bytesDownloaded = 1; - INT8U progressPercent = 2; + nullable INT8U progressPercent = 2; nullable INT64S platformCode = 3; } diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index bf0b7d22cfce3a..d0edad5d25cd9d 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -7031,13 +7031,13 @@ def descriptor(cls) -> ClusterObjectDescriptor: Fields = [ ClusterObjectFieldDescriptor(Label="softwareVersion", Tag=0, Type=uint), ClusterObjectFieldDescriptor(Label="bytesDownloaded", Tag=1, Type=uint), - ClusterObjectFieldDescriptor(Label="progressPercent", Tag=2, Type=uint), + ClusterObjectFieldDescriptor(Label="progressPercent", Tag=2, Type=typing.Union[Nullable, uint]), ClusterObjectFieldDescriptor(Label="platformCode", Tag=3, Type=typing.Union[Nullable, int]), ]) softwareVersion: 'uint' = 0 bytesDownloaded: 'uint' = 0 - progressPercent: 'uint' = 0 + progressPercent: 'typing.Union[Nullable, uint]' = NullValue platformCode: 'typing.Union[Nullable, int]' = NullValue diff --git a/src/include/platform/OTAImageProcessor.h b/src/include/platform/OTAImageProcessor.h index b5954394add880..95815230bc3136 100644 --- a/src/include/platform/OTAImageProcessor.h +++ b/src/include/platform/OTAImageProcessor.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include #include @@ -32,6 +33,21 @@ struct OTAImageProcessorParams uint64_t totalFileBytes = 0; }; +// TODO: Parse the header when the image is received +struct OTAImageProcessorHeader +{ + uint16_t vendorId; + uint16_t productId; + uint32_t softwareVersion; + CharSpan softwareVersionString; + uint64_t payloadSize; + uint16_t minApplicableSoftwareVersion; + uint16_t maxApplicableSoftwareVersion; + CharSpan releaseNotesUrl; + uint8_t imageDigestType; + ByteSpan imageDigest; +}; + /** * @class OTAImageProcessorInterface * @@ -83,20 +99,26 @@ class DLL_EXPORT OTAImageProcessorInterface /** * Called to check the current download status of the OTA image download. */ - virtual uint8_t GetPercentComplete() + virtual void GetPercentComplete(app::DataModel::Nullable & percent) { if (mParams.totalFileBytes == 0) { - return 0; + percent.SetNull(); } else { - return static_cast((mParams.downloadedBytes * 100) / mParams.totalFileBytes); + percent.SetNonNull(static_cast((mParams.downloadedBytes * 100) / mParams.totalFileBytes)); } } + /** + * Called to check the current number of bytes that have been downloaded of the OTA image + */ + virtual uint64_t GetBytesDownloaded() { return mParams.downloadedBytes; } + protected: OTAImageProcessorParams mParams; + OTAImageProcessorHeader mHeader; }; } // namespace chip diff --git a/src/include/platform/OTARequestorInterface.h b/src/include/platform/OTARequestorInterface.h index 7825eb1fde35c8..28febbfac64afe 100644 --- a/src/include/platform/OTARequestorInterface.h +++ b/src/include/platform/OTARequestorInterface.h @@ -65,7 +65,7 @@ class OTARequestorInterface virtual void ApplyUpdate() = 0; // Send NotifyUpdateApplied command - virtual void NotifyUpdateApplied() = 0; + virtual void NotifyUpdateApplied(uint32_t version) = 0; // Manually set OTA Provider parameters virtual void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) = 0; diff --git a/src/platform/Linux/OTAImageProcessorImpl.cpp b/src/platform/Linux/OTAImageProcessorImpl.cpp index 5c2fa16486ff8c..afcff9f326cfc1 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.cpp +++ b/src/platform/Linux/OTAImageProcessorImpl.cpp @@ -125,10 +125,16 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) void OTAImageProcessorImpl::HandleApply(intptr_t context) { + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + OTARequestorInterface * requestor = chip::GetRequestorInstance(); if (requestor != nullptr) { - requestor->NotifyUpdateApplied(); + requestor->NotifyUpdateApplied(imageProcessor->mHeader.softwareVersion); } } diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 04dcb3aeb59f82..e944d8ade4b3ec 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -8919,7 +8919,7 @@ struct Type uint32_t softwareVersion = static_cast(0); uint64_t bytesDownloaded = static_cast(0); - uint8_t progressPercent = static_cast(0); + DataModel::Nullable progressPercent; DataModel::Nullable platformCode; CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const; @@ -8934,7 +8934,7 @@ struct DecodableType uint32_t softwareVersion = static_cast(0); uint64_t bytesDownloaded = static_cast(0); - uint8_t progressPercent = static_cast(0); + DataModel::Nullable progressPercent; DataModel::Nullable platformCode; CHIP_ERROR Decode(TLV::TLVReader & reader);