diff --git a/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino b/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino new file mode 100644 index 000000000..34abcdc38 --- /dev/null +++ b/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino @@ -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); +} diff --git a/examples/ArduinoIoTCloud-DeferredOTA/arduino_secrets.h b/examples/ArduinoIoTCloud-DeferredOTA/arduino_secrets.h new file mode 100644 index 000000000..fc0b0661e --- /dev/null +++ b/examples/ArduinoIoTCloud-DeferredOTA/arduino_secrets.h @@ -0,0 +1,34 @@ +#include + +/* 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 diff --git a/examples/ArduinoIoTCloud-DeferredOTA/thingProperties.h b/examples/ArduinoIoTCloud-DeferredOTA/thingProperties.h new file mode 100644 index 000000000..549f80f7b --- /dev/null +++ b/examples/ArduinoIoTCloud-DeferredOTA/thingProperties.h @@ -0,0 +1,40 @@ +#include +#include + +#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 diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 4cac10c6d..6ac511cba 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -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 */ { @@ -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 @@ -499,11 +501,6 @@ 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. @@ -511,17 +508,26 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() if (_ota_req) { - /* Clear the error flag. */ - _ota_error = static_cast(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(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; } } diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index f29aafde7..fa04a6e33 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -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 ******************************************************************************/ @@ -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; @@ -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"); } @@ -153,6 +171,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #if OTA_ENABLED void onOTARequest(); #endif + }; /******************************************************************************