diff --git a/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino new file mode 100644 index 000000000..732c7d9c9 --- /dev/null +++ b/libraries/SFU/examples/OTAUpdate/OTAUpdate.ino @@ -0,0 +1,105 @@ +/* + * 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 +#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 + +BlockDevice* block_device = BlockDevice::get_default_instance(); +MBRBlockDevice mbr(block_device, 1); +FATFileSystem fs("ota"); + +WiFiClient client; + +/****************************************************************************** + * 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("'"); + + 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(client, "/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(); +} + +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 "" diff --git a/libraries/SFU/src/SFU.cpp b/libraries/SFU/src/SFU.cpp index f3e2d97e6..37c93e732 100644 --- a/libraries/SFU/src/SFU.cpp +++ b/libraries/SFU/src/SFU.cpp @@ -18,7 +18,175 @@ */ #include "SFU.h" +#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); const unsigned char SFU[0x20000] __attribute__ ((section(".second_stage_ota"), used)) = { #include "c33.h" }; + +/* 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(Client& client, const char* ota_path, const char* ota_url) { + int err = -1; + + FILE * file = fopen(ota_path, "wb"); + if (!file) + { + DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); + fclose(file); + return static_cast(OTAError::PORTENTA_C33_ErrorOpenUpdateFile); + } + + URI url(ota_url); + int port = 0; + + if (url.protocol_ == "http") { + port = 80; + } else if (url.protocol_ == "https") { + 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); + + return static_cast(OTAError::None); +} diff --git a/libraries/SFU/src/SFU.h b/libraries/SFU/src/SFU.h index f7182e373..6fb254128 100644 --- a/libraries/SFU/src/SFU.h +++ b/libraries/SFU/src/SFU.h @@ -21,12 +21,30 @@ #define _SFU_H_ #include "Arduino.h" +#include "Client.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, +}; class SFU { public: - static int begin(); - static int download(const char* url); - static int apply(); + static int begin() {}; + static int download(Client& client, const char* ota_path, const char* ota_url); + static int apply() { NVIC_SystemReset(); }; }; #endif // _SFU_H_