From eb1c163b7a599658beacd60047c0eda14619f8c2 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Fri, 9 May 2025 11:21:28 +0200 Subject: [PATCH 01/12] Removing copyCBORStringToArray and copyCBORStringToArray utility functions in favor of cloud utils defined --- src/cbor/IoTCloudMessageDecoder.cpp | 85 +++++++++++++++++------------ 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/cbor/IoTCloudMessageDecoder.cpp b/src/cbor/IoTCloudMessageDecoder.cpp index e25962219..8a17ba1f3 100644 --- a/src/cbor/IoTCloudMessageDecoder.cpp +++ b/src/cbor/IoTCloudMessageDecoder.cpp @@ -15,30 +15,9 @@ #include #include "IoTCloudMessageDecoder.h" +#include #include -static inline bool copyCBORStringToArray(CborValue * param, char * dest, size_t dest_size) { - if (cbor_value_is_text_string(param)) { - // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string - if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { - return true; - } - } - - return false; -} - -static inline size_t copyCBORByteToArray(CborValue * param, uint8_t * dest, size_t dest_size) { - if (cbor_value_is_byte_string(param)) { - // NOTE: keep in mind that _cbor_value_copy_string tries to put a \0 at the end of the string - if(_cbor_value_copy_string(param, dest, &dest_size, NULL) == CborNoError) { - return dest_size; - } - } - - return 0; -} - /****************************************************************************** MESSAGE DECODE FUNCTIONS ******************************************************************************/ @@ -46,8 +25,12 @@ static inline size_t copyCBORByteToArray(CborValue * param, uint8_t * dest, size MessageDecoder::Status ThingUpdateCommandDecoder::decode(CborValue* iter, Message *msg) { ThingUpdateCmd * thingCommand = (ThingUpdateCmd *) msg; + size_t dest_size = sizeof(thingCommand->params.thing_id); + // Message is composed of a single parameter, a string (thing_id) - if (!copyCBORStringToArray(iter, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + if (cbor::utils::copyCBORStringToArray( + iter, thingCommand->params.thing_id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } @@ -57,8 +40,14 @@ MessageDecoder::Status ThingUpdateCommandDecoder::decode(CborValue* iter, Messag MessageDecoder::Status ThingDetachCommandDecoder::decode(CborValue* iter, Message *msg) { ThingDetachCmd * thingCommand = (ThingDetachCmd *) msg; + size_t dest_size = sizeof(thingCommand->params.thing_id); + + // Message is composed of a single parameter, a string (thing_id) - if (!copyCBORStringToArray(iter, thingCommand->params.thing_id, sizeof(thingCommand->params.thing_id))) { + if (cbor::utils::copyCBORStringToArray( + iter, + thingCommand->params.thing_id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } @@ -125,33 +114,57 @@ MessageDecoder::Status LastValuesUpdateCommandDecoder::decode(CborValue* iter, M } MessageDecoder::Status OtaUpdateCommandDecoder::decode(CborValue* iter, Message *msg) { - CborError error = CborNoError; OtaUpdateCmdDown * ota = (OtaUpdateCmdDown *) msg; + size_t dest_size = sizeof(ota->params.id); // Message is composed 4 parameters: id, url, initialSha, finalSha - if (!copyCBORByteToArray(iter, ota->params.id, sizeof(ota->params.id))) { + + // decoding parameter id + if (cbor::utils::copyCBORByteToArray( + iter, + ota->params.id, + dest_size) == MessageDecoder::Status::Error) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + // decoding parameter url + if(cbor_value_advance(iter) != CborNoError) { + return MessageDecoder::Status::Error; + } + + dest_size = sizeof(ota->params.url); + + if (cbor::utils::copyCBORStringToArray(iter, + ota->params.url, + dest_size) == MessageDecoder::Status::Error) { + return MessageDecoder::Status::Error; + } - if ((error != CborNoError) || !copyCBORStringToArray(iter, ota->params.url, sizeof(ota->params.url))) { + // decoding parameter initialSha256 + if(cbor_value_advance(iter) != CborNoError) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + dest_size = sizeof(ota->params.initialSha256); + + if (cbor::utils::copyCBORByteToArray(iter, + ota->params.initialSha256, + dest_size) == MessageDecoder::Status::Error || + dest_size != sizeof(ota->params.initialSha256)) { + return MessageDecoder::Status::Error; + } - if ((error != CborNoError) || - copyCBORByteToArray(iter, ota->params.initialSha256, - sizeof(ota->params.initialSha256)) != sizeof(ota->params.initialSha256)) { + // decoding parameter finalSha256 + if(cbor_value_advance(iter) != CborNoError) { return MessageDecoder::Status::Error; } - error = cbor_value_advance(iter); + dest_size = sizeof(ota->params.finalSha256); - if ((error != CborNoError) || - copyCBORByteToArray(iter, ota->params.finalSha256, - sizeof(ota->params.finalSha256)) != sizeof(ota->params.finalSha256)) { + if (cbor::utils::copyCBORByteToArray(iter, + ota->params.finalSha256, + dest_size) == MessageDecoder::Status::Error || + dest_size != sizeof(ota->params.finalSha256)) { return MessageDecoder::Status::Error; } From 3736823c1d3aae1b25377ddb8e64191d8f1ae7d7 Mon Sep 17 00:00:00 2001 From: per1234 Date: Fri, 9 May 2025 02:48:46 -0700 Subject: [PATCH 02/12] Standardize license file Standardization in license documentation is important because, in addition to making it easy for humans to find this vital information, it allows machines to automate the process of license type determination, which is useful both for discovering suitable open source projects as well as checking open source license compliance. The open source license of this project is already stored in a standardized location in a dedicated license file. However, even though the project is licensed under a standardized open source license, additional text was added to the license file which offers the option to purchase an exception for proprietary use of the code. Even though this offer does not have any legal effect on the open source license, the presence of that text made it so that the open license type could not be identified with 100% confidence by machines (e.g., the Licensee Gem used by the GitHub website), which meant identification could only be made by a human carefully evaluating the license text. Since there is no need to place the exception offer in the license file, it can be moved to the readme, with the license file containing only the verbatim standardized open source license text. The result is that the project's open source license type can now be automatically identified, without making any change to the licensing. --- LICENSE | 14 -------------- README.md | 6 ++++++ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/LICENSE b/LICENSE index 4a1b324fd..f288702d2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,3 @@ -This file includes licensing information for ArduinoIoTCloud - -Copyright (c) 2019 ARDUINO SA (www.arduino.cc) - -The software is released under the GNU General Public License, which covers the main body -of the ArduinoIoTCloud code. The terms of this license can be found at: -https://www.gnu.org/licenses/gpl-3.0.en.html - -You can be released from the requirements of the above licenses by purchasing -a commercial license. Buying such a license is mandatory if you want to modify or -otherwise use the software for commercial activities involving the Arduino -software without disclosing the source code of your own applications. To purchase -a commercial license, send an email to license@arduino.cc - GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/README.md b/README.md index 4b248e784..93a0829a8 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,9 @@ Boards can authenticate to the ArduinoIoTCloud servers using 3 methods: * `DEVICE_CERTIFICATE` and `PRIVATE_KEY`. This values are stored inside the board secure element during the device provisioning phase. Boards that are using this method are: [`MKR 1000`](https://store.arduino.cc/arduino-mkr1000-wifi), [`MKR WiFi 1010`](https://store.arduino.cc/arduino-mkr-wifi-1010), [`MKR GSM 1400`](https://store.arduino.cc/arduino-mkr-gsm-1400-1415), [`MKR NB 1500`](https://store.arduino.cc/arduino-mkr-nb-1500-1413), [`Nano 33 IoT`](https://store.arduino.cc/arduino-nano-33-iot), [`Portenta H7`](https://store.arduino.cc/portenta-h7), [`Nano RP2040 Connect`](https://store.arduino.cc/products/arduino-nano-rp2040-connect), [`Nicla Vision`](https://store.arduino.cc/products/nicla-vision), [`OPTA WiFi`](https://store.arduino.cc/products/opta-wifi), [`OPTA RS485`](https://store.arduino.cc/products/opta-rs485), [`OPTA Lite`](https://store.arduino.cc/products/opta-lite), [`GIGA R1 WiFi`](https://store.arduino.cc/products/giga-r1-wifi), [`Portenta C33`](https://store.arduino.cc/products/portenta-c33) * `APP_EUI` and `APP_KEY`. This values are defined in the `thingProperties.h` file and included in the Sketch. Boards that are using this method are: [`MKR WAN 1300/1310`](https://store.arduino.cc/mkr-wan-1310) + +### License + +The ArduinoIoTCloud library is licensed under the GNU General Public License v3.0. + +You can be released from the requirements of the above license by purchasing a commercial license. Buying such a license is mandatory if you want to modify or otherwise use the software for commercial activities involving the Arduino software without disclosing the source code of your own applications. To purchase a commercial license, send an email to license@arduino.cc From 37df5dbcda0ea90287278a0d86e0db6deb0138be Mon Sep 17 00:00:00 2001 From: fabik111 Date: Thu, 13 Feb 2025 16:16:10 +0100 Subject: [PATCH 03/12] use network configurator in iotCloud update example of sketch that uses the NetworkConfigurator use NetworkConfigurator wrappers for agentsManager update ci test for networkConfigurator add Arduino_NetworkConfigurator dep to library.properties --- .github/workflows/compile-examples.yml | 24 ++++ .../ArduinoIoTCloud-NetConfig.ino | 62 ++++++++++ .../thingProperties.h | 47 ++++++++ library.properties | 2 +- src/AIoTC_Config.h | 7 ++ src/ArduinoIoTCloud.cpp | 3 + src/ArduinoIoTCloud.h | 9 ++ src/ArduinoIoTCloudTCP.cpp | 110 +++++++++++++----- src/ArduinoIoTCloudTCP.h | 6 + 9 files changed, 239 insertions(+), 31 deletions(-) create mode 100644 examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino create mode 100644 examples/ArduinoIoTCloud-NetConfig/thingProperties.h diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 5eb4f66d9..d30277a07 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -194,7 +194,11 @@ jobs: - name: ArduinoECCX08 - name: Arduino_Cellular - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -207,7 +211,11 @@ jobs: - name: arduino:mbed_nicla libraries: | - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -222,7 +230,11 @@ jobs: - name: ArduinoBearSSL - name: ArduinoECCX08 - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -237,7 +249,11 @@ jobs: - name: ArduinoBearSSL - name: ArduinoECCX08 - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-DeferredOTA - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule @@ -251,7 +267,11 @@ jobs: libraries: | - name: Arduino_Cellular - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule - examples/utility/Provisioning @@ -263,7 +283,11 @@ jobs: - name: arduino:renesas_uno libraries: | - name: Blues Wireless Notecard + - name: ArduinoBLE + - name: Arduino_KVStore + - name: Arduino_NetworkConfigurator sketch-paths: | + - examples/ArduinoIoTCloud-NetConfig - examples/ArduinoIoTCloud-Notecard - examples/ArduinoIoTCloud-Schedule # Nano ESP32 diff --git a/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino b/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino new file mode 100644 index 000000000..238b59f55 --- /dev/null +++ b/examples/ArduinoIoTCloud-NetConfig/ArduinoIoTCloud-NetConfig.ino @@ -0,0 +1,62 @@ +/* + This sketch demonstrates how to exchange data between your board and the Arduino IoT Cloud. + + * Connect a potentiometer (or other analog sensor) to A0. + * When the potentiometer (or sensor) value changes the data is sent to the Cloud. + * When you flip the switch in the Cloud dashboard the onboard LED lights gets turned ON or OFF. + + IMPORTANT: + This sketch works with WiFi, GSM, NB, Ethernet and Lora enabled boards supported by Arduino IoT Cloud. + On a LoRa board, if it is configured as a class A device (default and preferred option), + values from Cloud dashboard are received only after a value is sent to Cloud. + + The full list of compatible boards can be found here: + - https://github.com/arduino-libraries/ArduinoIoTCloud#what +*/ + +#include "thingProperties.h" + +#if !defined(LED_BUILTIN) && !defined(ARDUINO_NANO_ESP32) +static int const LED_BUILTIN = 2; +#endif + +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); ) { } + + /* Set the debug message level: + * - DBG_ERROR: Only show error messages + * - DBG_WARNING: Show warning and error messages + * - DBG_INFO: Show info, warning, and error messages + * - DBG_DEBUG: Show debug, info, warning, and error messages + * - DBG_VERBOSE: Show all messages + */ + setDebugMessageLevel(DBG_INFO); + + /* 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); + + ArduinoCloud.printDebugInfo(); +} + +void loop() { + ArduinoCloud.update(); + potentiometer = analogRead(A0); + seconds = millis() / 1000; +} + +/* + * '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-NetConfig/thingProperties.h b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h new file mode 100644 index 000000000..833927042 --- /dev/null +++ b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h @@ -0,0 +1,47 @@ +#if !defined(ARDUINO_SAMD_MKRWIFI1010) && !defined(ARDUINO_SAMD_NANO_33_IOT) && !defined(ARDUINO_NANO_RP2040_CONNECT) \ + && !defined(ARDUINO_PORTENTA_H7_M7) && !defined(ARDUINO_NICLA_VISION) && !defined(ARDUINO_OPTA) && !defined(ARDUINO_GIGA) \ + && !defined(ARDUINO_UNOR4_WIFI) && !defined(ARDUINO_PORTENTA_C33) +#error "This example is not compatible with this board." +#endif +#include +#include +#include "ConfiguratorAgents/agents/BLE/BLEAgent.h" +#include "ConfiguratorAgents/agents/Serial/SerialAgent.h" + +void onLedChange(); + +bool led; +int potentiometer; +int seconds; + +GenericConnectionHandler ArduinoIoTPreferredConnection; +KVStore kvStore; +NetworkConfiguratorClass NetworkConfigurator(ArduinoIoTPreferredConnection); +BLEAgentClass BLEAgent; +SerialAgentClass SerialAgent; + +void initProperties() { + NetworkConfigurator.addAgent(BLEAgent); + NetworkConfigurator.addAgent(SerialAgent); + NetworkConfigurator.setStorage(kvStore); + + /* For changing the default reset pin uncomment and set your preferred pin. + * Use DISABLE_PIN for disabling the reset procedure. + * The pin must be in the list of digital pins usable for interrupts. + * Please refer to the Arduino documentation for more details: + * https://docs.arduino.cc/language-reference/en/functions/external-interrupts/attachInterrupt/ + */ + //NetworkConfigurator.setReconfigurePin(your_pin); + + /* Otherwise if you need to monitor the pin status changes + * you can set a custom callback function that is fired on every change + */ + //NetworkConfigurator.setPinChangedCallback(your_callback); + + ArduinoCloud.setConfigurator(NetworkConfigurator); + + ArduinoCloud.addProperty(led, Permission::Write).onUpdate(onLedChange); + ArduinoCloud.addProperty(potentiometer, Permission::Read).publishOnChange(10); + ArduinoCloud.addProperty(seconds, Permission::Read).publishOnChange(1); + +} diff --git a/library.properties b/library.properties index 22e037e17..b2dbaea2a 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://github.com/arduino-libraries/ArduinoIoTCloud architectures=mbed,samd,esp8266,mbed_nano,mbed_portenta,mbed_nicla,esp32,mbed_opta,mbed_giga,renesas_portenta,renesas_uno,mbed_edge,stm32 includes=ArduinoIoTCloud.h -depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient,Arduino_CloudUtils,ArduinoBearSSL +depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient,Arduino_CloudUtils,ArduinoBearSSL,Arduino_NetworkConfigurator diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 89f04e20f..c02d20b78 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -148,6 +148,13 @@ #define BOARD_STM32H7 #endif +#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA) \ + || defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_PORTENTA_C33) + #define NETWORK_CONFIGURATOR_ENABLED (1) +#else + #define NETWORK_CONFIGURATOR_ENABLED (0) +#endif + /****************************************************************************** * CONSTANTS ******************************************************************************/ diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index a565cac44..74443ff5b 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -27,6 +27,9 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} +#if NETWORK_CONFIGURATOR_ENABLED +, _configurator{nullptr} +#endif , _time_service(TimeService) , _thing_id{"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} , _lib_version{AIOT_CONFIG_LIB_VERSION} diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 7cea9382e..d097dcc81 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -25,6 +25,9 @@ #include #include +#if NETWORK_CONFIGURATOR_ENABLED +#include +#endif #if defined(DEBUG_ERROR) || defined(DEBUG_WARNING) || defined(DEBUG_INFO) || defined(DEBUG_DEBUG) || defined(DEBUG_VERBOSE) # include @@ -101,6 +104,9 @@ class ArduinoIoTCloudClass inline unsigned long getInternalTime() { return _time_service.getTime(); } inline unsigned long getLocalTime() { return _time_service.getLocalTime(); } + #if NETWORK_CONFIGURATOR_ENABLED + inline void setConfigurator(NetworkConfiguratorClass & configurator) { _configurator = &configurator; } + #endif void addCallback(ArduinoIoTCloudEvent const event, OnCloudEventCallback callback); #define addProperty( v, ...) addPropertyReal(v, #v, __VA_ARGS__) @@ -146,6 +152,9 @@ class ArduinoIoTCloudClass protected: ConnectionHandler * _connection; + #if NETWORK_CONFIGURATOR_ENABLED + NetworkConfiguratorClass * _configurator; + #endif TimeServiceClass & _time_service; String _thing_id; String _lib_version; diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index dc8bb86d0..10276506b 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -35,7 +35,7 @@ #include /****************************************************************************** - LOCAL MODULE FUNCTIONS + LOCAL MODULE FUNCTIONS ******************************************************************************/ unsigned long getTime() @@ -48,7 +48,7 @@ unsigned long getTime() ******************************************************************************/ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() -: _state{State::ConnectPhy} +: _state{State::ConfigPhy} , _connection_attempt(0,0) , _message_stream(std::bind(&ArduinoIoTCloudTCP::sendMessage, this, std::placeholders::_1)) , _thing(&_message_stream) @@ -84,24 +84,16 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ _connection = &connection; _brokerAddress = brokerAddress; - ArduinoIoTAuthenticationMode authMode = ArduinoIoTAuthenticationMode::CERTIFICATE; + _authMode = ArduinoIoTAuthenticationMode::CERTIFICATE; #if defined (BOARD_HAS_SECRET_KEY) /* If board supports and sketch is configured for username and password login */ if(_password.length()) { - authMode = ArduinoIoTAuthenticationMode::PASSWORD; + _authMode = ArduinoIoTAuthenticationMode::PASSWORD; } #endif - /* Setup broker TLS client */ - _brokerClient.begin(connection, authMode); - -#if OTA_ENABLED - /* Setup OTA TLS client */ - _otaClient.begin(connection); -#endif - /* If board is configured for certificate authentication and mTLS */ - if(authMode == ArduinoIoTAuthenticationMode::CERTIFICATE) + if(_authMode == ArduinoIoTAuthenticationMode::CERTIFICATE) { #if defined(BOARD_HAS_SECURE_ELEMENT) if (!_selement.begin()) @@ -141,9 +133,6 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ _brokerPort = (brokerPort == DEFAULT_BROKER_PORT_AUTO) ? DEFAULT_BROKER_PORT_USER_PASS_AUTH : brokerPort; } - /* Setup TimeService */ - _time_service.begin(_connection); - /* Setup retry timers */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); return begin(enable_watchdog, _brokerAddress, _brokerPort); @@ -151,6 +140,7 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, uint16_t brokerPort) { + _enable_watchdog = enable_watchdog; _brokerAddress = brokerAddress; _brokerPort = brokerPort; @@ -195,20 +185,12 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, } #endif - /* Since we do not control what code the user inserts - * between ArduinoIoTCloudTCP::begin() and the first - * call to ArduinoIoTCloudTCP::update() it is wise to - * set a rather large timeout at first. - */ -#if defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_MBED) - if (enable_watchdog) { - /* Initialize watchdog hardware */ - watchdog_enable(); - /* Setup callbacks to feed the watchdog during offloaded network operations (connection/download)*/ - watchdog_enable_network_feed(_connection->getInterface()); +#if NETWORK_CONFIGURATOR_ENABLED + if(_configurator != nullptr){ + _configurator->enableBLE(false); + _configurator->begin(); } #endif - return 1; } @@ -225,12 +207,15 @@ void ArduinoIoTCloudTCP::update() State next_state = _state; switch (_state) { + case State::ConfigPhy: next_state = handle_ConfigPhy(); break; + case State::Init: next_state = handle_Init(); break; case State::ConnectPhy: next_state = handle_ConnectPhy(); break; case State::SyncTime: next_state = handle_SyncTime(); break; case State::ConnectMqttBroker: next_state = handle_ConnectMqttBroker(); break; case State::Connected: next_state = handle_Connected(); break; case State::Disconnect: next_state = handle_Disconnect(); break; } + _state = next_state; /* This watchdog feed is actually needed only by the RP2040 Connect because its @@ -241,11 +226,24 @@ void ArduinoIoTCloudTCP::update() watchdog_reset(); #endif + /* Poll the network configurator to check if it is updating the configuration. + * The polling must be performed only if the the first configuration is completed. + */ + #if NETWORK_CONFIGURATOR_ENABLED + if(_configurator != nullptr && _state > State::Init && _configurator->update() == NetworkConfiguratorStates::UPDATING_CONFIG){ + _state = State::ConfigPhy; + } + #endif + #if OTA_ENABLED /* OTA FSM needs to reach the Idle state before being able to run independently from * the mqttClient. The state can be reached only after the mqttClient is connected to * the broker. */ + if(_state <= State::Init){ + return; + } + if((_ota.getState() != OTACloudProcessInterface::Resume && _ota.getState() != OTACloudProcessInterface::OtaBegin) || _mqttClient.connected()) { @@ -262,6 +260,9 @@ void ArduinoIoTCloudTCP::update() int ArduinoIoTCloudTCP::connected() { + if (_state <= State::Init) { + return 0; + } return _mqttClient.connected(); } @@ -276,6 +277,55 @@ void ArduinoIoTCloudTCP::printDebugInfo() * PRIVATE MEMBER FUNCTIONS ******************************************************************************/ +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConfigPhy() +{ +#if NETWORK_CONFIGURATOR_ENABLED + if (_configurator == nullptr) { + return State::Init; + } + + if(_configurator->update() == NetworkConfiguratorStates::CONFIGURED) { + _configurator->disconnectAgent(); + return State::Init; + } + return State::ConfigPhy; +#else + return State::Init; +#endif + +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Init() +{ + /* Setup broker TLS client */ + /* Setup broker TLS client */ + _brokerClient.begin(*_connection, _authMode); + +#if OTA_ENABLED + /* Setup OTA TLS client */ + _otaClient.begin(*_connection); +#endif + + /* Setup TimeService */ + _time_service.begin(_connection); + + /* Since we do not control what code the user inserts + * between ArduinoIoTCloudTCP::begin() and the first + * call to ArduinoIoTCloudTCP::update() it is wise to + * set a rather large timeout at first. + */ +#if defined (ARDUINO_ARCH_SAMD) || defined (ARDUINO_ARCH_MBED) + if (_enable_watchdog) { + /* Initialize watchdog hardware */ + watchdog_enable(); + /* Setup callbacks to feed the watchdog during offloaded network operations (connection/download)*/ + watchdog_enable_network_feed(_connection->getInterface()); + } +#endif + + return State::ConnectPhy; +} + ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectPhy() { if (_connection->check() == NetworkConnectionState::CONNECTED) @@ -633,8 +683,8 @@ int ArduinoIoTCloudTCP::updateCertificate(String authorityKeyIdentifier, String #endif /****************************************************************************** - * EXTERN DEFINITION - ******************************************************************************/ +* EXTERN DEFINITION +******************************************************************************/ ArduinoIoTCloudTCP ArduinoCloud; diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 6560a5f70..2505393f2 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -120,6 +120,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass enum class State { + ConfigPhy, + Init, ConnectPhy, SyncTime, ConnectMqttBroker, @@ -133,11 +135,13 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ArduinoCloudThing _thing; ArduinoCloudDevice _device; + ArduinoIoTAuthenticationMode _authMode; String _brokerAddress; uint16_t _brokerPort; uint8_t _mqtt_data_buf[MQTT_TRANSMIT_BUFFER_SIZE]; int _mqtt_data_len; bool _mqtt_data_request_retransmit; + bool _enable_watchdog; #if defined(BOARD_HAS_SECRET_KEY) String _password; @@ -170,6 +174,8 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass inline String getTopic_dataout () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/o"); } inline String getTopic_datain () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/i"); } + State handle_ConfigPhy(); + State handle_Init(); State handle_ConnectPhy(); State handle_SyncTime(); State handle_ConnectMqttBroker(); From 00cc42d298e9478c7774972832962dc0b0ef0b33 Mon Sep 17 00:00:00 2001 From: fabik111 Date: Wed, 19 Mar 2025 16:30:20 +0100 Subject: [PATCH 04/12] add disconnect method --- src/ArduinoIoTCloud.h | 2 +- src/ArduinoIoTCloudTCP.cpp | 31 +++++++++++++++++++++++++------ src/ArduinoIoTCloudTCP.h | 7 +++++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index d097dcc81..6d5d8dd45 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -90,7 +90,7 @@ class ArduinoIoTCloudClass virtual void update () = 0; virtual int connected () = 0; virtual void printDebugInfo() = 0; - + virtual void disconnect () { } void push(); bool setTimestamp(String const & prop_name, unsigned long const timestamp); diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 10276506b..3a464286d 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -79,7 +79,7 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() * PUBLIC MEMBER FUNCTIONS ******************************************************************************/ -int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_watchdog, String brokerAddress, uint16_t brokerPort) +int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_watchdog, String brokerAddress, uint16_t brokerPort, bool auto_reconnect) { _connection = &connection; _brokerAddress = brokerAddress; @@ -135,14 +135,17 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ /* Setup retry timers */ _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); - return begin(enable_watchdog, _brokerAddress, _brokerPort); + return begin(enable_watchdog, _brokerAddress, _brokerPort, auto_reconnect); } -int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, uint16_t brokerPort) +int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, uint16_t brokerPort, bool auto_reconnect) { _enable_watchdog = enable_watchdog; _brokerAddress = brokerAddress; _brokerPort = brokerPort; + _auto_reconnect = auto_reconnect; + + _state = State::ConfigPhy; _mqttClient.setClient(_brokerClient); @@ -214,6 +217,7 @@ void ArduinoIoTCloudTCP::update() case State::ConnectMqttBroker: next_state = handle_ConnectMqttBroker(); break; case State::Connected: next_state = handle_Connected(); break; case State::Disconnect: next_state = handle_Disconnect(); break; + case State::Disconnected: break; } _state = next_state; @@ -273,6 +277,16 @@ void ArduinoIoTCloudTCP::printDebugInfo() DEBUG_INFO("MQTT Broker: %s:%d", _brokerAddress.c_str(), _brokerPort); } +void ArduinoIoTCloudTCP::disconnect() { + if (_state == State::ConfigPhy || _state == State::Init) { + return; + } + + _mqttClient.stop(); + _auto_reconnect = false; + _state = State::Disconnect; +} + /****************************************************************************** * PRIVATE MEMBER FUNCTIONS ******************************************************************************/ @@ -431,9 +445,13 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() DEBUG_INFO("Disconnected from Arduino IoT Cloud"); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - /* Setup timer for broker connection and restart */ - _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); - return State::ConnectPhy; + if(_auto_reconnect) { + /* Setup timer for broker connection and restart */ + _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); + return State::ConnectPhy; + } + + return State::Disconnected; } void ArduinoIoTCloudTCP::onMessage(int length) @@ -680,6 +698,7 @@ int ArduinoIoTCloudTCP::updateCertificate(String authorityKeyIdentifier, String } return 0; } + #endif /****************************************************************************** diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 2505393f2..d59c6de97 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -72,9 +72,10 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass virtual void update () override; virtual int connected () override; virtual void printDebugInfo() override; + virtual void disconnect () override; - int begin(ConnectionHandler & connection, bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO); - int begin(bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO); + int begin(ConnectionHandler & connection, bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO, bool auto_reconnect = true); + int begin(bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS, uint16_t brokerPort = DEFAULT_BROKER_PORT_AUTO, bool auto_reconnect = true); #if defined(BOARD_HAS_SECURE_ELEMENT) int updateCertificate(String authorityKeyIdentifier, String serialNumber, String notBefore, String notAfter, String signature); @@ -127,6 +128,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ConnectMqttBroker, Connected, Disconnect, + Disconnected, }; State _state; @@ -142,6 +144,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass int _mqtt_data_len; bool _mqtt_data_request_retransmit; bool _enable_watchdog; + bool _auto_reconnect; #if defined(BOARD_HAS_SECRET_KEY) String _password; From 3c158b99c35c5f6f9aab5310be43b25f1c8e0077 Mon Sep 17 00:00:00 2001 From: fabik111 Date: Mon, 31 Mar 2025 16:11:19 +0200 Subject: [PATCH 05/12] new Provisioning_2.0 sketch add GET_PROVISIONING_SKETCH_VERSION command add define for compile with test env --- .../utility/Provisioning_2.0/CSRHandler.cpp | 428 ++++++++++++++++++ .../utility/Provisioning_2.0/CSRHandler.h | 74 +++ .../Provisioning_2.0/ClaimingHandler.cpp | 192 ++++++++ .../Provisioning_2.0/ClaimingHandler.h | 53 +++ .../Provisioning_2.0/Provisioning_2.0.ino | 228 ++++++++++ .../utility/Provisioning_2.0/SecretsHelper.h | 20 + .../Provisioning_2.0/thingProperties.h | 33 ++ src/ArduinoIoTCloudTCP.cpp | 2 +- 8 files changed, 1029 insertions(+), 1 deletion(-) create mode 100644 examples/utility/Provisioning_2.0/CSRHandler.cpp create mode 100644 examples/utility/Provisioning_2.0/CSRHandler.h create mode 100644 examples/utility/Provisioning_2.0/ClaimingHandler.cpp create mode 100644 examples/utility/Provisioning_2.0/ClaimingHandler.h create mode 100644 examples/utility/Provisioning_2.0/Provisioning_2.0.ino create mode 100644 examples/utility/Provisioning_2.0/SecretsHelper.h create mode 100644 examples/utility/Provisioning_2.0/thingProperties.h diff --git a/examples/utility/Provisioning_2.0/CSRHandler.cpp b/examples/utility/Provisioning_2.0/CSRHandler.cpp new file mode 100644 index 000000000..1a6101e4b --- /dev/null +++ b/examples/utility/Provisioning_2.0/CSRHandler.cpp @@ -0,0 +1,428 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "CSRHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include "Arduino_DebugUtils.h" +#include + +#define RESPONSE_TIMEOUT 5000 +#define CONNECTION_RETRY_TIMEOUT_MS 2000 +#define BACKEND_INTERVAL_s 12 +#define MAX_CSR_REQUEST_INTERVAL 180000 +#define MAX_CSR_REQUEST_INTERVAL_ATTEMPTS 15 + +#ifdef COMPILE_TEST +constexpr char *server = "boards-v2.oniudra.cc"; +#else +constexpr char *server = "boards-v2.arduino.cc"; +#endif + +CSRHandlerClass::CSRHandlerClass() : + _ledFeedback{LEDFeedbackClass::getInstance()}, + _state{CSRHandlerStates::END}, + _nextRequestAt{0}, + _requestAttempt{0}, + _startWaitingResponse{0}, + _uhwid{nullptr}, + _certForCSR{nullptr}, + _connectionHandler{nullptr}, + _secureElement{nullptr}, + _tlsClient{nullptr}, + _client{nullptr}, + _fw_version{""}, + _deviceId{""}, + _issueYear{0}, + _issueMonth{0}, + _issueDay{0}, + _issueHour{0} { + memset(_serialNumber, 0, sizeof(_serialNumber)); + memset(_authorityKeyIdentifier, 0, sizeof(_authorityKeyIdentifier)); + memset(_signature, 0, sizeof(_signature)); +} + +CSRHandlerClass::~CSRHandlerClass() { + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + if (_client) { + delete _client; + _client = nullptr; + } + + if(_tlsClient){ + delete _tlsClient; + _tlsClient = nullptr; + } +} + +bool CSRHandlerClass::begin(ConnectionHandler &connectionHandler, SecureElement &secureElement, String &uhwid) { + if(_state != CSRHandlerStates::END) { + return true; + } + + if(uhwid == "") { + return false; + } + + _connectionHandler = &connectionHandler; + _secureElement = &secureElement; + _uhwid = &uhwid; + +#ifdef BOARD_HAS_WIFI + _fw_version = WiFi.firmwareVersion(); +#endif + if(!_tlsClient){ + _tlsClient = new TLSClientMqtt(); + } + _tlsClient->begin(*_connectionHandler); + _tlsClient->setTimeout(RESPONSE_TIMEOUT); + _client = new HttpClient(*_tlsClient, server, 443); + TimeService.begin(_connectionHandler); + _requestAttempt = 0; + _nextRequestAt = 0; + _startWaitingResponse = 0; + _state = CSRHandlerStates::BUILD_CSR; +} + +void CSRHandlerClass::end() { + if (_client) { + _client->stop(); + delete _client; + _client = nullptr; + } + + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + + if(_tlsClient){ + delete _tlsClient; + _tlsClient = nullptr; + } + _fw_version = ""; + _deviceId = ""; + _state = CSRHandlerStates::END; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::poll() { + switch (_state) { + case CSRHandlerStates::BUILD_CSR: _state = handleBuildCSR (); break; + case CSRHandlerStates::REQUEST_SIGNATURE: _state = handleRequestSignature (); break; + case CSRHandlerStates::WAITING_RESPONSE: _state = handleWaitingResponse (); break; + case CSRHandlerStates::PARSE_RESPONSE: _state = handleParseResponse (); break; + case CSRHandlerStates::BUILD_CERTIFICATE: _state = handleBuildCertificate (); break; + case CSRHandlerStates::CERT_CREATED: _state = handleCertCreated (); break; + case CSRHandlerStates::WAITING_COMPLETE_RES: _state = handleWaitingCompleteRes(); break; + case CSRHandlerStates::COMPLETED: break; + case CSRHandlerStates::ERROR: handleError (); break; + case CSRHandlerStates::END: break; + } + + return _state; +} + +void CSRHandlerClass::updateNextRequestAt() { + uint32_t delay; + if(_requestAttempt <= MAX_CSR_REQUEST_INTERVAL_ATTEMPTS) { + delay = BACKEND_INTERVAL_s * _requestAttempt * 1000; // use linear backoff since backend has a rate limit + }else { + delay = MAX_CSR_REQUEST_INTERVAL; + } + + _nextRequestAt = millis() + delay + jitter(); +} + +uint32_t CSRHandlerClass::jitter(uint32_t base, uint32_t max) { + srand(millis()); + return base + rand() % (max - base); +} + +bool CSRHandlerClass::postRequest(const char *url, String &postData) { + if(!_client){ + _client = new HttpClient(*_tlsClient, server, 443); + } + + uint32_t ts = getTimestamp(); + if(ts == 0){ + DEBUG_WARNING("CSRH::%s Failed getting timestamp", __FUNCTION__); + return false; + } + + String token = getAIoTCloudJWT(*_secureElement, *_uhwid, ts, 1); + + _requestAttempt++; + _client->beginRequest(); + + if(_client->post(url) == 0){ + _client->sendHeader("Host", server); + _client->sendHeader("Connection", "close"); + _client->sendHeader("Content-Type", "application/json;charset=UTF-8"); + _client->sendHeader("Authorization", "Bearer " + token); + _client->sendHeader("Content-Length", postData.length()); + _client->beginBody(); + _client->print(postData); + _startWaitingResponse = millis(); + return true; + } + return false; +} + +uint32_t CSRHandlerClass::getTimestamp() { + uint8_t getTsAttempt = 0; + uint32_t ts = 0; + do{ + TimeService.sync(); + ts = TimeService.getTime(); + getTsAttempt++; + }while(ts == 0 && getTsAttempt < 3); + + return ts; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleBuildCSR() { + if (!_certForCSR) { + _certForCSR = new ECP256Certificate(); + } + _certForCSR->begin(); + + _certForCSR->setSubjectCommonName(*_uhwid); + + if (!SElementCSR::build(*_secureElement, *_certForCSR, static_cast(SElementArduinoCloudSlot::Key), true)) { + DEBUG_ERROR("CSRH::%s Error generating CSR!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + return CSRHandlerStates::REQUEST_SIGNATURE; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleRequestSignature() { + CSRHandlerStates nextState = _state; + + if(millis() < _nextRequestAt) { + return nextState; + } + + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + return nextState; + } + + if(!_certForCSR){ + return CSRHandlerStates::BUILD_CSR; + } + + String csr = _certForCSR->getCSRPEM(); + csr.replace("\n", "\\n"); + + String PostData = "{\"csr\":\""; + PostData += csr; + PostData += "\"}"; + DEBUG_INFO("CSRH Downloading certificate..."); + + if(postRequest("/provisioning/v1/onboarding/provision/csr", PostData)){ + nextState = CSRHandlerStates::WAITING_RESPONSE; + } else { + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Failed sending request, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleWaitingResponse() { + CSRHandlerStates nextState = _state; + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + _client->stop(); + return CSRHandlerStates::REQUEST_SIGNATURE; + } + + if (millis() - _startWaitingResponse > RESPONSE_TIMEOUT) { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s CSR request timeout, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + + + int statusCode = _client->responseStatusCode(); + if(statusCode == 200){ + nextState = CSRHandlerStates::PARSE_RESPONSE; + } else { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s CSR request error code %d, retrying in %d ms", __FUNCTION__, statusCode ,_nextRequestAt - millis()); + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleParseResponse() { + String certResponse = _client->responseBody(); + _client->stop(); + + /* Parse the response in format: + * device_id|authority_key_identifier|not_before|serial|signature_asn1_x|signature_asn1_y + */ + char *response = (char *)certResponse.c_str(); + char *token[6]; + int i = 1; + token[0] = strtok(response, "|"); + for (; i < 6; i++) { + char *tok = strtok(NULL, "|"); + if(tok == NULL){ + break; + } + token[i] = tok; + } + + if(i < 6 || strlen(token[0]) != 36 || strlen(token[1]) != 40 + || strlen(token[2]) < 10 || strlen(token[3]) != 32 + || strlen(token[4]) != 64 || strlen(token[5]) != 64 + || sscanf(token[2], "%4d-%2d-%2dT%2d", &_issueYear, &_issueMonth, &_issueDay, &_issueHour) != 4){ + updateNextRequestAt(); + DEBUG_ERROR("CSRH::%s Error parsing response, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + return CSRHandlerStates::REQUEST_SIGNATURE; + } + + _deviceId = token[0]; + hex::decode(token[1], _authorityKeyIdentifier, sizeof(_authorityKeyIdentifier)); + hex::decode(token[3], _serialNumber, sizeof(_serialNumber)); + hex::decode(token[4], _signature, sizeof(_signature)); + hex::decode(token[5], &_signature[32], sizeof(_signature) - 32); + + return CSRHandlerStates::BUILD_CERTIFICATE; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleBuildCertificate() { + int expireYears = 31; + + if (!SElementArduinoCloudDeviceId::write(*_secureElement, _deviceId, SElementArduinoCloudSlot::DeviceId)) { + DEBUG_ERROR("CSRH::%s Error storing device id!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + ECP256Certificate cert; + cert.begin(); + + cert.setSubjectCommonName(_deviceId); + cert.setIssuerCountryName("US"); + cert.setIssuerOrganizationName("Arduino LLC US"); + cert.setIssuerOrganizationalUnitName("IT"); + cert.setIssuerCommonName("Arduino"); + cert.setSignature(_signature, sizeof(_signature)); + cert.setAuthorityKeyId(_authorityKeyIdentifier, sizeof(_authorityKeyIdentifier)); + cert.setSerialNumber(_serialNumber, sizeof(_serialNumber)); + cert.setIssueYear(_issueYear); + cert.setIssueMonth(_issueMonth); + cert.setIssueDay(_issueDay); + cert.setIssueHour(_issueHour); + cert.setExpireYears(expireYears); + + if (!SElementArduinoCloudCertificate::build(*_secureElement, cert, static_cast(SElementArduinoCloudSlot::Key))) { + DEBUG_ERROR("CSRH::%s Error building secureElement compressed cert!", __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + if (!SElementArduinoCloudCertificate::write(*_secureElement, cert, SElementArduinoCloudSlot::CompressedCertificate)) { + DEBUG_ERROR("CSRH::%s Error storing cert!" , __FUNCTION__); + _ledFeedback.setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + return CSRHandlerStates::ERROR; + } + + DEBUG_INFO("CSRH Certificate created!"); + _nextRequestAt = 0; + _requestAttempt = 0; + return CSRHandlerStates::CERT_CREATED; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleCertCreated() { + CSRHandlerStates nextState = _state; + + if(millis() < _nextRequestAt) { + return nextState; + } + + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + return nextState; + } + + String PostData = "{\"wifi_fw_version\":\""; + PostData += _fw_version; + PostData += "\"}"; + if(postRequest("/provisioning/v1/onboarding/provision/complete", PostData)){ + nextState = CSRHandlerStates::WAITING_COMPLETE_RES; + } else { + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Error sending complete request, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + } + + return nextState; +} + +CSRHandlerClass::CSRHandlerStates CSRHandlerClass::handleWaitingCompleteRes() { + CSRHandlerStates nextState = _state; + NetworkConnectionState connectionRes = _connectionHandler->check(); + if (connectionRes != NetworkConnectionState::CONNECTED) { + nextNetworkRetry(); + _client->stop(); + return CSRHandlerStates::CERT_CREATED; + } + + if (millis() - _startWaitingResponse > RESPONSE_TIMEOUT) { + _client->stop(); + updateNextRequestAt(); + DEBUG_WARNING("CSRH::%s Complete request timeout, retrying in %d ms", __FUNCTION__, _nextRequestAt - millis()); + nextState = CSRHandlerStates::CERT_CREATED; + } + + int statusCode = _client->responseStatusCode(); + if(statusCode == 200){ + if (_certForCSR) { + delete _certForCSR; + _certForCSR = nullptr; + } + DEBUG_INFO("CSRH Provisioning completed!"); + nextState = CSRHandlerStates::COMPLETED; + } else if (statusCode == 429 || statusCode == 503) { + updateNextRequestAt(); + nextState = CSRHandlerStates::CERT_CREATED; + } else { + DEBUG_WARNING("CSRH::%s Complete request error code %d, retrying in %d ms", __FUNCTION__, statusCode ,_nextRequestAt - millis()); + _requestAttempt = 0; + _nextRequestAt = 0; + nextState = CSRHandlerStates::REQUEST_SIGNATURE; + } + _client->stop(); + + return nextState; +} + +void CSRHandlerClass::nextNetworkRetry() { + _nextRequestAt = millis() + CONNECTION_RETRY_TIMEOUT_MS; +} + +void CSRHandlerClass::handleError() { + _ledFeedback.update(); +} diff --git a/examples/utility/Provisioning_2.0/CSRHandler.h b/examples/utility/Provisioning_2.0/CSRHandler.h new file mode 100644 index 000000000..1165ac05b --- /dev/null +++ b/examples/utility/Provisioning_2.0/CSRHandler.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once +#include +#include +#include +#include +#include +#include "Utility/LEDFeedback/LEDFeedback.h" +#define JITTER_BASE 0 +#define JITTER_MAX 1000 + +class CSRHandlerClass { +public: + CSRHandlerClass(); + ~CSRHandlerClass(); + enum class CSRHandlerStates { + BUILD_CSR, + REQUEST_SIGNATURE, + WAITING_RESPONSE, + PARSE_RESPONSE, + BUILD_CERTIFICATE, + CERT_CREATED, + WAITING_COMPLETE_RES, + COMPLETED, + ERROR, + END + }; + bool begin(ConnectionHandler &connectionHandler, SecureElement &secureElement, String &uhwid); + void end(); + CSRHandlerStates poll(); +private: + CSRHandlerStates _state; + unsigned long _nextRequestAt; + uint32_t _requestAttempt; + uint32_t _startWaitingResponse; + String *_uhwid; + String _fw_version; + + int _issueYear; + uint8_t _issueMonth; + uint8_t _issueDay; + uint8_t _issueHour; + byte _serialNumber[16]; + byte _authorityKeyIdentifier[20]; + byte _signature[64]; + String _deviceId; + + ECP256Certificate *_certForCSR; + ConnectionHandler *_connectionHandler; + SecureElement *_secureElement; + TLSClientMqtt *_tlsClient; + HttpClient *_client; + LEDFeedbackClass &_ledFeedback; + void updateNextRequestAt(); + void nextNetworkRetry(); + uint32_t jitter(uint32_t base = JITTER_BASE, uint32_t max = JITTER_MAX); + bool postRequest(const char *url, String &postData); + uint32_t getTimestamp(); + CSRHandlerStates handleBuildCSR(); + CSRHandlerStates handleRequestSignature(); + CSRHandlerStates handleWaitingResponse(); + CSRHandlerStates handleParseResponse(); + CSRHandlerStates handleBuildCertificate(); + CSRHandlerStates handleCertCreated(); + CSRHandlerStates handleWaitingCompleteRes(); + void handleError(); +}; diff --git a/examples/utility/Provisioning_2.0/ClaimingHandler.cpp b/examples/utility/Provisioning_2.0/ClaimingHandler.cpp new file mode 100644 index 000000000..7456f410b --- /dev/null +++ b/examples/utility/Provisioning_2.0/ClaimingHandler.cpp @@ -0,0 +1,192 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "ClaimingHandler.h" +#include +#include "Arduino_DebugUtils.h" +#include +#include "utility/HCI.h" +#include + +extern const char *SKETCH_VERSION; + +ClaimingHandlerClass::ClaimingHandlerClass(): + _uhwid {nullptr}, + _state {ClaimingHandlerStates::END}, + _secureElement {nullptr}, + _clearStoredCredentials {nullptr}, + _agentManager { AgentsManagerClass::getInstance()}, + _ledFeedback {LEDFeedbackClass::getInstance()} { + _receivedEvent = ClaimingReqEvents::NONE; + _ts = 0; +} + +bool ClaimingHandlerClass::begin(SecureElement &secureElement, String &uhwid, ClearStoredCredentialsHandler clearStoredCredentials) { + if(_state != ClaimingHandlerStates::END) { + return true; + } + + if(uhwid == "" || clearStoredCredentials == nullptr) { + return false; + } + + if (!_agentManager.addRequestHandler(RequestType::GET_ID, getIdRequestCb)) { + return false; + } + + if (!_agentManager.addRequestHandler(RequestType::RESET, resetStoredCredRequestCb)) { + return false; + } + + if(!_agentManager.addRequestHandler(RequestType::GET_BLE_MAC_ADDRESS, getBLEMacAddressRequestCb)) { + return false; + } + + if(!_agentManager.addRequestHandler(RequestType::GET_PROVISIONING_SKETCH_VERSION, getProvSketchVersionRequestCb)) { + return false; + } + + if (!_agentManager.addReturnTimestampCallback(setTimestamp)) { + return false; + } + + _agentManager.begin(); + _uhwid = &uhwid; + _secureElement = &secureElement; + _clearStoredCredentials = clearStoredCredentials; + _state = ClaimingHandlerStates::INIT; +} + +void ClaimingHandlerClass::end() { + if(_state == ClaimingHandlerStates::END) { + return; + } + + _agentManager.removeReturnTimestampCallback(); + _agentManager.removeRequestHandler(RequestType::GET_ID); + _agentManager.removeRequestHandler(RequestType::RESET); + _agentManager.end(); + _state = ClaimingHandlerStates::END; +} + +void ClaimingHandlerClass::poll() { + if(_state == ClaimingHandlerStates::END) { + return; + } + _ledFeedback.update(); + _agentManager.update(); + + switch (_receivedEvent) { + case ClaimingReqEvents::GET_ID: getIdReqHandler (); break; + case ClaimingReqEvents::RESET: resetStoredCredReqHandler (); break; + case ClaimingReqEvents::GET_BLE_MAC_ADDRESS: getBLEMacAddressReqHandler (); break; + case ClaimingReqEvents::GET_PROV_SKETCH_VERSION: getProvSketchVersionReqHandler(); break; + } + _receivedEvent = ClaimingReqEvents::NONE; + return; +} + +void ClaimingHandlerClass::getIdReqHandler() { + if (_ts != 0) { + byte _uhwidBytes[32]; + hex::decode(_uhwid->c_str(), _uhwidBytes, _uhwid->length()); + //Send UHWID + ProvisioningOutputMessage idMsg = {MessageOutputType::UHWID}; + idMsg.m.uhwid = _uhwidBytes; + _agentManager.sendMsg(idMsg); + + String token = getAIoTCloudJWT(*_secureElement, *_uhwid, _ts, 1); + if (token == "") { + DEBUG_ERROR("CH::%s Error: token not created", __FUNCTION__); + sendStatus(StatusMessage::ERROR); + return; + } + + //Send JWT + ProvisioningOutputMessage jwtMsg = {MessageOutputType::JWT}; + jwtMsg.m.jwt = token.c_str(); + _agentManager.sendMsg(jwtMsg); + _ts = 0; + } else { + DEBUG_ERROR("CH::%s Error: timestamp not provided" , __FUNCTION__); + sendStatus(StatusMessage::PARAMS_NOT_FOUND); + } +} + +void ClaimingHandlerClass::resetStoredCredReqHandler() { + if( !_clearStoredCredentials()){ + DEBUG_ERROR("CH::%s Error: reset stored credentials failed", __FUNCTION__); + sendStatus(StatusMessage::ERROR); + } else { + sendStatus(StatusMessage::RESET_COMPLETED); + } + +} + +void ClaimingHandlerClass::getBLEMacAddressReqHandler() { + uint8_t mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + bool activated = false; + ConfiguratorAgent * connectedAgent = _agentManager.getConnectedAgent(); + if(!_agentManager.isAgentEnabled(ConfiguratorAgent::AgentTypes::BLE) || (connectedAgent != nullptr && + connectedAgent->getAgentType() != ConfiguratorAgent::AgentTypes::BLE)) { + activated = true; + BLE.begin(); + } + + HCI.readBdAddr(mac); + + for(int i = 0; i < 3; i++){ + uint8_t byte = mac[i]; + mac[i] = mac[5-i]; + mac[5-i] = byte; + } + if (activated) { + BLE.end(); + } + + ProvisioningOutputMessage outputMsg; + outputMsg.type = MessageOutputType::BLE_MAC_ADDRESS; + outputMsg.m.BLEMacAddress = mac; + _agentManager.sendMsg(outputMsg); +} + +void ClaimingHandlerClass::getProvSketchVersionReqHandler() { + ProvisioningOutputMessage outputMsg; + outputMsg.type = MessageOutputType::PROV_SKETCH_VERSION; + outputMsg.m.provSketchVersion = SKETCH_VERSION; + _agentManager.sendMsg(outputMsg); +} + +void ClaimingHandlerClass::getIdRequestCb() { + DEBUG_VERBOSE("CH Get ID request received"); + _receivedEvent = ClaimingReqEvents::GET_ID; +} +void ClaimingHandlerClass::setTimestamp(uint64_t ts) { + _ts = ts; +} + +void ClaimingHandlerClass::resetStoredCredRequestCb() { + DEBUG_VERBOSE("CH Reset stored credentials request received"); + _receivedEvent = ClaimingReqEvents::RESET; +} + +void ClaimingHandlerClass::getBLEMacAddressRequestCb() { + DEBUG_VERBOSE("CH Get BLE MAC address request received"); + _receivedEvent = ClaimingReqEvents::GET_BLE_MAC_ADDRESS; +} + +void ClaimingHandlerClass::getProvSketchVersionRequestCb() { + DEBUG_VERBOSE("CH Get provisioning sketch version request received"); + _receivedEvent = ClaimingReqEvents::GET_PROV_SKETCH_VERSION; +} + +bool ClaimingHandlerClass::sendStatus(StatusMessage msg) { + ProvisioningOutputMessage statusMsg = { MessageOutputType::STATUS, { msg } }; + return _agentManager.sendMsg(statusMsg); +} diff --git a/examples/utility/Provisioning_2.0/ClaimingHandler.h b/examples/utility/Provisioning_2.0/ClaimingHandler.h new file mode 100644 index 000000000..fc6a2605e --- /dev/null +++ b/examples/utility/Provisioning_2.0/ClaimingHandler.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once +#include "Arduino.h" +#include "ConfiguratorAgents/AgentsManager.h" +#include +#include "Utility/LEDFeedback/LEDFeedback.h" + +typedef bool (*ClearStoredCredentialsHandler)(); +class ClaimingHandlerClass { +public: + ClaimingHandlerClass(); + bool begin(SecureElement &secureElement, String &uhwid, ClearStoredCredentialsHandler clearStoredCredentials); + void end(); + void poll(); +private: + String *_uhwid; + enum class ClaimingHandlerStates { + INIT, + END + }; + enum class ClaimingReqEvents { NONE, + GET_ID, + RESET, + GET_BLE_MAC_ADDRESS, + GET_PROV_SKETCH_VERSION}; + static inline ClaimingReqEvents _receivedEvent; + ClaimingHandlerStates _state; + AgentsManagerClass &_agentManager; + LEDFeedbackClass &_ledFeedback; + static inline uint64_t _ts; + SecureElement *_secureElement; + + bool sendStatus(StatusMessage msg); + /* Commands handlers */ + void getIdReqHandler(); + void resetStoredCredReqHandler(); + void getBLEMacAddressReqHandler(); + void getProvSketchVersionReqHandler(); + ClearStoredCredentialsHandler _clearStoredCredentials; + /* Callbacks for receiving commands */ + static void getIdRequestCb(); + static void setTimestamp(uint64_t ts); + static void resetStoredCredRequestCb(); + static void getBLEMacAddressRequestCb(); + static void getProvSketchVersionRequestCb(); +}; diff --git a/examples/utility/Provisioning_2.0/Provisioning_2.0.ino b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino new file mode 100644 index 000000000..8733f909e --- /dev/null +++ b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino @@ -0,0 +1,228 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +#include "thingProperties.h" +#include "CSRHandler.h" +#include "ClaimingHandler.h" +#include "SecretsHelper.h" +#include +#include +#include +#include +#include "Utility/LEDFeedback/LEDFeedback.h" + +const char *SKETCH_VERSION = "0.1.0"; + +enum class DeviceState { + HARDWARE_CHECK, + BEGIN, + NETWORK_CONFIG, + CSR, + BEGIN_CLOUD, + RUN, + ERROR + }; + +DeviceState _state = DeviceState::HARDWARE_CHECK; +SecureElement secureElement; + +String uhwid = ""; +bool resetEvent = false; + +CSRHandlerClass CSRHandler; +ClaimingHandlerClass ClaimingHandler; + +bool clearStoredCredentials() { + const uint8_t empty[4] = {0x00,0x00,0x00,0x00}; + if(!NetworkConfigurator.resetStoredConfiguration() || \ + !secureElement.writeSlot(static_cast(SElementArduinoCloudSlot::DeviceId), (byte*)empty, sizeof(empty)) || \ + !secureElement.writeSlot(static_cast(SElementArduinoCloudSlot::CompressedCertificate), (byte*)empty, sizeof(empty))) { + return false; + } + + ArduinoCloud.disconnect(); + resetEvent = true; + return true; + } + +void setup() { + Serial.begin(9600); + + delay(1500); + + setDebugMessageLevel(4); + + initProperties(); + AgentsManagerClass::getInstance().begin(); + LEDFeedbackClass::getInstance().begin(); + DEBUG_INFO("Starting Provisioning"); +} + +void sendStatus(StatusMessage msg) { + ProvisioningOutputMessage outMsg = { MessageOutputType::STATUS, { msg } }; + AgentsManagerClass::getInstance().sendMsg(outMsg); +} + +DeviceState handleHardwareCheck() { + // Init the secure element + if(!secureElement.begin()) { + DEBUG_ERROR("Sketch: Error during secureElement begin!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_BEGIN); + return DeviceState::ERROR; + } + + if (!secureElement.locked()) { + if (!secureElement.writeConfiguration()) { + DEBUG_ERROR("Sketch: Writing secureElement configuration failed!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_CONFIG); + return DeviceState::ERROR; + } + + if (!secureElement.lock()) { + DEBUG_ERROR("Sketch: Locking secureElement configuration failed!"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::HW_ERROR_SE_LOCK); + return DeviceState::ERROR; + } + DEBUG_INFO("secureElement locked successfully"); + } + + FlashFormatter flashFormatter; + // Check if the board storage is properly formatted + if(!flashFormatter.checkAndFormatPartition()) { + DEBUG_ERROR("Sketch: Error partitioning storage"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::FAIL_TO_PARTITION_STORAGE); + return DeviceState::ERROR; + } + + return DeviceState::BEGIN; +} + +DeviceState handleBegin() { + uhwid = GetUHWID(); + if(uhwid == ""){ + DEBUG_ERROR("Sketch: Error getting UHWID"); + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::ERROR); + sendStatus(StatusMessage::ERROR_GENERATING_UHWID); + return DeviceState::ERROR; + } + // Scan the network options + NetworkConfigurator.scanNetworkOptions(); + NetworkConfigurator.begin(); + ClaimingHandler.begin(secureElement, uhwid, clearStoredCredentials); + DEBUG_INFO("BLE Available"); + return DeviceState::NETWORK_CONFIG; +} + +DeviceState handleNetworkConfig() { + ClaimingHandler.poll(); + if(resetEvent){ + resetEvent = false; + } + NetworkConfiguratorStates s = NetworkConfigurator.update(); + + DeviceState nextState = _state; + if (s == NetworkConfiguratorStates::CONFIGURED) { + String deviceId = ""; + SElementArduinoCloudDeviceId::read(secureElement, deviceId, SElementArduinoCloudSlot::DeviceId); + + if (deviceId == "") { + CSRHandler.begin(ArduinoIoTPreferredConnection, secureElement, uhwid); + nextState = DeviceState::CSR; + } else { + nextState = DeviceState::BEGIN_CLOUD; + } + } + return nextState; +} + +DeviceState handleCSR() { + NetworkConfigurator.update(); + ClaimingHandler.poll(); + if(resetEvent) { + resetEvent = false; + CSRHandler.end(); + return DeviceState::NETWORK_CONFIG; + } + + DeviceState nextState = _state; + + CSRHandlerClass::CSRHandlerStates res = CSRHandler.poll(); + if (res == CSRHandlerClass::CSRHandlerStates::COMPLETED) { + CSRHandler.end(); + nextState = DeviceState::BEGIN_CLOUD; + } + + return nextState; +} + +DeviceState handleBeginCloud() { + // Close the connection to the peer (App mobile, FE, etc) + NetworkConfigurator.disconnectAgent(); + // Close the BLE connectivity + if (NetworkConfigurator.isAgentEnabled(ConfiguratorAgent::AgentTypes::BLE)) { + NetworkConfigurator.enableAgent(ConfiguratorAgent::AgentTypes::BLE, false); + } + // Connect to Arduino IoT Cloud +#ifdef COMPILE_TEST + ArduinoCloud.begin(ArduinoIoTPreferredConnection, false, "mqtts-sa.iot.oniudra.cc"); +#else + ArduinoCloud.begin(ArduinoIoTPreferredConnection); +#endif + ArduinoCloud.printDebugInfo(); + + return DeviceState::RUN; +} + +void cloudConnectedHandler(bool connected) { + static bool _status = false; + if(connected != _status){ + _status = connected; + if(connected){ + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::CONNECTED_TO_CLOUD); + } else { + LEDFeedbackClass::getInstance().setMode(LEDFeedbackClass::LEDFeedbackMode::NONE); + } + } +} + +DeviceState handleRun() { + ClaimingHandler.poll(); + if(resetEvent) { + resetEvent = false; + return DeviceState::NETWORK_CONFIG; + } + + DeviceState nextState = _state; + ArduinoCloud.update(); + + cloudConnectedHandler(ArduinoCloud.connected()); + + return nextState; +} + +DeviceState handleError() { + LEDFeedbackClass::getInstance().update(); + AgentsManagerClass::getInstance().update(); + return DeviceState::ERROR; +} + +void loop() { + switch (_state) { + case DeviceState::HARDWARE_CHECK: _state = handleHardwareCheck(); break; + case DeviceState::BEGIN: _state = handleBegin (); break; + case DeviceState::NETWORK_CONFIG : _state = handleNetworkConfig(); break; + case DeviceState::CSR: _state = handleCSR (); break; + case DeviceState::BEGIN_CLOUD: _state = handleBeginCloud (); break; + case DeviceState::RUN: _state = handleRun (); break; + case DeviceState::ERROR: _state = handleError (); break; + default: break; + } +} diff --git a/examples/utility/Provisioning_2.0/SecretsHelper.h b/examples/utility/Provisioning_2.0/SecretsHelper.h new file mode 100644 index 000000000..bdb6354d1 --- /dev/null +++ b/examples/utility/Provisioning_2.0/SecretsHelper.h @@ -0,0 +1,20 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include + +inline String GetUHWID() { + UniqueHWId Id; + if (Id.begin()) { + return Id.get(); + } + return ""; +} diff --git a/examples/utility/Provisioning_2.0/thingProperties.h b/examples/utility/Provisioning_2.0/thingProperties.h new file mode 100644 index 000000000..0bdc6c85f --- /dev/null +++ b/examples/utility/Provisioning_2.0/thingProperties.h @@ -0,0 +1,33 @@ +/* + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +#if defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKRNB1500) || defined(ARDUINO_SAMD_MKRWAN1300) || defined(ARDUINO_SAMD_MKRWAN1310) +#error "Board not supported for Provisioning 2.0" +#endif + +#include +#include +#include +#include "NetworkConfigurator.h" +#include "ConfiguratorAgents/agents/BLE/BLEAgent.h" +#include "ConfiguratorAgents/agents/Serial/SerialAgent.h" + +GenericConnectionHandler ArduinoIoTPreferredConnection; +KVStore kvStore; +BLEAgentClass BLEAgent; +SerialAgentClass SerialAgent; +NetworkConfiguratorClass NetworkConfigurator(ArduinoIoTPreferredConnection); + +void initProperties() { + + NetworkConfigurator.addAgent(BLEAgent); + NetworkConfigurator.addAgent(SerialAgent); + NetworkConfigurator.setStorage(kvStore); + ArduinoCloud.setConfigurator(NetworkConfigurator); +} + + diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 3a464286d..8cb6daba1 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -190,7 +190,7 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, #if NETWORK_CONFIGURATOR_ENABLED if(_configurator != nullptr){ - _configurator->enableBLE(false); + _configurator->enableAgent(ConfiguratorAgent::AgentTypes::BLE,false); _configurator->begin(); } #endif From 8404c21b5ac8fc647d5bfa8408c2dbd304e60367 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 20 May 2025 13:29:21 +0200 Subject: [PATCH 06/12] Added cloud utils cbor/utils directory in cmake include path --- extras/test/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 5435dfcf1..dfe322324 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -45,6 +45,11 @@ target_include_directories( ${cloudutils_SOURCE_DIR}/src/cbor ) +target_include_directories( + cloudutils INTERFACE + ${cloudutils_SOURCE_DIR}/src/cbor/utils +) + target_include_directories( cloudutils INTERFACE ${cloudutils_SOURCE_DIR}/src/interfaces From 5f09d2ba4400093faab4a830481a097a672127c2 Mon Sep 17 00:00:00 2001 From: fabik111 Date: Wed, 2 Apr 2025 17:53:51 +0200 Subject: [PATCH 07/12] Update NetworkConfigurator includes examples: NetConfig, use h-char-sequence examples: NetConfig, explicitly include NetworkConfigurator --- examples/ArduinoIoTCloud-NetConfig/thingProperties.h | 6 ++++-- examples/utility/Provisioning_2.0/CSRHandler.h | 2 +- examples/utility/Provisioning_2.0/ClaimingHandler.h | 4 ++-- examples/utility/Provisioning_2.0/Provisioning_2.0.ino | 2 +- examples/utility/Provisioning_2.0/thingProperties.h | 5 ++--- src/ArduinoIoTCloud.h | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/ArduinoIoTCloud-NetConfig/thingProperties.h b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h index 833927042..4eba7e49c 100644 --- a/examples/ArduinoIoTCloud-NetConfig/thingProperties.h +++ b/examples/ArduinoIoTCloud-NetConfig/thingProperties.h @@ -5,8 +5,10 @@ #endif #include #include -#include "ConfiguratorAgents/agents/BLE/BLEAgent.h" -#include "ConfiguratorAgents/agents/Serial/SerialAgent.h" +#include +#include +#include +#include void onLedChange(); diff --git a/examples/utility/Provisioning_2.0/CSRHandler.h b/examples/utility/Provisioning_2.0/CSRHandler.h index 1165ac05b..ae5956b7e 100644 --- a/examples/utility/Provisioning_2.0/CSRHandler.h +++ b/examples/utility/Provisioning_2.0/CSRHandler.h @@ -12,7 +12,7 @@ #include #include #include -#include "Utility/LEDFeedback/LEDFeedback.h" +#include "utility/LEDFeedback.h" #define JITTER_BASE 0 #define JITTER_MAX 1000 diff --git a/examples/utility/Provisioning_2.0/ClaimingHandler.h b/examples/utility/Provisioning_2.0/ClaimingHandler.h index fc6a2605e..77f2ebea6 100644 --- a/examples/utility/Provisioning_2.0/ClaimingHandler.h +++ b/examples/utility/Provisioning_2.0/ClaimingHandler.h @@ -8,9 +8,9 @@ #pragma once #include "Arduino.h" -#include "ConfiguratorAgents/AgentsManager.h" +#include "configuratorAgents/AgentsManager.h" #include -#include "Utility/LEDFeedback/LEDFeedback.h" +#include "utility/LEDFeedback.h" typedef bool (*ClearStoredCredentialsHandler)(); class ClaimingHandlerClass { diff --git a/examples/utility/Provisioning_2.0/Provisioning_2.0.ino b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino index 8733f909e..3ab02a278 100644 --- a/examples/utility/Provisioning_2.0/Provisioning_2.0.ino +++ b/examples/utility/Provisioning_2.0/Provisioning_2.0.ino @@ -13,7 +13,7 @@ #include #include #include -#include "Utility/LEDFeedback/LEDFeedback.h" +#include "utility/LEDFeedback.h" const char *SKETCH_VERSION = "0.1.0"; diff --git a/examples/utility/Provisioning_2.0/thingProperties.h b/examples/utility/Provisioning_2.0/thingProperties.h index 0bdc6c85f..7f51fa6ca 100644 --- a/examples/utility/Provisioning_2.0/thingProperties.h +++ b/examples/utility/Provisioning_2.0/thingProperties.h @@ -12,9 +12,8 @@ #include #include #include -#include "NetworkConfigurator.h" -#include "ConfiguratorAgents/agents/BLE/BLEAgent.h" -#include "ConfiguratorAgents/agents/Serial/SerialAgent.h" +#include "configuratorAgents/agents/BLEAgent.h" +#include "configuratorAgents/agents/SerialAgent.h" GenericConnectionHandler ArduinoIoTPreferredConnection; KVStore kvStore; diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 6d5d8dd45..4c12a6f7b 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -26,7 +26,7 @@ #include #if NETWORK_CONFIGURATOR_ENABLED -#include +#include #endif #if defined(DEBUG_ERROR) || defined(DEBUG_WARNING) || defined(DEBUG_INFO) || defined(DEBUG_DEBUG) || defined(DEBUG_VERBOSE) From 5404b8287301cf4de2c74a9ccc220d2ab2f84d8a Mon Sep 17 00:00:00 2001 From: pennam Date: Fri, 16 May 2025 08:44:47 +0200 Subject: [PATCH 08/12] style fixes --- src/ArduinoIoTCloud.h | 4 ++-- src/ArduinoIoTCloudTCP.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 4c12a6f7b..19e94555d 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -26,11 +26,11 @@ #include #if NETWORK_CONFIGURATOR_ENABLED -#include + #include #endif #if defined(DEBUG_ERROR) || defined(DEBUG_WARNING) || defined(DEBUG_INFO) || defined(DEBUG_DEBUG) || defined(DEBUG_VERBOSE) -# include + #include #endif #include "AIoTC_Const.h" diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 8cb6daba1..75c0ba900 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -182,7 +182,7 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, } #endif /* BOARD_HAS_OFFLOADED_ECCX08 */ -#if defined(ARDUINO_UNOWIFIR4) +#if defined (ARDUINO_UNOWIFIR4) if (String(WiFi.firmwareVersion()) < String("0.2.0")) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to connect to Arduino IoT Cloud, WiFi firmware needs to be >= 0.2.0, current %s", __FUNCTION__, WiFi.firmwareVersion()); } @@ -306,7 +306,6 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConfigPhy() #else return State::Init; #endif - } ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Init() From 2b11b7eb10006440f8ca09a23e9e2797de877e2d Mon Sep 17 00:00:00 2001 From: pennam Date: Fri, 16 May 2025 15:07:16 +0200 Subject: [PATCH 09/12] TCP: add NetworkConfigurator version to printDebugInfo --- src/ArduinoIoTCloudTCP.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 75c0ba900..f39e6ce9b 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -275,6 +275,9 @@ void ArduinoIoTCloudTCP::printDebugInfo() DEBUG_INFO("***** Arduino IoT Cloud - %s *****", AIOT_CONFIG_LIB_VERSION); DEBUG_INFO("Device ID: %s", getDeviceId().c_str()); DEBUG_INFO("MQTT Broker: %s:%d", _brokerAddress.c_str(), _brokerPort); +#if NETWORK_CONFIGURATOR_ENABLED + DEBUG_INFO("Network Configurator: %s", ANetworkConfigurator_LIB_VERSION); +#endif } void ArduinoIoTCloudTCP::disconnect() { From 463b522e6d50db8298a3dd674c0c006aa9abd985 Mon Sep 17 00:00:00 2001 From: pennam Date: Wed, 21 May 2025 09:02:05 +0200 Subject: [PATCH 10/12] Remove unused .gitmodule file --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb..000000000 From a47facc3cb9619cfc2a876e5acc09c69cca9e28e Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Wed, 21 May 2025 08:46:33 +0200 Subject: [PATCH 11/12] added unit test for OtaUpdateCmdDown message decode without field url --- extras/test/src/test_command_decode.cpp | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/extras/test/src/test_command_decode.cpp b/extras/test/src/test_command_decode.cpp index 8d75e6c90..1d47c99ee 100644 --- a/extras/test/src/test_command_decode.cpp +++ b/extras/test/src/test_command_decode.cpp @@ -667,6 +667,32 @@ SCENARIO("Test the decoding of command messages") { } } +/****************************************************************************/ + + WHEN("Decode the OtaUpdateCmdDown message without url field") + { + CommandDown command; + + /* + DA 00010100 # tag(65792) + 80 # array(1) + 50 # bytes(16) + C73CB045F9C2434585AFFA36A307BFE7"\xC7<\xB0E\xF9\xC2CE\x85\xAF\xFA6\xA3\a\xBF\xE7" + + */ + uint8_t const payload[] = {0xda, 0x00, 0x01, 0x01, 0x00, 0x81, 0x50, 0xc7, + 0x3c, 0xb0, 0x45, 0xf9, 0xc2, 0x43, 0x45, 0x85, + 0xaf, 0xfa, 0x36, 0xa3, 0x07, 0xbf, 0xe7}; + + size_t payload_length = sizeof(payload) / sizeof(uint8_t); + CBORMessageDecoder decoder; + MessageDecoder::Status err = decoder.decode((Message*)&command, payload, payload_length); + + THEN("The decode is unsuccessful") { + REQUIRE(err == MessageDecoder::Status::Error); + } + } + /****************************************************************************/ WHEN("Decode the OtaBeginUp message") From 52a24d6d902ad88fb7fdb8d9ea5f534e88a2b05c Mon Sep 17 00:00:00 2001 From: pennam Date: Thu, 22 May 2025 08:39:19 +0200 Subject: [PATCH 12/12] Release v2.6.0 --- library.properties | 2 +- src/AIoTC_Config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.properties b/library.properties index b2dbaea2a..fa2bb363d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ArduinoIoTCloud -version=2.5.1 +version=2.6.0 author=Arduino maintainer=Arduino sentence=This library allows connecting to the Arduino IoT Cloud service. diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index c02d20b78..b02d3d7b9 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -185,6 +185,6 @@ #define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) #endif -#define AIOT_CONFIG_LIB_VERSION "2.5.1" +#define AIOT_CONFIG_LIB_VERSION "2.6.0" #endif /* ARDUINO_AIOTC_CONFIG_H_ */