Skip to content

Commit

Permalink
[OTA] Add NotifyUpdateApplied API to OTA Requestor (#13555)
Browse files Browse the repository at this point in the history
- This API should be called after an image has been successfully applied
  • Loading branch information
carol-apple authored and pull[bot] committed Feb 19, 2024
1 parent 184d2fb commit 1e61377
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/app/clusters/ota-provider/ota-provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
98 changes: 74 additions & 24 deletions src/app/clusters/ota-requestor/OTARequestor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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<OTARequestor *>(context);
VerifyOrDie(requestorCore != nullptr);

ChipLogDetail(SoftwareUpdate, "NotifyUpdateApplied failure response %" PRIu8, status);
requestorCore->mOtaRequestorDriver->HandleError(UpdateFailureState::kNotifying, CHIP_ERROR_BAD_REQUEST);
SetUpdateStateAttribute(OTAUpdateStateEnum::kIdle);
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand All @@ -314,14 +326,28 @@ 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;
}

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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -376,6 +402,11 @@ void OTARequestor::ApplyUpdate()
ConnectToProvider(kApplyUpdate);
}

void OTARequestor::NotifyUpdateApplied()
{
ConnectToProvider(kNotifyUpdateApplied);
}

void OTARequestor::OnDownloadStateChanged(OTADownloader::State state)
{
VerifyOrReturn(mOtaRequestorDriver != nullptr);
Expand All @@ -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;
Expand All @@ -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 };
Expand Down Expand Up @@ -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;
Expand All @@ -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
20 changes: 20 additions & 0 deletions src/app/clusters/ota-requestor/OTARequestor.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe
kQueryImage = 0,
kStartBDX,
kApplyUpdate,
kNotifyUpdateApplied,
};

OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {}
Expand All @@ -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;
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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;
Expand Down
11 changes: 10 additions & 1 deletion src/include/platform/OTARequestorDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ struct UpdateDescription
ByteSpan metadataForRequestor;
};

enum class UpdateFailureState
{
kQuerying,
kDownloading,
kApplying,
kNotifying,
kAwaitingNextAction,
};

enum class UpdateNotFoundReason
{
Busy,
Expand All @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/include/platform/OTARequestorInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
16 changes: 16 additions & 0 deletions src/lib/shell/commands/Ota.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<FabricIndex>(strtoul(argv[0], nullptr, 10));
const NodeId providerNodeId = static_cast<NodeId>(strtoull(argv[1], nullptr, 10));
const EndpointId providerEndpointId = static_cast<EndpointId>(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)
Expand All @@ -85,6 +99,8 @@ void RegisterOtaCommands()
{ &QueryImageHandler, "query", "Query for a new image. Usage: ota query <fabric-index> <provider-node-id> <endpoint-id>" },
{ &ApplyImageHandler, "apply",
"Apply the current update. Usage ota apply <fabric-index> <provider-node-id> <endpoint-id>" },
{ &NotifyImageHandler, "notify",
"Notify the new image has been applied. Usage: ota notify <fabric-index> <provider-node-id> <endpoint-id>" },
};

sSubShell.RegisterCommands(subCommands, ArraySize(subCommands));
Expand Down
10 changes: 5 additions & 5 deletions src/platform/GenericOTARequestorDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ 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
}

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(); });
}

Expand All @@ -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(); });
}

Expand All @@ -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<System::Clock::Timeout>(delay), action, this);
Expand Down
5 changes: 2 additions & 3 deletions src/platform/GenericOTARequestorDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 1e61377

Please sign in to comment.