Skip to content

Deferred OTA #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 4, 2021
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