diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index 1789996f2d3279..38dcd3bb3845ac 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -34,3 +34,11 @@ config CHIP_OTA_REQUESTOR imply DFU_TARGET imply STREAM_FLASH imply STREAM_FLASH_ERASE + +config CHIP_OTA_REQUESTOR_BUFFER_SIZE + int "OTA Requestor image buffer size" + default 1024 + depends on CHIP_OTA_REQUESTOR + help + Configures size of the buffer used by OTA Requestor when downloading and + writing a new firmware image to flash. diff --git a/examples/lighting-app/nrfconnect/CMakeLists.txt b/examples/lighting-app/nrfconnect/CMakeLists.txt index 1452ce1d6ef7ee..95cb6d03a915bd 100644 --- a/examples/lighting-app/nrfconnect/CMakeLists.txt +++ b/examples/lighting-app/nrfconnect/CMakeLists.txt @@ -78,7 +78,9 @@ target_sources(app PRIVATE ${GEN_DIR}/lighting-app/zap-generated/CHIPClusters.cpp ${GEN_DIR}/lighting-app/zap-generated/IMClusterCommandHandler.cpp ${NRFCONNECT_COMMON}/util/LEDWidget.cpp - ${NRFCONNECT_COMMON}/util/ThreadUtil.cpp) + ${NRFCONNECT_COMMON}/util/ThreadUtil.cpp + ${CHIP_ROOT}/src/app/clusters/ota-requestor/OTARequestor.cpp + ${CHIP_ROOT}/src/app/clusters/ota-requestor/BDXDownloader.cpp) chip_configure_data_model(app INCLUDE_CLIENT_CALLBACKS diff --git a/examples/lighting-app/nrfconnect/main/AppTask.cpp b/examples/lighting-app/nrfconnect/main/AppTask.cpp index 74efec03379d84..8c4b1391f88dbf 100644 --- a/examples/lighting-app/nrfconnect/main/AppTask.cpp +++ b/examples/lighting-app/nrfconnect/main/AppTask.cpp @@ -23,28 +23,34 @@ #include "LEDWidget.h" #include "LightingManager.h" #include "ThreadUtil.h" -#include -#include #include #include #include +#include +#include #include - #include #include - -#include - #include +#include #include +#if CONFIG_CHIP_OTA_REQUESTOR +#include +#include +#include +#endif + #include #include #include LOG_MODULE_DECLARE(app); +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; + namespace { constexpr int kFactoryResetTriggerTimeout = 3000; @@ -65,10 +71,20 @@ bool sIsThreadProvisioned = false; bool sIsThreadEnabled = false; bool sHaveBLEConnections = false; -} // namespace +#if CONFIG_CHIP_OTA_REQUESTOR +class DummyOTARequestorDriver : public chip::OTARequestorDriver +{ + bool CheckImageDownloadAllowed() override { return true; } + chip::UserConsentAction RequestUserConsent() override { return chip::ImmediateYes; } + void ImageDownloadComplete() override {} +} sOTARequestorDriver; + +OTAImageProcessorImpl sOTAImageProcessor; +chip::BDXDownloader sBDXDownloader; +chip::OTARequestor sOTARequestor; +#endif -using namespace ::chip::Credentials; -using namespace ::chip::DeviceLayer; +} // namespace AppTask AppTask::sAppTask; @@ -119,9 +135,22 @@ int AppTask::Init() PlatformMgr().AddEventHandler(ChipEventHandler, 0); #endif + InitOTARequestor(); + return 0; } +void AppTask::InitOTARequestor() +{ +#if CONFIG_CHIP_OTA_REQUESTOR + sBDXDownloader.SetImageProcessorDelegate(&sOTAImageProcessor); + sOTARequestor.SetOtaRequestorDriver(&sOTARequestorDriver); + sOTARequestor.SetBDXDownloader(&sBDXDownloader); + sOTARequestor.SetServerInstance(&chip::Server::GetInstance()); + chip::SetRequestorInstance(&sOTARequestor); +#endif +} + int AppTask::StartApp() { int ret = Init(); diff --git a/examples/lighting-app/nrfconnect/main/include/AppTask.h b/examples/lighting-app/nrfconnect/main/include/AppTask.h index 12ff22ba963ca3..62c35ba0430d32 100644 --- a/examples/lighting-app/nrfconnect/main/include/AppTask.h +++ b/examples/lighting-app/nrfconnect/main/include/AppTask.h @@ -52,6 +52,7 @@ class AppTask friend AppTask & GetAppTask(void); int Init(); + void InitOTARequestor(); static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); static void ActionCompleted(LightingManager::Action_t aAction, int32_t aActor); diff --git a/src/app/clusters/ota-requestor/ClusterInterface.cpp b/src/app/clusters/ota-requestor/ClusterInterface.cpp index 03bee4216f9fe3..8ae3958a5108b0 100644 --- a/src/app/clusters/ota-requestor/ClusterInterface.cpp +++ b/src/app/clusters/ota-requestor/ClusterInterface.cpp @@ -20,6 +20,7 @@ * to the OTA Requestor object that handles them */ +#include #include // OTA Software Update Requestor Cluster AnnounceOtaProvider Command callback diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 99844744893263..7e4806a0a3f384 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -334,7 +334,7 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr } } -OTARequestor::OTATriggerResult OTARequestor::TriggerImmediateQuery() +OTARequestorInterface::OTATriggerResult OTARequestor::TriggerImmediateQuery() { if (mProviderNodeId != kUndefinedNodeId) diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h index bd342ceb415540..1a300dca43d4a7 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -48,19 +48,12 @@ class OTARequestor : public OTARequestorInterface // Application interface declarations -- start - // Return value for various trigger-type APIs - enum OTATriggerResult - { - kTriggerSuccessful = 0, - kNoProviderKnown = 1 - }; - // Application directs the Requestor to start the Image Query process // and download the new image if available - OTATriggerResult TriggerImmediateQuery(); + OTATriggerResult TriggerImmediateQuery() override; // Send ApplyImage - void ApplyUpdate(); + void ApplyUpdate() override; // A setter for the delegate class pointer void SetOtaRequestorDriver(OTARequestorDriver * driver) { mOtaRequestorDriver = driver; } @@ -95,7 +88,7 @@ class OTARequestor : public OTARequestorInterface // Handler for the AnnounceOTAProvider command EmberAfStatus HandleAnnounceOTAProvider( app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData); + const app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) override; // Virtual functions from OTARequestorInterface -- end /** @@ -120,7 +113,7 @@ class OTARequestor : public OTARequestorInterface * Called to indicate test mode. This is when the Requestor is used as a test tool and the the provider parameters are supplied * explicitly. */ - void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) + void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) override { mProviderNodeId = nodeId; mProviderFabricIndex = fabIndex; diff --git a/src/include/platform/OTARequestorInterface.h b/src/include/platform/OTARequestorInterface.h index 0af7452c51efe8..214fe76af2dc21 100644 --- a/src/include/platform/OTARequestorInterface.h +++ b/src/include/platform/OTARequestorInterface.h @@ -23,8 +23,7 @@ #include #include - -#include +#include #pragma once @@ -35,6 +34,13 @@ namespace chip { class OTARequestorInterface { public: + // Return value for various trigger-type APIs + enum OTATriggerResult + { + kTriggerSuccessful = 0, + kNoProviderKnown = 1 + }; + // Handler for the AnnounceOTAProvider command virtual EmberAfStatus HandleAnnounceOTAProvider( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, @@ -48,6 +54,15 @@ class OTARequestorInterface // Destructor virtual ~OTARequestorInterface() = default; + + // Send QueryImage command + virtual OTATriggerResult TriggerImmediateQuery() = 0; + + // Send ApplyImage command + virtual void ApplyUpdate() = 0; + + // Manually set OTA Provider parameters + virtual void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) = 0; }; // The instance of the class implementing OTARequestorInterface must be managed through diff --git a/src/lib/shell/commands/BUILD.gn b/src/lib/shell/commands/BUILD.gn index 85766987f32a3f..d669f6bf85427c 100644 --- a/src/lib/shell/commands/BUILD.gn +++ b/src/lib/shell/commands/BUILD.gn @@ -54,16 +54,6 @@ source_set("commands") { if (chip_enable_ota_requestor && chip_device_platform != "none") { sources += [ "Ota.cpp" ] configs += [ "${chip_root}/src/controller:config" ] - - if (chip_device_platform == "nrfconnect") { - sources += [ "DFUManager_nrfconnect.h" ] - } - if (chip_device_platform == "esp32") { - sources += [ - "DFUManager_esp32.h", - "OTAUpdater_esp32.h", - ] - } } if (chip_device_platform != "none") { diff --git a/src/lib/shell/commands/DFUManager_esp32.h b/src/lib/shell/commands/DFUManager_esp32.h deleted file mode 100644 index e18c4e04cfe3d2..00000000000000 --- a/src/lib/shell/commands/DFUManager_esp32.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "OTAUpdater_esp32.h" -#include "platform/ESP32/ESP32Utils.h" -#include -#include -#include -#include -#include -#include -#include - -namespace chip { -namespace Shell { - -class DFUManager : public bdx::Initiator -{ -public: - void SetInitialExchange(Messaging::ExchangeContext * ec) { mExchangeCtx = ec; } - CHIP_ERROR ApplyUpdate(); - CHIP_ERROR DiscardUpdate(); - -private: - void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) override; - - uint8_t mDfuBuffer[1024]; - bool mIsTransferComplete = false; -}; - -inline CHIP_ERROR DFUManager::ApplyUpdate() -{ - return DeviceLayer::Internal::ESP32Utils::MapError(OTAUpdater::GetInstance().Apply()); -} - -inline CHIP_ERROR DFUManager::DiscardUpdate() -{ - return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; -} - -inline void DFUManager::HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) -{ - using OutputEventType = bdx::TransferSession::OutputEventType; - using MessageType = bdx::MessageType; - using SendMessageFlags = Messaging::SendMessageFlags; - - CHIP_ERROR err = CHIP_NO_ERROR; - - if (event.EventType != OutputEventType::kNone) - { - ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); - } - - switch (event.EventType) - { - case OutputEventType::kNone: - if (mIsTransferComplete) - { - ChipLogProgress(BDX, "Transfer complete!"); - OTAUpdater::GetInstance().End(); - mTransfer.Reset(); - mIsTransferComplete = false; - } - break; - case OutputEventType::kMsgToSend: { - Messaging::SendFlags sendFlags; - VerifyOrReturn(mExchangeCtx != nullptr, ChipLogError(BDX, "mExchangeContext is null, cannot proceed")); - sendFlags.Set(SendMessageFlags::kFromInitiator, event.msgTypeData.MessageType == to_underlying(MessageType::ReceiveInit)); - sendFlags.Set(SendMessageFlags::kExpectResponse, event.msgTypeData.MessageType != to_underlying(MessageType::BlockAckEOF)); - err = mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData), - sendFlags); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(BDX, "SendMessage() failed: %" CHIP_ERROR_FORMAT, err.Format())); - break; - } - case OutputEventType::kAcceptReceived: { - VerifyOrReturn(CHIP_NO_ERROR == mTransfer.PrepareBlockQuery(), ChipLogError(BDX, "PrepareBlockQuery failed")); - break; - } - case OutputEventType::kBlockReceived: { - ChipLogDetail(BDX, "Got block length %zu", event.blockdata.Length); - if (OTAUpdater::GetInstance().IsInProgress() == false) - { - OTAUpdater::GetInstance().Begin(); - } - // TODO: Process/skip the Matter OTA header - OTAUpdater::GetInstance().Write(reinterpret_cast(event.blockdata.Data), event.blockdata.Length); - if (event.blockdata.IsEof) - { - err = mTransfer.PrepareBlockAck(); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(BDX, "PrepareBlockAck failed: %" CHIP_ERROR_FORMAT, err.Format())); - mIsTransferComplete = true; - } - else - { - err = mTransfer.PrepareBlockQuery(); - VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(BDX, "PrepareBlockQuery failed: %" CHIP_ERROR_FORMAT, err.Format())); - } - break; - } - case OutputEventType::kStatusReceived: { - ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); - OTAUpdater::GetInstance().Abort(); - mTransfer.Reset(); - mExchangeCtx->Close(); - break; - } - case OutputEventType::kInternalError: { - ChipLogError(BDX, "Transfer stopped due to internal error"); - OTAUpdater::GetInstance().Abort(); - mTransfer.Reset(); - mExchangeCtx->Close(); - break; - } - case OutputEventType::kTransferTimeout: { - ChipLogError(BDX, "Transfer timed out"); - OTAUpdater::GetInstance().Abort(); - mTransfer.Reset(); - mExchangeCtx->Close(); - break; - } - case OutputEventType::kInitReceived: - case OutputEventType::kAckReceived: - case OutputEventType::kQueryReceived: - case OutputEventType::kAckEOFReceived: - default: - ChipLogError(BDX, "Unexpected BDX event type: %" PRIu16, static_cast(event.EventType)); - } -} - -} // namespace Shell -} // namespace chip diff --git a/src/lib/shell/commands/DFUManager_nrfconnect.h b/src/lib/shell/commands/DFUManager_nrfconnect.h deleted file mode 100644 index 7a1f49e90a2a71..00000000000000 --- a/src/lib/shell/commands/DFUManager_nrfconnect.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace chip { -namespace Shell { - -class DFUManager : public bdx::Initiator -{ -public: - void SetInitialExchange(Messaging::ExchangeContext * ec) { mExchangeCtx = ec; } - CHIP_ERROR ApplyUpdate(); - CHIP_ERROR DiscardUpdate(); - -private: - void HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) override; - - uint8_t mDfuBuffer[1024]; - bool mIsTransferComplete = false; -}; - -inline CHIP_ERROR DFUManager::ApplyUpdate() -{ - return System::MapErrorZephyr(dfu_target_done(true)); -} - -inline CHIP_ERROR DFUManager::DiscardUpdate() -{ - return System::MapErrorZephyr(dfu_target_reset()); -} - -inline void DFUManager::HandleTransferSessionOutput(bdx::TransferSession::OutputEvent & event) -{ - using OutputEventType = bdx::TransferSession::OutputEventType; - using MessageType = bdx::MessageType; - using SendMessageFlags = Messaging::SendMessageFlags; - - CHIP_ERROR error = CHIP_NO_ERROR; - int status = 0; - - switch (event.EventType) - { - case OutputEventType::kNone: - if (mIsTransferComplete) - { - ChipLogProgress(BDX, "Transfer complete!"); - mTransfer.Reset(); - mIsTransferComplete = false; - } - break; - case OutputEventType::kMsgToSend: { - Messaging::SendFlags flags; - flags.Set(SendMessageFlags::kFromInitiator, event.msgTypeData.MessageType == to_underlying(MessageType::ReceiveInit)); - flags.Set(SendMessageFlags::kExpectResponse, event.msgTypeData.MessageType != to_underlying(MessageType::BlockAckEOF)); - - VerifyOrReturn(mExchangeCtx != nullptr, ChipLogError(BDX, "Exchange context is null")); - error = - mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData), flags); - VerifyOrReturn(error == CHIP_NO_ERROR, ChipLogError(BDX, "SendMessage() failed: %" CHIP_ERROR_FORMAT, error.Format())); - break; - } - case OutputEventType::kAcceptReceived: - ChipLogProgress(BDX, "Starting transfer of %uB", static_cast(event.transferAcceptData.Length)); - - dfu_target_mcuboot_set_buf(mDfuBuffer, sizeof(mDfuBuffer)); - dfu_target_reset(); - status = dfu_target_init(DFU_TARGET_IMAGE_TYPE_MCUBOOT, event.transferAcceptData.Length, nullptr); - VerifyOrReturn(status == 0, ChipLogError(BDX, "dfu_target_init() failed: %d", status)); - - error = mTransfer.PrepareBlockQuery(); - VerifyOrReturn(error == CHIP_NO_ERROR, - ChipLogError(BDX, "PrepareBlockQuery() failed: %" CHIP_ERROR_FORMAT, error.Format())); - break; - case OutputEventType::kBlockReceived: - ChipLogProgress(BDX, "Received %uB (total: %ukB)", static_cast(event.blockdata.Length), - static_cast(mTransfer.GetNumBytesProcessed()) / 1024u); - - status = dfu_target_write(event.blockdata.Data, event.blockdata.Length); - VerifyOrReturn(status == 0, ChipLogError(BDX, "dfu_target_write() failed: %d", status)); - - if (event.blockdata.IsEof) - { - error = mTransfer.PrepareBlockAck(); - VerifyOrReturn(error == CHIP_NO_ERROR, - ChipLogError(BDX, "PrepareBlockAck() failed: %" CHIP_ERROR_FORMAT, error.Format())); - mIsTransferComplete = true; - } - else - { - error = mTransfer.PrepareBlockQuery(); - VerifyOrReturn(error == CHIP_NO_ERROR, - ChipLogError(BDX, "PrepareBlockQuery() failed: %" CHIP_ERROR_FORMAT, error.Format())); - } - break; - case OutputEventType::kStatusReceived: - ChipLogError(BDX, "Received status %" PRIu16, to_underlying(event.statusData.statusCode)); - mTransfer.Reset(); - dfu_target_reset(); - break; - case OutputEventType::kInternalError: - ChipLogError(BDX, "Transfer stopped due to internal error"); - mTransfer.Reset(); - dfu_target_reset(); - break; - case OutputEventType::kTransferTimeout: - ChipLogError(BDX, "Transfer timed out"); - mTransfer.Reset(); - dfu_target_reset(); - break; - default: - ChipLogError(BDX, "Unexpected event type: %" PRIu16, to_underlying(event.EventType)); - break; - } -} - -} // namespace Shell -} // namespace chip diff --git a/src/lib/shell/commands/OTAUpdater_esp32.h b/src/lib/shell/commands/OTAUpdater_esp32.h deleted file mode 100644 index 53e10e951ff3b5..00000000000000 --- a/src/lib/shell/commands/OTAUpdater_esp32.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include "esp_err.h" -#include "esp_log.h" -#include "esp_ota_ops.h" -#include "esp_system.h" - -static const char * TAG = "OTAUpdater"; - -class OTAUpdater -{ -public: - static OTAUpdater & GetInstance(void) - { - static OTAUpdater instance; - return instance; - } - bool IsInProgress(void) { return otaUpdateInProgress; } - esp_err_t Begin(void); - esp_err_t End(void); - esp_err_t Write(const void * data, size_t length); - esp_err_t Abort(void); - esp_err_t Apply(); - -private: - bool otaUpdateInProgress = false; - const esp_partition_t * otaUpdatePartition = nullptr; - esp_ota_handle_t otaUpdateHandle; - uint32_t otaUpdateImageLen = 0; - - OTAUpdater(void) {} - ~OTAUpdater() {} -}; - -inline esp_err_t OTAUpdater::Begin(void) -{ - if (otaUpdateInProgress) - { - ESP_LOGW(TAG, "Already in progress"); - return ESP_ERR_INVALID_STATE; - } - - ESP_LOGI(TAG, "Begin OTA"); - otaUpdatePartition = esp_ota_get_next_update_partition(NULL); - if (otaUpdatePartition == NULL) - { - ESP_LOGE(TAG, "Partition not found"); - return ESP_ERR_NOT_FOUND; - } - ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", otaUpdatePartition->subtype, otaUpdatePartition->address); - - esp_err_t err = esp_ota_begin(otaUpdatePartition, OTA_WITH_SEQUENTIAL_WRITES, &otaUpdateHandle); - if (err != ESP_OK) - { - ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); - return err; - } - otaUpdateImageLen = 0; - otaUpdateInProgress = true; - return ESP_OK; -} - -inline esp_err_t OTAUpdater::Write(const void * data, size_t length) -{ - if (otaUpdateInProgress == false) - { - return ESP_ERR_INVALID_STATE; - } - esp_err_t err = esp_ota_write(otaUpdateHandle, data, length); - if (err != ESP_OK) - { - ESP_LOGE(TAG, "esp_ota_write failed (%s)", esp_err_to_name(err)); - Abort(); - return err; - } - otaUpdateImageLen += length; - ESP_LOGI(TAG, "Written image length %d", otaUpdateImageLen); - return ESP_OK; -} - -inline esp_err_t OTAUpdater::Abort(void) -{ - if (otaUpdateInProgress == false) - { - return ESP_ERR_INVALID_STATE; - } - - ESP_LOGI(TAG, "OTA Abort"); - otaUpdateInProgress = false; - otaUpdateImageLen = 0; - return esp_ota_abort(otaUpdateHandle); -} - -inline esp_err_t OTAUpdater::End(void) -{ - if (otaUpdateInProgress == false) - { - return ESP_ERR_INVALID_STATE; - } - ESP_LOGI(TAG, "OTA image length %d bytes", otaUpdateImageLen); - esp_err_t err = esp_ota_end(otaUpdateHandle); - if (err != ESP_OK) - { - if (err == ESP_ERR_OTA_VALIDATE_FAILED) - { - ESP_LOGE(TAG, "Image validation failed, image is corrupted"); - } - else - { - ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); - } - } - otaUpdateInProgress = false; - return err; -} - -inline esp_err_t OTAUpdater::Apply() -{ - if (otaUpdateInProgress == true) - { - return ESP_ERR_INVALID_STATE; - } - - esp_err_t err = esp_ota_set_boot_partition(otaUpdatePartition); - if (err != ESP_OK) - { - ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); - return err; - } - ESP_LOGI(TAG, "Applying, Boot partition set offset:0x%x", otaUpdatePartition->address); - return ESP_OK; -} diff --git a/src/lib/shell/commands/Ota.cpp b/src/lib/shell/commands/Ota.cpp index 512a8b29888bed..58cbe87b061ba4 100644 --- a/src/lib/shell/commands/Ota.cpp +++ b/src/lib/shell/commands/Ota.cpp @@ -15,377 +15,59 @@ * limitations under the License. */ -#include - -#include -#include -#include -#include -#include -#include #include #include #include -#include -#include #include -#include -#include -#include - -// TODO: Use common software update layer to store the image -#if CHIP_DEVICE_LAYER_TARGET_NRFCONNECT -#include "DFUManager_nrfconnect.h" -#endif - -#if CHIP_DEVICE_LAYER_TARGET_ESP32 -#include "DFUManager_esp32.h" -#endif -#include +#include +#include +#include +#include -using namespace chip; -using namespace chip::app; -using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands; -using namespace chip::bdx; -using namespace chip::Callback; +using namespace chip::DeviceLayer; namespace chip { -namespace Shell { -namespace { - -constexpr const char kRequestorLocation[] = { 'U', 'S' }; -constexpr EmberAfOTADownloadProtocol kRequestorProtocols[] = { EMBER_ZCL_OTA_DOWNLOAD_PROTOCOL_BDX_SYNCHRONOUS }; -constexpr bool kRequestorCanConsent = false; -constexpr uint16_t kBlockSize = 1024; -constexpr uint8_t kMaxUpdateTokenLen = 32; - -struct OTAContext -{ - OperationalDeviceProxy * deviceProxy = nullptr; - Transport::PeerAddress providerAddress = {}; - EndpointId providerEndpointId = 0; - uint8_t updateToken[kMaxUpdateTokenLen] = {}; - uint8_t updateTokenLen = 0; - uint32_t updateVersion = 0; - - void Clear(); -}; - -Shell::Engine sSubShell; -Controller::DeviceControllerInteractionModelDelegate sIMDelegate; -DFUManager sDfuManager; -OTAContext sOtaContext; - -inline void OTAContext::Clear() -{ - if (deviceProxy != nullptr) - { - deviceProxy->Disconnect(); - delete deviceProxy; - } - - *this = {}; -} - -CharSpan ExtractResourceName(CharSpan imageURI) -{ - // TODO: Implement a correct URI parser - constexpr char protocol[] = "bdx://"; - constexpr size_t protocolLen = sizeof(protocol) - 1; - - if (imageURI.size() >= protocolLen && strncmp(protocol, imageURI.data(), imageURI.size()) == 0) - { - imageURI = imageURI.SubSpan(protocolLen, imageURI.size() - protocolLen); - } - - for (size_t i = 0; i < imageURI.size(); ++i) - { - if (imageURI.data()[i] == '/') - { - return imageURI.SubSpan(i + 1, imageURI.size() - i - 1); - } - } - - return imageURI; -} - -void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) -{ - CharSpan imageURI = response.imageURI.ValueOr({}); - ByteSpan updateToken = response.updateToken.ValueOr({}); - CharSpan resourceName = ExtractResourceName(imageURI); - uint32_t version = response.softwareVersion.ValueOr(0u); - ChipLogProgress(SoftwareUpdate, "Received QueryImage response: %" PRIu16, static_cast(response.status)); - ChipLogProgress(SoftwareUpdate, " Image URI: %.*s", static_cast(imageURI.size()), imageURI.data()); - ChipLogProgress(SoftwareUpdate, " Resource: %.*s", static_cast(resourceName.size()), resourceName.data()); - ChipLogProgress(SoftwareUpdate, " Version: %" PRIu32, version); - - if (updateToken.size() > kMaxUpdateTokenLen) - { - ChipLogError(SoftwareUpdate, "Update token too long"); - return; - } - - memcpy(sOtaContext.updateToken, updateToken.data(), updateToken.size()); - sOtaContext.updateTokenLen = static_cast(updateToken.size()); - sOtaContext.updateVersion = version; - - OperationalDeviceProxy * deviceProxy = sOtaContext.deviceProxy; - VerifyOrReturn(deviceProxy != nullptr); - - Messaging::ExchangeManager * exchangeMgr = deviceProxy->GetExchangeManager(); - Optional session = deviceProxy->GetSecureSession(); - Messaging::ExchangeContext * exchangeCtx = nullptr; - - if (exchangeMgr != nullptr && session.HasValue()) - { - exchangeCtx = exchangeMgr->NewContext(session.Value(), &sDfuManager); - } - - if (exchangeCtx == nullptr) - { - ChipLogError(SoftwareUpdate, "Failed to allocate exchange"); - return; - } - - TransferSession::TransferInitData initOptions; - initOptions.TransferCtlFlags = TransferControlFlags::kReceiverDrive; - initOptions.MaxBlockSize = kBlockSize; - initOptions.FileDesLength = resourceName.size(); - initOptions.FileDesignator = reinterpret_cast(resourceName.data()); - - sDfuManager.SetInitialExchange(exchangeCtx); - CHIP_ERROR error = sDfuManager.InitiateTransfer(&DeviceLayer::SystemLayer(), TransferRole::kReceiver, initOptions, - System::Clock::Seconds16(20)); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Failed to initiate BDX transfer: %" CHIP_ERROR_FORMAT, error.Format()); - } -} - -void OnQueryImageFailure(void * /* context */, EmberAfStatus status) +// TODO: Remove weak functions when ESP32 all-clusters-app includes code from src/app/clusters/ota-requestor +// The code requires OTA Provider client cluster which interferes with OTA Provider server cluster, +// already enabled in the all-clusters-app. +__attribute__((weak)) void SetRequestorInstance(OTARequestorInterface * instance) {} +__attribute__((weak)) OTARequestorInterface * GetRequestorInstance() { - ChipLogError(SoftwareUpdate, "QueryImage failed: %" PRIu16, static_cast(status)); + return nullptr; } -} // namespace -} // namespace Shell -} // namespace chip - -#include -namespace chip { namespace Shell { namespace { -void OnQueryImageConnection(void * /* context */, OperationalDeviceProxy * deviceProxy) -{ - // Initialize cluster object - Controller::OtaSoftwareUpdateProviderCluster cluster; - CHIP_ERROR error = cluster.Associate(deviceProxy, sOtaContext.providerEndpointId); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Associate failed: %" CHIP_ERROR_FORMAT, error.Format()); - return; - } - - // Send QueryImage command - uint16_t vendorId = 0; - uint16_t productId = 0; - uint16_t hardwareVersion = 0; - uint16_t softwareVersion = 0; - - DeviceLayer::ConfigurationMgr().GetVendorId(vendorId); - DeviceLayer::ConfigurationMgr().GetProductId(productId); - DeviceLayer::ConfigurationMgr().GetHardwareVersion(hardwareVersion); - DeviceLayer::ConfigurationMgr().GetSoftwareVersion(softwareVersion); - - QueryImage::Type request; - request.vendorId = static_cast(vendorId); - request.productId = productId; - request.softwareVersion = softwareVersion; - request.protocolsSupported = kRequestorProtocols; - request.hardwareVersion.SetValue(hardwareVersion); - request.location.Emplace(kRequestorLocation); - request.requestorCanConsent.SetValue(kRequestorCanConsent); - error = cluster.InvokeCommand(request, /* context */ nullptr, OnQueryImageResponse, OnQueryImageFailure); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "QueryImage failed: %" CHIP_ERROR_FORMAT, error.Format()); - } -} - -void OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response) -{ - ChipLogProgress(SoftwareUpdate, "Received ApplyUpdate response: %" PRIu16, static_cast(response.action)); - - switch (response.action) - { - case EMBER_ZCL_OTA_APPLY_UPDATE_ACTION_PROCEED: { - CHIP_ERROR error = sDfuManager.ApplyUpdate(); - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Failed to apply update: %" CHIP_ERROR_FORMAT, error.Format()); - } - break; - } - case EMBER_ZCL_OTA_APPLY_UPDATE_ACTION_DISCONTINUE: { - CHIP_ERROR error = sDfuManager.DiscardUpdate(); - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Failed to discard update: %" CHIP_ERROR_FORMAT, error.Format()); - } - break; - } - default: - break; - } -} - -void OnApplyUpdateFailure(void * /* context */, EmberAfStatus status) -{ - ChipLogError(SoftwareUpdate, "ApplyUpdate failed: %" PRIu16, static_cast(status)); -} - -void OnApplyUpdateConnection(void * /* context */, OperationalDeviceProxy * deviceProxy) -{ - // Initialize cluster object - Controller::OtaSoftwareUpdateProviderCluster cluster; - CHIP_ERROR error = cluster.Associate(deviceProxy, sOtaContext.providerEndpointId); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "Associate failed: %" CHIP_ERROR_FORMAT, error.Format()); - return; - } - - // Send QueryImage command - uint16_t vendorId = 0; - uint16_t productId = 0; - uint16_t hardwareVersion = 0; - uint16_t softwareVersion = 0; - - DeviceLayer::ConfigurationMgr().GetVendorId(vendorId); - DeviceLayer::ConfigurationMgr().GetProductId(productId); - DeviceLayer::ConfigurationMgr().GetHardwareVersion(hardwareVersion); - DeviceLayer::ConfigurationMgr().GetSoftwareVersion(softwareVersion); - - ApplyUpdateRequest::Type request; - request.updateToken = ByteSpan(sOtaContext.updateToken, sOtaContext.updateTokenLen); - request.newVersion = sOtaContext.updateVersion; - - error = cluster.InvokeCommand(request, /* context */ nullptr, OnApplyUpdateResponse, OnApplyUpdateFailure); - - if (error != CHIP_NO_ERROR) - { - ChipLogError(SoftwareUpdate, "ApplyUpdate failed: %" CHIP_ERROR_FORMAT, error.Format()); - } -} - -void OnConnectionFailure(void * /* context */, NodeId nodeId, CHIP_ERROR error) -{ - ChipLogError(SoftwareUpdate, "Connection failed: %" CHIP_ERROR_FORMAT, error.Format()); -} - -template -void ConnectDeviceAsync(intptr_t) -{ - static Callback::Callback successCallback(OnConnected, nullptr); - static Callback::Callback failureCallback(OnConnectionFailure, nullptr); - - OperationalDeviceProxy * deviceProxy = sOtaContext.deviceProxy; - VerifyOrReturn(deviceProxy != nullptr); - - deviceProxy->UpdateDeviceData(sOtaContext.providerAddress, deviceProxy->GetMRPConfig()); - deviceProxy->Connect(&successCallback, &failureCallback, nullptr); -} - -template -CHIP_ERROR ConnectProvider(FabricIndex fabricIndex, NodeId nodeId, const Transport::PeerAddress & address) -{ - // Allocate new device proxy - FabricInfo * fabric = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); - VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - - DeviceProxyInitParams initParams = { .sessionManager = &Server::GetInstance().GetSecureSessionManager(), - .exchangeMgr = &Server::GetInstance().GetExchangeManager(), - .idAllocator = &Server::GetInstance().GetSessionIDAllocator(), - .fabricTable = &Server::GetInstance().GetFabricTable(), - .imDelegate = &sIMDelegate }; - - auto deviceProxy = Platform::New(initParams, fabric->GetPeerIdForNode(nodeId)); - VerifyOrReturnError(deviceProxy != nullptr, CHIP_ERROR_NO_MEMORY); - - // Initiate the connection and send QueryImage command using CHIP thread - streamer_printf(streamer_get(), "Connecting...\r\n"); - - sOtaContext.deviceProxy = deviceProxy; - sOtaContext.providerAddress = address; - DeviceLayer::PlatformMgr().ScheduleWork(ConnectDeviceAsync); - return CHIP_NO_ERROR; -} +Shell::Engine sSubShell; CHIP_ERROR QueryImageHandler(int argc, char ** argv) { - VerifyOrReturnError(argc == 5, CHIP_ERROR_INVALID_ARGUMENT); + 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)); - const char * ipAddressStr = argv[3]; - const uint16_t udpPort = static_cast(strtoul(argv[4], nullptr, 10)); - - Inet::IPAddress ipAddress; - VerifyOrReturnError(Inet::IPAddress::FromString(ipAddressStr, ipAddress), CHIP_ERROR_INVALID_ARGUMENT); - - sOtaContext.Clear(); - sOtaContext.providerEndpointId = providerEndpointId; - - return ConnectProvider(fabricIndex, providerNodeId, Transport::PeerAddress::UDP(ipAddress, udpPort)); -} -CHIP_ERROR ShowUpdateHandler(int argc, char ** argv) -{ - VerifyOrReturnError(argc == 0, CHIP_ERROR_INVALID_ARGUMENT); - - char token[kMaxUpdateTokenLen * 2 + 1]; - ReturnErrorOnFailure( - Encoding::BytesToUppercaseHexString(sOtaContext.updateToken, sOtaContext.updateTokenLen, token, sizeof(token))); - - streamer_printf(streamer_get(), "Token: %s\r\n", token); - streamer_printf(streamer_get(), "Version: %" PRIu32 "\r\n", sOtaContext.updateVersion); + GetRequestorInstance()->TestModeSetProviderParameters(providerNodeId, fabricIndex, providerEndpointId); + PlatformMgr().ScheduleWork([](intptr_t) { GetRequestorInstance()->TriggerImmediateQuery(); }); return CHIP_NO_ERROR; } CHIP_ERROR ApplyImageHandler(int argc, char ** argv) { - VerifyOrReturnError(argc == 7, CHIP_ERROR_INVALID_ARGUMENT); + 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)); - const char * ipAddressStr = argv[3]; - const uint16_t udpPort = static_cast(strtoul(argv[4], nullptr, 10)); - const char * updateTokenStr = argv[5]; - const uint32_t updateVersion = static_cast(strtoul(argv[6], nullptr, 10)); - - Inet::IPAddress ipAddress; - VerifyOrReturnError(Inet::IPAddress::FromString(ipAddressStr, ipAddress), CHIP_ERROR_INVALID_ARGUMENT); - uint8_t updateToken[kMaxUpdateTokenLen]; - size_t updateTokenLen = Encoding::HexToBytes(updateTokenStr, strlen(updateTokenStr), updateToken, sizeof(updateToken)); - VerifyOrReturnError(updateTokenLen > 0, CHIP_ERROR_INVALID_ARGUMENT); - - sOtaContext.Clear(); - sOtaContext.providerEndpointId = providerEndpointId; - sOtaContext.updateTokenLen = updateTokenLen; - sOtaContext.updateVersion = updateVersion; - memcpy(sOtaContext.updateToken, updateToken, updateTokenLen); - - return ConnectProvider(fabricIndex, providerNodeId, Transport::PeerAddress::UDP(ipAddress, udpPort)); + GetRequestorInstance()->TestModeSetProviderParameters(providerNodeId, fabricIndex, providerEndpointId); + PlatformMgr().ScheduleWork([](intptr_t) { GetRequestorInstance()->ApplyUpdate(); }); + return CHIP_NO_ERROR; } CHIP_ERROR OtaHandler(int argc, char ** argv) @@ -411,12 +93,9 @@ void RegisterOtaCommands() { // Register subcommands of the `ota` commands. static const shell_command_t subCommands[] = { - { &QueryImageHandler, "query", - "Query for a new image. Usage: ota query " }, - { &ShowUpdateHandler, "show-update", "Print the current update information" }, + { &QueryImageHandler, "query", "Query for a new image. Usage: ota query " }, { &ApplyImageHandler, "apply", - "Apply the current update. Usage ota apply " - " " }, + "Apply the current update. Usage ota apply " }, }; sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); diff --git a/src/platform/nrfconnect/BUILD.gn b/src/platform/nrfconnect/BUILD.gn index d2296794185015..0bf154e0ded158 100644 --- a/src/platform/nrfconnect/BUILD.gn +++ b/src/platform/nrfconnect/BUILD.gn @@ -70,4 +70,11 @@ static_library("nrfconnect") { "NFCManagerImpl.h", ] } + + if (chip_enable_ota_requestor) { + sources += [ + "OTAImageProcessorImpl.cpp", + "OTAImageProcessorImpl.h", + ] + } } diff --git a/src/platform/nrfconnect/OTAImageProcessorImpl.cpp b/src/platform/nrfconnect/OTAImageProcessorImpl.cpp new file mode 100644 index 00000000000000..a01417e956e247 --- /dev/null +++ b/src/platform/nrfconnect/OTAImageProcessorImpl.cpp @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OTAImageProcessorImpl.h" + +#include +#include + +#include +#include + +namespace chip { +namespace DeviceLayer { + +CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() +{ + ReturnErrorOnFailure(System::MapErrorZephyr(dfu_target_mcuboot_set_buf(mBuffer, sizeof(mBuffer)))); + ReturnErrorOnFailure(System::MapErrorZephyr(dfu_target_reset())); + return System::MapErrorZephyr(dfu_target_init(DFU_TARGET_IMAGE_TYPE_MCUBOOT, /* size */ 0, nullptr)); +} + +CHIP_ERROR OTAImageProcessorImpl::Finalize() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Abort() +{ + return System::MapErrorZephyr(dfu_target_reset()); +} + +CHIP_ERROR OTAImageProcessorImpl::Apply() +{ + return System::MapErrorZephyr(dfu_target_done(true)); +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) +{ + return System::MapErrorZephyr(dfu_target_write(block.data(), block.size())); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nrfconnect/OTAImageProcessorImpl.h b/src/platform/nrfconnect/OTAImageProcessorImpl.h new file mode 100644 index 00000000000000..ef510f06f3c219 --- /dev/null +++ b/src/platform/nrfconnect/OTAImageProcessorImpl.h @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace DeviceLayer { + +class OTAImageProcessorImpl : public OTAImageProcessorInterface +{ +public: + static constexpr size_t kBufferSize = CONFIG_CHIP_OTA_REQUESTOR_BUFFER_SIZE; + + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Abort() override; + CHIP_ERROR Apply() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + +private: + uint8_t mBuffer[kBufferSize]; +}; + +} // namespace DeviceLayer +} // namespace chip