1/*
2    Small example sketch demonstrating how to perform OTA via HTTP/S
3    utilizing a MKRGSM 1400 and the storage on the integrated
4    SARA U-201 GSM module.
5
6    Please, be careful because no verification is done on the
7    received OTA file, apart size verification of the transmitted
8    bytes using the HTTP Content-Length header.
9
10    For production-grade OTA procedure you might want to implement
11    a content verification procedure using a CRC calculation
12    or an hash (eg. MD5 or SHA256) comparison.
13
14    Circuit:
15    * MKR GSM 1400 board
16    * Antenna
17    * SIM card with a data plan
18
19    Steps to update a sketch:
20
21    1) Create a new sketch or update an existing one to be updated over-the-air.
22       The sketch needs to contain also the code below for future OTAs.
23       The sketch must include the SSU library via
24       #include <SSU.h>
25
26    2) In the IDE select: Sketch -> Export compiled Binary.
27
28    3) Open the location of the sketch (Sketch -> Show Sketch Folder) and upload
29       the .bin file to your HTTP/S server.
30
31    4) Upload this sketch after configuring the server, port and filename variables.
32
33    The sketch will download the OTA file, store it into the U-201 storage, and
34    will reset the board to trigger the SSU update procedure.
35
36
37    created 25 June 2020
38    by Giampaolo Mancini
39*/
40
41#include <MKRGSM.h>
42
43// This includes triggers the firmware update procedure
44// in the bootloader after reset.
45#include <SSU.h>
46
47// Do not change! SSU will look for these files!
48constexpr char UPDATE_FILE_NAME[] = "UPDATE.BIN";
49static constexpr char CHECK_FILE_NAME[] = "UPDATE.OK";
50
51#include "arduino_secrets.h"
52const char PINNUMBER[] = SECRET_PINNUMBER;
53// APN data
54const char GPRS_APN[] = SECRET_GPRS_APN;
55const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN;
56const char GPRS_PASSWORD[] = SECRET_GPRS_PASSWORD;
57
58// Change to GSMClient for non-SSL/TLS connection.
59// Not recommended.
60GSMSSLClient client;
61GPRS gprs;
62GSM gsmAccess;
63
64GSMFileUtils fileUtils;
65
66bool isHeaderComplete = false;
67String httpHeader;
68
69bool isDownloadComplete = false;
70unsigned int fileSize = 0;
71unsigned int totalWritten = 0;
72
73constexpr char server[] = "example.org";
74constexpr int port = 443;
75
76// Name of the new firmware file to be updated.
77constexpr char filename[] = "update.bin";
78
79
80void setup()
81{
82    unsigned long timeout = millis();
83
84    Serial.begin(9600);
85    while (!Serial && millis() - timeout < 5000)
86        ;
87
88    Serial.println("Starting OTA Update via HTTP and Arduino SSU.");
89    Serial.println();
90
91    bool connected = false;
92
93    Serial.print("Connecting to cellular network... ");
94    while (!connected) {
95        if ((gsmAccess.begin(PINNUMBER) == GSM_READY) && (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) {
96            connected = true;
97        } else {
98            Serial.println("Not connected");
99            delay(1000);
100        }
101    }
102
103    Serial.println("Connected.");
104    Serial.println();
105
106    // Modem has already been initialized in the sketch:
107    // begin FileUtils without MODEM initialization.
108    fileUtils.begin(false);
109
110    Serial.print("Connecting to ");
111    Serial.print(server);
112    Serial.print(":");
113    Serial.print(port);
114    Serial.print("... ");
115    if (client.connect(server, port)) {
116        Serial.println("Connected.");
117        Serial.print("Downloading ");
118        Serial.println(filename);
119        Serial.print("... ");
120        // Make the HTTP request:
121        client.print("GET /");
122        client.print(filename);
123        client.println(" HTTP/1.1");
124        client.print("Host: ");
125        client.println(server);
126        client.println("Connection: close");
127        client.println();
128    } else {
129        Serial.println("Connection failed");
130    }
131}
132
133void loop()
134{
135    while (client.available()) {
136        // Skip the HTTP header
137        if (!isHeaderComplete) {
138            const char c = client.read();
139            httpHeader += c;
140            if (httpHeader.endsWith("\r\n\r\n")) {
141                isHeaderComplete = true;
142
143                // Get the size of the OTA file from the
144                // HTTP Content-Length header.
145                fileSize = getContentLength();
146
147                Serial.println();
148                Serial.print("HTTP header complete. ");
149                Serial.print("OTA file size is ");
150                Serial.print(fileSize);
151                Serial.println(" bytes.");
152                if (fileSize == 0) {
153                    Serial.println("Unable to get OTA file size.");
154                    while (true)
155                        ;
156                }
157            }
158        } else {
159            // Read the OTA file in len-bytes blocks to preserve RAM.
160            constexpr size_t len { 512 };
161            char buf[len] { 0 };
162
163            // Read len bytes from HTTP client...
164            uint32_t read = client.readBytes(buf, len);
165            // and append them to the update file.
166            uint32_t written = fileUtils.appendFile(UPDATE_FILE_NAME, buf, read);
167
168            if (written != read) {
169                Serial.println("Error while saving data.");
170                while (true)
171                    ;
172            }
173
174            // Update the received byte counter
175            totalWritten += written;
176
177            // Check for full file received and stored
178            isDownloadComplete = totalWritten == fileSize;
179
180            Serial.print("Received: ");
181            Serial.print(totalWritten);
182            Serial.print("/");
183            Serial.println(fileSize);
184        }
185    }
186    if (isDownloadComplete) {
187        Serial.println();
188        Serial.println("Download complete.");
189        Serial.println("Enabling checkpoint.");
190        Serial.println();
191
192        // Create the checkpoint file: will be removed by SSU
193        // after successful update.
194        auto status = fileUtils.downloadFile(CHECK_FILE_NAME, { 0 }, 1);
195        if (status != 1) {
196            Serial.println("Unable to create checkpoint file.");
197            while (true)
198                ;
199        }
200
201        Serial.println("Resetting MCU in order to trigger SSU...");
202        Serial.println();
203        delay(500);
204        NVIC_SystemReset();
205    }
206}
207
208int getContentLength()
209{
210    const String contentLengthHeader = "Content-Length:";
211    const auto contentLengthHeaderLen = contentLengthHeader.length();
212
213    auto indexContentLengthStart = httpHeader.indexOf(contentLengthHeader);
214    if (indexContentLengthStart < 0) {
215        Serial.println("Unable to find Content-Length header (Start)");
216        return 0;
217    }
218    auto indexContentLengthStop = httpHeader.indexOf("\r\n", indexContentLengthStart);
219    if (indexContentLengthStart < 0) {
220        Serial.println("Unable to find Content-Length header (Stop)");
221        return 0;
222    }
223    auto contentLength = httpHeader.substring(indexContentLengthStart + contentLengthHeaderLen + 1, indexContentLengthStop);
224
225    contentLength.trim();
226    return contentLength.toInt();
227}
228