From 7877748293f922cdf837ba16356f08b99359e3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Kr=C3=B3lik?= <66667989+Damian-Nordic@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:26:03 +0100 Subject: [PATCH] [ota-requestor] Add "ota query" shell command (#11810) * Enable OTA Provider client cluster in lighting-app * Include client callbacks * Add shell commands for OTA requestor role ota query Connect to OTA provider node and query a new software/firmware image. If available, automatically download and apply the upgrade. For nRF Connect platform use DFU Target library to store a downloaded image in the secondary image slot in flash, so that it can be swapped with the primary/current image by MCUBoot bootloader on a subsequent boot. * Enable OTA requestor for lighting-app * Restyled by gn * Enable OTA Requestor only when built with BUILD_WITH_DFU * Address review comments * Add endpoint-id as another "query" command argument. * Add "show-update" command to view udpate token of the current update. * Add "apply" command for sending ApplyUpdate request. * Re-generate ZAP Co-authored-by: Restyled.io --- .../nrfconnect/app/overlay-ota_requestor.conf | 17 + config/nrfconnect/chip-module/CMakeLists.txt | 1 + config/nrfconnect/chip-module/Kconfig | 9 +- config/zephyr/Kconfig | 7 + .../lighting-common/lighting-app.zap | 78 ++++ .../lighting-app/nrfconnect/CMakeLists.txt | 5 + src/app/DeviceProxy.h | 2 + src/app/chip_data_model.cmake | 9 +- src/lib/shell/Commands.h | 6 + src/lib/shell/Engine.cpp | 3 + src/lib/shell/MainLoopZephyr.cpp | 2 +- src/lib/shell/commands/BUILD.gn | 11 +- .../shell/commands/DFUManager_nrfconnect.h | 142 ++++++ src/lib/shell/commands/Ota.cpp | 420 ++++++++++++++++++ src/platform/BUILD.gn | 4 + src/platform/device.gni | 3 + src/protocols/bdx/BdxTransferSession.h | 1 + .../zap-generated/CHIPClientCallbacks.cpp | 42 ++ .../zap-generated/CHIPClientCallbacks.h | 5 + .../zap-generated/CHIPClusters.cpp | 152 +++++++ .../lighting-app/zap-generated/CHIPClusters.h | 21 + .../zap-generated/IMClusterCommandHandler.cpp | 195 ++++++++ .../PluginApplicationCallbacks.h | 1 + .../zap-generated/callback-stub.cpp | 8 + .../zap-generated/endpoint_config.h | 7 +- .../lighting-app/zap-generated/gen_config.h | 5 + 26 files changed, 1150 insertions(+), 6 deletions(-) create mode 100644 config/nrfconnect/app/overlay-ota_requestor.conf create mode 100644 src/lib/shell/commands/DFUManager_nrfconnect.h create mode 100644 src/lib/shell/commands/Ota.cpp diff --git a/config/nrfconnect/app/overlay-ota_requestor.conf b/config/nrfconnect/app/overlay-ota_requestor.conf new file mode 100644 index 00000000000000..51f0acd210a330 --- /dev/null +++ b/config/nrfconnect/app/overlay-ota_requestor.conf @@ -0,0 +1,17 @@ +# +# 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. +# + +CONFIG_CHIP_OTA_REQUESTOR=y diff --git a/config/nrfconnect/chip-module/CMakeLists.txt b/config/nrfconnect/chip-module/CMakeLists.txt index b78bb83f021844..4852fefa702fe2 100644 --- a/config/nrfconnect/chip-module/CMakeLists.txt +++ b/config/nrfconnect/chip-module/CMakeLists.txt @@ -196,6 +196,7 @@ chip_gn_arg_bool ("chip_enable_openthread" CONFIG_NET_L2_OPENTH chip_gn_arg_bool ("chip_config_network_layer_ble" CONFIG_BT) chip_gn_arg_bool ("chip_inet_config_enable_ipv4" CONFIG_NET_IPV4) chip_gn_arg_bool ("chip_enable_nfc" CONFIG_CHIP_NFC_COMMISSIONING) +chip_gn_arg_bool ("chip_enable_ota_requestor" CONFIG_CHIP_OTA_REQUESTOR) chip_gn_arg_bool ("chip_build_tests" CONFIG_CHIP_BUILD_TESTS) chip_gn_arg_bool ("chip_monolithic_tests" CONFIG_CHIP_BUILD_TESTS) chip_gn_arg_bool ("chip_inet_config_enable_tcp_endpoint" CONFIG_CHIP_BUILD_TESTS) diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index 0969370e93f5a5..1789996f2d3279 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -26,4 +26,11 @@ config CHIP_NFC_COMMISSIONING imply NFC_NDEF_URI_REC imply NFC_NDEF_URI_MSG help - Enables NFC commissioning by sharing onboarding payload in NFC tag. \ No newline at end of file + Enables NFC commissioning by sharing onboarding payload in NFC tag. + +# See config/zephyr/Kconfig for full definition +config CHIP_OTA_REQUESTOR + bool + imply DFU_TARGET + imply STREAM_FLASH + imply STREAM_FLASH_ERASE diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig index 63aa1b37cde36b..63b9e727d17fbb 100644 --- a/config/zephyr/Kconfig +++ b/config/zephyr/Kconfig @@ -101,6 +101,13 @@ config CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT help Enables Thread Sleepy End Device support in Matter. +config CHIP_OTA_REQUESTOR + bool "Enable OTA requestor" + help + Enables OTA (Over-the-air) Requestor role that allows a device to perform + Device Firmware Upgrade by quering and downloading a new firmware image + from an external OTA Provider node. + config APP_LINK_WITH_CHIP bool "Link 'app' with Connected Home over IP" default y diff --git a/examples/lighting-app/lighting-common/lighting-app.zap b/examples/lighting-app/lighting-common/lighting-app.zap index 226d9e2a50cb7e..9f2bb1b57128c3 100644 --- a/examples/lighting-app/lighting-common/lighting-app.zap +++ b/examples/lighting-app/lighting-common/lighting-app.zap @@ -1276,6 +1276,84 @@ } ] }, + { + "name": "OTA Software Update Provider", + "code": 41, + "mfgCode": null, + "define": "OTA_PROVIDER_CLUSTER", + "side": "client", + "enabled": 1, + "commands": [ + { + "name": "QueryImage", + "code": 0, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "ApplyUpdateRequest", + "code": 1, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "NotifyUpdateApplied", + "code": 2, + "mfgCode": null, + "source": "client", + "incoming": 0, + "outgoing": 1 + } + ], + "attributes": [ + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "included": 0, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 0, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "OTA Software Update Provider", + "code": 41, + "mfgCode": null, + "define": "OTA_PROVIDER_CLUSTER", + "side": "server", + "enabled": 0, + "commands": [ + { + "name": "QueryImageResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "ApplyUpdateResponse", + "code": 4, + "mfgCode": null, + "source": "server", + "incoming": 1, + "outgoing": 0 + } + ], + "attributes": [] + }, { "name": "General Commissioning", "code": 48, diff --git a/examples/lighting-app/nrfconnect/CMakeLists.txt b/examples/lighting-app/nrfconnect/CMakeLists.txt index a5df33a082121f..698c0bd65621de 100644 --- a/examples/lighting-app/nrfconnect/CMakeLists.txt +++ b/examples/lighting-app/nrfconnect/CMakeLists.txt @@ -37,6 +37,7 @@ endif() option(BUILD_WITH_DFU "Build target with Device Firmware Upgrade support" OFF) if(BUILD_WITH_DFU) + list(INSERT OVERLAY_CONFIG 0 ${CHIP_ROOT}/config/nrfconnect/app/overlay-ota_requestor.conf) if(${BOARD} STREQUAL "nrf5340dk_nrf5340_cpuapp") list(INSERT OVERLAY_CONFIG 0 ${CHIP_ROOT}/config/nrfconnect/app/overlay-multi_image_dfu_support.conf) set(mcuboot_OVERLAY_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/configuration/mcuboot_multi_image_dfu.conf CACHE INTERNAL "") @@ -61,6 +62,7 @@ target_compile_options(app PRIVATE -Werror) target_include_directories(app PRIVATE main/include ${LIGHTING_COMMON} + ${GEN_DIR} ${GEN_DIR}/app-common ${GEN_DIR}/lighting-app ${NLIO_ROOT} @@ -74,13 +76,16 @@ target_sources(app PRIVATE main/ZclCallbacks.cpp ${GEN_DIR}/lighting-app/zap-generated/attribute-size.cpp ${GEN_DIR}/lighting-app/zap-generated/callback-stub.cpp + ${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) chip_configure_data_model(app + INCLUDE_CLIENT_CALLBACKS INCLUDE_SERVER ZAP_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../lighting-common/lighting-app.zap + GEN_DIR ${GEN_DIR}/lighting-app/zap-generated ) if(BUILD_WITH_DFU) diff --git a/src/app/DeviceProxy.h b/src/app/DeviceProxy.h index 9e4a6d0b90b659..87e2e1e8c6650b 100644 --- a/src/app/DeviceProxy.h +++ b/src/app/DeviceProxy.h @@ -81,6 +81,8 @@ class DLL_EXPORT DeviceProxy virtual bool IsActive() const { return true; } + uint32_t GetMRPIdleInterval() const { return mMrpIdleInterval; } + uint32_t GetMRPActiveInterval() const { return mMrpActiveInterval; } void GetMRPIntervals(uint32_t & idleInterval, uint32_t & activeInterval) const { idleInterval = mMrpIdleInterval; diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index f0f68443fdf191..ab31d7fa06829c 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -54,7 +54,14 @@ endfunction() # supported by the application. # function(chip_configure_data_model APP_TARGET) - cmake_parse_arguments(ARG "INCLUDE_SERVER" "ZAP_FILE" "" ${ARGN}) + cmake_parse_arguments(ARG "INCLUDE_CLIENT_CALLBACKS;INCLUDE_SERVER" "ZAP_FILE;GEN_DIR" "" ${ARGN}) + + if (ARG_INCLUDE_CLIENT_CALLBACKS) + target_sources(${APP_TARGET} PRIVATE + ${CHIP_APP_BASE_DIR}/util/im-client-callbacks.cpp + ${ARG_GEN_DIR}/CHIPClientCallbacks.cpp + ) + endif() if (ARG_INCLUDE_SERVER) target_sources(${APP_TARGET} PRIVATE diff --git a/src/lib/shell/Commands.h b/src/lib/shell/Commands.h index f5bbf9b34c6445..35b12b5382ecfd 100644 --- a/src/lib/shell/Commands.h +++ b/src/lib/shell/Commands.h @@ -50,6 +50,12 @@ void RegisterConfigCommands(); */ void RegisterDeviceCommands(); +/** + * This function registers the OTA (Over-the-Air) software update commands. + * + */ +void RegisterOtaCommands(); + /** * This function registers the device onboarding codes commands. * diff --git a/src/lib/shell/Engine.cpp b/src/lib/shell/Engine.cpp index 4ed7bd5692a68c..5c7b10bf4364c0 100644 --- a/src/lib/shell/Engine.cpp +++ b/src/lib/shell/Engine.cpp @@ -114,6 +114,9 @@ void Engine::RegisterDefaultCommands() #if CHIP_DEVICE_CONFIG_ENABLE_DNSSD RegisterDnsCommands(); #endif +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + RegisterOtaCommands(); +#endif } } // namespace Shell diff --git a/src/lib/shell/MainLoopZephyr.cpp b/src/lib/shell/MainLoopZephyr.cpp index fc7dc9809be373..e70a29c52c9dce 100644 --- a/src/lib/shell/MainLoopZephyr.cpp +++ b/src/lib/shell/MainLoopZephyr.cpp @@ -37,7 +37,7 @@ static int RegisterCommands(const struct device * dev) SYS_INIT(RegisterCommands, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); -SHELL_CMD_ARG_REGISTER(matter, NULL, "Matter commands", cmd_matter, 1, 4); +SHELL_CMD_ARG_REGISTER(matter, NULL, "Matter commands", cmd_matter, 1, 10); namespace chip { namespace Shell { diff --git a/src/lib/shell/commands/BUILD.gn b/src/lib/shell/commands/BUILD.gn index 83cf85cf15d560..37dab9d07a7d63 100644 --- a/src/lib/shell/commands/BUILD.gn +++ b/src/lib/shell/commands/BUILD.gn @@ -25,6 +25,8 @@ source_set("commands") { "Meta.cpp", ] + public_deps = [ "${chip_root}/src/lib/shell:shell_core" ] + if (chip_device_platform != "none") { sources += [ "Config.cpp", @@ -49,7 +51,14 @@ source_set("commands") { sources += [ "Dns.cpp" ] } - public_deps = [ "${chip_root}/src/lib/shell:shell_core" ] + 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 != "none") { public_deps += [ "${chip_root}/src/app/server" ] diff --git a/src/lib/shell/commands/DFUManager_nrfconnect.h b/src/lib/shell/commands/DFUManager_nrfconnect.h new file mode 100644 index 00000000000000..7a1f49e90a2a71 --- /dev/null +++ b/src/lib/shell/commands/DFUManager_nrfconnect.h @@ -0,0 +1,142 @@ +/* + * + * 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/Ota.cpp b/src/lib/shell/commands/Ota.cpp new file mode 100644 index 00000000000000..4d0ed87dd3443d --- /dev/null +++ b/src/lib/shell/commands/Ota.cpp @@ -0,0 +1,420 @@ +/* + * + * 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 + +#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 + +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands; +using namespace chip::bdx; +using namespace chip::Callback; + +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) +{ + ChipLogError(SoftwareUpdate, "QueryImage failed: %" PRIu16, static_cast(status)); +} + +void OnQueryImageConnection(void * /* context */, DeviceProxy * 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().GetProductRevision(hardwareVersion); + DeviceLayer::ConfigurationMgr().GetFirmwareRevision(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 */, DeviceProxy * 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().GetProductRevision(hardwareVersion); + DeviceLayer::ConfigurationMgr().GetFirmwareRevision(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->GetMRPIdleInterval(), + deviceProxy->GetMRPActiveInterval()); + deviceProxy->Connect(&successCallback, &failureCallback); +} + +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(), + .fabricInfo = fabric, + .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; +} + +CHIP_ERROR QueryImageHandler(int argc, char ** argv) +{ + VerifyOrReturnError(argc == 5, 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); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ApplyImageHandler(int argc, char ** argv) +{ + VerifyOrReturnError(argc == 7, 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)); +} + +CHIP_ERROR OtaHandler(int argc, char ** argv) +{ + if (argc == 0) + { + sSubShell.ForEachCommand(PrintCommandHelp, nullptr); + return CHIP_NO_ERROR; + } + + CHIP_ERROR error = sSubShell.ExecCommand(argc, argv); + + if (error != CHIP_NO_ERROR) + { + streamer_printf(streamer_get(), "Error: %" CHIP_ERROR_FORMAT "\r\n", error.Format()); + } + + return error; +} +} // namespace + +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" }, + { &ApplyImageHandler, "apply", + "Apply the current update. Usage ota apply " + " " }, + }; + + sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); + + // Register the root `ota` command in the top-level shell. + static const shell_command_t otaCommand = { &OtaHandler, "ota", "OTA commands" }; + + Engine::Root().RegisterCommands(&otaCommand, 1); +} + +} // namespace Shell +} // namespace chip diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index c3ae225822d412..21a448c89d351e 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -115,6 +115,10 @@ if (chip_device_platform != "none") { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_NFC=1" ] } + if (chip_enable_ota_requestor) { + defines += [ "CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR=1" ] + } + if (chip_device_project_config_include != "") { defines += [ "CHIP_DEVICE_PROJECT_CONFIG_INCLUDE=${chip_device_project_config_include}" ] } diff --git a/src/platform/device.gni b/src/platform/device.gni index 2dce770b78cb10..a6a66f67eddc45 100755 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -63,6 +63,9 @@ declare_args() { # Enable NFC support chip_enable_nfc = false + # Enable OTA requestor support + chip_enable_ota_requestor = false + # Select DNS-SD implementation if (chip_device_platform == "linux" || chip_device_platform == "esp32" || chip_device_platform == "mbed" || chip_device_platform == "p6" || diff --git a/src/protocols/bdx/BdxTransferSession.h b/src/protocols/bdx/BdxTransferSession.h index c2836a866441b9..35882b05a15fe9 100644 --- a/src/protocols/bdx/BdxTransferSession.h +++ b/src/protocols/bdx/BdxTransferSession.h @@ -277,6 +277,7 @@ class DLL_EXPORT TransferSession uint64_t GetStartOffset() const { return mStartOffset; } uint64_t GetTransferLength() const { return mTransferLength; } uint16_t GetTransferBlockSize() const { return mTransferMaxBlockSize; } + size_t GetNumBytesProcessed() const { return mNumBytesProcessed; } TransferSession(); diff --git a/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.cpp b/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.cpp index e28673418b09cd..daeade20704895 100644 --- a/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.cpp +++ b/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.cpp @@ -133,3 +133,45 @@ namespace { // Singleton instance of the callbacks manager app::CHIPDeviceCallbacksMgr & gCallbacks = app::CHIPDeviceCallbacksMgr::GetInstance(); + +bool emberAfOtaSoftwareUpdateProviderClusterApplyUpdateResponseCallback(EndpointId endpoint, app::CommandSender * commandObj, + uint8_t action, uint32_t delayedActionTime) +{ + ChipLogProgress(Zcl, "ApplyUpdateResponse:"); + ChipLogProgress(Zcl, " action: %" PRIu8 "", action); + ChipLogProgress(Zcl, " delayedActionTime: %" PRIu32 "", delayedActionTime); + + GET_CLUSTER_RESPONSE_CALLBACKS("OtaSoftwareUpdateProviderClusterApplyUpdateResponseCallback"); + + Callback::Callback * cb = + Callback::Callback::FromCancelable(onSuccessCallback); + cb->mCall(cb->mContext, action, delayedActionTime); + return true; +} + +bool emberAfOtaSoftwareUpdateProviderClusterQueryImageResponseCallback(EndpointId endpoint, app::CommandSender * commandObj, + uint8_t status, uint32_t delayedActionTime, + chip::CharSpan imageURI, uint32_t softwareVersion, + chip::CharSpan softwareVersionString, + chip::ByteSpan updateToken, bool userConsentNeeded, + chip::ByteSpan metadataForRequestor) +{ + ChipLogProgress(Zcl, "QueryImageResponse:"); + ChipLogProgress(Zcl, " status: %" PRIu8 "", status); + ChipLogProgress(Zcl, " delayedActionTime: %" PRIu32 "", delayedActionTime); + ChipLogProgress(Zcl, " imageURI: %.*s", static_cast(imageURI.size()), imageURI.data()); + ChipLogProgress(Zcl, " softwareVersion: %" PRIu32 "", softwareVersion); + ChipLogProgress(Zcl, " softwareVersionString: %.*s", static_cast(softwareVersionString.size()), + softwareVersionString.data()); + ChipLogProgress(Zcl, " updateToken: %zu", updateToken.size()); + ChipLogProgress(Zcl, " userConsentNeeded: %d", userConsentNeeded); + ChipLogProgress(Zcl, " metadataForRequestor: %zu", metadataForRequestor.size()); + + GET_CLUSTER_RESPONSE_CALLBACKS("OtaSoftwareUpdateProviderClusterQueryImageResponseCallback"); + + Callback::Callback * cb = + Callback::Callback::FromCancelable(onSuccessCallback); + cb->mCall(cb->mContext, status, delayedActionTime, imageURI, softwareVersion, softwareVersionString, updateToken, + userConsentNeeded, metadataForRequestor); + return true; +} diff --git a/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.h b/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.h index fc342f8e9f5788..499a1924413099 100644 --- a/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.h +++ b/zzz_generated/lighting-app/zap-generated/CHIPClientCallbacks.h @@ -36,5 +36,10 @@ // #6308 should handle IM error code on the application side, either modify this function or remove this. // Cluster Specific Response Callbacks +typedef void (*OtaSoftwareUpdateProviderClusterApplyUpdateResponseCallback)(void * context, uint8_t action, + uint32_t delayedActionTime); +typedef void (*OtaSoftwareUpdateProviderClusterQueryImageResponseCallback)( + void * context, uint8_t status, uint32_t delayedActionTime, chip::CharSpan imageURI, uint32_t softwareVersion, + chip::CharSpan softwareVersionString, chip::ByteSpan updateToken, bool userConsentNeeded, chip::ByteSpan metadataForRequestor); // List specific responses diff --git a/zzz_generated/lighting-app/zap-generated/CHIPClusters.cpp b/zzz_generated/lighting-app/zap-generated/CHIPClusters.cpp index 4292753792e80b..9edc3818bdcd48 100644 --- a/zzz_generated/lighting-app/zap-generated/CHIPClusters.cpp +++ b/zzz_generated/lighting-app/zap-generated/CHIPClusters.cpp @@ -34,6 +34,158 @@ namespace Controller { // TODO(#4503): length should be passed to commands when byte string is in argument list. // TODO(#4503): Commands should take group id as an argument. +// OtaSoftwareUpdateProvider Cluster Commands +CHIP_ERROR OtaSoftwareUpdateProviderCluster::ApplyUpdateRequest(Callback::Cancelable * onSuccessCallback, + Callback::Cancelable * onFailureCallback, + chip::ByteSpan updateToken, uint32_t newVersion) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TLV::TLVWriter * writer = nullptr; + uint8_t argSeqNumber = 0; + + // Used when encoding non-empty command. Suppress error message when encoding empty commands. + (void) writer; + (void) argSeqNumber; + + VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE); + + app::CommandPathParams cmdParams = { mEndpoint, /* group id */ 0, mClusterId, + OtaSoftwareUpdateProvider::Commands::ApplyUpdateRequest::Id, + (app::CommandPathFlags::kEndpointIdValid) }; + + CommandSenderHandle sender( + Platform::New(mDevice->GetInteractionModelDelegate(), mDevice->GetExchangeManager())); + + VerifyOrReturnError(sender != nullptr, CHIP_ERROR_NO_MEMORY); + + SuccessOrExit(err = sender->PrepareCommand(cmdParams)); + + VerifyOrExit((writer = sender->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + // updateToken: octetString + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), updateToken)); + // newVersion: int32u + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), newVersion)); + + SuccessOrExit(err = sender->FinishCommand()); + + // #6308: This is a temporary solution before we fully support IM on application side and should be replaced by IMDelegate. + mDevice->AddIMResponseHandler(sender.get(), onSuccessCallback, onFailureCallback); + + SuccessOrExit(err = mDevice->SendCommands(sender.get())); + + // We have successfully sent the command, and the callback handler will be responsible to free the object, release the object + // now. + sender.release(); +exit: + return err; +} + +CHIP_ERROR OtaSoftwareUpdateProviderCluster::NotifyUpdateApplied(Callback::Cancelable * onSuccessCallback, + Callback::Cancelable * onFailureCallback, + chip::ByteSpan updateToken, uint32_t softwareVersion) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TLV::TLVWriter * writer = nullptr; + uint8_t argSeqNumber = 0; + + // Used when encoding non-empty command. Suppress error message when encoding empty commands. + (void) writer; + (void) argSeqNumber; + + VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE); + + app::CommandPathParams cmdParams = { mEndpoint, /* group id */ 0, mClusterId, + OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::Id, + (app::CommandPathFlags::kEndpointIdValid) }; + + CommandSenderHandle sender( + Platform::New(mDevice->GetInteractionModelDelegate(), mDevice->GetExchangeManager())); + + VerifyOrReturnError(sender != nullptr, CHIP_ERROR_NO_MEMORY); + + SuccessOrExit(err = sender->PrepareCommand(cmdParams)); + + VerifyOrExit((writer = sender->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + // updateToken: octetString + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), updateToken)); + // softwareVersion: int32u + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), softwareVersion)); + + SuccessOrExit(err = sender->FinishCommand()); + + // #6308: This is a temporary solution before we fully support IM on application side and should be replaced by IMDelegate. + mDevice->AddIMResponseHandler(sender.get(), onSuccessCallback, onFailureCallback); + + SuccessOrExit(err = mDevice->SendCommands(sender.get())); + + // We have successfully sent the command, and the callback handler will be responsible to free the object, release the object + // now. + sender.release(); +exit: + return err; +} + +CHIP_ERROR OtaSoftwareUpdateProviderCluster::QueryImage(Callback::Cancelable * onSuccessCallback, + Callback::Cancelable * onFailureCallback, chip::VendorId vendorId, + uint16_t productId, uint32_t softwareVersion, uint8_t protocolsSupported, + uint16_t hardwareVersion, chip::CharSpan location, bool requestorCanConsent, + chip::ByteSpan metadataForProvider) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TLV::TLVWriter * writer = nullptr; + uint8_t argSeqNumber = 0; + + // Used when encoding non-empty command. Suppress error message when encoding empty commands. + (void) writer; + (void) argSeqNumber; + + VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE); + + app::CommandPathParams cmdParams = { mEndpoint, /* group id */ 0, mClusterId, + OtaSoftwareUpdateProvider::Commands::QueryImage::Id, + (app::CommandPathFlags::kEndpointIdValid) }; + + CommandSenderHandle sender( + Platform::New(mDevice->GetInteractionModelDelegate(), mDevice->GetExchangeManager())); + + VerifyOrReturnError(sender != nullptr, CHIP_ERROR_NO_MEMORY); + + SuccessOrExit(err = sender->PrepareCommand(cmdParams)); + + VerifyOrExit((writer = sender->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + // vendorId: vendorId + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), vendorId)); + // productId: int16u + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), productId)); + // softwareVersion: int32u + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), softwareVersion)); + // protocolsSupported: OTADownloadProtocol + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), protocolsSupported)); + // hardwareVersion: int16u + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), hardwareVersion)); + // location: charString + SuccessOrExit(err = writer->PutString(TLV::ContextTag(argSeqNumber++), location)); + // requestorCanConsent: boolean + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), requestorCanConsent)); + // metadataForProvider: octetString + SuccessOrExit(err = writer->Put(TLV::ContextTag(argSeqNumber++), metadataForProvider)); + + SuccessOrExit(err = sender->FinishCommand()); + + // #6308: This is a temporary solution before we fully support IM on application side and should be replaced by IMDelegate. + mDevice->AddIMResponseHandler(sender.get(), onSuccessCallback, onFailureCallback); + + SuccessOrExit(err = mDevice->SendCommands(sender.get())); + + // We have successfully sent the command, and the callback handler will be responsible to free the object, release the object + // now. + sender.release(); +exit: + return err; +} + +// OtaSoftwareUpdateProvider Cluster Attributes + // OnOff Cluster Commands // OnOff Cluster Attributes CHIP_ERROR OnOffCluster::ReadAttributeOnOff(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback) diff --git a/zzz_generated/lighting-app/zap-generated/CHIPClusters.h b/zzz_generated/lighting-app/zap-generated/CHIPClusters.h index c6c08f07b2e991..241a1160a0c53b 100644 --- a/zzz_generated/lighting-app/zap-generated/CHIPClusters.h +++ b/zzz_generated/lighting-app/zap-generated/CHIPClusters.h @@ -30,6 +30,27 @@ namespace chip { namespace Controller { +class DLL_EXPORT OtaSoftwareUpdateProviderCluster : public ClusterBase +{ +public: + OtaSoftwareUpdateProviderCluster() : ClusterBase(app::Clusters::OtaSoftwareUpdateProvider::Id) {} + ~OtaSoftwareUpdateProviderCluster() {} + + // Cluster Commands + CHIP_ERROR ApplyUpdateRequest(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, + chip::ByteSpan updateToken, uint32_t newVersion); + CHIP_ERROR NotifyUpdateApplied(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, + chip::ByteSpan updateToken, uint32_t softwareVersion); + CHIP_ERROR QueryImage(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, + chip::VendorId vendorId, uint16_t productId, uint32_t softwareVersion, uint8_t protocolsSupported, + uint16_t hardwareVersion, chip::CharSpan location, bool requestorCanConsent, + chip::ByteSpan metadataForProvider); + + // Cluster Attributes + +private: +}; + class DLL_EXPORT OnOffCluster : public ClusterBase { public: diff --git a/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp b/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp index 6dec594acc5130..8d4bca0ff085b6 100644 --- a/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp +++ b/zzz_generated/lighting-app/zap-generated/IMClusterCommandHandler.cpp @@ -708,6 +708,198 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP } // namespace NetworkCommissioning +namespace OtaSoftwareUpdateProvider { + +void DispatchClientCommand(CommandSender * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) +{ + // We are using TLVUnpackError and TLVError here since both of them can be CHIP_END_OF_TLV + // When TLVError is CHIP_END_OF_TLV, it means we have iterated all of the items, which is not a real error. + // Any error value TLVUnpackError means we have received an illegal value. + // The following variables are used for all commands to save code size. + CHIP_ERROR TLVError = CHIP_NO_ERROR; + CHIP_ERROR TLVUnpackError = CHIP_NO_ERROR; + uint32_t validArgumentCount = 0; + uint32_t expectArgumentCount = 0; + uint32_t currentDecodeTagId = 0; + bool wasHandled = false; + { + switch (aCommandPath.mCommandId) + { + case Commands::ApplyUpdateResponse::Id: { + expectArgumentCount = 2; + uint8_t action; + uint32_t delayedActionTime; + bool argExists[2]; + + memset(argExists, 0, sizeof argExists); + + while ((TLVError = aDataTlv.Next()) == CHIP_NO_ERROR) + { + // Since call to aDataTlv.Next() is CHIP_NO_ERROR, the read head always points to an element. + // Skip this element if it is not a ContextTag, not consider it as an error if other values are valid. + if (!TLV::IsContextTag(aDataTlv.GetTag())) + { + continue; + } + currentDecodeTagId = TLV::TagNumFromTag(aDataTlv.GetTag()); + if (currentDecodeTagId < 2) + { + if (argExists[currentDecodeTagId]) + { + ChipLogProgress(Zcl, "Duplicate TLV tag %" PRIx32, TLV::TagNumFromTag(aDataTlv.GetTag())); + TLVUnpackError = CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT; + break; + } + else + { + argExists[currentDecodeTagId] = true; + validArgumentCount++; + } + } + switch (currentDecodeTagId) + { + case 0: + TLVUnpackError = aDataTlv.Get(action); + break; + case 1: + TLVUnpackError = aDataTlv.Get(delayedActionTime); + break; + default: + // Unsupported tag, ignore it. + ChipLogProgress(Zcl, "Unknown TLV tag during processing."); + break; + } + if (CHIP_NO_ERROR != TLVUnpackError) + { + break; + } + } + + if (CHIP_END_OF_TLV == TLVError) + { + // CHIP_END_OF_TLV means we have iterated all items in the structure, which is not a real error. + TLVError = CHIP_NO_ERROR; + } + + if (CHIP_NO_ERROR == TLVError && CHIP_NO_ERROR == TLVUnpackError && 2 == validArgumentCount) + { + wasHandled = emberAfOtaSoftwareUpdateProviderClusterApplyUpdateResponseCallback( + aCommandPath.mEndpointId, apCommandObj, action, delayedActionTime); + } + break; + } + case Commands::QueryImageResponse::Id: { + expectArgumentCount = 8; + uint8_t status; + uint32_t delayedActionTime; + chip::CharSpan imageURI; + uint32_t softwareVersion; + chip::CharSpan softwareVersionString; + chip::ByteSpan updateToken; + bool userConsentNeeded; + chip::ByteSpan metadataForRequestor; + bool argExists[8]; + + memset(argExists, 0, sizeof argExists); + + while ((TLVError = aDataTlv.Next()) == CHIP_NO_ERROR) + { + // Since call to aDataTlv.Next() is CHIP_NO_ERROR, the read head always points to an element. + // Skip this element if it is not a ContextTag, not consider it as an error if other values are valid. + if (!TLV::IsContextTag(aDataTlv.GetTag())) + { + continue; + } + currentDecodeTagId = TLV::TagNumFromTag(aDataTlv.GetTag()); + if (currentDecodeTagId < 8) + { + if (argExists[currentDecodeTagId]) + { + ChipLogProgress(Zcl, "Duplicate TLV tag %" PRIx32, TLV::TagNumFromTag(aDataTlv.GetTag())); + TLVUnpackError = CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_ELEMENT; + break; + } + else + { + argExists[currentDecodeTagId] = true; + validArgumentCount++; + } + } + switch (currentDecodeTagId) + { + case 0: + TLVUnpackError = aDataTlv.Get(status); + break; + case 1: + TLVUnpackError = aDataTlv.Get(delayedActionTime); + break; + case 2: + TLVUnpackError = aDataTlv.Get(imageURI); + break; + case 3: + TLVUnpackError = aDataTlv.Get(softwareVersion); + break; + case 4: + TLVUnpackError = aDataTlv.Get(softwareVersionString); + break; + case 5: + TLVUnpackError = aDataTlv.Get(updateToken); + break; + case 6: + TLVUnpackError = aDataTlv.Get(userConsentNeeded); + break; + case 7: + TLVUnpackError = aDataTlv.Get(metadataForRequestor); + break; + default: + // Unsupported tag, ignore it. + ChipLogProgress(Zcl, "Unknown TLV tag during processing."); + break; + } + if (CHIP_NO_ERROR != TLVUnpackError) + { + break; + } + } + + if (CHIP_END_OF_TLV == TLVError) + { + // CHIP_END_OF_TLV means we have iterated all items in the structure, which is not a real error. + TLVError = CHIP_NO_ERROR; + } + + if (CHIP_NO_ERROR == TLVError && CHIP_NO_ERROR == TLVUnpackError && 8 == validArgumentCount) + { + wasHandled = emberAfOtaSoftwareUpdateProviderClusterQueryImageResponseCallback( + aCommandPath.mEndpointId, apCommandObj, status, delayedActionTime, imageURI, softwareVersion, + softwareVersionString, updateToken, userConsentNeeded, metadataForRequestor); + } + break; + } + default: { + // Unrecognized command ID, error status will apply. + ReportCommandUnsupported(apCommandObj, aCommandPath); + return; + } + } + } + + if (CHIP_NO_ERROR != TLVError || CHIP_NO_ERROR != TLVUnpackError || expectArgumentCount != validArgumentCount || !wasHandled) + { + apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::InvalidCommand); + ChipLogProgress(Zcl, + "Failed to dispatch command, %" PRIu32 "/%" PRIu32 " arguments parsed, TLVError=%" CHIP_ERROR_FORMAT + ", UnpackError=%" CHIP_ERROR_FORMAT " (last decoded tag = %" PRIu32, + validArgumentCount, expectArgumentCount, TLVError.Format(), TLVUnpackError.Format(), currentDecodeTagId); + // A command with no arguments would never write currentDecodeTagId. If + // progress logging is also disabled, it would look unused. Silence that + // warning. + UNUSED_VAR(currentDecodeTagId); + } +} + +} // namespace OtaSoftwareUpdateProvider + namespace OnOff { void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) @@ -1062,6 +1254,9 @@ void DispatchSingleClusterResponseCommand(const ConcreteCommandPath & aCommandPa SuccessOrExit(aReader.EnterContainer(dataTlvType)); switch (aCommandPath.mClusterId) { + case Clusters::OtaSoftwareUpdateProvider::Id: + Clusters::OtaSoftwareUpdateProvider::DispatchClientCommand(apCommandObj, aCommandPath, aReader); + break; default: ChipLogError(Zcl, "Unknown cluster " ChipLogFormatMEI, ChipLogValueMEI(aCommandPath.mClusterId)); apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::UnsupportedCluster); diff --git a/zzz_generated/lighting-app/zap-generated/PluginApplicationCallbacks.h b/zzz_generated/lighting-app/zap-generated/PluginApplicationCallbacks.h index 24fe9f82edd973..a455958acd1e0d 100644 --- a/zzz_generated/lighting-app/zap-generated/PluginApplicationCallbacks.h +++ b/zzz_generated/lighting-app/zap-generated/PluginApplicationCallbacks.h @@ -33,6 +33,7 @@ MatterIdentifyPluginServerInitCallback(); \ MatterLevelControlPluginServerInitCallback(); \ MatterNetworkCommissioningPluginServerInitCallback(); \ + MatterOtaSoftwareUpdateProviderPluginClientInitCallback(); \ MatterOccupancySensingPluginServerInitCallback(); \ MatterOnOffPluginClientInitCallback(); \ MatterOnOffPluginServerInitCallback(); \ diff --git a/zzz_generated/lighting-app/zap-generated/callback-stub.cpp b/zzz_generated/lighting-app/zap-generated/callback-stub.cpp index 10201b804e5033..5e7d5bcbbe1379 100644 --- a/zzz_generated/lighting-app/zap-generated/callback-stub.cpp +++ b/zzz_generated/lighting-app/zap-generated/callback-stub.cpp @@ -62,6 +62,9 @@ void emberAfClusterInitCallback(EndpointId endpoint, ClusterId clusterId) case ZCL_NETWORK_COMMISSIONING_CLUSTER_ID: emberAfNetworkCommissioningClusterInitCallback(endpoint); break; + case ZCL_OTA_PROVIDER_CLUSTER_ID: + emberAfOtaSoftwareUpdateProviderClusterInitCallback(endpoint); + break; case ZCL_OCCUPANCY_SENSING_CLUSTER_ID: emberAfOccupancySensingClusterInitCallback(endpoint); break; @@ -144,6 +147,11 @@ void __attribute__((weak)) emberAfNetworkCommissioningClusterInitCallback(Endpoi // To prevent warning (void) endpoint; } +void __attribute__((weak)) emberAfOtaSoftwareUpdateProviderClusterInitCallback(EndpointId endpoint) +{ + // To prevent warning + (void) endpoint; +} void __attribute__((weak)) emberAfOccupancySensingClusterInitCallback(EndpointId endpoint) { // To prevent warning diff --git a/zzz_generated/lighting-app/zap-generated/endpoint_config.h b/zzz_generated/lighting-app/zap-generated/endpoint_config.h index 712a93de58d719..8ddb9f94c49616 100644 --- a/zzz_generated/lighting-app/zap-generated/endpoint_config.h +++ b/zzz_generated/lighting-app/zap-generated/endpoint_config.h @@ -793,7 +793,7 @@ }; #define ZAP_CLUSTER_MASK(mask) CLUSTER_MASK_##mask -#define GENERATED_CLUSTER_COUNT 20 +#define GENERATED_CLUSTER_COUNT 21 #define GENERATED_CLUSTERS \ { \ { 0x001D, ZAP_ATTRIBUTE_INDEX(0), 5, 0, ZAP_CLUSTER_MASK(SERVER), NULL }, /* Endpoint: 0, Cluster: Descriptor (server) */ \ @@ -803,6 +803,9 @@ 654, \ ZAP_CLUSTER_MASK(SERVER) | ZAP_CLUSTER_MASK(INIT_FUNCTION), \ chipFuncArrayBasicServer }, /* Endpoint: 0, Cluster: Basic (server) */ \ + { \ + 0x0029, ZAP_ATTRIBUTE_INDEX(24), 0, 0, ZAP_CLUSTER_MASK(CLIENT), NULL \ + }, /* Endpoint: 0, Cluster: OTA Software Update Provider (client) */ \ { \ 0x0030, ZAP_ATTRIBUTE_INDEX(24), 3, 264, ZAP_CLUSTER_MASK(SERVER), NULL \ }, /* Endpoint: 0, Cluster: General Commissioning (server) */ \ @@ -879,7 +882,7 @@ // This is an array of EmberAfEndpointType structures. #define GENERATED_ENDPOINT_TYPES \ { \ - { ZAP_CLUSTER_INDEX(0), 12, 1322 }, { ZAP_CLUSTER_INDEX(12), 6, 82 }, { ZAP_CLUSTER_INDEX(18), 2, 6 }, \ + { ZAP_CLUSTER_INDEX(0), 13, 1322 }, { ZAP_CLUSTER_INDEX(13), 6, 82 }, { ZAP_CLUSTER_INDEX(19), 2, 6 }, \ } // Largest attribute size is needed for various buffers diff --git a/zzz_generated/lighting-app/zap-generated/gen_config.h b/zzz_generated/lighting-app/zap-generated/gen_config.h index e80cb1f3b93d65..ea32f453756625 100644 --- a/zzz_generated/lighting-app/zap-generated/gen_config.h +++ b/zzz_generated/lighting-app/zap-generated/gen_config.h @@ -40,6 +40,7 @@ #define EMBER_AF_IDENTIFY_CLUSTER_SERVER_ENDPOINT_COUNT (1) #define EMBER_AF_LEVEL_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT (1) #define EMBER_AF_NETWORK_COMMISSIONING_CLUSTER_SERVER_ENDPOINT_COUNT (1) +#define EMBER_AF_OTA_PROVIDER_CLUSTER_CLIENT_ENDPOINT_COUNT (1) #define EMBER_AF_OCCUPANCY_SENSING_CLUSTER_SERVER_ENDPOINT_COUNT (1) #define EMBER_AF_ON_OFF_CLUSTER_CLIENT_ENDPOINT_COUNT (1) #define EMBER_AF_ON_OFF_CLUSTER_SERVER_ENDPOINT_COUNT (1) @@ -114,6 +115,10 @@ #define EMBER_AF_PLUGIN_NETWORK_COMMISSIONING_SERVER #define EMBER_AF_PLUGIN_NETWORK_COMMISSIONING +// Use this macro to check if the client side of the OTA Software Update Provider cluster is included +#define ZCL_USING_OTA_PROVIDER_CLUSTER_CLIENT +#define EMBER_AF_PLUGIN_OTA_SOFTWARE_UPDATE_PROVIDER_CLIENT + // Use this macro to check if the server side of the Occupancy Sensing cluster is included #define ZCL_USING_OCCUPANCY_SENSING_CLUSTER_SERVER #define EMBER_AF_PLUGIN_OCCUPANCY_SENSING_SERVER