Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
This sketch demonstrates how to handle deferred OTA from Arduino IoT Cloud.

Deferred OTA can be triggered using the arduino-cloud-cli with the following command:
./arduino-cloud-cli ota upload --device-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --file filename.ino.bin --deferred
The update file and the download link will be available to be used within one week.

* always_deny callback will always postpone the OTA update
* always_allow callback will immediately apply the OTA update
* ask_user_via_serial callback will read user input from serial to apply or postpone OTA update

This sketch is compatible with:
- MKR WIFI 1010
- Nano 33 IoT
- Portenta
- Nano RP2040
*/

#include "arduino_secrets.h"
#include "thingProperties.h"

#if defined(ESP32)
static int const LED_BUILTIN = 2;
#endif

bool always_deny() {
return false;
}

bool always_allow() {
return true;
}

static bool ask_user_via_serial_first_run = true;
bool ask_user_via_serial() {
if (ask_user_via_serial_first_run) {
Serial.println("Apply OTA? y / [n]");
ask_user_via_serial_first_run = false;
}
if (Serial.available()) {
char c = Serial.read();
if (c == 'y' || c == 'Y') {
return true;
}
}
return false;
}

bool onOTARequestCallback()
{
/* Select the preferred behaviour changing the called function */
//return always_deny();
//return always_allow();
return ask_user_via_serial();
}

void setup() {
/* Initialize serial and wait up to 5 seconds for port to open */
Serial.begin(9600);
for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime > 5000); ) { }

/* Configure LED pin as an output */
pinMode(LED_BUILTIN, OUTPUT);

/* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */
initProperties();

/* Initialize Arduino IoT Cloud library */
ArduinoCloud.begin(ArduinoIoTPreferredConnection);

/* Setup OTA callback */
ArduinoCloud.onOTARequestCb(onOTARequestCallback);

setDebugMessageLevel(DBG_INFO);
ArduinoCloud.printDebugInfo();
}

void loop() {
ArduinoCloud.update();
}

/*
* 'onLedChange' is called when the "led" property of your Thing changes
*/
void onLedChange() {
Serial.print("LED set to ");
Serial.println(led);
digitalWrite(LED_BUILTIN, led);
}
34 changes: 34 additions & 0 deletions examples/ArduinoIoTCloud-DeferredOTA/arduino_secrets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <Arduino_ConnectionHandler.h>

/* MKR1000, MKR WiFi 1010 */
#if defined(BOARD_HAS_WIFI)
#define SECRET_SSID "YOUR_WIFI_NETWORK_NAME"
#define SECRET_PASS "YOUR_WIFI_PASSWORD"
#endif

/* ESP8266 */
#if defined(BOARD_ESP8266)
#define SECRET_DEVICE_KEY "my-device-password"
#endif

/* MKR GSM 1400 */
#if defined(BOARD_HAS_GSM)
#define SECRET_PIN ""
#define SECRET_APN ""
#define SECRET_LOGIN ""
#define SECRET_PASS ""
#endif

/* MKR WAN 1300/1310 */
#if defined(BOARD_HAS_LORA)
#define SECRET_APP_EUI ""
#define SECRET_APP_KEY ""
#endif

/* MKR NB 1500 */
#if defined(BOARD_HAS_NB)
#define SECRET_PIN ""
#define SECRET_APN ""
#define SECRET_LOGIN ""
#define SECRET_PASS ""
#endif
40 changes: 40 additions & 0 deletions examples/ArduinoIoTCloud-DeferredOTA/thingProperties.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

#if defined(BOARD_HAS_WIFI)
#elif defined(BOARD_HAS_GSM)
#elif defined(BOARD_HAS_LORA)
#elif defined(BOARD_HAS_NB)
#else
#error "Arduino IoT Cloud currently only supports MKR1000, MKR WiFi 1010, MKR WAN 1300/1310, MKR NB 1500 and MKR GSM 1400"
#endif

#define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

void onLedChange();

bool led;

void initProperties() {
#if defined(BOARD_ESP8266)
ArduinoCloud.setBoardId(BOARD_ID);
ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY);
#endif
ArduinoCloud.setThingId(THING_ID);
#if defined(BOARD_HAS_WIFI) || defined(BOARD_HAS_GSM) || defined(BOARD_HAS_NB)
ArduinoCloud.addProperty(led, Permission::Write).onUpdate(onLedChange);
#elif defined(BOARD_HAS_LORA)
ArduinoCloud.addProperty(led, 1, READWRITE, ON_CHANGE, onLedChange);
#endif
}

#if defined(BOARD_HAS_WIFI)
WiFiConnectionHandler ArduinoIoTPreferredConnection(SECRET_SSID, SECRET_PASS);
#elif defined(BOARD_HAS_GSM)
GSMConnectionHandler ArduinoIoTPreferredConnection(SECRET_PIN, SECRET_APN, SECRET_LOGIN, SECRET_PASS);
#elif defined(BOARD_HAS_LORA)
LoRaConnectionHandler ArduinoIoTPreferredConnection(SECRET_APP_EUI, SECRET_APP_KEY, _lora_band::EU868, NULL, _lora_class::CLASS_A);
#elif defined(BOARD_HAS_NB)
NBConnectionHandler ArduinoIoTPreferredConnection(SECRET_PIN, SECRET_APN, SECRET_LOGIN, SECRET_PASS);
#endif
36 changes: 21 additions & 15 deletions src/ArduinoIoTCloudTCP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP()
, _ota_img_sha256{"Inv."}
, _ota_url{""}
, _ota_req{false}
, _ask_user_before_executing_ota{false}
, _get_ota_confirmation{nullptr}
#endif /* OTA_ENABLED */
{

Expand Down Expand Up @@ -238,8 +240,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress,
addPropertyReal(_ota_cap, "OTA_CAP", Permission::Read);
addPropertyReal(_ota_error, "OTA_ERROR", Permission::Read);
addPropertyReal(_ota_img_sha256, "OTA_SHA256", Permission::Read);
addPropertyReal(_ota_url, "OTA_URL", Permission::ReadWrite).onSync(DEVICE_WINS);
addPropertyReal(_ota_req, "OTA_REQ", Permission::ReadWrite).onSync(DEVICE_WINS);
addPropertyReal(_ota_url, "OTA_URL", Permission::ReadWrite).onSync(CLOUD_WINS);
addPropertyReal(_ota_req, "OTA_REQ", Permission::ReadWrite).onSync(CLOUD_WINS);
#endif /* OTA_ENABLED */

#if OTA_STORAGE_PORTENTA_QSPI
Expand Down Expand Up @@ -499,29 +501,33 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected()
_mqtt_data_request_retransmit = false;
}

/* Check if any properties need encoding and send them to
* the cloud if necessary.
*/
sendPropertiesToCloud();

#if OTA_ENABLED
/* Request a OTA download if the hidden property
* OTA request has been set.
*/

if (_ota_req)
{
/* Clear the error flag. */
_ota_error = static_cast<int>(OTAError::None);
/* Transmit the cleared error flag to the cloud. */
sendPropertiesToCloud();
/* Clear the request flag. */
_ota_req = false;
/* Call member function to handle OTA request. */
onOTARequest();
bool const ota_execution_allowed_by_user = (_get_ota_confirmation != nullptr && _get_ota_confirmation());
bool const perform_ota_now = ota_execution_allowed_by_user || !_ask_user_before_executing_ota;
if (perform_ota_now) {
/* Clear the error flag. */
_ota_error = static_cast<int>(OTAError::None);
/* Clear the request flag. */
_ota_req = false;
/* Transmit the cleared error and request flags to the cloud. */
sendPropertiesToCloud();
/* Call member function to handle OTA request. */
onOTARequest();
}
}
#endif /* OTA_ENABLED */

/* Check if any properties need encoding and send them to
* the cloud if necessary.
*/
sendPropertiesToCloud();

return State::Connected;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/ArduinoIoTCloudTCP.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ static uint16_t const DEFAULT_BROKER_PORT_SECURE_AUTH = 8883;
static char const DEFAULT_BROKER_ADDRESS_USER_PASS_AUTH[] = "mqtts-up.iot.arduino.cc";
static uint16_t const DEFAULT_BROKER_PORT_USER_PASS_AUTH = 8884;

/******************************************************************************
* TYPEDEF
******************************************************************************/

typedef bool (*onOTARequestCallbackFunc)(void);

/******************************************************************************
* CLASS DECLARATION
******************************************************************************/
Expand Down Expand Up @@ -80,6 +86,16 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
inline String getBrokerAddress() const { return _brokerAddress; }
inline uint16_t getBrokerPort () const { return _brokerPort; }

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

private:
static const int MQTT_TRANSMIT_BUFFER_SIZE = 256;
Expand Down Expand Up @@ -130,6 +146,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
String _ota_img_sha256;
String _ota_url;
bool _ota_req;
bool _ask_user_before_executing_ota;
onOTARequestCallbackFunc _get_ota_confirmation;
#endif /* OTA_ENABLED */

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

};

/******************************************************************************
Expand Down