From 8eda2ad4f0dcdeb3c29bdf10dcb4260818dbe148 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 24 Aug 2023 17:25:36 +0200 Subject: [PATCH 1/5] SFU: Add download function --- libraries/SFU/src/SFU.cpp | 190 ++++++++++++++++++++++++++++++++++++++ libraries/SFU/src/SFU.h | 22 ++++- 2 files changed, 210 insertions(+), 2 deletions(-) diff --git a/libraries/SFU/src/SFU.cpp b/libraries/SFU/src/SFU.cpp index f3e2d97e6..0bd9246a0 100644 --- a/libraries/SFU/src/SFU.cpp +++ b/libraries/SFU/src/SFU.cpp @@ -18,7 +18,197 @@ */ #include "SFU.h" +#include +#include +#include "FATFileSystem.h" +#include +#include +#include + +#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (5*1000UL); +#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (5*60*1000UL); const unsigned char SFU[0x20000] __attribute__ ((section(".second_stage_ota"), used)) = { #include "c33.h" }; + +BlockDevice* block_device = BlockDevice::get_default_instance(); +MBRBlockDevice mbr(block_device, 1); +FATFileSystem fs("ota"); + +/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ +#include +#include +#include +#include +#include + +struct URI { + public: + URI(const std::string& url_s) { + this->parse(url_s); + } + std::string protocol_, host_, path_, query_; + private: + void parse(const std::string& url_s); +}; + +using namespace std; + +// ctors, copy, equality, ... +// TODO: change me into something embedded friendly (this function adds ~100KB to flash) +void URI::parse(const string& url_s) +{ + const string prot_end("://"); + string::const_iterator prot_i = search(url_s.begin(), url_s.end(), + prot_end.begin(), prot_end.end()); + protocol_.reserve(distance(url_s.begin(), prot_i)); + transform(url_s.begin(), prot_i, + back_inserter(protocol_), + ptr_fun(tolower)); // protocol is icase + if( prot_i == url_s.end() ) + return; + advance(prot_i, prot_end.length()); + string::const_iterator path_i = find(prot_i, url_s.end(), '/'); + host_.reserve(distance(prot_i, path_i)); + transform(prot_i, path_i, + back_inserter(host_), + ptr_fun(tolower)); // host is icase + string::const_iterator query_i = find(path_i, url_s.end(), '?'); + path_.assign(path_i, query_i); + if( query_i != url_s.end() ) + ++query_i; + query_.assign(query_i, url_s.end()); +} + +int SFU::download(const char* ota_url) { + int err = -1; + + if ((err = fs.reformat(&mbr)) != 0) + { + DEBUG_ERROR("%s: fs.reformat() failed with %d", __FUNCTION__, err); + return static_cast(OTAError::PORTENTA_C33_ErrorReformat); + } + + FILE * file = fopen("/ota/UPDATE.BIN.OTA", "wb"); + if (!file) + { + DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_ErrorOpenUpdateFile); + } + + URI url(ota_url); + Client * client = nullptr; + int port = 0; + + if (url.protocol_ == "http") { + client = new WiFiClient(); + port = 80; + } else if (url.protocol_ == "https") { + client = new WiFiSSLClient(); + port = 443; + } else { + DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, url.host_.c_str()); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_UrlParseError); + } + + if (!client->connect(url.host_.c_str(), port)) + { + DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_ServerConnectError); + } + + client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); + client->println(String("Host: ") + url.host_.c_str()); + client->println("Connection: close"); + client->println(); + + /* Receive HTTP header. */ + String http_header; + bool is_header_complete = false, + is_http_header_timeout = false; + for (unsigned long const start = millis(); !is_header_complete;) + { + is_http_header_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; + if (is_http_header_timeout) break; + + if (client->available()) + { + char const c = client->read(); + + http_header += c; + if (http_header.endsWith("\r\n\r\n")) + is_header_complete = true; + } + } + + if (!is_header_complete) + { + DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":""); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_HttpHeaderError); + } + + /* Extract concent length from HTTP header. A typical entry looks like + * "Content-Length: 123456" + */ + char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length"); + if (!content_length_ptr) + { + DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_ErrorParseHttpHeader); + } + /* Find start of numerical value. */ + char * ptr = const_cast(content_length_ptr); + for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { } + /* Extract numerical value. */ + String content_length_str; + for (; isDigit(*ptr); ptr++) content_length_str += *ptr; + int const content_length_val = atoi(content_length_str.c_str()); + DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val); + + /* Receive as many bytes as are indicated by the HTTP header - or die trying. */ + int bytes_received = 0; + bool is_http_data_timeout = false; + for(unsigned long const start = millis(); bytes_received < content_length_val;) + { + is_http_data_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms; + if (is_http_data_timeout) break; + + if (client->available()) + { + char const c = client->read(); + + if (fwrite(&c, 1, sizeof(c), file) != sizeof(c)) + { + DEBUG_ERROR("%s: Writing of firmware image to flash failed", __FUNCTION__); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_ErrorWriteUpdateFile); + } + + bytes_received++; + } + } + + if (bytes_received != content_length_val) { + DEBUG_ERROR("%s: Error receiving HTTP data %s (%d bytes received, %d expected)", __FUNCTION__, is_http_data_timeout ? "(timeout)":"", bytes_received, content_length_val); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_HttpDataError); + } + + DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file)); + fclose(file); + + /* Unmount the filesystem. */ + if ((err = fs.unmount()) != 0) + { + DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); + return static_cast(OTAError::PORTENTA_C33_ErrorUnmount); + } + + return static_cast(OTAError::None); +} diff --git a/libraries/SFU/src/SFU.h b/libraries/SFU/src/SFU.h index f7182e373..b29c601c4 100644 --- a/libraries/SFU/src/SFU.h +++ b/libraries/SFU/src/SFU.h @@ -22,11 +22,29 @@ #include "Arduino.h" +#define PORTENTA_C33_OTA_ERROR_BASE (-300) + +enum class OTAError : int +{ + None = 0, + DownloadFailed = 1, + PORTENTA_C33_UrlParseError = PORTENTA_C33_OTA_ERROR_BASE - 0, + PORTENTA_C33_ServerConnectError = PORTENTA_C33_OTA_ERROR_BASE - 1, + PORTENTA_C33_HttpHeaderError = PORTENTA_C33_OTA_ERROR_BASE - 2, + PORTENTA_C33_HttpDataError = PORTENTA_C33_OTA_ERROR_BASE - 3, + PORTENTA_C33_ErrorOpenUpdateFile = PORTENTA_C33_OTA_ERROR_BASE - 4, + PORTENTA_C33_ErrorWriteUpdateFile = PORTENTA_C33_OTA_ERROR_BASE - 5, + PORTENTA_C33_ErrorParseHttpHeader = PORTENTA_C33_OTA_ERROR_BASE - 6, + PORTENTA_C33_ErrorFlashInit = PORTENTA_C33_OTA_ERROR_BASE - 7, + PORTENTA_C33_ErrorReformat = PORTENTA_C33_OTA_ERROR_BASE - 8, + PORTENTA_C33_ErrorUnmount = PORTENTA_C33_OTA_ERROR_BASE - 9, +}; + class SFU { public: - static int begin(); + static int begin() {}; static int download(const char* url); - static int apply(); + static int apply() { NVIC_SystemReset(); }; }; #endif // _SFU_H_ From 51bd3b17424a5d8ad65f7458be7e19aa6d991b3b Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 29 Aug 2023 09:07:41 +0200 Subject: [PATCH 2/5] SFU: Add examples --- .../SFU/examples/OTAUpdate/OTAUpdate.ino | 81 +++++++++++++++++++ .../SFU/examples/OTAUpdate/arduino_secrets.h | 2 + 2 files changed, 83 insertions(+) create mode 100644 libraries/SFU/examples/OTAUpdate/OTAUpdate.ino create mode 100644 libraries/SFU/examples/OTAUpdate/arduino_secrets.h diff --git a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino new file mode 100644 index 000000000..28126c5c4 --- /dev/null +++ b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino @@ -0,0 +1,81 @@ +/* + * This example demonstrates how to use to update the firmware of the Arduino Portenta C33 using + * a firmware image stored on the QSPI. + * + * Steps: + * 1) Create a sketch for the Portenta C33 and verify + * that it both compiles and works on a board. + * 2) In the IDE select: Sketch -> Export compiled Binary. + * 3) Create an OTA update file utilising the tools 'lzss.py' and 'bin2ota.py' stored in + * https://github.com/arduino-libraries/ArduinoIoTCloud/tree/master/extras/tools . + * A) ./lzss.py --encode SKETCH.bin SKETCH.lzss + * B) ./bin2ota.py PORTENTA_C33 SKETCH.lzss SKETCH.ota + * 4) Upload the OTA file to a network reachable location, e.g. OTAUsage.ino.PORTENTA_C33.ota + * has been uploaded to: http://downloads.arduino.cc/ota/OTAUsage.ino.PORTENTA_C33.ota + * 5) Perform an OTA update via steps outlined below. + */ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#include +#include +#include "arduino_secrets.h" + +/****************************************************************************** + * CONSTANT + ******************************************************************************/ + +/* Please enter your sensitive data in the Secret tab/arduino_secrets.h */ +static char const SSID[] = SECRET_SSID; /* your network SSID (name) */ +static char const PASS[] = SECRET_PASS; /* your network password (use for WPA, or use as key for WEP) */ + +#if defined(ARDUINO_PORTENTA_C33) +static char const OTA_FILE_LOCATION[] = "http://downloads.arduino.cc/ota/OTAUsage.ino.PORTENTA_C33.ota"; +#else +#error "Board not supported" +#endif + +/****************************************************************************** + * SETUP/LOOP + ******************************************************************************/ + +void setup() +{ + Serial.begin(115200); + while (!Serial) {} + + Debug.setDebugLevel(DBG_VERBOSE); + + if (WiFi.status() == WL_NO_SHIELD) + { + Serial.println("Communication with WiFi module failed!"); + return; + } + + int status = WL_IDLE_STATUS; + while (status != WL_CONNECTED) + { + Serial.print ("Attempting to connect to '"); + Serial.print (SSID); + Serial.println("'"); + status = WiFi.begin(SSID, PASS); + delay(10000); + } + Serial.print ("You're connected to '"); + Serial.print (WiFi.SSID()); + Serial.println("'"); + + SFU::begin(); + + SFU::download(OTA_FILE_LOCATION); + + SFU::apply(); +} + +void loop() +{ + +} diff --git a/libraries/SFU/examples/OTAUpdate/arduino_secrets.h b/libraries/SFU/examples/OTAUpdate/arduino_secrets.h new file mode 100644 index 000000000..0c9fdd556 --- /dev/null +++ b/libraries/SFU/examples/OTAUpdate/arduino_secrets.h @@ -0,0 +1,2 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" From b15972ff90c0646b8e91120161ecb4ed9dda194e Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 3 Oct 2023 11:38:47 +0200 Subject: [PATCH 3/5] SFU: Remove filesystem and block device dependency from download function --- libraries/SFU/src/SFU.cpp | 24 ++---------------------- libraries/SFU/src/SFU.h | 3 +-- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/libraries/SFU/src/SFU.cpp b/libraries/SFU/src/SFU.cpp index 0bd9246a0..1193e3624 100644 --- a/libraries/SFU/src/SFU.cpp +++ b/libraries/SFU/src/SFU.cpp @@ -18,9 +18,6 @@ */ #include "SFU.h" -#include -#include -#include "FATFileSystem.h" #include #include #include @@ -32,10 +29,6 @@ const unsigned char SFU[0x20000] __attribute__ ((section(".second_stage_ota"), u #include "c33.h" }; -BlockDevice* block_device = BlockDevice::get_default_instance(); -MBRBlockDevice mbr(block_device, 1); -FATFileSystem fs("ota"); - /* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ #include #include @@ -81,16 +74,10 @@ void URI::parse(const string& url_s) query_.assign(query_i, url_s.end()); } -int SFU::download(const char* ota_url) { +int SFU::download(const char* ota_path, const char* ota_url) { int err = -1; - if ((err = fs.reformat(&mbr)) != 0) - { - DEBUG_ERROR("%s: fs.reformat() failed with %d", __FUNCTION__, err); - return static_cast(OTAError::PORTENTA_C33_ErrorReformat); - } - - FILE * file = fopen("/ota/UPDATE.BIN.OTA", "wb"); + FILE * file = fopen(ota_path, "wb"); if (!file) { DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); @@ -203,12 +190,5 @@ int SFU::download(const char* ota_url) { DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file)); fclose(file); - /* Unmount the filesystem. */ - if ((err = fs.unmount()) != 0) - { - DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); - return static_cast(OTAError::PORTENTA_C33_ErrorUnmount); - } - return static_cast(OTAError::None); } diff --git a/libraries/SFU/src/SFU.h b/libraries/SFU/src/SFU.h index b29c601c4..6a76c7c39 100644 --- a/libraries/SFU/src/SFU.h +++ b/libraries/SFU/src/SFU.h @@ -37,13 +37,12 @@ enum class OTAError : int PORTENTA_C33_ErrorParseHttpHeader = PORTENTA_C33_OTA_ERROR_BASE - 6, PORTENTA_C33_ErrorFlashInit = PORTENTA_C33_OTA_ERROR_BASE - 7, PORTENTA_C33_ErrorReformat = PORTENTA_C33_OTA_ERROR_BASE - 8, - PORTENTA_C33_ErrorUnmount = PORTENTA_C33_OTA_ERROR_BASE - 9, }; class SFU { public: static int begin() {}; - static int download(const char* url); + static int download(const char* ota_path, const char* ota_url); static int apply() { NVIC_SystemReset(); }; }; From 7a31968baa63618e54b0a763ea47e76fceedaed6 Mon Sep 17 00:00:00 2001 From: pennam Date: Tue, 3 Oct 2023 11:39:36 +0200 Subject: [PATCH 4/5] SFU: OTAUpdate handle filesystem mount and unmount --- .../SFU/examples/OTAUpdate/OTAUpdate.ino | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino index 28126c5c4..85f3de879 100644 --- a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino +++ b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino @@ -20,6 +20,9 @@ ******************************************************************************/ #include +#include +#include +#include #include #include #include "arduino_secrets.h" @@ -38,6 +41,10 @@ static char const OTA_FILE_LOCATION[] = "http://downloads.arduino.cc/ota/OTAUsag #error "Board not supported" #endif +BlockDevice* block_device = BlockDevice::get_default_instance(); +MBRBlockDevice mbr(block_device, 1); +FATFileSystem fs("ota"); + /****************************************************************************** * SETUP/LOOP ******************************************************************************/ @@ -68,9 +75,24 @@ void setup() Serial.print (WiFi.SSID()); Serial.println("'"); + int err = -1; + /* Mount the filesystem. */ + if (err = fs.mount(&mbr) != 0) + { + DEBUG_ERROR("%s: fs.mount() failed with %d", __FUNCTION__, err); + return; + } + SFU::begin(); - SFU::download(OTA_FILE_LOCATION); + SFU::download("/ota/UPDATE.BIN.OTA", OTA_FILE_LOCATION); + + /* Unmount the filesystem. */ + if ((err = fs.unmount()) != 0) + { + DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); + return; + } SFU::apply(); } From 6f2f6e46d4bd9bde4d04545bd0f7b8d7c0e8e3e0 Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 5 Oct 2023 16:53:43 +0200 Subject: [PATCH 5/5] SFU: download: take client as a funciotn parameter --- .../SFU/examples/OTAUpdate/OTAUpdate.ino | 4 +++- libraries/SFU/src/SFU.cpp | 24 +++++++++---------- libraries/SFU/src/SFU.h | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino index 85f3de879..732c7d9c9 100644 --- a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino +++ b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino @@ -45,6 +45,8 @@ BlockDevice* block_device = BlockDevice::get_default_instance(); MBRBlockDevice mbr(block_device, 1); FATFileSystem fs("ota"); +WiFiClient client; + /****************************************************************************** * SETUP/LOOP ******************************************************************************/ @@ -85,7 +87,7 @@ void setup() SFU::begin(); - SFU::download("/ota/UPDATE.BIN.OTA", OTA_FILE_LOCATION); + SFU::download(client, "/ota/UPDATE.BIN.OTA", OTA_FILE_LOCATION); /* Unmount the filesystem. */ if ((err = fs.unmount()) != 0) diff --git a/libraries/SFU/src/SFU.cpp b/libraries/SFU/src/SFU.cpp index 1193e3624..37c93e732 100644 --- a/libraries/SFU/src/SFU.cpp +++ b/libraries/SFU/src/SFU.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (5*1000UL); #define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (5*60*1000UL); @@ -74,7 +75,7 @@ void URI::parse(const string& url_s) query_.assign(query_i, url_s.end()); } -int SFU::download(const char* ota_path, const char* ota_url) { +int SFU::download(Client& client, const char* ota_path, const char* ota_url) { int err = -1; FILE * file = fopen(ota_path, "wb"); @@ -86,14 +87,11 @@ int SFU::download(const char* ota_path, const char* ota_url) { } URI url(ota_url); - Client * client = nullptr; int port = 0; if (url.protocol_ == "http") { - client = new WiFiClient(); port = 80; } else if (url.protocol_ == "https") { - client = new WiFiSSLClient(); port = 443; } else { DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, url.host_.c_str()); @@ -101,17 +99,17 @@ int SFU::download(const char* ota_path, const char* ota_url) { return static_cast(OTAError::PORTENTA_C33_UrlParseError); } - if (!client->connect(url.host_.c_str(), port)) + if (!client.connect(url.host_.c_str(), port)) { DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); fclose(file); return static_cast(OTAError::PORTENTA_C33_ServerConnectError); } - client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); - client->println(String("Host: ") + url.host_.c_str()); - client->println("Connection: close"); - client->println(); + client.println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); + client.println(String("Host: ") + url.host_.c_str()); + client.println("Connection: close"); + client.println(); /* Receive HTTP header. */ String http_header; @@ -122,9 +120,9 @@ int SFU::download(const char* ota_path, const char* ota_url) { is_http_header_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; if (is_http_header_timeout) break; - if (client->available()) + if (client.available()) { - char const c = client->read(); + char const c = client.read(); http_header += c; if (http_header.endsWith("\r\n\r\n")) @@ -166,9 +164,9 @@ int SFU::download(const char* ota_path, const char* ota_url) { is_http_data_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms; if (is_http_data_timeout) break; - if (client->available()) + if (client.available()) { - char const c = client->read(); + char const c = client.read(); if (fwrite(&c, 1, sizeof(c), file) != sizeof(c)) { diff --git a/libraries/SFU/src/SFU.h b/libraries/SFU/src/SFU.h index 6a76c7c39..6fb254128 100644 --- a/libraries/SFU/src/SFU.h +++ b/libraries/SFU/src/SFU.h @@ -21,6 +21,7 @@ #define _SFU_H_ #include "Arduino.h" +#include "Client.h" #define PORTENTA_C33_OTA_ERROR_BASE (-300) @@ -42,7 +43,7 @@ enum class OTAError : int class SFU { public: static int begin() {}; - static int download(const char* ota_path, const char* ota_url); + static int download(Client& client, const char* ota_path, const char* ota_url); static int apply() { NVIC_SystemReset(); }; };