Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTA] Add support for applying image for Linux/Darwin platform #16482

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/standalone/CHIPProjectConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
#define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 4
#endif

#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION
#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 1
#endif

#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING
#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "1.0"
#endif

//
// Default of 8 ECs is not sufficient for some of the unit tests
// that try to validate multiple simultaneous interactions.
Expand Down
2 changes: 1 addition & 1 deletion examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ CHIP_ERROR AppTask::Init()
gRequestorCore.Init(Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the gDownloader and Image Processor objects
Expand Down
2 changes: 1 addition & 1 deletion examples/ota-requestor-app/efr32/src/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ void AppTask::InitOTARequestor()

gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the Downloader and Image Processor objects
Expand Down
18 changes: 17 additions & 1 deletion examples/ota-requestor-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ static void InitOTARequestor(void)
gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan::fromCharString(gOtaDownloadPath));
gImageProcessor.SetOTAImageFile(gOtaDownloadPath);
gImageProcessor.SetOTADownloader(&gDownloader);

// Set the image processor instance used for handling image being downloaded
Expand Down Expand Up @@ -213,5 +213,21 @@ int main(int argc, char * argv[])
{
VerifyOrDie(ChipLinuxAppInit(argc, argv, &cmdLineOptions) == 0);
ChipLinuxAppMainLoop();

// If the event loop had been stopped due to an update being applied, boot into the new image
if (gRequestorCore.GetCurrentUpdateState() == OTARequestor::OTAUpdateStateEnum::kApplying)
{
if (kMaxFilePathSize <= strlen(kImageExecPath))
{
ChipLogError(SoftwareUpdate, "Buffer too small for the new image file path: %s", kImageExecPath);
return -1;
}

argv[0] = kImageExecPath;
execv(argv[0], argv);
Damian-Nordic marked this conversation as resolved.
Show resolved Hide resolved

// If successfully executing the new iamge, execv should not return
ChipLogError(SoftwareUpdate, "The OTA image is invalid");
}
return 0;
}
2 changes: 1 addition & 1 deletion examples/platform/efr32/OTAConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ void OTAConfig::Init()
gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_TIMEOUT);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(chip::CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the Downloader and Image Processor objects
Expand Down
34 changes: 34 additions & 0 deletions src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,40 @@ CHIP_ERROR DefaultOTARequestorStorage::LoadUpdateToken(MutableByteSpan & updateT
return Load(DefaultStorageKeyAllocator::OTAUpdateToken(), updateToken);
}

CHIP_ERROR DefaultOTARequestorStorage::StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState)
{
return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), &currentUpdateState,
sizeof(currentUpdateState));
}

CHIP_ERROR DefaultOTARequestorStorage::LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState)
{
uint16_t size = static_cast<uint16_t>(sizeof(currentUpdateState));
return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), &currentUpdateState, size);
}

CHIP_ERROR DefaultOTARequestorStorage::ClearCurrentUpdateState()
{
return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState());
}

CHIP_ERROR DefaultOTARequestorStorage::StoreTargetVersion(uint32_t targetVersion)
{
return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion,
sizeof(targetVersion));
}

CHIP_ERROR DefaultOTARequestorStorage::LoadTargetVersion(uint32_t & targetVersion)
carol-apple marked this conversation as resolved.
Show resolved Hide resolved
{
uint16_t size = static_cast<uint16_t>(sizeof(targetVersion));
return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion, size);
}

CHIP_ERROR DefaultOTARequestorStorage::ClearTargetVersion()
{
return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTATargetVersion());
}

CHIP_ERROR DefaultOTARequestorStorage::Load(const char * key, MutableByteSpan & buffer)
{
uint16_t size = static_cast<uint16_t>(buffer.size());
Expand Down
8 changes: 8 additions & 0 deletions src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class DefaultOTARequestorStorage : public OTARequestorStorage
CHIP_ERROR ClearUpdateToken() override;
CHIP_ERROR LoadUpdateToken(MutableByteSpan & updateToken) override;

CHIP_ERROR StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState) override;
CHIP_ERROR LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState) override;
CHIP_ERROR ClearCurrentUpdateState() override;

CHIP_ERROR StoreTargetVersion(uint32_t targetVersion) override;
CHIP_ERROR LoadTargetVersion(uint32_t & targetVersion) override;
CHIP_ERROR ClearTargetVersion() override;

private:
CHIP_ERROR Load(const char * key, MutableByteSpan & buffer);
PersistentStorageDelegate * mPersistentStorage = nullptr;
Expand Down
11 changes: 11 additions & 0 deletions src/app/clusters/ota-requestor/GenericOTARequestorDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,23 @@ void GenericOTARequestorDriver::Init(OTARequestorInterface * requestor, OTAImage
if (error != CHIP_NO_ERROR)
{
ChipLogError(SoftwareUpdate, "Failed to confirm image: %" CHIP_ERROR_FORMAT, error.Format());
mRequestor->Reset();
return;
}

mRequestor->NotifyUpdateApplied();
});
}
else if ((mRequestor->GetCurrentUpdateState() != OTAUpdateStateEnum::kIdle))
{
// Not running a new image for the first time but also not in the idle state may indicate there is a problem
mRequestor->Reset();
}
else
{
// Start the first periodic query timer
StartDefaultProviderTimer();
}
}

bool GenericOTARequestorDriver::CanConsent()
Expand Down
128 changes: 114 additions & 14 deletions src/app/clusters/ota-requestor/OTARequestor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,35 @@ void OTARequestor::InitState(intptr_t context)
OTARequestor * requestorCore = reinterpret_cast<OTARequestor *>(context);
VerifyOrDie(requestorCore != nullptr);

// This results in the initial periodic timer kicking off
requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);

// This initialization may occur due to the following:
// 1) Regular boot up - the states should already be correct
// 2) Reboot from applying an image - once the image has been confirmed, the provider will be notified of the new version and
// all relevant states will reset for a new OTA update. If the image cannot be confirmed, the driver will be responsible for
// resetting the states appropriately, including the current update state.
OtaRequestorServerSetUpdateState(requestorCore->mCurrentUpdateState);
OtaRequestorServerSetUpdateStateProgress(app::DataModel::NullNullable);
}

CHIP_ERROR OTARequestor::Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver,
BDXDownloader & downloader)
{
mServer = &server;
mCASESessionManager = server.GetCASESessionManager();
mStorage = &storage;
mOtaRequestorDriver = &driver;
mBdxDownloader = &downloader;

ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(mCurrentVersion));

// Load data from KVS
LoadCurrentUpdateInfo();

// Schedule the initializations that needs to be performed in the CHIP context
DeviceLayer::PlatformMgr().ScheduleWork(InitState, reinterpret_cast<intptr_t>(this));

return chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(OnCommissioningCompleteRequestor, reinterpret_cast<intptr_t>(this));
}

void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response)
{
LogQueryImageResponse(response);
Expand Down Expand Up @@ -159,7 +182,6 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse
memcpy(fileDesignator.data(), update.fileDesignator.data(), update.fileDesignator.size());
fileDesignator.reduce_size(update.fileDesignator.size());
requestorCore->mFileDesignator = fileDesignator;
requestorCore->StoreCurrentUpdateInfo();

requestorCore->mOtaRequestorDriver->UpdateAvailable(update,
System::Clock::Seconds32(response.delayedActionTime.ValueOr(0)));
Expand Down Expand Up @@ -253,6 +275,22 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error
requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, error);
}

void OTARequestor::Reset()
{
mProviderLocation.ClearValue();
mUpdateToken.reduce_size(0);
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);
mTargetVersion = 0;

// Persist in case of a reboot or crash
StoreCurrentUpdateInfo();
}

void OTARequestor::Shutdown(void)
{
mServer->DispatchShutDownAndStopEventLoop();
}

EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const AnnounceOtaProvider::DecodableType & commandData)
Expand Down Expand Up @@ -365,14 +403,14 @@ void OTARequestor::CancelImageUpdate()
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kUnknown);
}

CHIP_ERROR OTARequestor::GetUpdateProgress(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress)
CHIP_ERROR OTARequestor::GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress)
{
VerifyOrReturnError(OtaRequestorServerGetUpdateStateProgress(endpointId, progress) == EMBER_ZCL_STATUS_SUCCESS,
CHIP_ERROR_BAD_REQUEST);
return CHIP_NO_ERROR;
}

CHIP_ERROR OTARequestor::GetState(EndpointId endpointId, OTAUpdateStateEnum & state)
CHIP_ERROR OTARequestor::GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state)
{
VerifyOrReturnError(OtaRequestorServerGetUpdateState(endpointId, state) == EMBER_ZCL_STATUS_SUCCESS, CHIP_ERROR_BAD_REQUEST);
return CHIP_NO_ERROR;
Expand Down Expand Up @@ -504,6 +542,10 @@ void OTARequestor::DownloadUpdateDelayedOnUserConsent()
void OTARequestor::ApplyUpdate()
{
RecordNewUpdateState(OTAUpdateStateEnum::kApplying, OTAChangeReasonEnum::kSuccess);

// If image is successfully applied, the device will reboot so persist all relevant data
StoreCurrentUpdateInfo();

ConnectToProvider(kApplyUpdate);
}

Expand All @@ -520,9 +562,6 @@ void OTARequestor::NotifyUpdateApplied()

OtaRequestorServerOnVersionApplied(mCurrentVersion, productId);

// There is no response for a notify so consider this OTA complete
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);

ConnectToProvider(kNotifyUpdateApplied);
}

Expand Down Expand Up @@ -788,27 +827,88 @@ CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy &
Controller::OtaSoftwareUpdateProviderCluster cluster;
cluster.Associate(&deviceProxy, mProviderLocation.Value().endpoint);

mProviderLocation.ClearValue(); // Clearing the last used provider location to start afresh on reboot
// There is no response for a notify so consider this OTA complete. Clear the provider location and reset any states to indicate
// so.
Reset();

return cluster.InvokeCommand(args, this, OnNotifyUpdateAppliedResponse, OnNotifyUpdateAppliedFailure);
}

void OTARequestor::StoreCurrentUpdateInfo()
carol-apple marked this conversation as resolved.
Show resolved Hide resolved
{
// TODO: change OTA requestor storage interface to store both values at once
CHIP_ERROR error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value());
CHIP_ERROR error = CHIP_NO_ERROR;
if (mProviderLocation.HasValue())
{
error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value());
}
else
{
error = mStorage->ClearCurrentProviderLocation();
}

if (error == CHIP_NO_ERROR)
if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
if (mUpdateToken.size() > 0)
{
error = mStorage->StoreUpdateToken(mUpdateToken);
}
else
{
error = mStorage->ClearUpdateToken();
}
}

if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
mStorage->StoreUpdateToken(mUpdateToken);
error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState);
}

if (error != CHIP_NO_ERROR)
if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
if (mTargetVersion > 0)
{
error = mStorage->StoreTargetVersion(mTargetVersion);
}
else
{
error = mStorage->ClearTargetVersion();
}
}

if ((error != CHIP_NO_ERROR) && (error != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
ChipLogError(SoftwareUpdate, "Failed to store current update: %" CHIP_ERROR_FORMAT, error.Format());
}
}

void OTARequestor::LoadCurrentUpdateInfo()
{
mStorage->LoadDefaultProviders(mDefaultOtaProviderList);

ProviderLocationType providerLocation;
if (mStorage->LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR)
{
mProviderLocation.SetValue(providerLocation);
}

MutableByteSpan updateToken(mUpdateTokenBuffer);
if (mStorage->LoadUpdateToken(updateToken) == CHIP_NO_ERROR)
{
mUpdateToken = updateToken;
}

if (mStorage->LoadCurrentUpdateState(mCurrentUpdateState) != CHIP_NO_ERROR)
{
mCurrentUpdateState = OTAUpdateStateEnum::kIdle;
}

if (mStorage->LoadTargetVersion(mTargetVersion) != CHIP_NO_ERROR)
{
mTargetVersion = 0;
}
}

// Invoked when the device becomes commissioned
void OTARequestor::OnCommissioningCompleteRequestor(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
Expand Down
Loading