Skip to content

Commit bd5b7fd

Browse files
authored
Merge pull request #157 from pennam/sfu-rebased
SFU add download function and OTA example
2 parents 9667d7e + 6f2f6e4 commit bd5b7fd

File tree

4 files changed

+296
-3
lines changed

4 files changed

+296
-3
lines changed

Diff for: libraries/SFU/examples/OTAUpdate/OTAUpdate.ino

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* This example demonstrates how to use to update the firmware of the Arduino Portenta C33 using
3+
* a firmware image stored on the QSPI.
4+
*
5+
* Steps:
6+
* 1) Create a sketch for the Portenta C33 and verify
7+
* that it both compiles and works on a board.
8+
* 2) In the IDE select: Sketch -> Export compiled Binary.
9+
* 3) Create an OTA update file utilising the tools 'lzss.py' and 'bin2ota.py' stored in
10+
* https://github.com/arduino-libraries/ArduinoIoTCloud/tree/master/extras/tools .
11+
* A) ./lzss.py --encode SKETCH.bin SKETCH.lzss
12+
* B) ./bin2ota.py PORTENTA_C33 SKETCH.lzss SKETCH.ota
13+
* 4) Upload the OTA file to a network reachable location, e.g. OTAUsage.ino.PORTENTA_C33.ota
14+
* has been uploaded to: http://downloads.arduino.cc/ota/OTAUsage.ino.PORTENTA_C33.ota
15+
* 5) Perform an OTA update via steps outlined below.
16+
*/
17+
18+
/******************************************************************************
19+
* INCLUDE
20+
******************************************************************************/
21+
22+
#include <SFU.h>
23+
#include <BlockDevice.h>
24+
#include <MBRBlockDevice.h>
25+
#include <FATFileSystem.h>
26+
#include <WiFiC3.h>
27+
#include <Arduino_DebugUtils.h>
28+
#include "arduino_secrets.h"
29+
30+
/******************************************************************************
31+
* CONSTANT
32+
******************************************************************************/
33+
34+
/* Please enter your sensitive data in the Secret tab/arduino_secrets.h */
35+
static char const SSID[] = SECRET_SSID; /* your network SSID (name) */
36+
static char const PASS[] = SECRET_PASS; /* your network password (use for WPA, or use as key for WEP) */
37+
38+
#if defined(ARDUINO_PORTENTA_C33)
39+
static char const OTA_FILE_LOCATION[] = "http://downloads.arduino.cc/ota/OTAUsage.ino.PORTENTA_C33.ota";
40+
#else
41+
#error "Board not supported"
42+
#endif
43+
44+
BlockDevice* block_device = BlockDevice::get_default_instance();
45+
MBRBlockDevice mbr(block_device, 1);
46+
FATFileSystem fs("ota");
47+
48+
WiFiClient client;
49+
50+
/******************************************************************************
51+
* SETUP/LOOP
52+
******************************************************************************/
53+
54+
void setup()
55+
{
56+
Serial.begin(115200);
57+
while (!Serial) {}
58+
59+
Debug.setDebugLevel(DBG_VERBOSE);
60+
61+
if (WiFi.status() == WL_NO_SHIELD)
62+
{
63+
Serial.println("Communication with WiFi module failed!");
64+
return;
65+
}
66+
67+
int status = WL_IDLE_STATUS;
68+
while (status != WL_CONNECTED)
69+
{
70+
Serial.print ("Attempting to connect to '");
71+
Serial.print (SSID);
72+
Serial.println("'");
73+
status = WiFi.begin(SSID, PASS);
74+
delay(10000);
75+
}
76+
Serial.print ("You're connected to '");
77+
Serial.print (WiFi.SSID());
78+
Serial.println("'");
79+
80+
int err = -1;
81+
/* Mount the filesystem. */
82+
if (err = fs.mount(&mbr) != 0)
83+
{
84+
DEBUG_ERROR("%s: fs.mount() failed with %d", __FUNCTION__, err);
85+
return;
86+
}
87+
88+
SFU::begin();
89+
90+
SFU::download(client, "/ota/UPDATE.BIN.OTA", OTA_FILE_LOCATION);
91+
92+
/* Unmount the filesystem. */
93+
if ((err = fs.unmount()) != 0)
94+
{
95+
DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err);
96+
return;
97+
}
98+
99+
SFU::apply();
100+
}
101+
102+
void loop()
103+
{
104+
105+
}

Diff for: libraries/SFU/examples/OTAUpdate/arduino_secrets.h

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#define SECRET_SSID ""
2+
#define SECRET_PASS ""

Diff for: libraries/SFU/src/SFU.cpp

+168
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,175 @@
1818
*/
1919

2020
#include "SFU.h"
21+
#include <Arduino_DebugUtils.h>
22+
#include <WiFiC3.h>
23+
#include <WiFiSSLClient.h>
24+
#include <Client.h>
25+
26+
#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (5*1000UL);
27+
#define AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (5*60*1000UL);
2128

2229
const unsigned char SFU[0x20000] __attribute__ ((section(".second_stage_ota"), used)) = {
2330
#include "c33.h"
2431
};
32+
33+
/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */
34+
#include <string>
35+
#include <algorithm>
36+
#include <cctype>
37+
#include <functional>
38+
#include <iostream>
39+
40+
struct URI {
41+
public:
42+
URI(const std::string& url_s) {
43+
this->parse(url_s);
44+
}
45+
std::string protocol_, host_, path_, query_;
46+
private:
47+
void parse(const std::string& url_s);
48+
};
49+
50+
using namespace std;
51+
52+
// ctors, copy, equality, ...
53+
// TODO: change me into something embedded friendly (this function adds ~100KB to flash)
54+
void URI::parse(const string& url_s)
55+
{
56+
const string prot_end("://");
57+
string::const_iterator prot_i = search(url_s.begin(), url_s.end(),
58+
prot_end.begin(), prot_end.end());
59+
protocol_.reserve(distance(url_s.begin(), prot_i));
60+
transform(url_s.begin(), prot_i,
61+
back_inserter(protocol_),
62+
ptr_fun<int,int>(tolower)); // protocol is icase
63+
if( prot_i == url_s.end() )
64+
return;
65+
advance(prot_i, prot_end.length());
66+
string::const_iterator path_i = find(prot_i, url_s.end(), '/');
67+
host_.reserve(distance(prot_i, path_i));
68+
transform(prot_i, path_i,
69+
back_inserter(host_),
70+
ptr_fun<int,int>(tolower)); // host is icase
71+
string::const_iterator query_i = find(path_i, url_s.end(), '?');
72+
path_.assign(path_i, query_i);
73+
if( query_i != url_s.end() )
74+
++query_i;
75+
query_.assign(query_i, url_s.end());
76+
}
77+
78+
int SFU::download(Client& client, const char* ota_path, const char* ota_url) {
79+
int err = -1;
80+
81+
FILE * file = fopen(ota_path, "wb");
82+
if (!file)
83+
{
84+
DEBUG_ERROR("%s: fopen() failed", __FUNCTION__);
85+
fclose(file);
86+
return static_cast<int>(OTAError::PORTENTA_C33_ErrorOpenUpdateFile);
87+
}
88+
89+
URI url(ota_url);
90+
int port = 0;
91+
92+
if (url.protocol_ == "http") {
93+
port = 80;
94+
} else if (url.protocol_ == "https") {
95+
port = 443;
96+
} else {
97+
DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, url.host_.c_str());
98+
fclose(file);
99+
return static_cast<int>(OTAError::PORTENTA_C33_UrlParseError);
100+
}
101+
102+
if (!client.connect(url.host_.c_str(), port))
103+
{
104+
DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str());
105+
fclose(file);
106+
return static_cast<int>(OTAError::PORTENTA_C33_ServerConnectError);
107+
}
108+
109+
client.println(String("GET ") + url.path_.c_str() + " HTTP/1.1");
110+
client.println(String("Host: ") + url.host_.c_str());
111+
client.println("Connection: close");
112+
client.println();
113+
114+
/* Receive HTTP header. */
115+
String http_header;
116+
bool is_header_complete = false,
117+
is_http_header_timeout = false;
118+
for (unsigned long const start = millis(); !is_header_complete;)
119+
{
120+
is_http_header_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms;
121+
if (is_http_header_timeout) break;
122+
123+
if (client.available())
124+
{
125+
char const c = client.read();
126+
127+
http_header += c;
128+
if (http_header.endsWith("\r\n\r\n"))
129+
is_header_complete = true;
130+
}
131+
}
132+
133+
if (!is_header_complete)
134+
{
135+
DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":"");
136+
fclose(file);
137+
return static_cast<int>(OTAError::PORTENTA_C33_HttpHeaderError);
138+
}
139+
140+
/* Extract concent length from HTTP header. A typical entry looks like
141+
* "Content-Length: 123456"
142+
*/
143+
char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length");
144+
if (!content_length_ptr)
145+
{
146+
DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__);
147+
fclose(file);
148+
return static_cast<int>(OTAError::PORTENTA_C33_ErrorParseHttpHeader);
149+
}
150+
/* Find start of numerical value. */
151+
char * ptr = const_cast<char *>(content_length_ptr);
152+
for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { }
153+
/* Extract numerical value. */
154+
String content_length_str;
155+
for (; isDigit(*ptr); ptr++) content_length_str += *ptr;
156+
int const content_length_val = atoi(content_length_str.c_str());
157+
DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val);
158+
159+
/* Receive as many bytes as are indicated by the HTTP header - or die trying. */
160+
int bytes_received = 0;
161+
bool is_http_data_timeout = false;
162+
for(unsigned long const start = millis(); bytes_received < content_length_val;)
163+
{
164+
is_http_data_timeout = (millis() - start) > AIOT_CONFIG_PORTENTA_C33_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms;
165+
if (is_http_data_timeout) break;
166+
167+
if (client.available())
168+
{
169+
char const c = client.read();
170+
171+
if (fwrite(&c, 1, sizeof(c), file) != sizeof(c))
172+
{
173+
DEBUG_ERROR("%s: Writing of firmware image to flash failed", __FUNCTION__);
174+
fclose(file);
175+
return static_cast<int>(OTAError::PORTENTA_C33_ErrorWriteUpdateFile);
176+
}
177+
178+
bytes_received++;
179+
}
180+
}
181+
182+
if (bytes_received != content_length_val) {
183+
DEBUG_ERROR("%s: Error receiving HTTP data %s (%d bytes received, %d expected)", __FUNCTION__, is_http_data_timeout ? "(timeout)":"", bytes_received, content_length_val);
184+
fclose(file);
185+
return static_cast<int>(OTAError::PORTENTA_C33_HttpDataError);
186+
}
187+
188+
DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file));
189+
fclose(file);
190+
191+
return static_cast<int>(OTAError::None);
192+
}

Diff for: libraries/SFU/src/SFU.h

+21-3
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,30 @@
2121
#define _SFU_H_
2222

2323
#include "Arduino.h"
24+
#include "Client.h"
25+
26+
#define PORTENTA_C33_OTA_ERROR_BASE (-300)
27+
28+
enum class OTAError : int
29+
{
30+
None = 0,
31+
DownloadFailed = 1,
32+
PORTENTA_C33_UrlParseError = PORTENTA_C33_OTA_ERROR_BASE - 0,
33+
PORTENTA_C33_ServerConnectError = PORTENTA_C33_OTA_ERROR_BASE - 1,
34+
PORTENTA_C33_HttpHeaderError = PORTENTA_C33_OTA_ERROR_BASE - 2,
35+
PORTENTA_C33_HttpDataError = PORTENTA_C33_OTA_ERROR_BASE - 3,
36+
PORTENTA_C33_ErrorOpenUpdateFile = PORTENTA_C33_OTA_ERROR_BASE - 4,
37+
PORTENTA_C33_ErrorWriteUpdateFile = PORTENTA_C33_OTA_ERROR_BASE - 5,
38+
PORTENTA_C33_ErrorParseHttpHeader = PORTENTA_C33_OTA_ERROR_BASE - 6,
39+
PORTENTA_C33_ErrorFlashInit = PORTENTA_C33_OTA_ERROR_BASE - 7,
40+
PORTENTA_C33_ErrorReformat = PORTENTA_C33_OTA_ERROR_BASE - 8,
41+
};
2442

2543
class SFU {
2644
public:
27-
static int begin();
28-
static int download(const char* url);
29-
static int apply();
45+
static int begin() {};
46+
static int download(Client& client, const char* ota_path, const char* ota_url);
47+
static int apply() { NVIC_SystemReset(); };
3048
};
3149

3250
#endif // _SFU_H_

0 commit comments

Comments
 (0)