Skip to content

Add esp32 OTA support #350

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/compile-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ jobs:
# Install ESP32 platform via Boards Manager
- name: esp32:esp32
source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
libraries:
libraries: |
- name: Arduino_ESP32_OTA
sketch-paths:

steps:
Expand Down
3 changes: 3 additions & 0 deletions extras/tools/bin2ota.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
magic_number = 0x2341025F.to_bytes(4,byteorder='little')
elif board == "OPTA":
magic_number = 0x23410064.to_bytes(4,byteorder='little')
# Magic number for all ESP32 boards not related to (VID/PID)
elif board == "ESP32":
magic_number = 0x45535033.to_bytes(4,byteorder='little')
else:
print ("Error,", board, "is not a supported board type")
sys.exit()
Expand Down
6 changes: 5 additions & 1 deletion src/AIoTC_Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@
#define OTA_STORAGE_PORTENTA_QSPI (0)
#endif

#if (OTA_STORAGE_SFU || OTA_STORAGE_SSU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI) && !defined(ARDUINO_AVR_UNO_WIFI_REV2)
#if defined(ARDUINO_ARCH_ESP32)
#define OTA_STORAGE_ESP (1)
#endif

#if (OTA_STORAGE_SFU || OTA_STORAGE_SSU || OTA_STORAGE_SNU || OTA_STORAGE_PORTENTA_QSPI || OTA_STORAGE_ESP) && !defined(ARDUINO_AVR_UNO_WIFI_REV2)
#define OTA_ENABLED (1)
#else
#define OTA_ENABLED (0)
Expand Down
104 changes: 31 additions & 73 deletions src/ArduinoIoTCloudTCP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#ifdef HAS_TCP
#include <ArduinoIoTCloudTCP.h>

#ifdef BOARD_HAS_ECCX08
#include "tls/BearSSLTrustAnchors.h"
#include "tls/utility/CryptoUtil.h"
Expand All @@ -34,32 +35,18 @@
#endif

#ifdef BOARD_HAS_OFFLOADED_ECCX08
#include <ArduinoECCX08.h>
#include "tls/utility/CryptoUtil.h"
#include <ArduinoECCX08.h>
#include "tls/utility/CryptoUtil.h"
#endif

#ifdef BOARD_STM32H7
# include "tls/utility/SHA256.h"
# include <stm32h7xx_hal_rtc_ex.h>
# include <WiFi.h>
#if OTA_ENABLED
#include "utility/ota/OTA.h"
#endif

#include "utility/ota/OTA.h"
#include "utility/ota/FlashSHA256.h"
#include <algorithm>
#include "cbor/CBOREncoder.h"

#include "utility/watchdog/Watchdog.h"


/******************************************************************************
* EXTERN
******************************************************************************/

#ifdef BOARD_STM32H7
extern RTC_HandleTypeDef RTCHandle;
#endif

/******************************************************************************
LOCAL MODULE FUNCTIONS
******************************************************************************/
Expand Down Expand Up @@ -151,61 +138,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress,
#endif /* AVR */

#if OTA_ENABLED && !defined(__AVR__)
#if defined(BOARD_STM32H7)
/* The length of the application can be retrieved the same way it was
* communicated to the bootloader, that is by writing to the non-volatile
* storage registers of the RTC.
*/
SHA256 sha256;
uint32_t const app_start = 0x8040000;
uint32_t const app_size = HAL_RTCEx_BKUPRead(&RTCHandle, RTC_BKP_DR3);

sha256.begin();
uint32_t b = 0;
uint32_t bytes_read = 0; for(uint32_t a = app_start;
bytes_read < app_size;
bytes_read += sizeof(b), a += sizeof(b))
{
/* Read the next chunk of memory. */
memcpy(&b, reinterpret_cast<const void *>(a), sizeof(b));
/* Feed it to SHA256. */
sha256.update(reinterpret_cast<uint8_t *>(&b), sizeof(b));
}
/* Retrieve the final hash string. */
uint8_t sha256_hash[SHA256::HASH_SIZE] = {0};
sha256.finalize(sha256_hash);
String sha256_str;
std::for_each(sha256_hash,
sha256_hash + SHA256::HASH_SIZE,
[&sha256_str](uint8_t const elem)
{
char buf[4];
snprintf(buf, 4, "%02X", elem);
sha256_str += buf;
});
DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", bytes_read, app_size);
#elif defined(ARDUINO_ARCH_SAMD)
/* Calculate the SHA256 checksum over the firmware stored in the flash of the
* MCU. Note: As we don't know the length per-se we read chunks of the flash
* until we detect one containing only 0xFF (= flash erased). This only works
* for firmware updated via OTA and second stage bootloaders (SxU family)
* because only those erase the complete flash before performing an update.
* Since the SHA256 firmware image is only required for the cloud servers to
* perform a version check after the OTA update this is a acceptable trade off.
* The bootloader is excluded from the calculation and occupies flash address
* range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte).
*/
String const sha256_str = FlashSHA256::calc(0x2000, 0x40000 - 0x2000);
#elif defined(ARDUINO_NANO_RP2040_CONNECT)
/* The maximum size of a RP2040 OTA update image is 1 MByte (that is 1024 *
* 1024 bytes or 0x100'000 bytes).
*/
String const sha256_str = FlashSHA256::calc(XIP_BASE, 0x100000);
#else
# error "No method for SHA256 checksum calculation over application image defined for this architecture."
#endif
DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(sha256_str.c_str()), sha256_str.c_str());
_ota_img_sha256 = sha256_str;
_ota_img_sha256 = getOTAImageSHA256();
DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(_ota_img_sha256.c_str()), _ota_img_sha256.c_str());
#endif /* OTA_ENABLED */

#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050)
Expand Down Expand Up @@ -300,6 +234,11 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress,
_ota_cap = true;
#endif

#if defined(ARDUINO_ARCH_ESP32) && OTA_ENABLED
/* NOTE: here is possible to check if current partition scheme is OTA compatible */
_ota_cap = true;
#endif

#ifdef BOARD_HAS_OFFLOADED_ECCX08
if (String(WiFi.firmwareVersion()) < String("1.4.4")) {
DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to connect to Arduino IoT Cloud, NINA firmware needs to be >= 1.4.4, current %s", __FUNCTION__, WiFi.firmwareVersion());
Expand Down Expand Up @@ -840,6 +779,25 @@ void ArduinoIoTCloudTCP::onOTARequest()
bool const use_ethernet = _connection->getInterface() == NetworkAdapter::ETHERNET ? true : false;
_ota_error = portenta_h7_onOTARequest(_ota_url.c_str(), use_ethernet);
#endif

#ifdef ARDUINO_ARCH_ESP32
_ota_error = esp32_onOTARequest(_ota_url.c_str());
#endif
}

String ArduinoIoTCloudTCP::getOTAImageSHA256()
{
#if defined (ARDUINO_ARCH_SAMD)
return samd_getOTAImageSHA256();
#elif defined (ARDUINO_NANO_RP2040_CONNECT)
return rp2040_connect_getOTAImageSHA256();
#elif defined (BOARD_STM32H7)
return portenta_h7_getOTAImageSHA256();
#elif defined (ARDUINO_ARCH_ESP32)
return esp32_getOTAImageSHA256();
#else
# error "No method for SHA256 checksum calculation over application image defined for this architecture."
#endif
}
#endif

Expand Down
1 change: 1 addition & 0 deletions src/ArduinoIoTCloudTCP.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass

#if OTA_ENABLED
void onOTARequest();
String getOTAImageSHA256();
void sendDevicePropertyToCloud(String const name);
#endif

Expand Down
2 changes: 1 addition & 1 deletion src/tls/bearssl/dec32be.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

#include <AIoTC_Config.h>
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050)
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32)

#include "inner.h"

Expand Down
2 changes: 1 addition & 1 deletion src/tls/bearssl/enc32be.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

#include <AIoTC_Config.h>
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050)
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32)

#include "inner.h"

Expand Down
2 changes: 1 addition & 1 deletion src/tls/bearssl/sha2small.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

#include <AIoTC_Config.h>
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050)
#if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) || defined(ARDUINO_ARCH_ESP32)

#include "inner.h"

Expand Down
123 changes: 123 additions & 0 deletions src/utility/ota/OTA-esp32.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
This file is part of ArduinoIoTCloud.

Copyright 2020 ARDUINO SA (http://www.arduino.cc/)

This software is released under the GNU General Public License version 3,
which covers the main part of arduino-cli.
The terms of this license can be found at:
https://www.gnu.org/licenses/gpl-3.0.en.html

You can be released from the requirements of the above licenses by purchasing
a commercial license. Buying such a license is mandatory if you want to modify or
otherwise use the software for commercial activities involving the Arduino
software without disclosing the source code of your own applications. To purchase
a commercial license, send an email to license@arduino.cc.
*/

/******************************************************************************
* INCLUDE
******************************************************************************/

#include <AIoTC_Config.h>

#if defined ARDUINO_ARCH_ESP32 && OTA_ENABLED

#include "OTA.h"
#include <Arduino_DebugUtils.h>
#include <Arduino_ESP32_OTA.h>
#include "tls/utility/SHA256.h"

#include <esp_ota_ops.h>

/******************************************************************************
* FUNCTION DEFINITION
******************************************************************************/

int esp32_onOTARequest(char const * ota_url)
{
Arduino_ESP32_OTA::Error ota_err = Arduino_ESP32_OTA::Error::None;
Arduino_ESP32_OTA ota;

/* Initialize the board for OTA handling. */
if ((ota_err = ota.begin()) != Arduino_ESP32_OTA::Error::None)
{
DEBUG_ERROR("Arduino_ESP32_OTA::begin() failed with %d", static_cast<int>(ota_err));
return static_cast<int>(ota_err);
}

/* Download the OTA file from the web storage location. */
int const ota_download = ota.download(ota_url);
if (ota_download <= 0)
{
DEBUG_ERROR("Arduino_ESP_OTA::download() failed with %d", ota_download);
return ota_download;
}
DEBUG_VERBOSE("Arduino_ESP_OTA::download() %d bytes downloaded", static_cast<int>(ota_download));

/* Verify update integrity and apply */
if ((ota_err = ota.update()) != Arduino_ESP32_OTA::Error::None)
{
DEBUG_ERROR("Arduino_ESP_OTA::update() failed with %d", static_cast<int>(ota_err));
return static_cast<int>(ota_err);
}

/* Perform the reset to reboot */
ota.reset();

return static_cast<int>(OTAError::None);
}

String esp32_getOTAImageSHA256()
{
const esp_partition_t *running = esp_ota_get_running_partition();
if (!running) {
DEBUG_ERROR("ESP32::SHA256 Running partition could not be found");
return String();
}

uint8_t *b = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE);
if(b == nullptr) {
DEBUG_ERROR("ESP32::SHA256 Not enough memory to allocate buffer");
return String();
}

SHA256 sha256;
uint32_t const app_start = running->address;
uint32_t const app_size = ESP.getSketchSize();
uint32_t read_bytes = 0;

sha256.begin();
for(uint32_t a = app_start; read_bytes < app_size; )
{
/* Check if we are reading last sector and compute used size */
uint32_t const read_size = read_bytes + SPI_FLASH_SEC_SIZE < app_size ? SPI_FLASH_SEC_SIZE : app_size - read_bytes;

/* Use always 4 bytes aligned reads */
if (!ESP.flashRead(a, reinterpret_cast<uint32_t*>(b), (read_size + 3) & ~3)) {
DEBUG_ERROR("ESP32::SHA256 Could not read data from flash");
return String();
}
sha256.update(b, read_size);
a += read_size;
read_bytes += read_size;
}
free(b);

/* Retrieve the final hash string. */
uint8_t sha256_hash[SHA256::HASH_SIZE] = {0};
sha256.finalize(sha256_hash);
String sha256_str;
std::for_each(sha256_hash,
sha256_hash + SHA256::HASH_SIZE,
[&sha256_str](uint8_t const elem)
{
char buf[4];
snprintf(buf, 4, "%02X", elem);
sha256_str += buf;
});
DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", read_bytes, app_size);
return sha256_str;
}

#endif /* ARDUINO_ARCH_ESP32 */
9 changes: 9 additions & 0 deletions src/utility/ota/OTA-nano-rp2040.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "mbed.h"
#include "FATFileSystem.h"
#include "FlashIAPBlockDevice.h"
#include "utility/ota/FlashSHA256.h"

/******************************************************************************
* FUNCTION DEFINITION
Expand Down Expand Up @@ -249,4 +250,12 @@ int rp2040_connect_onOTARequest(char const * ota_url)
return static_cast<int>(OTAError::None);
}

String rp2040_connect_getOTAImageSHA256()
{
/* The maximum size of a RP2040 OTA update image is 1 MByte (that is 1024 *
* 1024 bytes or 0x100'000 bytes).
*/
return FlashSHA256::calc(XIP_BASE, 0x100000);
}

#endif /* ARDUINO_NANO_RP2040_CONNECT */
Loading