Skip to content

Commit

Permalink
[OTA] Add support for applying image for Linux/Darwin platform
Browse files Browse the repository at this point in the history
  • Loading branch information
carol-apple committed Mar 21, 2022
1 parent 193b4d0 commit f947007
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 76 deletions.
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 "v1.0"
#endif

//
// Default of 8 ECs is not sufficient for some of the unit tests
// that try to validate multiple simultaneous interactions.
Expand Down
18 changes: 18 additions & 0 deletions examples/ota-requestor-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,23 @@ 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;
}

char execFilePathBuf[kMaxFilePathSize];
strncpy(execFilePathBuf, kImageExecPath, strlen(kImageExecPath));
argv[0] = execFilePathBuf;
execv(argv[0], argv);

// If successfully executing the new iamge, execv should not return
ChipLogError(SoftwareUpdate, "The OTA image is invalid");
}
return 0;
}
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)
{
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
127 changes: 116 additions & 11 deletions src/app/clusters/ota-requestor/OTARequestor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,40 @@ 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);
if (requestorCore->mCurrentUpdateState != OTAUpdateStateEnum::kApplying)
{
// This results in the initial periodic timer kicking off
requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);
}
else
{
// This may have been a reboot from applying an image
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 +187,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 +280,11 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error
requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, error);
}

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 +397,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 +536,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 +556,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,19 +821,53 @@ 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()
{
// 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 (mUpdateToken.size() > 0)
{
error = mStorage->StoreUpdateToken(mUpdateToken);
}
else
{
error = mStorage->ClearUpdateToken();
}
}

if (error == CHIP_NO_ERROR)
{
error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState);
}

if (error == CHIP_NO_ERROR)
{
mStorage->StoreUpdateToken(mUpdateToken);
if (mTargetVersion > 0)
{
error = mStorage->StoreTargetVersion(mTargetVersion);
}
else
{
error = mStorage->ClearTargetVersion();
}
}

if (error != CHIP_NO_ERROR)
Expand All @@ -809,6 +876,44 @@ void OTARequestor::StoreCurrentUpdateInfo()
}
}

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::kUnknown;
}

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

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

// Invoked when the device becomes commissioned
void OTARequestor::OnCommissioningCompleteRequestor(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
Expand Down
62 changes: 23 additions & 39 deletions src/app/clusters/ota-requestor/OTARequestor.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe
OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {}

//////////// OTARequestorInterface Implementation ///////////////
void Shutdown(void) override;

EmberAfStatus HandleAnnounceOTAProvider(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) override;
Expand All @@ -63,15 +65,19 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe
// Initiate the session to send NotifyUpdateApplied command
void NotifyUpdateApplied() override;

// Get image update progress in percents unit
CHIP_ERROR GetUpdateProgress(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress) override;
// Get the value of the UpdateStateProgress attribute (in percentage) of the OTA Software Update Requestor Cluster on the given
// endpoint
CHIP_ERROR GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress) override;

// Get requestor state
CHIP_ERROR GetState(EndpointId endpointId, OTAUpdateStateEnum & state) override;
// Get the value of the UpdateState attribute of the OTA Software Update Requestor Cluster on the given endpoint
CHIP_ERROR GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state) override;

// Get the current state of the OTA update
OTAUpdateStateEnum GetCurrentUpdateState() override { return mCurrentUpdateState; }

// Get the target version of the OTA update
uint32_t GetTargetVersion() override { return mTargetVersion; }

// Application directs the Requestor to cancel image update in progress. All the Requestor state is
// cleared, UpdateState is reset to Idle
void CancelImageUpdate() override;
Expand Down Expand Up @@ -100,42 +106,10 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe
//////////// OTARequestor public APIs ///////////////

/**
* Called to perform some initialization including:
* - Set server instance used to get access to the system resources necessary to open CASE sessions and drive
* BDX transfers
* - Set the OTA requestor driver instance used to communicate download progress and errors
* - Set the BDX downloader instance used for initiating BDX downloads
* Called to perform some initialization. Note that some states that must be initalized in the CHIP context will be deferred to
* InitState.
*/
CHIP_ERROR 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));

storage.LoadDefaultProviders(mDefaultOtaProviderList);

ProviderLocationType providerLocation;
if (storage.LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR)
{
mProviderLocation.SetValue(providerLocation);
}

MutableByteSpan updateToken(mUpdateTokenBuffer);
if (storage.LoadUpdateToken(updateToken) == CHIP_NO_ERROR)
{
mUpdateToken = updateToken;
}

// 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));
}
CHIP_ERROR Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver, BDXDownloader & downloader);

private:
using QueryImageResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType;
Expand Down Expand Up @@ -285,6 +259,16 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe
*/
void StoreCurrentUpdateInfo();

/**
* Load current update information to KVS
*/
void LoadCurrentUpdateInfo();

/**
* Reset the states for the next OTA update
*/
void Reset();

/**
* Session connection callbacks
*/
Expand Down
Loading

0 comments on commit f947007

Please sign in to comment.