Skip to content

Commit ab0af75

Browse files
authored
Merge pull request #281 from pennam/deferred_ota
Deferred OTA
2 parents 5bf1a82 + 93bb40a commit ab0af75

File tree

5 files changed

+203
-15
lines changed

5 files changed

+203
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
This sketch demonstrates how to handle deferred OTA from Arduino IoT Cloud.
3+
4+
Deferred OTA can be triggered using the arduino-cloud-cli with the following command:
5+
./arduino-cloud-cli ota upload --device-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --file filename.ino.bin --deferred
6+
The update file and the download link will be available to be used within one week.
7+
8+
* always_deny callback will always postpone the OTA update
9+
* always_allow callback will immediately apply the OTA update
10+
* ask_user_via_serial callback will read user input from serial to apply or postpone OTA update
11+
12+
This sketch is compatible with:
13+
- MKR WIFI 1010
14+
- Nano 33 IoT
15+
- Portenta
16+
- Nano RP2040
17+
*/
18+
19+
#include "arduino_secrets.h"
20+
#include "thingProperties.h"
21+
22+
#if defined(ESP32)
23+
static int const LED_BUILTIN = 2;
24+
#endif
25+
26+
bool always_deny() {
27+
return false;
28+
}
29+
30+
bool always_allow() {
31+
return true;
32+
}
33+
34+
static bool ask_user_via_serial_first_run = true;
35+
bool ask_user_via_serial() {
36+
if (ask_user_via_serial_first_run) {
37+
Serial.println("Apply OTA? y / [n]");
38+
ask_user_via_serial_first_run = false;
39+
}
40+
if (Serial.available()) {
41+
char c = Serial.read();
42+
if (c == 'y' || c == 'Y') {
43+
return true;
44+
}
45+
}
46+
return false;
47+
}
48+
49+
bool onOTARequestCallback()
50+
{
51+
/* Select the preferred behaviour changing the called function */
52+
//return always_deny();
53+
//return always_allow();
54+
return ask_user_via_serial();
55+
}
56+
57+
void setup() {
58+
/* Initialize serial and wait up to 5 seconds for port to open */
59+
Serial.begin(9600);
60+
for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime > 5000); ) { }
61+
62+
/* Configure LED pin as an output */
63+
pinMode(LED_BUILTIN, OUTPUT);
64+
65+
/* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */
66+
initProperties();
67+
68+
/* Initialize Arduino IoT Cloud library */
69+
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
70+
71+
/* Setup OTA callback */
72+
ArduinoCloud.onOTARequestCb(onOTARequestCallback);
73+
74+
setDebugMessageLevel(DBG_INFO);
75+
ArduinoCloud.printDebugInfo();
76+
}
77+
78+
void loop() {
79+
ArduinoCloud.update();
80+
}
81+
82+
/*
83+
* 'onLedChange' is called when the "led" property of your Thing changes
84+
*/
85+
void onLedChange() {
86+
Serial.print("LED set to ");
87+
Serial.println(led);
88+
digitalWrite(LED_BUILTIN, led);
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <Arduino_ConnectionHandler.h>
2+
3+
/* MKR1000, MKR WiFi 1010 */
4+
#if defined(BOARD_HAS_WIFI)
5+
#define SECRET_SSID "YOUR_WIFI_NETWORK_NAME"
6+
#define SECRET_PASS "YOUR_WIFI_PASSWORD"
7+
#endif
8+
9+
/* ESP8266 */
10+
#if defined(BOARD_ESP8266)
11+
#define SECRET_DEVICE_KEY "my-device-password"
12+
#endif
13+
14+
/* MKR GSM 1400 */
15+
#if defined(BOARD_HAS_GSM)
16+
#define SECRET_PIN ""
17+
#define SECRET_APN ""
18+
#define SECRET_LOGIN ""
19+
#define SECRET_PASS ""
20+
#endif
21+
22+
/* MKR WAN 1300/1310 */
23+
#if defined(BOARD_HAS_LORA)
24+
#define SECRET_APP_EUI ""
25+
#define SECRET_APP_KEY ""
26+
#endif
27+
28+
/* MKR NB 1500 */
29+
#if defined(BOARD_HAS_NB)
30+
#define SECRET_PIN ""
31+
#define SECRET_APN ""
32+
#define SECRET_LOGIN ""
33+
#define SECRET_PASS ""
34+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <ArduinoIoTCloud.h>
2+
#include <Arduino_ConnectionHandler.h>
3+
4+
#if defined(BOARD_HAS_WIFI)
5+
#elif defined(BOARD_HAS_GSM)
6+
#elif defined(BOARD_HAS_LORA)
7+
#elif defined(BOARD_HAS_NB)
8+
#else
9+
#error "Arduino IoT Cloud currently only supports MKR1000, MKR WiFi 1010, MKR WAN 1300/1310, MKR NB 1500 and MKR GSM 1400"
10+
#endif
11+
12+
#define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
13+
#define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
14+
15+
void onLedChange();
16+
17+
bool led;
18+
19+
void initProperties() {
20+
#if defined(BOARD_ESP8266)
21+
ArduinoCloud.setBoardId(BOARD_ID);
22+
ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY);
23+
#endif
24+
ArduinoCloud.setThingId(THING_ID);
25+
#if defined(BOARD_HAS_WIFI) || defined(BOARD_HAS_GSM) || defined(BOARD_HAS_NB)
26+
ArduinoCloud.addProperty(led, Permission::Write).onUpdate(onLedChange);
27+
#elif defined(BOARD_HAS_LORA)
28+
ArduinoCloud.addProperty(led, 1, READWRITE, ON_CHANGE, onLedChange);
29+
#endif
30+
}
31+
32+
#if defined(BOARD_HAS_WIFI)
33+
WiFiConnectionHandler ArduinoIoTPreferredConnection(SECRET_SSID, SECRET_PASS);
34+
#elif defined(BOARD_HAS_GSM)
35+
GSMConnectionHandler ArduinoIoTPreferredConnection(SECRET_PIN, SECRET_APN, SECRET_LOGIN, SECRET_PASS);
36+
#elif defined(BOARD_HAS_LORA)
37+
LoRaConnectionHandler ArduinoIoTPreferredConnection(SECRET_APP_EUI, SECRET_APP_KEY, _lora_band::EU868, NULL, _lora_class::CLASS_A);
38+
#elif defined(BOARD_HAS_NB)
39+
NBConnectionHandler ArduinoIoTPreferredConnection(SECRET_PIN, SECRET_APN, SECRET_LOGIN, SECRET_PASS);
40+
#endif

src/ArduinoIoTCloudTCP.cpp

+21-15
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP()
9595
, _ota_img_sha256{"Inv."}
9696
, _ota_url{""}
9797
, _ota_req{false}
98+
, _ask_user_before_executing_ota{false}
99+
, _get_ota_confirmation{nullptr}
98100
#endif /* OTA_ENABLED */
99101
{
100102

@@ -238,8 +240,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress,
238240
addPropertyReal(_ota_cap, "OTA_CAP", Permission::Read);
239241
addPropertyReal(_ota_error, "OTA_ERROR", Permission::Read);
240242
addPropertyReal(_ota_img_sha256, "OTA_SHA256", Permission::Read);
241-
addPropertyReal(_ota_url, "OTA_URL", Permission::ReadWrite).onSync(DEVICE_WINS);
242-
addPropertyReal(_ota_req, "OTA_REQ", Permission::ReadWrite).onSync(DEVICE_WINS);
243+
addPropertyReal(_ota_url, "OTA_URL", Permission::ReadWrite).onSync(CLOUD_WINS);
244+
addPropertyReal(_ota_req, "OTA_REQ", Permission::ReadWrite).onSync(CLOUD_WINS);
243245
#endif /* OTA_ENABLED */
244246

245247
#if OTA_STORAGE_PORTENTA_QSPI
@@ -499,29 +501,33 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected()
499501
_mqtt_data_request_retransmit = false;
500502
}
501503

502-
/* Check if any properties need encoding and send them to
503-
* the cloud if necessary.
504-
*/
505-
sendPropertiesToCloud();
506-
507504
#if OTA_ENABLED
508505
/* Request a OTA download if the hidden property
509506
* OTA request has been set.
510507
*/
511508

512509
if (_ota_req)
513510
{
514-
/* Clear the error flag. */
515-
_ota_error = static_cast<int>(OTAError::None);
516-
/* Transmit the cleared error flag to the cloud. */
517-
sendPropertiesToCloud();
518-
/* Clear the request flag. */
519-
_ota_req = false;
520-
/* Call member function to handle OTA request. */
521-
onOTARequest();
511+
bool const ota_execution_allowed_by_user = (_get_ota_confirmation != nullptr && _get_ota_confirmation());
512+
bool const perform_ota_now = ota_execution_allowed_by_user || !_ask_user_before_executing_ota;
513+
if (perform_ota_now) {
514+
/* Clear the error flag. */
515+
_ota_error = static_cast<int>(OTAError::None);
516+
/* Clear the request flag. */
517+
_ota_req = false;
518+
/* Transmit the cleared error and request flags to the cloud. */
519+
sendPropertiesToCloud();
520+
/* Call member function to handle OTA request. */
521+
onOTARequest();
522+
}
522523
}
523524
#endif /* OTA_ENABLED */
524525

526+
/* Check if any properties need encoding and send them to
527+
* the cloud if necessary.
528+
*/
529+
sendPropertiesToCloud();
530+
525531
return State::Connected;
526532
}
527533
}

src/ArduinoIoTCloudTCP.h

+19
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ static uint16_t const DEFAULT_BROKER_PORT_SECURE_AUTH = 8883;
4949
static char const DEFAULT_BROKER_ADDRESS_USER_PASS_AUTH[] = "mqtts-up.iot.arduino.cc";
5050
static uint16_t const DEFAULT_BROKER_PORT_USER_PASS_AUTH = 8884;
5151

52+
/******************************************************************************
53+
* TYPEDEF
54+
******************************************************************************/
55+
56+
typedef bool (*onOTARequestCallbackFunc)(void);
57+
5258
/******************************************************************************
5359
* CLASS DECLARATION
5460
******************************************************************************/
@@ -80,6 +86,16 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
8086
inline String getBrokerAddress() const { return _brokerAddress; }
8187
inline uint16_t getBrokerPort () const { return _brokerPort; }
8288

89+
#if OTA_ENABLED
90+
/* The callback is triggered when the OTA is initiated and it gets executed until _ota_req flag is cleared.
91+
* It should return true when the OTA can be applied or false otherwise.
92+
* See example ArduinoIoTCloud-DeferredOTA.ino
93+
*/
94+
void onOTARequestCb(onOTARequestCallbackFunc cb) {
95+
_get_ota_confirmation = cb;
96+
_ask_user_before_executing_ota = true;
97+
}
98+
#endif
8399

84100
private:
85101
static const int MQTT_TRANSMIT_BUFFER_SIZE = 256;
@@ -130,6 +146,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
130146
String _ota_img_sha256;
131147
String _ota_url;
132148
bool _ota_req;
149+
bool _ask_user_before_executing_ota;
150+
onOTARequestCallbackFunc _get_ota_confirmation;
133151
#endif /* OTA_ENABLED */
134152

135153
inline String getTopic_shadowout() { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/o"); }
@@ -153,6 +171,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
153171
#if OTA_ENABLED
154172
void onOTARequest();
155173
#endif
174+
156175
};
157176

158177
/******************************************************************************

0 commit comments

Comments
 (0)