diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 16c303e00307aa..52dd940b17d0ab 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -39,6 +39,7 @@ AppConfig ApplicationBasic ApplicationIdentifier ApplicationLauncher +ApplyUpdateRequest approver appspot aps @@ -340,6 +341,7 @@ env esd ESPPORT Espressif +esptool eth EthernetNetworkDiagnostics ethernets @@ -690,6 +692,11 @@ optionsMask optionsOverride orgs OTA +OTAProviderIpAddress +OTAProviderNodeId +OTAProviderSerialPort +OTARequesterImpl +OTARequestorSerialPort OTBR otcli PAA @@ -848,6 +855,7 @@ SetpointRaiseLower SetUpPINCode SetupQRCode sexualized +shubhamdp SIGINT SiLabs SiliconLabs diff --git a/examples/ota-provider-app/esp32/.gitignore b/examples/ota-provider-app/esp32/.gitignore new file mode 100644 index 00000000000000..234526a082ad26 --- /dev/null +++ b/examples/ota-provider-app/esp32/.gitignore @@ -0,0 +1,5 @@ +*.vscode + +/build/ +/sdkconfig +/sdkconfig.old diff --git a/examples/ota-provider-app/esp32/CMakeLists.txt b/examples/ota-provider-app/esp32/CMakeLists.txt new file mode 100644 index 00000000000000..07edf22b17cb82 --- /dev/null +++ b/examples/ota-provider-app/esp32/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. + +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../common/cmake/idf_flashing.cmake) + +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/config/esp32/components" + "${CMAKE_CURRENT_LIST_DIR}/../../common/QRCode" +) + +project(chip-ota-provider-app) +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++14;-Os;-DLWIP_IPV6_SCOPES=0;-DCHIP_HAVE_CONFIG_H" APPEND) +idf_build_set_property(C_COMPILE_OPTIONS "-Os;-DLWIP_IPV6_SCOPES=0" APPEND) + +flashing_script() diff --git a/examples/ota-provider-app/esp32/README.md b/examples/ota-provider-app/esp32/README.md new file mode 100644 index 00000000000000..5cf19e3aebe287 --- /dev/null +++ b/examples/ota-provider-app/esp32/README.md @@ -0,0 +1,73 @@ +# CHIP ESP32 OTA Provider Example + +A prototype application that demonstrates OTA provider capabilities. + +## Supported Devices + +- This example supports ESP32 and ESP32C3. For details please check + [here](https://github.com/shubhamdp/connectedhomeip/tree/shubhamdp-patch-1/examples/all-clusters-app/esp32#supported-devices). + +## Building the Example Application + +- If you are building for the first time please check + [Building the Example Application](https://github.com/shubhamdp/connectedhomeip/tree/shubhamdp-patch-1/examples/all-clusters-app/esp32#building-the-example-application) + guide. +- Otherwise, `idf.py build` works! + +## Flashing the Example Application + +``` +idf.py -p flash +``` + +## Flashing the hello-world.bin OTA image + +Flash hello-world OTA image on OTA Provider's "ota_data" flash partition. Please +find hello-world.bin +[here](http://shubhamdp.github.io/esp_ota/esp32/hello-world-flash-in-ota-provider-partition.bin). +This OTA image is built for ESP32, it will not work on other devices. This is +the OTA upgrade image and will be sent to OTA requestor. + +``` +esptool.py -p write_flash 0x206400 hello-world-flash-in-ota-provider-partition.bin +``` + +NOTE: This is a modified binary which contains the size of OTA image at first 4 +bytes. + +Run the idf monitor + +``` +idf.py -p monitor +``` + +## Commissioning over BLE using chip-tool + +- Please build the standalone chip-tool as described [here](../../chip-tool) +- Commissioning the OTA Provider + +``` +./out/debug/chip-tool pairing ble-wifi 12345 0 20202021 3841 +``` + +--- + +Please note down the IP Address and Node ID of OTA Provider, these are required +for [OTA Requestor Example](../../ota-requestor-app/esp32). Once OTA provider is +commissioned then head over to +[OTA Requestor Example](../../ota-requestor-app/esp32). + +--- + +## Features + +- Can complete full BDX transfer +- Provide the full OTA image to Requestor + +## Limitations + +- Synchronous BDX transfer only +- Does not check VID/PID +- Only one transfer at a time +- Does not check incoming UpdateTokens +- Does not support the header defined in Matter Specification. diff --git a/examples/ota-provider-app/esp32/main/BdxOtaSender.cpp b/examples/ota-provider-app/esp32/main/BdxOtaSender.cpp new file mode 100644 index 00000000000000..34677c17c00496 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/BdxOtaSender.cpp @@ -0,0 +1,193 @@ +/* + * + * 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 + +using chip::bdx::StatusCode; +using chip::bdx::TransferControlFlags; +using chip::bdx::TransferSession; + +void BdxOtaSender::SetCallbacks(BdxOtaSenderCallbacks callbacks) +{ + mOnBlockQueryCallback = callbacks.onBlockQuery; + mOnTransferCompleteCallback = callbacks.onTransferComplete; + mOnTransferFailedCallback = callbacks.onTransferFailed; +} + +void BdxOtaSender::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (event.EventType != TransferSession::OutputEventType::kNone) + { + ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + } + + switch (event.EventType) + { + case TransferSession::OutputEventType::kNone: + break; + case TransferSession::OutputEventType::kMsgToSend: { + chip::Messaging::SendFlags sendFlags; + if (!event.msgTypeData.HasMessageType(chip::Protocols::SecureChannel::MsgType::StatusReport)) + { + // All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and the + // end of the transfer. + sendFlags.Set(chip::Messaging::SendMessageFlags::kExpectResponse); + } + VerifyOrReturn(mExchangeCtx != nullptr, ChipLogError(BDX, "%s: mExchangeCtx is null", __FUNCTION__)); + err = mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData), + sendFlags); + if (err != CHIP_NO_ERROR) + { + ChipLogError(BDX, "SendMessage failed: %s", chip::ErrorStr(err)); + } + break; + } + case TransferSession::OutputEventType::kInitReceived: { + // TransferSession will automatically reject a transfer if there are no + // common supported control modes. It will also default to the smaller + // block size. + TransferSession::TransferAcceptData acceptData; + acceptData.ControlMode = TransferControlFlags::kReceiverDrive; // OTA must use receiver drive + acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize(); + acceptData.StartOffset = mTransfer.GetStartOffset(); + acceptData.Length = mTransfer.GetTransferLength(); + VerifyOrReturn(mTransfer.AcceptTransfer(acceptData) == CHIP_NO_ERROR, + ChipLogError(BDX, "%s: %s", __FUNCTION__, chip::ErrorStr(err))); + break; + } + case TransferSession::OutputEventType::kQueryReceived: { + TransferSession::BlockData blockData; + uint16_t blockSize = mTransfer.GetTransferBlockSize(); + uint16_t bytesToRead = blockSize; + + chip::System::PacketBufferHandle blockBuf = chip::System::PacketBufferHandle::New(bytesToRead); + if (blockBuf.IsNull()) + { + // TODO: AbortTransfer() needs to support GeneralStatusCode failures as well as BDX specific errors. + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + + if (mOnBlockQueryCallback != nullptr && mOnBlockQueryCallback->mCall != nullptr) + { + if (CHIP_NO_ERROR != + mOnBlockQueryCallback->mCall(mOnBlockQueryCallback->mContext, blockBuf, blockData.Length, blockData.IsEof, + mNumBytesSent)) + { + ChipLogError(BDX, "onBlockQuery Callback failed"); + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + } + else + { + ChipLogError(BDX, "onBlockQuery Callback not set"); + mTransfer.AbortTransfer(StatusCode::kUnknown); + return; + } + + blockData.Data = blockBuf->Start(); + mNumBytesSent = static_cast(mNumBytesSent + blockData.Length); + + VerifyOrReturn(CHIP_NO_ERROR == mTransfer.PrepareBlock(blockData), + ChipLogError(BDX, "%s: PrepareBlock failed: %s", __FUNCTION__, chip::ErrorStr(err))); + break; + } + case TransferSession::OutputEventType::kAckReceived: + break; + case TransferSession::OutputEventType::kAckEOFReceived: + ChipLogDetail(BDX, "Transfer completed, got AckEOF"); + if (mOnTransferCompleteCallback != nullptr && mOnTransferCompleteCallback->mCall != nullptr) + { + mOnTransferCompleteCallback->mCall(mOnTransferCompleteCallback->mContext); + } + else + { + ChipLogError(BDX, "onTransferComplete Callback not set"); + } + Reset(); + break; + case TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxSenderStatusReceived); + } + else + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + Reset(); + break; + case TransferSession::OutputEventType::kInternalError: + ChipLogError(BDX, "InternalError"); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxSenderInternal); + } + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + Reset(); + break; + case TransferSession::OutputEventType::kTransferTimeout: + ChipLogError(BDX, "Transfer timed out"); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxSenderTimeOut); + } + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + Reset(); + break; + case TransferSession::OutputEventType::kAcceptReceived: + case TransferSession::OutputEventType::kBlockReceived: + default: + // TransferSession should prevent this case from happening. + ChipLogError(BDX, "%s: unsupported event type", __FUNCTION__); + } +} + +void BdxOtaSender::Reset() +{ + mTransfer.Reset(); + if (mExchangeCtx != nullptr) + { + mExchangeCtx->Close(); + } + mNumBytesSent = 0; +} + +uint16_t BdxOtaSender::GetTransferBlockSize(void) +{ + return mTransfer.GetTransferBlockSize(); +} + +uint64_t BdxOtaSender::GetTransferLength() +{ + return mTransfer.GetTransferLength(); +} diff --git a/examples/ota-provider-app/esp32/main/CHIPDeviceManager.cpp b/examples/ota-provider-app/esp32/main/CHIPDeviceManager.cpp new file mode 100644 index 00000000000000..9a24074d6cceef --- /dev/null +++ b/examples/ota-provider-app/esp32/main/CHIPDeviceManager.cpp @@ -0,0 +1,105 @@ +/* + * + * 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. + */ + +/** + * @file + * This file implements the CHIP Device Interface that is used by + * applications to interact with the CHIP stack + * + */ + +#include + +#include "CHIPDeviceManager.h" +#include +#include +#include +#include +#include +#include + +using namespace ::chip; + +namespace chip { + +namespace DeviceManager { + +using namespace ::chip::DeviceLayer; + +void CHIPDeviceManager::CommonDeviceEventHandler(const ChipDeviceEvent * event, intptr_t arg) +{ + CHIPDeviceManagerCallbacks * cb = reinterpret_cast(arg); + if (cb != nullptr) + { + cb->DeviceEventCallback(event, reinterpret_cast(cb)); + } +} + +CHIP_ERROR CHIPDeviceManager::Init(CHIPDeviceManagerCallbacks * cb) +{ + CHIP_ERROR err; + mCB = cb; + RendezvousInformationFlags flags = RendezvousInformationFlags(CONFIG_RENDEZVOUS_MODE); + + // Initialize the CHIP stack. + err = PlatformMgr().InitChipStack(); + SuccessOrExit(err); + + if (flags.Has(RendezvousInformationFlag::kBLE)) + { + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + } + else if (flags.Has(RendezvousInformationFlag::kSoftAP)) + { + // TODO(cecille): Fix for the case where BLE and SoftAP are both enabled.` + ConnectivityMgr().SetBLEAdvertisingEnabled(false); + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + else + { + // If rendezvous is bypassed, enable SoftAP so that the device can still + // be communicated with via its SoftAP as needed. + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + + err = Platform::MemoryInit(); + SuccessOrExit(err); + + // Register a function to receive events from the CHIP device layer. Note that calls to + // this function will happen on the CHIP event loop thread, not the app_main thread. + PlatformMgr().AddEventHandler(CHIPDeviceManager::CommonDeviceEventHandler, reinterpret_cast(cb)); + + // Start a task to run the CHIP Device event loop. + err = PlatformMgr().StartEventLoopTask(); + SuccessOrExit(err); + +exit: + return err; +} +} // namespace DeviceManager +} // namespace chip + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t mask, uint8_t type, uint16_t size, + uint8_t * value) +{ + chip::DeviceManager::CHIPDeviceManagerCallbacks * cb = + chip::DeviceManager::CHIPDeviceManager::GetInstance().GetCHIPDeviceManagerCallbacks(); + if (cb != nullptr) + { + cb->PostAttributeChangeCallback(path.mEndpointId, path.mClusterId, path.mAttributeId, mask, type, size, value); + } +} diff --git a/examples/ota-provider-app/esp32/main/CMakeLists.txt b/examples/ota-provider-app/esp32/main/CMakeLists.txt new file mode 100644 index 00000000000000..a85dcdb35d4b82 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/CMakeLists.txt @@ -0,0 +1,51 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. +# +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +idf_component_register(PRIV_INCLUDE_DIRS + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/ota-provider-app/" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-provider-app" + SRC_DIRS + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/ota-provider-app/zap-generated" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated/attributes" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/reporting" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/administrator-commissioning-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/basic" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/bindings" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/descriptor" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/diagnostic-logs-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ethernet_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/thread_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/wifi_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/software_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/general_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/general-commissioning-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/network-commissioning" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/operational-credentials-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ota-provider" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-provider-app/ota-provider-common" + EXCLUDE_SRCS + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-provider-app/ota-provider-common/BdxOtaSender.cpp" + PRIV_REQUIRES chip QRCode bt console) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 14) +target_compile_options(${COMPONENT_LIB} PRIVATE "-DLWIP_IPV6_SCOPES=0" "-DCHIP_HAVE_CONFIG_H") diff --git a/examples/ota-provider-app/esp32/main/DeviceCallbacks.cpp b/examples/ota-provider-app/esp32/main/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..043d452011e7a4 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/DeviceCallbacks.cpp @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ +#include "DeviceCallbacks.h" + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include +#include +#include +#include + +static const char * TAG = "echo-devicecallbacks"; + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; + +void DeviceCallbacks::DeviceEventCallback(const ChipDeviceEvent * event, intptr_t arg) +{ + switch (event->Type) + { + case DeviceEventType::kInternetConnectivityChange: + OnInternetConnectivityChange(event); + break; + + case DeviceEventType::kSessionEstablished: + OnSessionEstablished(event); + break; + case DeviceEventType::kInterfaceIpAddressChanged: + if ((event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV4_Assigned) || + (event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV6_Assigned)) + { + // MDNS server restart on any ip assignment: if link local ipv6 is configured, that + // will not trigger a 'internet connectivity change' as there is no internet + // connectivity. MDNS still wants to refresh its listening interfaces to include the + // newly selected address. + chip::app::DnssdServer::Instance().StartServer(); + } + break; + } + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} + +void DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t mask, + uint8_t type, uint16_t size, uint8_t * value) +{ + ESP_LOGI(TAG, + "PostAttributeChangeCallback - Cluster ID: '" ChipLogFormatMEI + "', EndPoint ID: '0x%02x', Attribute ID: '" ChipLogFormatMEI "'", + ChipLogValueMEI(clusterId), endpointId, ChipLogValueMEI(attributeId)); + + // TODO handle this callback in switch statement + ESP_LOGI(TAG, "Unhandled cluster ID: " ChipLogFormatMEI, ChipLogValueMEI(clusterId)); + + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} + +void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event) +{ + if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established) + { + ESP_LOGI(TAG, "Server ready at: %s:%d", event->InternetConnectivityChange.address, CHIP_PORT); + chip::app::DnssdServer::Instance().StartServer(); + } + else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv4 connectivity..."); + } + if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) + { + ESP_LOGI(TAG, "IPv6 Server ready..."); + chip::app::DnssdServer::Instance().StartServer(); + } + else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv6 connectivity..."); + } +} + +void DeviceCallbacks::OnSessionEstablished(const ChipDeviceEvent * event) +{ + if (event->SessionEstablished.IsCommissioner) + { + ESP_LOGI(TAG, "Commissioner detected!"); + } +} diff --git a/examples/ota-provider-app/esp32/main/Kconfig.projbuild b/examples/ota-provider-app/esp32/main/Kconfig.projbuild new file mode 100644 index 00000000000000..96a7f14d5c69ec --- /dev/null +++ b/examples/ota-provider-app/esp32/main/Kconfig.projbuild @@ -0,0 +1,50 @@ +# +# Copyright (c) 2020 Project CHIP Authors +# All rights reserved. +# +# 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. +# +# Description: +# Configuration options CHIP ESP32 demo application. +# + +menu "Demo" + + choice + prompt "Rendezvous Mode" + default RENDEZVOUS_MODE_BLE + help + Specifies the Rendezvous mode of the peripheral. + + config RENDEZVOUS_MODE_BYPASS + bool "Bypass" + config RENDEZVOUS_MODE_WIFI + bool "Wi-Fi" + config RENDEZVOUS_MODE_BLE + bool "BLE" + config RENDEZVOUS_MODE_THREAD + bool "Thread" + config RENDEZVOUS_MODE_ETHERNET + bool "Ethernet" + endchoice + + config RENDEZVOUS_MODE + int + range 0 8 + default 0 if RENDEZVOUS_MODE_BYPASS + default 1 if RENDEZVOUS_MODE_WIFI + default 2 if RENDEZVOUS_MODE_BLE + default 4 if RENDEZVOUS_MODE_THREAD + default 8 if RENDEZVOUS_MODE_ETHERNET + +endmenu diff --git a/examples/ota-provider-app/esp32/main/include/BdxOtaSender.h b/examples/ota-provider-app/esp32/main/include/BdxOtaSender.h new file mode 100644 index 00000000000000..4940d676dedd58 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/include/BdxOtaSender.h @@ -0,0 +1,103 @@ +/* + * + * 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 + +#pragma once + +enum BdxSenderErrorTypes +{ + kErrorBdxSenderNoError = 0, + kErrorBdxSenderStatusReceived, + kErrorBdxSenderInternal, + kErrorBdxSenderTimeOut, +}; + +/** + * @brief + * This callback is called when bdx session receives a BlockQuery message. + * + * @param[in] context User context + * @param[out] blockBuf Preallocated PacketBuffer, implementor should fill the block buf with the data + * @param[out] size Size of data + * @param[out] isEof Flag which tells whether it is final block or not + * @param[in] offset Offset to read data from + * + * @return CHIP_NO_ERROR on success; return appropriate error code otherwise + */ +typedef CHIP_ERROR (*OnBdxBlockQuery)(void * context, chip::System::PacketBufferHandle & blockBuf, size_t & size, bool & isEof, + uint32_t offset); + +/** + * @brief + * This callback is called when bdx session receives BlockAckEOF message + * + * @param[in] context User contex + */ +typedef void (*OnBdxTransferComplete)(void * context); + +/** + * @brief + * This callback is called when bdx transfer receives StatusReport messages, + * if there is any internal error, or transfer timed out + * + * @param[in] context User context + * @param[in] status Error code + */ +typedef void (*OnBdxTransferFailed)(void * context, BdxSenderErrorTypes status); + +struct BdxOtaSenderCallbacks +{ + chip::Callback::Callback * onBlockQuery = nullptr; + chip::Callback::Callback * onTransferComplete = nullptr; + chip::Callback::Callback * onTransferFailed = nullptr; +}; + +class BdxOtaSender : public chip::bdx::Responder +{ +public: + void SetCallbacks(BdxOtaSenderCallbacks callbacks); + + /** + * @brief + * Get negotiated bdx tranfer block size + * + * @return Negotiated bdx tranfer block size + */ + uint16_t GetTransferBlockSize(void); + + /** + * @brief + * Get the predetermined definite length for the transfer + * + * @return Predetermined definite length for the transfer + */ + uint64_t GetTransferLength(void); + +private: + // Inherited from bdx::TransferFacilitator + void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event) override; + + void Reset(); + + uint32_t mNumBytesSent = 0; + + chip::Callback::Callback * mOnBlockQueryCallback = nullptr; + chip::Callback::Callback * mOnTransferCompleteCallback = nullptr; + chip::Callback::Callback * mOnTransferFailedCallback = nullptr; +}; diff --git a/examples/ota-provider-app/esp32/main/include/CHIPDeviceManager.h b/examples/ota-provider-app/esp32/main/include/CHIPDeviceManager.h new file mode 100644 index 00000000000000..ee424b366df1b8 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/include/CHIPDeviceManager.h @@ -0,0 +1,127 @@ +/* + * + * 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. + */ + +/** + * @file + * This file contains definitions for the CHIP DeviceManager Interface + * + * This object will co-ordinate multiple activities such as + * initialisation, rendezvous, session mgmt and other such + * activities within the CHIP stack. This is a singleton object. + */ + +#ifndef CHIP_DEVICEMANAGER_H_ +#define CHIP_DEVICEMANAGER_H_ + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace chip { +namespace DeviceManager { + +/** + * @brief + * This class provides a skeleton for all the callback functions. The functions will be + * called by other objects within the CHIP stack for specific events. + * Applications interested in receiving specific callbacks can specialize this class and handle + * these events in their implementation of this class. + */ +class DLL_EXPORT CHIPDeviceManagerCallbacks +{ +public: + /** + * @brief + * Called when CHIP Device events (PublicEventTypes) are triggered. + * + * @param event ChipDeviceEvent that occurred + * @param arg arguments specific to the event, if any + */ + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + /** + * @brief + * Called after an attribute has been changed + * + * @param endpoint endpoint id + * @param clusterID cluster id + * @param attributeId attribute id that was changed + * @param mask mask of the attribute + * @param manufacturerCode manufacturer code + * @param type attribute type + * @param size size of the attribute + * @param value pointer to the new value + */ + virtual void PostAttributeChangeCallback(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint8_t type, uint16_t size, uint8_t * value) + {} + virtual ~CHIPDeviceManagerCallbacks() {} +}; + +/** + * @brief + * A common class that drives other components of the CHIP stack + */ +class DLL_EXPORT CHIPDeviceManager +{ +public: + CHIPDeviceManager(const CHIPDeviceManager &) = delete; + CHIPDeviceManager(const CHIPDeviceManager &&) = delete; + CHIPDeviceManager & operator=(const CHIPDeviceManager &) = delete; + + static CHIPDeviceManager & GetInstance() + { + static CHIPDeviceManager instance; + return instance; + } + + /** + * @brief + * Initialise CHIPDeviceManager + * + * @param cb Application's instance of the CHIPDeviceManagerCallbacks for consuming events + */ + CHIP_ERROR Init(CHIPDeviceManagerCallbacks * cb); + + /** + * @brief + * Fetch a pointer to the registered CHIPDeviceManagerCallbacks object. + * + */ + CHIPDeviceManagerCallbacks * GetCHIPDeviceManagerCallbacks() { return mCB; } + + /** + * Use internally for registration of the ChipDeviceEvents + */ + static void CommonDeviceEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + +private: + CHIPDeviceManagerCallbacks * mCB = nullptr; + CHIPDeviceManager() {} +}; + +} // namespace DeviceManager +} // namespace chip + +#endif /* CHIP_DEVICEMANAGER_H_ */ diff --git a/examples/ota-provider-app/esp32/main/include/DeviceCallbacks.h b/examples/ota-provider-app/esp32/main/include/DeviceCallbacks.h new file mode 100644 index 00000000000000..d94560b8448d86 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/include/DeviceCallbacks.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.h + * + * Implementations for the DeviceManager callbacks for this application + * + **/ + +#ifndef DEVICE_CALLBACKS_H +#define DEVICE_CALLBACKS_H + +#include "CHIPDeviceManager.h" +#include +#include + +class DeviceCallbacks : public chip::DeviceManager::CHIPDeviceManagerCallbacks +{ +public: + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + virtual void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint8_t type, uint16_t size, uint8_t * value); + +private: + void OnInternetConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnSessionEstablished(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); + void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); +}; + +#endif // DEVICE_CALLBACKS_H diff --git a/examples/ota-provider-app/esp32/main/main.cpp b/examples/ota-provider-app/esp32/main/main.cpp new file mode 100644 index 00000000000000..3d2191c6afef96 --- /dev/null +++ b/examples/ota-provider-app/esp32/main/main.cpp @@ -0,0 +1,234 @@ +/* + * + * 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 "CHIPDeviceManager.h" +#include "DeviceCallbacks.h" +#include "esp_heap_caps_init.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_spi_flash.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nvs_flash.h" +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +using chip::BitFlags; +using chip::app::Clusters::OTAProviderDelegate; +using chip::bdx::TransferControlFlags; +using chip::Callback::Callback; +using chip::Messaging::ExchangeManager; +using namespace ::chip; +using namespace ::chip::System; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceManager; +using namespace ::chip::DeviceLayer; + +CHIP_ERROR OnBlockQuery(void * context, chip::System::PacketBufferHandle & blockBuf, size_t & size, bool & isEof, uint32_t offset); +void OnTransferComplete(void * context); +void OnTransferFailed(void * context, BdxSenderErrorTypes status); + +namespace { +const char * TAG = "ota-provider-app"; +static DeviceCallbacks EchoCallbacks; +BdxOtaSender bdxServer; + +// TODO: this should probably be done dynamically +constexpr chip::EndpointId kOtaProviderEndpoint = 0; +constexpr uint32_t kMaxBdxBlockSize = 1024; +constexpr chip::System::Clock::Timeout kBdxTimeout = chip::System::Clock::Seconds16(5 * 60); // Specification mandates >= 5 minutes +constexpr chip::System::Clock::Timeout kBdxPollFreq = chip::System::Clock::Milliseconds32(500); +const char * otaFilename = "hello-world.bin"; +const esp_partition_t * otaPartition = nullptr; +uint32_t otaImageLen = 0; +uint32_t otaTransferInProgress = false; + +chip::Callback::Callback onBlockQueryCallback(OnBlockQuery, nullptr); +chip::Callback::Callback onTransferCompleteCallback(OnTransferComplete, nullptr); +chip::Callback::Callback onTransferFailedCallback(OnTransferFailed, nullptr); +} // namespace + +CHIP_ERROR OnBlockQuery(void * context, chip::System::PacketBufferHandle & blockBuf, size_t & size, bool & isEof, uint32_t offset) +{ + if (otaTransferInProgress == false) + { + if (otaPartition == nullptr || otaImageLen == 0) + { + ESP_LOGE(TAG, "OTA partition not found"); + return CHIP_ERROR_OPEN_FAILED; + } + otaTransferInProgress = true; + } + + uint16_t blockBufAvailableLength = blockBuf->AvailableDataLength(); + uint16_t transferBlockSize = bdxServer.GetTransferBlockSize(); + + size = (blockBufAvailableLength < transferBlockSize) ? blockBufAvailableLength : transferBlockSize; + + // There are two types of messages requestor can use to query a block: `BlockQuery` and `BlockQueryWithSkip` so, + // to handle both case in a single callback offset is used which is managed by the `BdxOtaSender`. + // So, offset + size will not overflow and even if it overflows the esp_partition_read API will return an error. + if (offset + size >= otaImageLen) + { + size = otaImageLen - offset; + isEof = true; + } + else + { + isEof = false; + } + + esp_err_t err = esp_partition_read(otaPartition, offset + sizeof(otaImageLen), blockBuf->Start(), size); + if (err != ESP_OK) + { + ESP_LOGI(TAG, "Failed to read %d bytes from offset %d", size, offset + sizeof(otaImageLen)); + size = 0; + isEof = false; + return CHIP_ERROR_READ_FAILED; + } + + ESP_LOGI(TAG, "Read %d bytes from offset %d", size, offset + sizeof(otaImageLen)); + return CHIP_NO_ERROR; +} + +void OnTransferComplete(void * context) +{ + ESP_LOGI(TAG, "OTA Image Transfer Complete"); + otaTransferInProgress = false; +} + +void OnTransferFailed(void * context, BdxSenderErrorTypes status) +{ + ESP_LOGI(TAG, "OTA Image Transfer Failed, status:%x", status); + otaTransferInProgress = false; +} + +extern "C" void app_main() +{ + ESP_LOGI(TAG, "OTA Provider!"); + + /* Print chip information */ + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + ESP_LOGI(TAG, "This is ESP32 chip with %d CPU cores, WiFi%s%s, ", chip_info.cores, + (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); + + ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision); + + ESP_LOGI(TAG, "%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024), + (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); + + // Initialize the ESP NVS layer. + esp_err_t err = nvs_flash_init(); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "nvs_flash_init() failed: %s", esp_err_to_name(err)); + return; + } + + OTAProviderExample otaProvider; + + CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance(); + + CHIP_ERROR error = deviceMgr.Init(&EchoCallbacks); + if (error != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(error)); + return; + } + + chip::Server::GetInstance().Init(); + + // Initialize device attestation config + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); + + // Register handler to handle bdx messages + error = chip::Server::GetInstance().GetExchangeManager().RegisterUnsolicitedMessageHandlerForProtocol(chip::Protocols::BDX::Id, + &bdxServer); + if (error != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "RegisterUnsolicitedMessageHandler failed: %s", chip::ErrorStr(error)); + return; + } + + BdxOtaSenderCallbacks callbacks; + callbacks.onBlockQuery = &onBlockQueryCallback; + callbacks.onTransferComplete = &onTransferCompleteCallback; + callbacks.onTransferFailed = &onTransferFailedCallback; + bdxServer.SetCallbacks(callbacks); + + // If OTA image is available in flash storage then set to update available + otaPartition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "ota_data"); + if (otaPartition != nullptr) + { + ESP_LOGI(TAG, "Partition found %s address:0x%x size:0x%x", otaPartition->label, otaPartition->address, otaPartition->size); + + // TODO: Use the OTA image header specified in the specification + // Right now we are using just image length instead of full header + esp_partition_read(otaPartition, 0, &otaImageLen, sizeof(otaImageLen)); + if (otaImageLen > otaPartition->size) + { + otaImageLen = 0; + } + ESP_LOGI(TAG, "OTA image length %d bytes", otaImageLen); + } + else + { + ESP_LOGE(TAG, "OTA partition not found"); + } + + if (otaImageLen > 0) + { + otaProvider.SetQueryImageBehavior(OTAProviderExample::kRespondWithUpdateAvailable); + otaProvider.SetOTAFilePath(otaFilename); + } + + chip::app::Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, &otaProvider); + + BitFlags bdxFlags; + bdxFlags.Set(TransferControlFlags::kReceiverDrive); + error = bdxServer.PrepareForTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kSender, bdxFlags, + kMaxBdxBlockSize, kBdxTimeout, kBdxPollFreq); + if (error != CHIP_NO_ERROR) + { + ChipLogError(BDX, "Failed to init BDX server: %s", chip::ErrorStr(error)); + return; + } + + // Run the UI Loop + while (true) + { + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} diff --git a/examples/ota-provider-app/esp32/partitions.csv b/examples/ota-provider-app/esp32/partitions.csv new file mode 100644 index 00000000000000..671212345e7887 --- /dev/null +++ b/examples/ota-provider-app/esp32/partitions.csv @@ -0,0 +1,10 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +# Factory partition size about 1.9MB +factory, app, factory, , 1945K, +# OTA partition of size 1M +ota_data, data, nvs, , 1M, +# ota_1, app, ota_1, , 512K, diff --git a/examples/ota-provider-app/esp32/sdkconfig.defaults b/examples/ota-provider-app/esp32/sdkconfig.defaults new file mode 100644 index 00000000000000..aaae13fbb5c092 --- /dev/null +++ b/examples/ota-provider-app/esp32/sdkconfig.defaults @@ -0,0 +1,57 @@ +# +# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2018 Nest Labs, Inc. +# All rights reserved. +# +# 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. +# +# Description: +# Some useful defaults for the demo app configuration. +# + + +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +#enable BT +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +#enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" + +# Add RTC memory to system heap +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# Product id +CONFIG_DEVICE_VENDOR_ID=0x235A +CONFIG_DEVICE_PRODUCT_ID=0x4591 + +# Main task needs a bit more stack than the default +# default is 3584, bump this up to 6k. +CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144 + +# Serial Flasher config +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" + +# discriminator +CONFIG_USE_TEST_SETUP_DISCRIMINATOR=0xF01 diff --git a/examples/ota-provider-app/esp32/third_party/connectedhomeip b/examples/ota-provider-app/esp32/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/ota-provider-app/esp32/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file diff --git a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp index 379301ef4d70e9..3cc95d329c7b58 100644 --- a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp +++ b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp @@ -62,14 +62,14 @@ bool GenerateBdxUri(const Span & fileDesignator, Span outUri, size_t static constexpr char bdxPrefix[] = "bdx://"; chip::NodeId nodeId = chip::kTestDeviceNodeId; // TODO: read this dynamically size_t nodeIdHexStrLen = sizeof(nodeId) * 2; - size_t expectedLength = strlen(bdxPrefix) + nodeIdHexStrLen + fileDesignator.size(); + size_t expectedLength = strlen(bdxPrefix) + nodeIdHexStrLen + 1 + fileDesignator.size(); if (expectedLength >= availableSize) { return false; } - size_t written = static_cast(snprintf(outUri.data(), availableSize, "%s" ChipLogFormatX64 "%s", bdxPrefix, + size_t written = static_cast(snprintf(outUri.data(), availableSize, "%s" ChipLogFormatX64 "/%s", bdxPrefix, ChipLogValueX64(nodeId), fileDesignator.data())); return expectedLength == written; diff --git a/examples/ota-requestor-app/esp32/.gitignore b/examples/ota-requestor-app/esp32/.gitignore new file mode 100644 index 00000000000000..234526a082ad26 --- /dev/null +++ b/examples/ota-requestor-app/esp32/.gitignore @@ -0,0 +1,5 @@ +*.vscode + +/build/ +/sdkconfig +/sdkconfig.old diff --git a/examples/ota-requestor-app/esp32/CMakeLists.txt b/examples/ota-requestor-app/esp32/CMakeLists.txt new file mode 100644 index 00000000000000..2e98c02cef57b8 --- /dev/null +++ b/examples/ota-requestor-app/esp32/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. + +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../common/cmake/idf_flashing.cmake) + +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/third_party/connectedhomeip/config/esp32/components" + "${CMAKE_CURRENT_LIST_DIR}/../../common/QRCode" +) + +project(chip-ota-requester-app) +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++14;-Os;-DLWIP_IPV6_SCOPES=0;-DCHIP_HAVE_CONFIG_H" APPEND) +idf_build_set_property(C_COMPILE_OPTIONS "-Os;-DLWIP_IPV6_SCOPES=0" APPEND) + +flashing_script() diff --git a/examples/ota-requestor-app/esp32/README.md b/examples/ota-requestor-app/esp32/README.md new file mode 100644 index 00000000000000..1d9a783db5bdee --- /dev/null +++ b/examples/ota-requestor-app/esp32/README.md @@ -0,0 +1,97 @@ +# CHIP ESP32 OTA Requestor Example + +A prototype application that demonstrates OTA Requestor capabilities. + +## Prerequisite + +Before moving ahead, make sure you have +[OTA Provider](../../ota-provider-app/esp32) is commissioned and running. + +## Supported Devices + +- This example supports ESP32 and ESP32C3. For details please check + [here](https://github.com/shubhamdp/connectedhomeip/tree/shubhamdp-patch-1/examples/all-clusters-app/esp32#supported-devices). + +## Building the Example Application + +- If you are building for the first time please check + [Building the Example Application](https://github.com/shubhamdp/connectedhomeip/tree/shubhamdp-patch-1/examples/all-clusters-app/esp32#building-the-example-application) + guide. +- Otherwise, `idf.py build` works! + +## Flashing the Example Application + +``` +idf.py -p flash monitor +``` + +## Commissioning over BLE using chip-tool + +- Please build the standalone chip-tool as described [here](../../chip-tool) +- Commissioning the OTA Requestor + +``` +./out/debug/chip-tool pairing ble-wifi 12346 0 20202021 3840 +``` + +## Query for an OTA Image + +After commissioning is successful, query for OTA image. Head over to ESP32 +console and fire the following command. This command creates a CASE session with +OTA Provider and start the OTA image transfer using BDX protocol. + +``` +esp32> QueryImage +``` + +OTA image transfer takes some time and once done it prints following log + +``` +I (322620) OTARequesterImpl: Transfer complete! +``` + +## Apply update request + +Once transfer is complete OTA Requestor should take permission from the OTA +Provider for applying the OTA image. + +``` +esp32> ApplyUpdateRequest +``` + +After this step device should reboot and start running hello world example. + +## ESP32 OTA Requestor with Linux OTA Provider + +- Build the [Linux OTA Provider](../../ota-provider-app/linux) +- Run the Linux OTA Provider with + [hello world OTA image](http://shubhamdp.github.io/esp_ota/esp32/hello-world-flash-in-ota-provider-partition.bin). + This OTA image is built for ESP32, it will not work on other devices. + +``` +./out/debug/chip-ota-provider-app -f hello-world-flash-in-ota-provider-partition.bin +``` + +- Provision the Linux OTA Provider using chip-tool + +``` +./out/debug/chip-tool pairing 12345 20202021 +``` + +- Note down the OTA Provider IP address and Node Id and repeat the steps for + ESP32 OTA Requestor app + +--- + +## Features + +- Can perform the actual OTA if the image provided is valid +- Code for running a full BDX download exists in BDX +- Can send QueryImage and ApplyUpdateRequest commands triggered from console +- Downloads a file over BDX served by an OTA Provider server + +## Limitations + +- Does not verify QueryImageResponse status +- Does not support AnnounceOTAProvider command or OTA Requestor attributes +- Does not support the header defined in Matter Specification. diff --git a/examples/ota-requestor-app/esp32/main/BDXDownloader.cpp b/examples/ota-requestor-app/esp32/main/BDXDownloader.cpp new file mode 100644 index 00000000000000..db1f08f1f40f25 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/BDXDownloader.cpp @@ -0,0 +1,155 @@ +/* + * + * 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 "BDXDownloader.h" + +#include +#include +#include +#include + +using namespace chip::bdx; + +void BdxDownloader::SetInitialExchange(chip::Messaging::ExchangeContext * ec) +{ + mExchangeCtx = ec; +} + +void BdxDownloader::SetCallbacks(BdxDownloaderCallbacks callbacks) +{ + mOnBlockReceivedCallback = callbacks.onBlockReceived; + mOnTransferCompleteCallback = callbacks.onTransferComplete; + mOnTransferFailedCallback = callbacks.onTransferFailed; +} + +void BdxDownloader::HandleTransferSessionOutput(TransferSession::OutputEvent & event) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (event.EventType != TransferSession::OutputEventType::kNone) + { + ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType)); + } + + switch (event.EventType) + { + case TransferSession::OutputEventType::kNone: + if (mIsTransferComplete) + { + if (mOnTransferCompleteCallback != nullptr && mOnTransferCompleteCallback->mCall != nullptr) + { + mOnTransferCompleteCallback->mCall(mOnTransferCompleteCallback->mContext); + } + else + { + ChipLogError(BDX, "onTransferComplete Callback not set"); + } + mTransfer.Reset(); + mIsTransferComplete = false; + } + break; + case TransferSession::OutputEventType::kMsgToSend: { + chip::Messaging::SendFlags sendFlags; + VerifyOrReturn(mExchangeCtx != nullptr, ChipLogError(BDX, "mExchangeContext is null, cannot proceed")); + if (event.msgTypeData.MessageType == static_cast(MessageType::ReceiveInit)) + { + sendFlags.Set(chip::Messaging::SendMessageFlags::kFromInitiator); + } + if (event.msgTypeData.MessageType != static_cast(MessageType::BlockAckEOF)) + { + sendFlags.Set(chip::Messaging::SendMessageFlags::kExpectResponse); + } + 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 TransferSession::OutputEventType::kAcceptReceived: + VerifyOrReturn(CHIP_NO_ERROR == mTransfer.PrepareBlockQuery(), ChipLogError(BDX, "PrepareBlockQuery failed")); + break; + case TransferSession::OutputEventType::kBlockReceived: { + ChipLogDetail(BDX, "Got block length %zu", event.blockdata.Length); + + // TODO: while convenient, we should not do a synchronous block write in our example application - this is bad practice + if (mOnBlockReceivedCallback != nullptr && mOnBlockReceivedCallback->mCall != nullptr) + { + mOnBlockReceivedCallback->mCall(mOnBlockReceivedCallback->mContext, event.blockdata); + } + else + { + ChipLogError(BDX, "onBlockReceived Callback not set"); + } + 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 TransferSession::OutputEventType::kStatusReceived: + ChipLogError(BDX, "Got StatusReport %x", static_cast(event.statusData.statusCode)); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxDownloaderStatusReceived); + } + else + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + mTransfer.Reset(); + mExchangeCtx->Close(); + break; + case TransferSession::OutputEventType::kInternalError: + ChipLogError(BDX, "InternalError"); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxDownloaderInternal); + } + else + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + mTransfer.Reset(); + mExchangeCtx->Close(); + break; + case TransferSession::OutputEventType::kTransferTimeout: + ChipLogError(BDX, "Transfer timed out"); + if (mOnTransferFailedCallback != nullptr && mOnTransferFailedCallback->mCall != nullptr) + { + mOnTransferFailedCallback->mCall(mOnTransferFailedCallback->mContext, kErrorBdxDownloaderTimeOut); + } + else + { + ChipLogError(BDX, "onTransferFailed Callback not set"); + } + mTransfer.Reset(); + mExchangeCtx->Close(); + break; + case TransferSession::OutputEventType::kInitReceived: + case TransferSession::OutputEventType::kAckReceived: + case TransferSession::OutputEventType::kQueryReceived: + case TransferSession::OutputEventType::kAckEOFReceived: + default: + ChipLogError(BDX, "Unexpected BDX event type: %" PRIu16, static_cast(event.EventType)); + } +} diff --git a/examples/ota-requestor-app/esp32/main/CHIPDeviceManager.cpp b/examples/ota-requestor-app/esp32/main/CHIPDeviceManager.cpp new file mode 100644 index 00000000000000..9a24074d6cceef --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/CHIPDeviceManager.cpp @@ -0,0 +1,105 @@ +/* + * + * 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. + */ + +/** + * @file + * This file implements the CHIP Device Interface that is used by + * applications to interact with the CHIP stack + * + */ + +#include + +#include "CHIPDeviceManager.h" +#include +#include +#include +#include +#include +#include + +using namespace ::chip; + +namespace chip { + +namespace DeviceManager { + +using namespace ::chip::DeviceLayer; + +void CHIPDeviceManager::CommonDeviceEventHandler(const ChipDeviceEvent * event, intptr_t arg) +{ + CHIPDeviceManagerCallbacks * cb = reinterpret_cast(arg); + if (cb != nullptr) + { + cb->DeviceEventCallback(event, reinterpret_cast(cb)); + } +} + +CHIP_ERROR CHIPDeviceManager::Init(CHIPDeviceManagerCallbacks * cb) +{ + CHIP_ERROR err; + mCB = cb; + RendezvousInformationFlags flags = RendezvousInformationFlags(CONFIG_RENDEZVOUS_MODE); + + // Initialize the CHIP stack. + err = PlatformMgr().InitChipStack(); + SuccessOrExit(err); + + if (flags.Has(RendezvousInformationFlag::kBLE)) + { + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + } + else if (flags.Has(RendezvousInformationFlag::kSoftAP)) + { + // TODO(cecille): Fix for the case where BLE and SoftAP are both enabled.` + ConnectivityMgr().SetBLEAdvertisingEnabled(false); + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + else + { + // If rendezvous is bypassed, enable SoftAP so that the device can still + // be communicated with via its SoftAP as needed. + ConnectivityMgr().SetWiFiAPMode(ConnectivityManager::kWiFiAPMode_Enabled); + } + + err = Platform::MemoryInit(); + SuccessOrExit(err); + + // Register a function to receive events from the CHIP device layer. Note that calls to + // this function will happen on the CHIP event loop thread, not the app_main thread. + PlatformMgr().AddEventHandler(CHIPDeviceManager::CommonDeviceEventHandler, reinterpret_cast(cb)); + + // Start a task to run the CHIP Device event loop. + err = PlatformMgr().StartEventLoopTask(); + SuccessOrExit(err); + +exit: + return err; +} +} // namespace DeviceManager +} // namespace chip + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t mask, uint8_t type, uint16_t size, + uint8_t * value) +{ + chip::DeviceManager::CHIPDeviceManagerCallbacks * cb = + chip::DeviceManager::CHIPDeviceManager::GetInstance().GetCHIPDeviceManagerCallbacks(); + if (cb != nullptr) + { + cb->PostAttributeChangeCallback(path.mEndpointId, path.mClusterId, path.mAttributeId, mask, type, size, value); + } +} diff --git a/examples/ota-requestor-app/esp32/main/CMakeLists.txt b/examples/ota-requestor-app/esp32/main/CMakeLists.txt new file mode 100644 index 00000000000000..72c8734dab8d02 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/CMakeLists.txt @@ -0,0 +1,50 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. +# +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +idf_component_register(PRIV_INCLUDE_DIRS + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/ota-requestor-app/" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-requestor-app/ota-requestor-common" + SRC_DIRS + "${CMAKE_CURRENT_LIST_DIR}" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/ota-requestor-app/zap-generated" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated/attributes" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/reporting" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/administrator-commissioning-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/basic" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/bindings" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/descriptor" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/diagnostic-logs-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ethernet_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/thread_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/wifi_network_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/software_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/general_diagnostics_server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/general-commissioning-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/network-commissioning" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/operational-credentials-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-requestor-app/ota-requestor-common" + EXCLUDE_SRCS + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/ota-requestor-app/ota-requestor-common/BDXDownloader.cpp" + PRIV_REQUIRES chip QRCode bt console app_update) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 14) +target_compile_options(${COMPONENT_LIB} PRIVATE "-DLWIP_IPV6_SCOPES=0" "-DCHIP_HAVE_CONFIG_H") diff --git a/examples/ota-requestor-app/esp32/main/DeviceCallbacks.cpp b/examples/ota-requestor-app/esp32/main/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..043d452011e7a4 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/DeviceCallbacks.cpp @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ +#include "DeviceCallbacks.h" + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include +#include +#include +#include + +static const char * TAG = "echo-devicecallbacks"; + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; + +void DeviceCallbacks::DeviceEventCallback(const ChipDeviceEvent * event, intptr_t arg) +{ + switch (event->Type) + { + case DeviceEventType::kInternetConnectivityChange: + OnInternetConnectivityChange(event); + break; + + case DeviceEventType::kSessionEstablished: + OnSessionEstablished(event); + break; + case DeviceEventType::kInterfaceIpAddressChanged: + if ((event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV4_Assigned) || + (event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV6_Assigned)) + { + // MDNS server restart on any ip assignment: if link local ipv6 is configured, that + // will not trigger a 'internet connectivity change' as there is no internet + // connectivity. MDNS still wants to refresh its listening interfaces to include the + // newly selected address. + chip::app::DnssdServer::Instance().StartServer(); + } + break; + } + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} + +void DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t mask, + uint8_t type, uint16_t size, uint8_t * value) +{ + ESP_LOGI(TAG, + "PostAttributeChangeCallback - Cluster ID: '" ChipLogFormatMEI + "', EndPoint ID: '0x%02x', Attribute ID: '" ChipLogFormatMEI "'", + ChipLogValueMEI(clusterId), endpointId, ChipLogValueMEI(attributeId)); + + // TODO handle this callback in switch statement + ESP_LOGI(TAG, "Unhandled cluster ID: " ChipLogFormatMEI, ChipLogValueMEI(clusterId)); + + ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); +} + +void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event) +{ + if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established) + { + ESP_LOGI(TAG, "Server ready at: %s:%d", event->InternetConnectivityChange.address, CHIP_PORT); + chip::app::DnssdServer::Instance().StartServer(); + } + else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv4 connectivity..."); + } + if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) + { + ESP_LOGI(TAG, "IPv6 Server ready..."); + chip::app::DnssdServer::Instance().StartServer(); + } + else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) + { + ESP_LOGE(TAG, "Lost IPv6 connectivity..."); + } +} + +void DeviceCallbacks::OnSessionEstablished(const ChipDeviceEvent * event) +{ + if (event->SessionEstablished.IsCommissioner) + { + ESP_LOGI(TAG, "Commissioner detected!"); + } +} diff --git a/examples/ota-requestor-app/esp32/main/Kconfig.projbuild b/examples/ota-requestor-app/esp32/main/Kconfig.projbuild new file mode 100644 index 00000000000000..96a7f14d5c69ec --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/Kconfig.projbuild @@ -0,0 +1,50 @@ +# +# Copyright (c) 2020 Project CHIP Authors +# All rights reserved. +# +# 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. +# +# Description: +# Configuration options CHIP ESP32 demo application. +# + +menu "Demo" + + choice + prompt "Rendezvous Mode" + default RENDEZVOUS_MODE_BLE + help + Specifies the Rendezvous mode of the peripheral. + + config RENDEZVOUS_MODE_BYPASS + bool "Bypass" + config RENDEZVOUS_MODE_WIFI + bool "Wi-Fi" + config RENDEZVOUS_MODE_BLE + bool "BLE" + config RENDEZVOUS_MODE_THREAD + bool "Thread" + config RENDEZVOUS_MODE_ETHERNET + bool "Ethernet" + endchoice + + config RENDEZVOUS_MODE + int + range 0 8 + default 0 if RENDEZVOUS_MODE_BYPASS + default 1 if RENDEZVOUS_MODE_WIFI + default 2 if RENDEZVOUS_MODE_BLE + default 4 if RENDEZVOUS_MODE_THREAD + default 8 if RENDEZVOUS_MODE_ETHERNET + +endmenu diff --git a/examples/ota-requestor-app/esp32/main/OTARequesterImpl.cpp b/examples/ota-requestor-app/esp32/main/OTARequesterImpl.cpp new file mode 100644 index 00000000000000..8c523855e5573a --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/OTARequesterImpl.cpp @@ -0,0 +1,328 @@ +/* + * + * 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 "BDXDownloader.h" +#include "OTARequesterImpl.h" +#include "OTAUpdater.h" + +#define TAG "OTARequesterImpl" + +using chip::ByteSpan; +using chip::CharSpan; +using chip::DeviceProxy; +using chip::EndpointId; +using chip::FabricIndex; +using chip::NodeId; +using chip::OnDeviceConnected; +using chip::OnDeviceConnectionFailure; +using chip::Optional; +using chip::PeerId; +using chip::Server; +using chip::VendorId; +using chip::bdx::TransferSession; +using chip::Callback::Callback; +using chip::Inet::IPAddress; +using chip::System::Layer; +using chip::Transport::PeerAddress; +using namespace chip::Messaging; +using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands; + +void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response); +void OnQueryImageFailure(void * context, EmberAfStatus status); +void OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response); +void OnApplyUpdateRequestFailure(void * context, EmberAfStatus status); + +void OnConnected(void * context, chip::DeviceProxy * deviceProxy); +void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error); + +void OnBlockReceived(void * context, const chip::bdx::TransferSession::BlockData & blockdata); +void OnTransferComplete(void * context); +void OnTransferFailed(void * context, BdxDownloaderErrorTypes status); + +enum OTARequestorCommands +{ + kCommandQueryImage = 0, + kCommandApplyUpdateRequest, + kCommandNotifyUpdateApplied, +}; + +namespace { +// TODO: Encapsulate these globals and the callbacks in some class +ExchangeContext * exchangeCtx = nullptr; +BdxDownloader bdxDownloader; +enum OTARequestorCommands operationalDeviceContext; + +constexpr uint8_t kMaxUpdateTokenLen = 32; // must be between 8 and 32 +uint8_t otaUpdateToken[kMaxUpdateTokenLen] = { 0 }; +uint8_t otaUpdateTokenLen = 0; + +/* Callbacks for operational device proxy connect response */ +Callback onConnectedCallback(OnConnected, &operationalDeviceContext); +Callback onConnectionFailureCallback(OnConnectionFailure, nullptr); + +/* Callbacks for BDX data transfer */ +Callback onBlockReceivedCallback(OnBlockReceived, nullptr); +Callback onTransferCompleteCallback(OnTransferComplete, nullptr); +Callback onTransferFailedCallback(OnTransferFailed, nullptr); + +FabricIndex providerFabricIndex = 1; +} // namespace + +void OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) +{ + ChipLogDetail(SoftwareUpdate, "QueryImageResponse responded with action %" PRIu8, response.status); + + if (response.updateToken.HasValue()) + { + otaUpdateTokenLen = response.updateToken.Value().size(); + memcpy(otaUpdateToken, response.updateToken.Value().data(), otaUpdateTokenLen); + } + if (response.imageURI.HasValue() == false) + { + ChipLogError(BDX, "OTA image URI missing"); + return; + } + + // TODO: Handle image URI for protocol other than bdx + // Ignore the first 23 "bdx:///" + char fileDesignator[128]; // 128 is arbitrary value + memset(fileDesignator, 0, sizeof(fileDesignator)); + size_t fileDesignatorLength = response.imageURI.Value().size() - 23 + 1; // + 1 for \0 + if ((response.imageURI.Value().size() - 23) > sizeof(fileDesignator)) + { + fileDesignatorLength = sizeof(fileDesignator); + } + strlcpy(fileDesignator, response.imageURI.Value().data() + 23, fileDesignatorLength); + + TransferSession::TransferInitData initOptions; + initOptions.TransferCtlFlags = chip::bdx::TransferControlFlags::kReceiverDrive; + initOptions.MaxBlockSize = 1024; + initOptions.FileDesLength = static_cast(fileDesignatorLength); + initOptions.FileDesignator = reinterpret_cast(fileDesignator); + + chip::OperationalDeviceProxy * operationalDeviceProxy = Server::GetInstance().GetOperationalDeviceProxy(); + if (operationalDeviceProxy != nullptr) + { + chip::Messaging::ExchangeManager * exchangeMgr = operationalDeviceProxy->GetExchangeManager(); + chip::Optional session = operationalDeviceProxy->GetSecureSession(); + if (exchangeMgr != nullptr && session.HasValue()) + { + exchangeCtx = exchangeMgr->NewContext(session.Value(), &bdxDownloader); + } + if (exchangeCtx == nullptr) + { + ChipLogError(BDX, "unable to allocate ec: exchangeMgr=%p sessionExists? %u", exchangeMgr, session.HasValue()); + return; + } + } + else + { + ChipLogError(BDX, "Failed to get OperationalDeviceProxy"); + return; + } + + bdxDownloader.SetInitialExchange(exchangeCtx); + + BdxDownloaderCallbacks bdxCallbacks; + bdxCallbacks.onBlockReceived = &onBlockReceivedCallback; + bdxCallbacks.onTransferComplete = &onTransferCompleteCallback; + bdxCallbacks.onTransferFailed = &onTransferFailedCallback; + bdxDownloader.SetCallbacks(bdxCallbacks); + + // This will kick of a timer which will regularly check for updates to the bdx::TransferSession state machine. + bdxDownloader.InitiateTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kReceiver, initOptions, + chip::System::Clock::Seconds16(20)); +} + +void OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response) +{ + ChipLogDetail(SoftwareUpdate, "ApplyUpdateResponse responded with action %" PRIu8, response.action); + // Providing arbitrary value + OTAUpdater::GetInstance().Apply(3); +} + +void OnQueryImageFailure(void * context, EmberAfStatus status) +{ + ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); +} + +void OnApplyUpdateRequestFailure(void * context, EmberAfStatus status) +{ + ChipLogDetail(SoftwareUpdate, "ApplyUpdateRequest failure response %" PRIu8, status); +} + +void OnConnected(void * context, chip::DeviceProxy * deviceProxy) +{ + ChipLogDetail(SoftwareUpdate, "Callback OnConnected"); + uint8_t * command = reinterpret_cast(context); + + chip::Controller::OtaSoftwareUpdateProviderCluster cluster; + constexpr EndpointId kOtaProviderEndpoint = 0; + + CHIP_ERROR err = cluster.Associate(deviceProxy, kOtaProviderEndpoint); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Associate() failed: %s", chip::ErrorStr(err)); + return; + } + + switch (*command) + { + case kCommandQueryImage: { + // These parameters are chosen arbitrarily + constexpr VendorId kExampleVendorId = VendorId::Common; + constexpr uint16_t kExampleProductId = CONFIG_DEVICE_PRODUCT_ID; + constexpr uint16_t kExampleHWVersion = 0; + constexpr uint16_t kExampleSoftwareVersion = 0; + constexpr EmberAfOTADownloadProtocol kExampleProtocolsSupported[] = { EMBER_ZCL_OTA_DOWNLOAD_PROTOCOL_BDX_SYNCHRONOUS }; + const char locationBuf[] = { 'U', 'S' }; + CharSpan exampleLocation(locationBuf); + constexpr bool kExampleClientCanConsent = false; + ByteSpan metadata; + + QueryImage::Type args; + args.vendorId = kExampleVendorId; + args.productId = kExampleProductId; + args.softwareVersion = kExampleSoftwareVersion; + args.protocolsSupported = kExampleProtocolsSupported; + args.hardwareVersion.Emplace(kExampleHWVersion); + args.location.Emplace(exampleLocation); + args.requestorCanConsent.Emplace(kExampleClientCanConsent); + args.metadataForProvider.Emplace(metadata); + + err = cluster.InvokeCommand(args, nullptr, OnQueryImageResponse, OnQueryImageFailure); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "QueryImage() failed: %" CHIP_ERROR_FORMAT, err.Format()); + } + break; + } + case kCommandApplyUpdateRequest: { + constexpr uint32_t kNewVersion = 1; + + ApplyUpdateRequest::Type args; + args.updateToken = ByteSpan(otaUpdateToken, otaUpdateTokenLen); + args.newVersion = kNewVersion; + + err = cluster.InvokeCommand(args, nullptr, OnApplyUpdateResponse, OnApplyUpdateRequestFailure); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "ApplyUpdateRequest() failed: %s", chip::ErrorStr(err)); + } + break; + } + default: + break; + } +} + +void OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERROR error) +{ + ChipLogError(SoftwareUpdate, "failed to connect to 0x%" PRIX64 ": %" CHIP_ERROR_FORMAT, deviceId, error.Format()); +} + +void OnBlockReceived(void * context, const chip::bdx::TransferSession::BlockData & blockdata) +{ + if (OTAUpdater::GetInstance().IsInProgress() == false) + { + OTAUpdater::GetInstance().Begin(); + } + // TODO: Process/skip the Matter OTA header + OTAUpdater::GetInstance().Write(reinterpret_cast(blockdata.Data), blockdata.Length); +} + +void OnTransferComplete(void * context) +{ + ESP_LOGI(TAG, "Transfer complete!"); + OTAUpdater::GetInstance().End(); +} + +void OnTransferFailed(void * context, BdxDownloaderErrorTypes status) +{ + ESP_LOGI(TAG, "Transfer Failed, status:%x", status); + OTAUpdater::GetInstance().Abort(); +} + +void ConnectToProvider(const char * ipAddress, uint32_t nodeId) +{ + NodeId providerNodeId = nodeId; + + chip::OperationalDeviceProxy * operationalDeviceProxy = Server::GetInstance().GetOperationalDeviceProxy(); + if (operationalDeviceProxy != nullptr && operationalDeviceProxy->GetDeviceId() != providerNodeId) + { + operationalDeviceProxy->Disconnect(); + delete operationalDeviceProxy; + operationalDeviceProxy = nullptr; + } + + if (operationalDeviceProxy == nullptr) + { + Server * server = &(Server::GetInstance()); + chip::FabricInfo * fabric = server->GetFabricTable().FindFabricWithIndex(providerFabricIndex); + + chip::DeviceProxyInitParams initParams = { + .sessionManager = &(server->GetSecureSessionManager()), + .exchangeMgr = &(server->GetExchangeManager()), + .idAllocator = &(server->GetSessionIDAllocator()), + .fabricInfo = fabric, + // TODO: Determine where this should be instantiated + .imDelegate = chip::Platform::New(), + }; + + PeerId peerID = fabric->GetPeerId(); + peerID.SetNodeId(providerNodeId); + operationalDeviceProxy = new chip::OperationalDeviceProxy(initParams, peerID); + server->SetOperationalDeviceProxy(operationalDeviceProxy); + + // Explicitly calling UpdateDeviceData() should not be needed once OperationalDeviceProxy can resolve IP address from node + // ID and fabric index + IPAddress ipAddr; + IPAddress::FromString(ipAddress, ipAddr); + PeerAddress addr = PeerAddress::UDP(ipAddr, CHIP_PORT); + uint32_t idleInterval; + uint32_t activeInterval; + operationalDeviceProxy->GetMRPIntervals(idleInterval, activeInterval); + operationalDeviceProxy->UpdateDeviceData(addr, idleInterval, activeInterval); + } + + CHIP_ERROR err = operationalDeviceProxy->Connect(&onConnectedCallback, &onConnectionFailureCallback); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot establish connection to peer device: %" CHIP_ERROR_FORMAT, err.Format()); + } +} + +void OTARequesterImpl::SendQueryImageCommand(const char * ipAddress, uint32_t nodeId) +{ + operationalDeviceContext = kCommandQueryImage; + ConnectToProvider(ipAddress, nodeId); +} + +void OTARequesterImpl::SendApplyUpdateRequestCommand(const char * ipAddress, uint32_t nodeId) +{ + operationalDeviceContext = kCommandApplyUpdateRequest; + ConnectToProvider(ipAddress, nodeId); +} diff --git a/examples/ota-requestor-app/esp32/main/OTAUpdater.cpp b/examples/ota-requestor-app/esp32/main/OTAUpdater.cpp new file mode 100644 index 00000000000000..82156fee97a10c --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/OTAUpdater.cpp @@ -0,0 +1,155 @@ +/* + * + * 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 + +using namespace ::chip; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; + +namespace { + +const char * TAG = "OTAUpdate"; +bool otaUpdateInProgress = false; +const esp_partition_t * otaUpdatePartition = nullptr; +esp_ota_handle_t otaUpdateHandle; +uint32_t otaUpdateImageLen = 0; + +} // namespace + +bool OTAUpdater::IsInProgress(void) +{ + return otaUpdateInProgress; +} + +esp_err_t OTAUpdater::Begin(void) +{ + if (otaUpdateInProgress == true) + { + ESP_LOGW(TAG, "Already in progress"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Begin"); + 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; +} + +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; +} + +esp_err_t OTAUpdater::Abort(void) +{ + if (otaUpdateInProgress == false) + { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Abort"); + otaUpdateInProgress = false; + otaUpdateImageLen = 0; + return esp_ota_abort(otaUpdateHandle); +} + +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; +} + +void RestartTimerHandler(Layer * systemLayer, void * appState) +{ + ESP_LOGI(TAG, "Prepare to restart system!"); + esp_restart(); +} + +// TODO: Apply update after delayed action time +// TODO: Handle applying update after reboot +esp_err_t OTAUpdater::Apply(uint32_t delayedActionTime) +{ + 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); + // Allow requestor to send the Ack for the previous message + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(delayedActionTime * 1000), RestartTimerHandler, + nullptr); + return ESP_OK; +} diff --git a/examples/ota-requestor-app/esp32/main/include/BDXDownloader.h b/examples/ota-requestor-app/esp32/main/include/BDXDownloader.h new file mode 100644 index 00000000000000..a05b772cea0008 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/include/BDXDownloader.h @@ -0,0 +1,85 @@ +/* + * + * 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 + +#pragma once + +enum BdxDownloaderErrorTypes +{ + kErrorBdxDownloaderNoError = 0, + kErrorBdxDownloaderStatusReceived, + kErrorBdxDownloaderInternal, + kErrorBdxDownloaderTimeOut, +}; + +/** + * @brief + * This callback is called when bdx transfer receives Block or BlockEOF message + * + * @param[in] context User context + * @param[in] blockData BlockData structure which contains pointer to data, length of data and IsEof flag + */ +typedef void (*OnBdxBlockReceived)(void * context, const chip::bdx::TransferSession::BlockData & blockdata); + +/** + * @brief + * This callback is called after BlockEOF message is processed + * + * @param[in] context User context + */ +typedef void (*OnBdxTransferComplete)(void * context); + +/** + * @brief + * This callback is called when bdx transfer receives StatusReport messages, + * if there is any internal error, or transfer timed out + * + * @param[in] context User context + * @param[in] status Error code + */ +typedef void (*OnBdxTransferFailed)(void * context, BdxDownloaderErrorTypes status); + +// TODO: With this approach we might end up adding callback for every bdx event/message. +// Can be refactored into a single callback with events +struct BdxDownloaderCallbacks +{ + chip::Callback::Callback * onBlockReceived = nullptr; + chip::Callback::Callback * onTransferComplete = nullptr; + chip::Callback::Callback * onTransferFailed = nullptr; +}; + +class BdxDownloader : public chip::bdx::Initiator +{ +public: + void SetInitialExchange(chip::Messaging::ExchangeContext * ec); + + void SetCallbacks(BdxDownloaderCallbacks callbacks); + +private: + // inherited from bdx::Endpoint + void HandleTransferSessionOutput(chip::bdx::TransferSession::OutputEvent & event); + + bool mIsTransferComplete = false; + + chip::Callback::Callback * mOnBlockReceivedCallback = nullptr; + chip::Callback::Callback * mOnTransferCompleteCallback = nullptr; + chip::Callback::Callback * mOnTransferFailedCallback = nullptr; +}; diff --git a/examples/ota-requestor-app/esp32/main/include/CHIPDeviceManager.h b/examples/ota-requestor-app/esp32/main/include/CHIPDeviceManager.h new file mode 100644 index 00000000000000..ee424b366df1b8 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/include/CHIPDeviceManager.h @@ -0,0 +1,127 @@ +/* + * + * 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. + */ + +/** + * @file + * This file contains definitions for the CHIP DeviceManager Interface + * + * This object will co-ordinate multiple activities such as + * initialisation, rendezvous, session mgmt and other such + * activities within the CHIP stack. This is a singleton object. + */ + +#ifndef CHIP_DEVICEMANAGER_H_ +#define CHIP_DEVICEMANAGER_H_ + +#include +#include +#include + +#include + +#include +#include + +#include + +namespace chip { +namespace DeviceManager { + +/** + * @brief + * This class provides a skeleton for all the callback functions. The functions will be + * called by other objects within the CHIP stack for specific events. + * Applications interested in receiving specific callbacks can specialize this class and handle + * these events in their implementation of this class. + */ +class DLL_EXPORT CHIPDeviceManagerCallbacks +{ +public: + /** + * @brief + * Called when CHIP Device events (PublicEventTypes) are triggered. + * + * @param event ChipDeviceEvent that occurred + * @param arg arguments specific to the event, if any + */ + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + /** + * @brief + * Called after an attribute has been changed + * + * @param endpoint endpoint id + * @param clusterID cluster id + * @param attributeId attribute id that was changed + * @param mask mask of the attribute + * @param manufacturerCode manufacturer code + * @param type attribute type + * @param size size of the attribute + * @param value pointer to the new value + */ + virtual void PostAttributeChangeCallback(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint8_t type, uint16_t size, uint8_t * value) + {} + virtual ~CHIPDeviceManagerCallbacks() {} +}; + +/** + * @brief + * A common class that drives other components of the CHIP stack + */ +class DLL_EXPORT CHIPDeviceManager +{ +public: + CHIPDeviceManager(const CHIPDeviceManager &) = delete; + CHIPDeviceManager(const CHIPDeviceManager &&) = delete; + CHIPDeviceManager & operator=(const CHIPDeviceManager &) = delete; + + static CHIPDeviceManager & GetInstance() + { + static CHIPDeviceManager instance; + return instance; + } + + /** + * @brief + * Initialise CHIPDeviceManager + * + * @param cb Application's instance of the CHIPDeviceManagerCallbacks for consuming events + */ + CHIP_ERROR Init(CHIPDeviceManagerCallbacks * cb); + + /** + * @brief + * Fetch a pointer to the registered CHIPDeviceManagerCallbacks object. + * + */ + CHIPDeviceManagerCallbacks * GetCHIPDeviceManagerCallbacks() { return mCB; } + + /** + * Use internally for registration of the ChipDeviceEvents + */ + static void CommonDeviceEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + +private: + CHIPDeviceManagerCallbacks * mCB = nullptr; + CHIPDeviceManager() {} +}; + +} // namespace DeviceManager +} // namespace chip + +#endif /* CHIP_DEVICEMANAGER_H_ */ diff --git a/examples/ota-requestor-app/esp32/main/include/DeviceCallbacks.h b/examples/ota-requestor-app/esp32/main/include/DeviceCallbacks.h new file mode 100644 index 00000000000000..d94560b8448d86 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/include/DeviceCallbacks.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file DeviceCallbacks.h + * + * Implementations for the DeviceManager callbacks for this application + * + **/ + +#ifndef DEVICE_CALLBACKS_H +#define DEVICE_CALLBACKS_H + +#include "CHIPDeviceManager.h" +#include +#include + +class DeviceCallbacks : public chip::DeviceManager::CHIPDeviceManagerCallbacks +{ +public: + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + virtual void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t mask, uint8_t type, uint16_t size, uint8_t * value); + +private: + void OnInternetConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnSessionEstablished(const chip::DeviceLayer::ChipDeviceEvent * event); + void OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); + void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); +}; + +#endif // DEVICE_CALLBACKS_H diff --git a/examples/ota-requestor-app/esp32/main/include/OTARequesterImpl.h b/examples/ota-requestor-app/esp32/main/include/OTARequesterImpl.h new file mode 100644 index 00000000000000..01fb7c701dba0e --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/include/OTARequesterImpl.h @@ -0,0 +1,36 @@ +/* + * + * 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 + +class OTARequesterImpl +{ +public: + static OTARequesterImpl & GetInstance(void) + { + static OTARequesterImpl instance; + return instance; + } + + void SendQueryImageCommand(const char * ipAddress, uint32_t nodeId); + + void SendApplyUpdateRequestCommand(const char * ipAddress, uint32_t nodeId); + + void SendNotifyUpdateAppliedCommand(const char * ipAddress, uint32_t nodeId); + +private: + OTARequesterImpl() {} +}; diff --git a/examples/ota-requestor-app/esp32/main/include/OTAUpdater.h b/examples/ota-requestor-app/esp32/main/include/OTAUpdater.h new file mode 100644 index 00000000000000..725ea7bbcd62ba --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/include/OTAUpdater.h @@ -0,0 +1,105 @@ +/* + * + * 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 + +#pragma once + +/** + * @class OTAUpdater + * + * @brief This is a helper class to perform OTA on ESP devices. + * It supports preparing the flash region for new OTA image, + * writing OTA data to the flash region, successfully ending an OTA, + * aborting the OTA process, and booting from the new OTA image. + */ +class OTAUpdater +{ +public: + static OTAUpdater & GetInstance(void) + { + static OTAUpdater instance; + return instance; + } + + /** + * @brief + * Test if OTA is in progress or not. + * + * @return \c true if OTA is in progress; + * return \c false if OTA is not in progress; + */ + bool IsInProgress(void); + + /** + * @brief + * Begins an OTA data writing. + * This function finds the available flash region to write the OTA data and erase the region. + * + * @return \c ESP_OK on success; + * return \c ESP_ERR_INVALID_STATE if OTA is in progress; + * return appropriate error code otherwise; + */ + esp_err_t Begin(void); + + /** + * @brief + * Finish OTA update and validate newly written OTA image. + * + * @return \c ESP_OK on success; + * return \c ESP_ERR_INVALID_STATE if OTA is not in progress; + * return \c ESP_ERR_OTA_VALIDATE_FAILED if OTA image is invalid; + * return appropriate error code otherwise; + */ + esp_err_t End(void); + + /** + * @brief + * Write OTA data. This function can be called multiple times as data is received during the OTA operation. + * Data is written sequentially to the partition. + * + * @param[in] data OTA data + * @param[in] length Length of OTA data + * + * @return \c ESP_OK on success; + * return \c ESP_ERR_OTA_VALIDATE_FAILED First byte of image contains invalid app image magic byte; + * return appropriate error code otherwise; + */ + esp_err_t Write(const void * data, size_t length); + + /** + * @brief + * Abort the OTA. + * + * @return \c ESP_OK on success; + * return appropriate error code otherwise; + */ + esp_err_t Abort(void); + + /** + * @brief + * Apply an OTA update. + * Configures the boot partition to newly written OTA partition and restart device to boot from new app. + * + * @return \c ESP_OK on success; + * return appropriate error code otherwise; + */ + esp_err_t Apply(uint32_t delayedActionTime); + +private: + OTAUpdater(void) {} + ~OTAUpdater() {} +}; diff --git a/examples/ota-requestor-app/esp32/main/main.cpp b/examples/ota-requestor-app/esp32/main/main.cpp new file mode 100644 index 00000000000000..b12cc9c056f030 --- /dev/null +++ b/examples/ota-requestor-app/esp32/main/main.cpp @@ -0,0 +1,188 @@ +/* + * + * 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 "CHIPDeviceManager.h" +#include "DeviceCallbacks.h" +#include "esp_heap_caps_init.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_spi_flash.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nvs_flash.h" +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "OTARequesterImpl.h" +#include +#include + +using namespace ::chip; +using namespace ::chip::System; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceManager; +using namespace ::chip::DeviceLayer; + +struct CmdArgs +{ + struct arg_str * ipAddr; + struct arg_int * nodeId; + struct arg_end * end; +}; + +namespace { +const char * TAG = "ota-requester-app"; +static DeviceCallbacks EchoCallbacks; +CmdArgs queryImageCmdArgs, applyUpdateCmdArgs; +} // namespace + +void QueryImageTimerHandler(Layer * systemLayer, void * appState) +{ + ESP_LOGI(TAG, "Calling SendQueryImageCommand()"); + OTARequesterImpl::GetInstance().SendQueryImageCommand(queryImageCmdArgs.ipAddr->sval[0], queryImageCmdArgs.nodeId->ival[0]); +} + +void ApplyUpdateTimerHandler(Layer * systemLayer, void * appState) +{ + ESP_LOGI(TAG, "Calling SendApplyUpdateRequestCommand()"); + OTARequesterImpl::GetInstance().SendApplyUpdateRequestCommand(queryImageCmdArgs.ipAddr->sval[0], + queryImageCmdArgs.nodeId->ival[0]); +} + +int ESPQueryImageCmdHandler(int argc, char ** argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &queryImageCmdArgs); + if (nerrors != 0) + { + arg_print_errors(stderr, queryImageCmdArgs.end, argv[0]); + return 1; + } + ESP_LOGI(TAG, "ipAddr:%s nodeId:%x", queryImageCmdArgs.ipAddr->sval[0], queryImageCmdArgs.nodeId->ival[0]); + + /* Start one shot timer with 1 second timeout to send ApplyUpdateRequest command */ + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(1 * 1000), QueryImageTimerHandler, nullptr); + return 0; +} + +int ESPApplyUpdateCmdHandler(int argc, char ** argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &applyUpdateCmdArgs); + if (nerrors != 0) + { + arg_print_errors(stderr, applyUpdateCmdArgs.end, argv[0]); + return 1; + } + ESP_LOGI(TAG, "ipAddr:%s nodeId:%x", applyUpdateCmdArgs.ipAddr->sval[0], applyUpdateCmdArgs.nodeId->ival[0]); + + /* Start one shot timer with 1 second timeout to Query for OTA image */ + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(1 * 1000), ApplyUpdateTimerHandler, nullptr); + return 0; +} + +void ESPInitConsole(void) +{ + esp_console_repl_t * repl = NULL; + esp_console_repl_config_t replConfig = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + esp_console_dev_uart_config_t uartConfig = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + replConfig.prompt = "esp32 >"; + + esp_console_register_help_command(); + + esp_console_cmd_t queryImageCommand, applyUpdateCommand; + memset(&queryImageCommand, 0, sizeof(queryImageCommand)); + memset(&applyUpdateCommand, 0, sizeof(applyUpdateCommand)); + + queryImageCmdArgs.ipAddr = arg_str0(NULL, NULL, "", "OTA Provider IP Address"); + queryImageCmdArgs.nodeId = arg_int0(NULL, NULL, "", "OTA Provider Node ID in decimal"); + queryImageCmdArgs.end = arg_end(1); + + queryImageCommand.command = "QueryImage", queryImageCommand.help = "Query for OTA image", + queryImageCommand.func = &ESPQueryImageCmdHandler, queryImageCommand.argtable = &queryImageCmdArgs; + + applyUpdateCmdArgs.ipAddr = arg_str0(NULL, NULL, "", "OTA Provider IP Address"); + applyUpdateCmdArgs.nodeId = arg_int0(NULL, NULL, "", "OTA Provider Node ID in decimal"); + applyUpdateCmdArgs.end = arg_end(1); + + applyUpdateCommand.command = "ApplyUpdateRequest", applyUpdateCommand.help = "Request to OTA update image", + applyUpdateCommand.func = &ESPApplyUpdateCmdHandler, applyUpdateCommand.argtable = &applyUpdateCmdArgs; + + esp_console_cmd_register(&queryImageCommand); + esp_console_cmd_register(&applyUpdateCommand); + + esp_console_new_repl_uart(&uartConfig, &replConfig, &repl); + esp_console_start_repl(repl); +} + +extern "C" void app_main() +{ + ESP_LOGI(TAG, "OTA Requester!"); + + /* Print chip information */ + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + ESP_LOGI(TAG, "This is ESP32 chip with %d CPU cores, WiFi%s%s, ", chip_info.cores, + (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); + + ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision); + + ESP_LOGI(TAG, "%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024), + (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); + + // Initialize the ESP NVS layer. + esp_err_t err = nvs_flash_init(); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "nvs_flash_init() failed: %s", esp_err_to_name(err)); + return; + } + + CHIPDeviceManager & deviceMgr = CHIPDeviceManager::GetInstance(); + + CHIP_ERROR error = deviceMgr.Init(&EchoCallbacks); + if (error != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "device.Init() failed: %s", ErrorStr(error)); + return; + } + + chip::Server::GetInstance().Init(); + + // Initialize device attestation config + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); + + ESPInitConsole(); + + // Run the UI Loop + while (true) + { + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} diff --git a/examples/ota-requestor-app/esp32/partitions.csv b/examples/ota-requestor-app/esp32/partitions.csv new file mode 100644 index 00000000000000..56849bb3037058 --- /dev/null +++ b/examples/ota-requestor-app/esp32/partitions.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +# OTA partitions of size 1.5M each +ota_0, app, ota_0, , 1524K, +ota_1, app, ota_1, , 1524K, diff --git a/examples/ota-requestor-app/esp32/sdkconfig.defaults b/examples/ota-requestor-app/esp32/sdkconfig.defaults new file mode 100644 index 00000000000000..aa3e6cb62047a6 --- /dev/null +++ b/examples/ota-requestor-app/esp32/sdkconfig.defaults @@ -0,0 +1,54 @@ +# +# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2018 Nest Labs, Inc. +# All rights reserved. +# +# 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. +# +# Description: +# Some useful defaults for the demo app configuration. +# + + +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +#enable BT +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +#enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" + +# Add RTC memory to system heap +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# Product id +CONFIG_DEVICE_VENDOR_ID=0x235A +CONFIG_DEVICE_PRODUCT_ID=0x4590 + +# Main task needs a bit more stack than the default +# default is 3584, bump this up to 4k. +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 + +# Serial Flasher config +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" diff --git a/examples/ota-requestor-app/esp32/third_party/connectedhomeip b/examples/ota-requestor-app/esp32/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/ota-requestor-app/esp32/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file