diff --git a/.github/workflows/arduino-compile.yml b/.github/workflows/arduino-compile.yml index 365a3cda..1414a2af 100644 --- a/.github/workflows/arduino-compile.yml +++ b/.github/workflows/arduino-compile.yml @@ -30,10 +30,12 @@ jobs: - source-path: ./ - name: TBPubSubClient - name: ArduinoHttpClient - - { name: ArduinoJson, version: 6.21.4 } + - { name: ArduinoJson, version: 6.21.5 } - name: StreamUtils - name: WiFiEsp - name: TinyGSM + - name: Seeed_Arduino_mbedtls + - name: arduino-timer strategy: matrix: diff --git a/.github/workflows/esp32-compile.yml b/.github/workflows/esp32-compile.yml index eb2c6aa3..8a9d4b28 100644 --- a/.github/workflows/esp32-compile.yml +++ b/.github/workflows/esp32-compile.yml @@ -30,8 +30,7 @@ jobs: - source-path: ./ - name: TBPubSubClient - name: ArduinoHttpClient - - { name: ArduinoJson, version: 6.21.4 } - - name: StreamUtils + - { name: ArduinoJson, version: 6.21.5 } strategy: matrix: diff --git a/.github/workflows/esp8266-compile.yml b/.github/workflows/esp8266-compile.yml index 36b9b39c..bf1705da 100644 --- a/.github/workflows/esp8266-compile.yml +++ b/.github/workflows/esp8266-compile.yml @@ -28,11 +28,11 @@ jobs: LIBRARIES: | # Install the additionally needed dependency from the respository - source-path: ./ + - name: arduino-timer - name: TBPubSubClient - name: ArduinoHttpClient - - { name: ArduinoJson, version: 6.21.4 } + - { name: ArduinoJson, version: 6.21.5 } - name: Seeed_Arduino_mbedtls - - name: StreamUtils strategy: matrix: diff --git a/.github/workflows/espidf-compile-v4.4.yml b/.github/workflows/espidf-compile-v4.4.yml index 3cafedd0..a3f658fa 100644 --- a/.github/workflows/espidf-compile-v4.4.yml +++ b/.github/workflows/espidf-compile-v4.4.yml @@ -36,3 +36,21 @@ jobs: esp_idf_version: v4.4 target: esp32 path: 'examples/0015-espressif_esp32_process_OTA_MQTT' + - name: Build process RPC example on v4.4 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v4.4 + target: esp32 + path: 'examples/0016-espressif_esp32_rpc' + - name: Build process Shared Attribute example on v4.4 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v4.4 + target: esp32 + path: 'examples/0017-espressif_esp32_process_shared_attribute_update' + - name: Build process Provision example on v4.4 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v4.4 + target: esp32 + path: 'examples/0018-espressif_esp32_provision_device' diff --git a/.github/workflows/espidf-compile-v5.1.yml b/.github/workflows/espidf-compile-v5.1.yml index 86c6b2dc..f64612df 100644 --- a/.github/workflows/espidf-compile-v5.1.yml +++ b/.github/workflows/espidf-compile-v5.1.yml @@ -36,3 +36,21 @@ jobs: esp_idf_version: v5.1 target: esp32 path: 'examples/0015-espressif_esp32_process_OTA_MQTT' + - name: Build process RPC example on v5.1 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.1 + target: esp32 + path: 'examples/0016-espressif_esp32_rpc' + - name: Build process Shared Attribute example on v5.1 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.1 + target: esp32 + path: 'examples/0017-espressif_esp32_process_shared_attribute_update' + - name: Build process Provision example on v5.1 + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: v5.1 + target: esp32 + path: 'examples/0018-espressif_esp32_provision_device' diff --git a/CMakeLists.txt b/CMakeLists.txt index bfc3c2db..4e36be0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.12) set(srcs src/Arduino_HTTP_Client.cpp src/Arduino_MQTT_Client.cpp - src/Callback_Watchdog.cpp src/Arduino_ESP32_Updater.cpp src/Arduino_ESP8266_Updater.cpp src/Espressif_Updater.cpp @@ -41,4 +40,4 @@ if(ESP_PLATFORM) return() endif() -project(ThingsBoardClientSDK VERSION 0.13.0) +project(ThingsBoardClientSDK VERSION 0.14.0) diff --git a/Kconfig b/Kconfig index 33981165..1176ccc1 100644 --- a/Kconfig +++ b/Kconfig @@ -4,7 +4,7 @@ menu "ThingsBoard Client SDK Configurations" bool "Replace static with dynami library usage" default n help - If this is enabled the library will use DynamicJsonDocument to receive messages from the MQTT broker and use Vector to hold Subscription data instead of Arrays. Removing the need to declare MaxFieldsAmount, MaxSubscribtions, MaxAttributes and MaxRPC template arguments. But instead increasing heap allocation and usage drastically. + If this is enabled the library will use DynamicJsonDocument to receive messages from the MQTT broker and use Vector to hold Subscription data instead of Arrays. Removing the need to declare MaxResponse, MaxSubscriptions, MaxAttributes, MaxRPC and MaxRequestRPC template arguments. But instead increasing heap allocation and usage drastically. config THINGSBOARD_ENABLE_DEBUG bool "Enable additional debug log messages" diff --git a/README.md b/README.md index bfe4ffdd..7d4da430 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,10 @@ Following dependencies are installed automatically or must be installed, too: **Needs to be installed manually:** - [MQTT PubSub Client](https://github.com/thingsboard/pubsubclient) — for interacting with `MQTT`, when using the `Arduino_MQTT_Client` instance as an argument to `ThingsBoard`. - [Arduino Http Client](https://github.com/arduino-libraries/ArduinoHttpClient) — for interacting with `HTTP/S` when using the `Arduino_HTTP_Client` instance as an argument to `ThingsBoardHttp`. - - [MbedTLS Library](https://github.com/Seeed-Studio/Seeed_Arduino_mbedtls) — needed to create hashes for the OTA update (`ESP8266` only, already included in `ESP32` base firmware). + - [MbedTLS Library](https://github.com/Seeed-Studio/Seeed_Arduino_mbedtls) — needed to create hashes for the OTA update for non `Espressif` boards. + - [Arduino Timer](https://github.com/contrem/arduino-timer) - needed to create non-blocking callback timers for non `Espressif` boards. - [WiFiEsp Client](https://github.com/bportaluri/WiFiEsp) — needed when using a `Arduino Uno` with a `ESP8266`. - - [StreamUtils](https://github.com/bblanchon/StreamUtils) — needed when sending arbitrary amount of payload even if the buffer size is too small to hold that complete payload is wanted, aforementioned feature is automatically enabled if the library is installed + - [StreamUtils](https://github.com/bblanchon/StreamUtils) — needed when sending arbitrary amount of payload even if the buffer size is too small to hold that complete payload is wanted, aforementioned feature is automatically enabled if the library is installed. ## Supported ThingsBoard Features @@ -90,17 +91,17 @@ Example implementations for all base features, mentioned above, can be found in ### Over `MQTT`: -All possible features are implemented over `MQTT`: +All possible features are implemented over `MQTT` over a specific `IAPI_Implementation` instance: - - [Telemetry data upload](https://thingsboard.io/docs/reference/mqtt-api/#telemetry-upload-api) - - [Device attribute publish](https://thingsboard.io/docs/reference/mqtt-api/#publish-attribute-update-to-the-server) - - [Server-side RPC](https://thingsboard.io/docs/reference/mqtt-api/#server-side-rpc) - - [Client-side RPC](https://thingsboard.io/docs/reference/mqtt-api/#client-side-rpc) - - [Request attribute values](https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server) - - [Attribute update subscription](https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server) - - [Device provisioning](https://thingsboard.io/docs/reference/mqtt-api/#device-provisioning) - - [Device claiming](https://thingsboard.io/docs/reference/mqtt-api/#claiming-devices) - - [Firmware OTA update](https://thingsboard.io/docs/reference/mqtt-api/#firmware-api) + - [Telemetry data upload](https://thingsboard.io/docs/reference/mqtt-api/#telemetry-upload-api) / `ThingsBoardSized` + - [Device attribute publish](https://thingsboard.io/docs/reference/mqtt-api/#publish-attribute-update-to-the-server) / `ThingsBoardSized` + - [Server-side RPC](https://thingsboard.io/docs/reference/mqtt-api/#server-side-rpc) / `Server_Side_RPC` + - [Client-side RPC](https://thingsboard.io/docs/reference/mqtt-api/#client-side-rpc) / `Client_Side_RPC` + - [Request attribute values](https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server) / `Attribute_Request_Callback` + - [Attribute update subscription](https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server) / `Shared_Attribute_Update` + - [Device provisioning](https://thingsboard.io/docs/reference/mqtt-api/#device-provisioning) / `Provision` + - [Device claiming](https://thingsboard.io/docs/reference/mqtt-api/#claiming-devices) / `ThingsBoardSized` + - [Firmware OTA update](https://thingsboard.io/docs/reference/mqtt-api/#firmware-api) / `OTA_Firmware_Update` ### Over `HTTP(S)`: @@ -124,17 +125,6 @@ If the device is causing problems that are not already described in more detail #include ``` -### No PROGMEM support causing crashes - -If the device is crashing with a `Exception` especially `Exception (3)`, more specifically `LoadStoreError` or `LoadStoreErrorCause` this might be caused because all constant variables are per default in flash memory to decrease the overall memory footprint of the library. This can cause crashes if the underlying used libraries or the board itself don't support `PROGMEM`, to mitigate that add a `#define THINGSBOARD_ENABLE_PROGMEM 0` before including the ThingsBoard header file. This will simply remove all constants from the flash memory region and should therefore resolve any incompatibilities. - -```cpp -// If not set the value is 1 per default if the pgmspace include exists, -// set to 0 if the board has problems with PROGMEM variables and does not seem to work correctly -#define THINGSBOARD_ENABLE_PROGMEM 0 -#include -``` - ### Not enough space for JSON serialization The buffer size for the serialized JSON is fixed to 64 bytes. The SDK will not send data if the size of it is bigger than the configured internal buffer size. Respective logs in the `"Serial Monitor"` window will indicate the condition: @@ -152,11 +142,11 @@ WiFiClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object // ThingsBoard tb(mqttClient); -// The SDK setup with 128 bytes for JSON payload -ThingsBoard tb(mqttClient, 128); +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128); void setup() { // Increase internal buffer size after inital creation. @@ -174,28 +164,34 @@ All internal methods call attempt to utilize the stack as far as possible and co ```cpp // If not set the value is 0 per default, -// set to 1 if the MaxFieldsAmount template argument should be automatically deduced instead +// set to 1 if the MaxResponse template argument should be automatically deduced instead #define THINGSBOARD_ENABLE_DYNAMIC 1 #include ``` ### Too much data fields must be serialized -The received `JSON` payload, as well as the `sendAttributes` and `sendTelemetry` methods, use the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) this requires the `MaxFieldsAmount` template argument to be passed in the constructor template list. The default value is 8, if more than that are sent, the `"Serial Monitor"` window will get a respective log showing an error: +The `sendAttributes` and `sendTelemetry` methods, use the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) this requires the `MaxKeyValuePairAmount` template argument to be passed in the method template list. If more key-value pairs are sent than specified, the `"Serial Monitor"` window will get a respective log showing an error: ``` [TB] Unable to serialize key-value json -[TB] Too many JSON fields passed (26), increase MaxFieldsAmount (8) accordingly +[TB] Attempt too enter to many JSON fields into StaticJsonDocument (5), increase (MaxKeyValuePairAmount) (3) accordingly ``` +To fix the issue we simply have to increase the template argument for the method to the actually required amount. + +Alternatively to remove the need for the `MaxKeyValuePairAmount`template argument in the method template list and to ensure the size the method has is always enough to send messages, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This makes the library use the [`DynamicJsonDocument`](https://arduinojson.org/v6/api/dynamicjsondocument/) instead of the default [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/). Be aware though as this places the json structure onto the heap. + +------------------------ + Additionally, the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) is also used to deserialize the received payload for every kind of response received by the server, besides the `OTA` binary data. -This means that if the `MaxFieldsAmount` template argument is smaller than the amount of requested client or shared attributes, the `"Serial Monitor"` window will get a respective log showing an error: +This means that if the `MaxResponse` template argument is smaller than the amount of received key-value pairs, the `"Serial Monitor"` window will get a respective log showing an error: ``` -[TB] Unable to de-serialize received json data with error (DeserializationError::NoMemory) +[TB] Attempt too enter to many JSON fields into StaticJsonDocument (12), increase (MaxResponse) (8) accordingly ``` -To fix the issue we simply have to increase the template argument to the required amount. +To fix the issue we simply have to increase the template argument for the method to the actually required amount. ```cpp // Initialize underlying client, used to establish a connection @@ -211,14 +207,14 @@ Arduino_MQTT_Client mqttClient(espClient); ThingsBoardSized<32> tb(mqttClient, 128); ``` -Alternatively to remove the need for the `MaxFieldsAmount` template argument in the constructor template list and to ensure the size the buffer should have is always enough to hold sent or received messages, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This makes the library use the [`DynamicJsonDocument`](https://arduinojson.org/v6/api/dynamicjsondocument/) instead of the default [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/). Be aware though as this copies sent or received payloads onto the heap. +Alternatively to remove the need for the `MaxResponse`template argument in the constructor template list and to ensure the size the buffer should have is always enough to hold received messages, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This makes the library use the [`DynamicJsonDocument`](https://arduinojson.org/v6/api/dynamicjsondocument/) instead of the default [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/). Be aware though as this places the json structure onto the heap. ### Too many subscriptions -The possible event subscription classes that are passed to internal methods, use arrays which reside on the stack those require the `MaxSubscribtions` template argument to be passed in the constructor template list. The default value is 2, if the method call attempts to subscribe more than that many events in total, the `"Serial Monitor"` window will get a respective log showing an error: +The possible event subscription classes that are passed to internal methods, use arrays which reside on the stack those require the `MaxSubscriptions` template argument to be passed in the constructor template list. The default value is 1, if the method call attempts to subscribe more than that many events in total, the `"Serial Monitor"` window will get a respective log showing an error: ``` -[TB] Too many server-side RPC subscriptions, increase MaxSubscribtions or unsubscribe +[TB] Too many shared attribute update subscriptions, increase MaxSubscriptions or unsubscribe ``` Important is that both server-side RPC and request attribute values are temporary, meaning once the request has been received it is deleted, and it is therefore possible to subscribe another event again. However, all other subscriptions like client-side RPC or attribute update subscription are permanent meaning once the event has been subscribed we can only unsubscribe all events to make more room. @@ -232,27 +228,32 @@ WiFiClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload, 8 fields for JSON object and 2 maximum subscriptions of every possible type -// ThingsBoard tb(mqttClient); +// Initialize used apis with Shared_Attribute_Update API with 1 maximum Shared_Attribute_Update subscription at once +// Shared_Attribute_Update shared_attr; + +// Initialize used apis with Shared_Attribute_Update API with 2 maximum Shared_Attribute_Update subscription at once +Shared_Attribute_Update<2U> shared_attr; +const std::array apis = { + &shared_attr +}; -// The SDK setup with 128 bytes for JSON payload, 32 fields for JSON object and 8 maximum subscriptions of every possible type -ThingsBoardSized<32, 8> tb(mqttClient, 128); +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object +// ThingsBoard tb(mqttClient, Default_Payload, apis); + +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128, apis); ``` -Alternatively, to remove the need for the `MaxSubscribtions` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This will replace the internal implementation with a growing vector instead, meaning all the subscribed callback data will reside on the heap instead. +Alternatively, to remove the need for the `MaxSubscriptions` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This will replace the internal implementation with a growing vector instead, meaning all the subscribed callback data will reside on the heap instead. ### Too many attributes -The possible attribute values that are passed to the `Shared_Attribute_Callback` or `Attribute_Request_Callback`, use arrays which reside on the stack those require the `MaxAttributes` template argument to be passed in the constructor template list. The default value is 5, if we attempt to subscribe or request more attributes than that, the `"Serial Monitor"` window will get a respective log showing a crash: +The possible attribute values that are passed to the `Shared_Attribute_Callback` or `Attribute_Request_Callback`, use arrays which reside on the stack those require the `MaxAttributes` template argument to be passed in the constructor template list. The default value is 1, if we attempt to subscribe or request more attributes than that, the `"Serial Monitor"` window will get a respective log showing a crash: ``` Assertion `m_size < Capacity' failed. ``` -Important is that the minimum size used has to be 5 if the OTA update is used, this is the case, because internally the OTA update process requests or subscribes to updates of 5 attributes. If the amount is decreased below those 5 then the OTA update process can not be started correctly and will not work anymore. - -Additionally, the size passed in the template list of the `Shared_Attribute_Callback` or `Attribute_Request_Callback` class, should be the same as the `ThingsBoardSized` class template list. If it isn't, it will not be possible to call the internal methods. - Therefore, the only thing that needs to be done is to increase the size accordingly. ```cpp @@ -262,18 +263,27 @@ WiFiClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type and 5 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback -// ThingsBoard tb(mqttClient); +// Initialize used apis with Shared_Attribute_Update API, 1 maximum Shared_Attribute_Update subscription at once, 1 maximum attribute subscribed per individual subscription +// Shared_Attribute_Update shared_attr; + +// Initialize used apis with Shared_Attribute_Update API, 2 maximum Shared_Attribute_Update subscription at once, 5 maximum attribute subscribed per individual subscription +Shared_Attribute_Update<2U, 5U> shared_attr; +const std::array apis = { + &shared_attr +}; + +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object +// ThingsBoard tb(mqttClient, Default_Payload, apis); -// The SDK setup with 128 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type and 6 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback -ThingsBoardSized<32, 8, 6> tb(mqttClient, 128); +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128, apis); ``` Alternatively, to remove the need for the `MaxAttributes` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This will replace the internal implementation with a growing vector instead, meaning all the subscribed attribute data will reside on the heap instead. ### Server-side RPC response overflowed -The possible response in subscribed `RPC_Callback` methods, use the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) this requires the `MaxRPC` template argument to be passed in the constructor template list. The default value is 0, if we attempt to return more key-value pairs in the `JSON` that, the `"Serial Monitor"` window will get a respective log showing an error: +The possible response in subscribed `RPC_Callback` methods, use the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) this requires the `MaxRPC` template argument to be passed in the constructor template list. The default value is 0, if we attempt to return more key-value pairs in the `JSON` than that, the `"Serial Monitor"` window will get a respective log showing an error: ``` [TB] Server-side RPC response overflowed, increase MaxRPC (0) @@ -288,17 +298,142 @@ WiFiClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type, 5 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback and 0 possible key-value pairs that can be passed as a response from a server-side RPC call -// ThingsBoard tb(mqttClient); +// Initialize used apis with Shared_Attribute_Update API, 1 maximum Shared_Attribute_Update subscription at once, 0 maximum attribute serialized in the response +// Shared_Attribute_Update shared_attr; + +// Initialize used apis with Server_Side_RPC API, 2 maximum Server_Side_RPC subscription at once, 1 maximum attribute serialized in the response +Server_Side_RPC<2U, 1U> rpc; +const std::array apis = { + &rpc +}; -// The SDK setup with 128 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type, 6 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback and 2 possible key-value pairs that can be passed as a response from a server-side RPC call -ThingsBoardSized<32, 8, 6, 5> tb(mqttClient, 128); +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object +// ThingsBoard tb(mqttClient, Default_Payload, apis); + +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128, apis); +``` + +Alternatively, to remove the need for the `MaxRPC` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This will instead expect an additional parameter response size in the `RPC_Callback` constructor argument list, which shows the internal size the [`JsonDocument`](https://arduinojson.org/v6/api/jsondocument/) needs to have to contain the response. Use `JSON_OBJECT_SIZE()` and pass the amount of key value pair to calculate the estimated size. See https://arduinojson.org/v6/assistant/ for more information. + +### Server-side RPC response overflowed + +The possible request in subscribed `RPC_Request_Callback` methods, use the [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/) this requires the `MaxRequestRPC` template argument to be passed in the constructor template list. The default value is 1, if we attempt to send more key-value pairs in the `JSON` than that, the `"Serial Monitor"` window will get a respective log showing an error: + +``` +[TB] Client-side RPC request overflowed, increase MaxRequestRPC (2) +``` + +```cpp +// Initialize underlying client, used to establish a connection +WiFiClient espClient; + +// Initalize the Mqtt client instance +Arduino_MQTT_Client mqttClient(espClient); + +// Initialize used apis with Shared_Attribute_Update API, 1 maximum Shared_Attribute_Update subscription at once, 1 maximum attribute serialized in the request +// Shared_Attribute_Update shared_attr; + +// Initialize used apis with Server_Side_RPC API, 2 maximum Server_Side_RPC subscription at once, 2 maximum attribute serialized in the request +Client_Side_RPC<2U, 2U> request_rpc; +const std::array apis = { + &request_rpc +}; + +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object +// ThingsBoard tb(mqttClient, Default_Payload, apis); + +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128, apis); ``` -Alternatively, to remove the need for the `MaxRPC` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This will instead expect an additional parameter `responseSize` in the `RPC_Callback` constructor argument list, which shows the internal size the [`JsonDocument`](https://arduinojson.org/v6/api/jsondocument/) needs to have to contain the response. Use `JSON_OBJECT_SIZE()` and pass the amount of key value pair to calculate the estimated size. See https://arduinojson.org/v6/assistant/ for more information. +Alternatively, to remove the need for the `MaxRequestRPC` template argument in the constructor template list, see the [Dynamic ThingsBoard section](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#dynamic-thingsboard-usage) section. This makes the library use the [`DynamicJsonDocument`](https://arduinojson.org/v6/api/dynamicjsondocument/) instead of the default [`StaticJsonDocument`](https://arduinojson.org/v6/api/staticjsondocument/). Be aware though as this copies the requests onto the heap. ## Tips and Tricks +### Custom API Implementation Instance + +The `ThingsBoardSized` class instance only supports a minimal subset of the actual API, see the [Supported ThingsBoard Features](https://github.com/thingsboard/thingsboard-client-sdk?tab=readme-ov-file#supported-thingsboard-features) section. But with the usage of the `IAPI_Implementation` base class, it is possible to write an own implementation that implements an additional API implementation or changes the behavior for an already existing API implementation. + +For that a `class` needs to inherit the `API_Implemenatation` class and `override` the needed methods shown below: + +```cpp +#ifndef Custom_API_Implementation_h +#define Custom_API_Implementation_h + +// Local includes. +#include "IAPI_Implementation.h" + + +class Custom_API_Implementation : public IAPI_Implementation { + public: + API_Process_Type Get_Process_Type() override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + // Nothing to do + } + + char const * Get_Response_Topic_String() const override { + return nullptr; + } + + bool Unsubscribe() override { + return true; + } + + bool Resubscribe_Topic() override { + return true; + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + // Nothing to do + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + // Nothing to do + } +}; + +#endif // Custom_API_Implementation_h +``` + +Once that has been done it can simply be passed to the `ThingsBoard` instance, either using the constructor or using the `Subscribe_IAPI_Implementation` method. + +```cpp +// Initialize underlying client, used to establish a connection +WiFiClient espClient; + +// Initalize the Mqtt client instance +Arduino_MQTT_Client mqttClient(espClient); + +// Initialize used apis with Custom API +Custom_IAPI_Implementation custom_api; +const std::array apis = { + &custom_api +}; + +// The SDK setup with 64 bytes for JSON payload, 8 fields for JSON object and maximal 7 API endpoints subscribed at once +// ThingsBoard tb(mqttClient, Default_Payload, apis); + +// The SDK setup with 128 bytes for JSON payload and 8 fields for JSON object and maximal 10 API endpoints subscribed at once +ThingsBoardSized<8, 10> tb(mqttClient, 128, apis); + +// Optional alternative way to subscribe the Custom API ater the class instance has already been created +// tb.Subscribe_IAPI_Implementation(custom_api); +``` + ### Custom Updater Instance When using the `ThingsBoard` class instance, the class used to flash the binary data onto the device is not hard coded, @@ -341,7 +476,7 @@ Once that has been done it can simply be passed instead of the `Espressif_Update // Initalize the Updater client instance used to flash binary to flash memory Custom_Updater updater; -const OTA_Update_Callback callback(&progressCallback, &updatedCallback, CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); +const OTA_Update_Callback callback(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, &finished_callback, &progress_callback, &update_starting_callback, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); ``` ### Custom HTTP Instance @@ -362,11 +497,11 @@ For that a `class` needs to inherit the `IHTTP_Client` interface and `override` class Custom_HTTP_Client : public IHTTP_Client { public: - void set_keep_alive(const bool& keep_alive) override { + void set_keep_alive(bool keep_alive) override { // Nothing to do } - int connect(const char *host, const uint16_t& port) override { + int connect(const char *host, uint16_t port) override { return 0; } @@ -382,13 +517,20 @@ class Custom_HTTP_Client : public IHTTP_Client { return 200; } - int get(const char *url_path) override{ + int get(const char *url_path) override { return 0; } - String get_response_body() override{ +#if THINGSBOARD_ENABLE_STL + std::string get_response_body() override { + return std::string(); + } +#else + String get_response_body() override { return String(); } + +#endif // THINGSBOARD_ENABLE_STL }; ``` @@ -423,23 +565,23 @@ For that a `class` needs to inherit the `IMQTT_Client` interface and `override` class Custom_MQTT_Client : public IMQTT_Client { public: - void set_data_callback(data_function cb) override { + void set_data_callback(Callback::function callback) override { // Nothing to do } - void set_connect_callback(connect_function cb) override { + void set_connect_callback(Callback::function callback) override { // Nothing to do } - bool set_buffer_size(const uint16_t& buffer_size) override{ + bool set_buffer_size(uint16_t buffer_size) override { return true; } - uint16_t get_buffer_size() override { + uint16_t get_buffer_size() override { return 0U; } - void set_server(const char *domain, const uint16_t& port) override { + void set_server(const char *domain, uint16_t port) override { // Nothing to do } @@ -506,8 +648,11 @@ WiFiClient espClient; // Initalize the Mqtt client instance Custom_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload -ThingsBoard tb(mqttClient); +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object +// ThingsBoard tb(mqttClient); + +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32> tb(mqttClient, 128); ``` ### Custom Logger Instance @@ -543,11 +688,11 @@ WiFiClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// The SDK setup with 64 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type, 5 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback and 0 possible key-value pairs that can be passed as a response from a server-side RPC call and the default logging instance (DefaultLogger) +// The SDK setup with 64 bytes for JSON payload and 8 fields for JSON object // ThingsBoard tb(mqttClient); -// The SDK setup with 128 bytes for JSON payload, 32 fields for JSON object, 8 maximum subscriptions of every possible type, 6 possible attribute values that can be passed to Shared_Attribute_Callback or Attribute_Request_Callback, 2 possible key-value pairs that can be passed as a response from a server-side RPC call and a custom logging instance (CustomLogger) -ThingsBoardSized<32, 8, 6, 5, CustomLogger> tb(mqttClient, 128); +// The SDK setup with 128 bytes for JSON payload and 32 fields for JSON object +ThingsBoardSized<32, Default_Response_Amount, CustomLogger> tb(mqttClient, 128); ``` ## Have a question or proposal? diff --git a/docs/Doxyfile b/docs/Doxyfile index c2bbab92..6a019bd7 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "ThingsBoard Client SDK" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.13.0 +PROJECT_NUMBER = 0.14.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1978,7 +1978,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2124,7 +2124,7 @@ DIA_PATH = # and usage relations if the target is undocumented or is not a class. # The default value is: YES. -HIDE_UNDOC_RELATIONS = YES +HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: @@ -2133,7 +2133,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = NO +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2220,7 +2220,7 @@ UML_LIMIT_NUM_FIELDS = 10 # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -TEMPLATE_RELATIONS = NO +TEMPLATE_RELATIONS = YES # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the @@ -2293,7 +2293,7 @@ DIRECTORY_GRAPH = YES # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. @@ -2305,7 +2305,7 @@ DOT_IMAGE_FORMAT = png # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -INTERACTIVE_SVG = NO +INTERACTIVE_SVG = YES # The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. diff --git a/examples/0000-arduino_send_telemetry/0000-arduino_send_telemetry.ino b/examples/0000-arduino_send_telemetry/0000-arduino_send_telemetry.ino index b87ea051..74282f37 100644 --- a/examples/0000-arduino_send_telemetry/0000-arduino_send_telemetry.ino +++ b/examples/0000-arduino_send_telemetry/0000-arduino_send_telemetry.ino @@ -12,64 +12,32 @@ #include -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; -#else constexpr uint16_t THINGSBOARD_PORT = 1883U; -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 9600U; -constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD PROGMEM = 9600U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 9600U; constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD = 9600U; -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)"; -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)"; constexpr char TEMPERATURE_KEY[] = "temperature"; constexpr char HUMIDITY_KEY[] = "humidity"; -#endif // Serial driver for ESP @@ -78,34 +46,22 @@ SoftwareSerial soft(2U, 3U); // RX, TX WiFiEspClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// Initialize ThingsBoard instance +// Initialize ThingsBoard instance with the maximum needed buffer size ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -141,11 +97,7 @@ void setup() { WiFi.init(&soft); // check for the presence of the shield if (WiFi.status() == WL_NO_SHIELD) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("WiFi shield not present")); -#else Serial.println("WiFi shield not present"); -#endif // don't continue while (true); } @@ -166,20 +118,12 @@ void loop() { snprintf(message, sizeof(message), CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); Serial.println(message); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending telemetry data...")); -#else Serial.println("Sending telemetry data..."); -#endif // Uploads new telemetry to ThingsBoard using MQTT. // See https://thingsboard.io/docs/reference/mqtt-api/#telemetry-upload-api // for more details diff --git a/examples/0001-arduino_send_batch/0001-arduino_send_batch.ino b/examples/0001-arduino_send_batch/0001-arduino_send_batch.ino index 43b41592..abdb22d2 100644 --- a/examples/0001-arduino_send_batch/0001-arduino_send_batch.ino +++ b/examples/0001-arduino_send_batch/0001-arduino_send_batch.ino @@ -12,70 +12,35 @@ #include -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; -#else constexpr uint16_t THINGSBOARD_PORT = 1883U; -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 9600U; -constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD PROGMEM = 9600U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 9600U; constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD = 9600U; -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)"; -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -constexpr char DEVICE_TYPE_KEY[] PROGMEM = "device_type"; -constexpr char ACTIVE_KEY[] PROGMEM = "active"; -constexpr char SENSOR_VALUE[] PROGMEM = "sensor"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)"; constexpr char TEMPERATURE_KEY[] = "temperature"; constexpr char HUMIDITY_KEY[] = "humidity"; constexpr char DEVICE_TYPE_KEY[] = "device_type"; constexpr char ACTIVE_KEY[] = "active"; constexpr char SENSOR_VALUE[] = "sensor"; -#endif // Serial driver for ESP @@ -84,34 +49,22 @@ SoftwareSerial soft(2U, 3U); // RX, TX WiFiEspClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// Initialize ThingsBoard instance +// Initialize ThingsBoard instance with the maximum needed buffer size ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -142,11 +95,7 @@ void setup() { WiFi.init(&soft); // check for the presence of the shield if (WiFi.status() == WL_NO_SHIELD) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("WiFi shield not present")); -#else Serial.println("WiFi shield not present"); -#endif // don't continue while (true); } @@ -167,30 +116,22 @@ void loop() { snprintf(message, sizeof(message), CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); Serial.println(message); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending telemetry data...")); -#else Serial.println("Sending telemetry data..."); -#endif - const uint8_t data_items = 2U; - Telemetry data[data_items] = { + constexpr size_t TELEMETRY_SIZE = 2U; + Telemetry data[TELEMETRY_SIZE] = { { TEMPERATURE_KEY, 42.2 }, { HUMIDITY_KEY, 80 }, }; /* For C++98 compiler, shipped with Arduino IDE version 1.6.6 or less: - Telemetry data[data_items] = { + Telemetry data[TELEMETRY_SIZE] = { Telemetry( TEMPERATURE_KEY, 42.2 ), Telemetry( HUMIDITY_KEY, 80 ), }; @@ -201,24 +142,20 @@ void loop() { // See https://thingsboard.io/docs/reference/mqtt-api/#telemetry-upload-api // for more details Telemetry* begin = data; - Telemetry* end = data + data_items; - tb.sendTelemetry(begin, end); + Telemetry* end = data + TELEMETRY_SIZE; + tb.sendTelemetry(begin, end); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending attributes data...")); -#else Serial.println("Sending attributes data..."); -#endif - const int attribute_items = 2; - Attribute attributes[attribute_items] = { + constexpr size_t ATTRIBUTES_SIZE = 2U; + Attribute attributes[ATTRIBUTES_SIZE] = { { DEVICE_TYPE_KEY, SENSOR_VALUE }, { ACTIVE_KEY, true }, }; /* For C++98 compiler, shipped with Arduino IDE version 1.6.6 or less: - Attribute attributes[data_items] = { + Attribute attributes[ATTRIBUTES_SIZE] = { Attribute( DEVICE_TYPE_KEY, SENSOR_VALUE ), Attribute( ACTIVE_KEY, true ), }; @@ -229,8 +166,8 @@ void loop() { // See https://thingsboard.io/docs/reference/mqtt-api/#publish-attribute-update-to-the-server // for more details begin = attributes; - end = attributes + attribute_items; - tb.sendAttributes(begin, end); + end = attributes + ATTRIBUTES_SIZE; + tb.sendAttributes(begin, end); tb.loop(); } diff --git a/examples/0002-arduino_rpc/0002-arduino_rpc.ino b/examples/0002-arduino_rpc/0002-arduino_rpc.ino index 784fcbc7..80e7029b 100644 --- a/examples/0002-arduino_rpc/0002-arduino_rpc.ino +++ b/examples/0002-arduino_rpc/0002-arduino_rpc.ino @@ -6,76 +6,43 @@ // - ESP8266 connected to Arduino Uno #include +#include #include #include #include #include -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; -#else constexpr uint16_t THINGSBOARD_PORT = 1883U; -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 9600U; -constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD PROGMEM = 9600U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 9600U; constexpr uint32_t SERIAL_ESP8266_DEBUG_BAUD = 9600U; -#endif - -#if THINGSBOARD_ENABLE_PROGMEM -constexpr const char RPC_JSON_METHOD[] PROGMEM = "example_json"; -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)"; -constexpr const char RPC_TEMPERATURE_METHOD[] PROGMEM = "example_set_temperature"; -constexpr const char RPC_SWITCH_METHOD[] PROGMEM = "example_set_switch"; -constexpr const char RPC_TEMPERATURE_KEY[] PROGMEM = "temp"; -constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch"; -#else constexpr const char RPC_JSON_METHOD[] = "example_json"; constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)"; constexpr const char RPC_TEMPERATURE_METHOD[] = "example_set_temperature"; constexpr const char RPC_SWITCH_METHOD[] = "example_set_switch"; constexpr const char RPC_TEMPERATURE_KEY[] = "temp"; -constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch"; -#endif +constexpr const char RPC_SWITCH_KEY[] = "switch"; +constexpr uint8_t MAX_RPC_SUBSCRIPTIONS = 3U; +constexpr uint8_t MAX_RPC_RESPONSE = 5U; // Serial driver for ESP @@ -84,8 +51,13 @@ SoftwareSerial soft(2U, 3U); // RX, TX WiFiEspClient espClient; // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// Initialize ThingsBoard instance with the maximum needed buffer size and the adjusted maximum amount of rpc key value pairs we want to send as a response -ThingsBoardSized tb(mqttClient, MAX_MESSAGE_SIZE); +// Initialize used apis +Server_Side_RPC rpc; +const IAPI_Implementation* apis[1U] = { + &rpc +}; +// Initialize ThingsBoard instance with the maximum needed buffer size +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis + 0U, apis + 1U); // Statuses for subscribing to rpc bool subscribed = false; @@ -94,27 +66,15 @@ bool subscribed = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -145,11 +105,7 @@ void setup() { WiFi.init(&soft); // check for the presence of the shield if (WiFi.status() == WL_NO_SHIELD) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("WiFi shield not present")); -#else Serial.println("WiFi shield not present"); -#endif // don't continue while (true); } @@ -162,11 +118,7 @@ void setup() { /// @param data Data containing the rpc data that was called and its current value /// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods void processGetJson(const JsonVariantConst &data, JsonDocument &response) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Received the json RPC method")); -#else Serial.println("Received the json RPC method"); -#endif // Size of the response document needs to be configured to the size of the innerDoc + 1. StaticJsonDocument innerDoc; @@ -183,20 +135,12 @@ void processGetJson(const JsonVariantConst &data, JsonDocument &response) { /// @param data Data containing the rpc data that was called and its current value /// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods void processTemperatureChange(const JsonVariantConst &data, JsonDocument &response) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Received the set temperature RPC method")); -#else Serial.println("Received the set temperature RPC method"); -#endif // Process data const float example_temperature = data[RPC_TEMPERATURE_KEY]; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Example temperature: ")); -#else Serial.print("Example temperature: "); -#endif Serial.println(example_temperature); // Ensure to only pass values do not store by copy, or if they do increase the MaxRPC template parameter accordingly to ensure that the value can be deserialized.RPC_Callback. @@ -204,7 +148,7 @@ void processTemperatureChange(const JsonVariantConst &data, JsonDocument &respon response["string"] = "exampleResponseString"; response["int"] = 5; response["float"] = 5.0f; - response["double"] = 10.0d; + response["double"] = 10.0; response["bool"] = true; } @@ -220,11 +164,7 @@ void processSwitchChange(const JsonVariantConst &data, JsonDocument &response) { // Process data const bool switch_state = data[RPC_SWITCH_KEY]; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Example switch state: ")); -#else Serial.print("Example switch state: "); -#endif Serial.println(switch_state); response.set(22.02); @@ -244,23 +184,14 @@ void loop() { snprintf(message, sizeof(message), CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); Serial.println(message); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!subscribed) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribing for RPC...")); -#else Serial.println("Subscribing for RPC..."); -#endif - constexpr uint8_t CALLBACK_SIZE = 3U; - const RPC_Callback callbacks[CALLBACK_SIZE] = { + const RPC_Callback callbacks[MAX_RPC_SUBSCRIPTIONS] = { // Requires additional memory in the JsonDocument for the JsonDocument that will be copied into the response { RPC_JSON_METHOD, processGetJson }, // Requires additional memory in the JsonDocument for 5 key-value pairs that do not copy their value into the JsonDocument itself @@ -271,22 +202,12 @@ void loop() { // Perform a subscription. All consequent data processing will happen in // processTemperatureChange() and processSwitchChange() functions, // as denoted by callbacks array. - const RPC_Callback* begin = callbacks; - const RPC_Callback* end = callbacks + CALLBACK_SIZE; - if (!tb.RPC_Subscribe(begin, end)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to subscribe for RPC")); -#else + if (!rpc.RPC_Subscribe(callbacks + 0U, callbacks + MAX_RPC_SUBSCRIPTIONS)) { Serial.println("Failed to subscribe for RPC"); -#endif return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribe done")); -#else Serial.println("Subscribe done"); -#endif subscribed = true; } diff --git a/examples/0003-esp8266_esp32_send_data/0003-esp8266_esp32_send_data.ino b/examples/0003-esp8266_esp32_send_data/0003-esp8266_esp32_send_data.ino index 592d4e4d..c31cf1c8 100644 --- a/examples/0003-esp8266_esp32_send_data/0003-esp8266_esp32_send_data.ino +++ b/examples/0003-esp8266_esp32_send_data/0003-esp8266_esp32_send_data.ino @@ -40,120 +40,46 @@ #include #endif - -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif #if USING_HTTPS // HTTP port used to communicate with the server, 80 is the default unencrypted HTTP port, // whereas 443 would be the default encrypted SSL HTTPS port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 443U; -#else constexpr uint16_t THINGSBOARD_PORT = 443U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 80U; #else constexpr uint16_t THINGSBOARD_PORT = 80U; #endif -#endif #else // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -187,17 +113,10 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)\n"; -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)\n"; constexpr char TEMPERATURE_KEY[] = "temperature"; constexpr char HUMIDITY_KEY[] = "humidity"; -#endif // Initialize underlying client, used to establish a connection @@ -214,34 +133,25 @@ ThingsBoardHttp tb(httpClient, TOKEN, THINGSBOARD_SERVER, THINGSBOARD_PORT); #else // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +// Initialize used apis +const std::array apis = {}; +// Initialize ThingsBoard instance with the maximum needed buffer size +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); #endif /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -286,11 +196,7 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } @@ -299,18 +205,10 @@ void loop() { // Uploads new telemetry to ThingsBoard using HTTP. // See https://thingsboard.io/docs/reference/http-api/#telemetry-upload-api // for more details -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending temperature data...")); -#else Serial.println("Sending temperature data..."); -#endif tb.sendTelemetryData(TEMPERATURE_KEY, random(10, 31)); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending humidity data...")); -#else Serial.println("Sending humidity data..."); -#endif tb.sendTelemetryData(HUMIDITY_KEY, random(40, 90)); #if !USING_HTTPS diff --git a/examples/0004-arduino-sim900_send_telemetry/0004-arduino-sim900_send_telemetry.ino b/examples/0004-arduino-sim900_send_telemetry/0004-arduino-sim900_send_telemetry.ino index 5c0e926d..b3eb5551 100644 --- a/examples/0004-arduino-sim900_send_telemetry/0004-arduino-sim900_send_telemetry.ino +++ b/examples/0004-arduino-sim900_send_telemetry/0004-arduino-sim900_send_telemetry.ino @@ -24,64 +24,32 @@ // Your GPRS credentials // Leave empty, if missing user or pass -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char APN[] PROGMEM = "internet"; -constexpr char USER[] PROGMEM = ""; -constexpr char PASS[] PROGMEM = ""; -#else constexpr char APN[] = "internet"; constexpr char USER[] = ""; constexpr char PASS[] = ""; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; -#else constexpr uint16_t THINGSBOARD_PORT = 1883U; -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)"; -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)"; constexpr char TEMPERATURE_KEY[] = "temperature"; constexpr char HUMIDITY_KEY[] = "humidity"; -#endif // Serial port for GSM shield @@ -96,7 +64,7 @@ TinyGsmClient client(modem); // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(client); -// Initialize ThingsBoard instance +// Initialize ThingsBoard instance with the maximum needed buffer size ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); // Set to true, if modem is connected @@ -126,19 +94,11 @@ void setup() { // Restart takes quite some time // To skip it, call init() instead of restart() -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Initializing modem...")); -#else Serial.println("Initializing modem..."); -#endif modem.restart(); String modemInfo = modem.getModemInfo(); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Modem: ")); -#else Serial.print("Modem: "); -#endif Serial.println(modemInfo); // Unlock your SIM card with a PIN @@ -149,48 +109,24 @@ void loop() { delay(1000); if (!modemConnected) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Waiting for network...")); -#else Serial.print("Waiting for network..."); -#endif if (!modem.waitForNetwork()) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" fail")); -#else Serial.println(" fail"); -#endif delay(10000); return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" OK")); -#else Serial.println(" OK"); -#endif -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Connecting to ")); -#else Serial.print("Connecting to "); -#endif Serial.print(APN); if (!modem.gprsConnect(APN, USER, PASS)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" fail")); -#else Serial.println(" fail"); -#endif delay(10000); return; } modemConnected = true; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" OK")); -#else Serial.println(" OK"); -#endif } if (!tb.connected()) { @@ -200,11 +136,7 @@ void loop() { snprintf(message, sizeof(message), CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); Serial.println(message); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } @@ -212,18 +144,10 @@ void loop() { // Uploads new telemetry to ThingsBoard using HTTP. // See https://thingsboard.io/docs/reference/http-api/#telemetry-upload-api // for more details -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending temperature data...")); -#else Serial.println("Sending temperature data..."); -#endif tb.sendTelemetryData(TEMPERATURE_KEY, random(10, 31)); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending humidity data...")); -#else Serial.println("Sending humidity data..."); -#endif tb.sendTelemetryData(HUMIDITY_KEY, random(40, 90)); tb.loop(); diff --git a/examples/0005-arduino-sim900_send_telemetry_http/0005-arduino-sim900_send_telemetry_http.ino b/examples/0005-arduino-sim900_send_telemetry_http/0005-arduino-sim900_send_telemetry_http.ino index 286f3499..9fe91a91 100644 --- a/examples/0005-arduino-sim900_send_telemetry_http/0005-arduino-sim900_send_telemetry_http.ino +++ b/examples/0005-arduino-sim900_send_telemetry_http/0005-arduino-sim900_send_telemetry_http.ino @@ -24,54 +24,27 @@ // Your GPRS credentials // Leave empty, if missing user or pass -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char APN[] PROGMEM = "internet"; -constexpr char USER[] PROGMEM = ""; -constexpr char PASS[] PROGMEM = ""; -#else constexpr char APN[] = "internet"; constexpr char USER[] = ""; constexpr char PASS[] = ""; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // HTTP port used to communicate with the server, 80 is the default unencrypted HTTP port, // whereas 443 would be the default encrypted SSL HTTPS port -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 80U; -#else constexpr uint16_t THINGSBOARD_PORT = 80U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -#else constexpr char TEMPERATURE_KEY[] = "temperature"; constexpr char HUMIDITY_KEY[] = "humidity"; -#endif // Serial port for GSM shield @@ -121,19 +94,11 @@ void setup() { // Restart takes quite some time // To skip it, call init() instead of restart() -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Initializing modem...")); -#else Serial.println("Initializing modem..."); -#endif modem.restart(); String modemInfo = modem.getModemInfo(); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Modem: ")); -#else Serial.print("Modem: "); -#endif Serial.println(modemInfo); // Unlock your SIM card with a PIN @@ -144,64 +109,32 @@ void loop() { delay(1000); if (!modemConnected) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Waiting for network...")); -#else Serial.print("Waiting for network..."); -#endif if (!modem.waitForNetwork()) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" fail")); -#else Serial.println(" fail"); -#endif delay(10000); return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" OK")); -#else Serial.println(" OK"); -#endif -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Connecting to ")); -#else Serial.print("Connecting to "); -#endif Serial.print(APN); if (!modem.gprsConnect(APN, USER, PASS)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" fail")); -#else Serial.println(" fail"); -#endif delay(10000); return; } modemConnected = true; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F(" OK")); -#else Serial.println(" OK"); -#endif } // Uploads new telemetry to ThingsBoard using HTTP. // See https://thingsboard.io/docs/reference/http-api/#telemetry-upload-api // for more details -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending temperature data...")); -#else Serial.println("Sending temperature data..."); -#endif tb.sendTelemetryData(TEMPERATURE_KEY, random(10, 31)); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending humidity data...")); -#else Serial.println("Sending humidity data..."); -#endif tb.sendTelemetryData(HUMIDITY_KEY, random(40, 90)); } diff --git a/examples/0006-esp8266_esp32_process_shared_attribute_update/0006-esp8266_esp32_process_shared_attribute_update.ino b/examples/0006-esp8266_esp32_process_shared_attribute_update/0006-esp8266_esp32_process_shared_attribute_update.ino index 35d4e7b0..c3c628dd 100644 --- a/examples/0006-esp8266_esp32_process_shared_attribute_update/0006-esp8266_esp32_process_shared_attribute_update.ino +++ b/examples/0006-esp8266_esp32_process_shared_attribute_update/0006-esp8266_esp32_process_shared_attribute_update.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include @@ -18,109 +19,40 @@ #define ENCRYPTED false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif // Maximum amount of attributs we can request or subscribe, has to be set both in the ThingsBoard template list and Attribute_Request_Callback template list // and should be the same as the amount of variables in the passed array. If it is less not all variables will be requested or subscribed -#if THINGSBOARD_ENABLE_PROGMEM -constexpr size_t MAX_ATTRIBUTES PROGMEM = 6U; -#else constexpr size_t MAX_ATTRIBUTES = 6U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -154,15 +86,14 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)\n"; -constexpr const char FW_TAG_KEY[] PROGMEM = "fw_tag"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)\n"; constexpr const char FW_TAG_KEY[] = "fw_tag"; -#endif +char constexpr FW_VER_KEY[] = "fw_version"; +char constexpr FW_TITLE_KEY[] = "fw_title"; +char constexpr FW_CHKS_KEY[] = "fw_checksum"; +char constexpr FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm"; +char constexpr FW_SIZE_KEY[] = "fw_size"; // Initialize underlying client, used to establish a connection @@ -173,8 +104,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +Shared_Attribute_Update<1U, MAX_ATTRIBUTES> shared_update; +const std::array apis = { + &shared_update +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoardSized tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Statuses for subscribing to shared attributes bool subscribed = false; @@ -183,27 +119,15 @@ bool subscribed = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -258,38 +182,22 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!subscribed) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribing for shared attribute updates...")); -#else Serial.println("Subscribing for shared attribute updates..."); -#endif // Shared attributes we want to request from the server constexpr std::array SUBSCRIBED_SHARED_ATTRIBUTES = {FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TAG_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Shared_Attribute_Callback callback(&processSharedAttributeUpdate, SUBSCRIBED_SHARED_ATTRIBUTES.cbegin(), SUBSCRIBED_SHARED_ATTRIBUTES.cend()); - if (!tb.Shared_Attributes_Subscribe(callback)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to subscribe for shared attribute updates")); -#else + const Shared_Attribute_Callback callback(&processSharedAttributeUpdate, SUBSCRIBED_SHARED_ATTRIBUTES); + if (!shared_update.Shared_Attributes_Subscribe(callback)) { Serial.println("Failed to subscribe for shared attribute updates"); -#endif return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribe done")); -#else Serial.println("Subscribe done"); -#endif subscribed = true; } diff --git a/examples/0007-esp8266_esp32_claim_device/0007-esp8266_esp32_claim_device.ino b/examples/0007-esp8266_esp32_claim_device/0007-esp8266_esp32_claim_device.ino index a5a78ea8..9eaf6163 100644 --- a/examples/0007-esp8266_esp32_claim_device/0007-esp8266_esp32_claim_device.ino +++ b/examples/0007-esp8266_esp32_claim_device/0007-esp8266_esp32_claim_device.ino @@ -23,101 +23,36 @@ #define USE_RANDOM_PASSWORD_FALLBACK false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 128U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 128U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -151,24 +86,14 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif // Possible character options used to generate a password if none is provided. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CONNECTING_MSG[] PROGMEM = "Connecting to: (%s) with token (%s)\n"; -constexpr char PASSWORD_OPTIONS[] PROGMEM = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; -#else constexpr char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)\n"; constexpr char PASSWORD_OPTIONS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; -#endif // See https://thingsboard.io/docs/user-guide/claiming-devices/ // to know how to claim a device once the request has been sent to Thingsboard cloud -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t CLAIMING_REQUEST_DURATION_MS PROGMEM = (3U * 60U * 1000U); -#else constexpr uint32_t CLAIMING_REQUEST_DURATION_MS = (3U * 60U * 1000U); -#endif // Optionally keep the claiming request secret key empty, // and a random password will be generated for the claiming request instead. std::string claimingRequestSecretKey = ""; @@ -192,27 +117,15 @@ bool claimingRequestSent = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -269,11 +182,7 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } @@ -289,7 +198,7 @@ void loop() { // if the string is empty or null, automatically checked by the sendClaimingRequest method claimingRequestSecretKey; #endif - Serial.printf("Sending claiming request with password (%s) being (%u) characters long and a timeout of (%u)ms\n", secretKey.c_str(), secretKey.length(), CLAIMING_REQUEST_DURATION_MS); + Serial.printf("Sending claiming request with password (%s) being (%u) characters long and a timeout of (%lu)ms\n", secretKey.c_str(), secretKey.length(), CLAIMING_REQUEST_DURATION_MS); claimingRequestSent = tb.Claim_Request(secretKey.c_str(), CLAIMING_REQUEST_DURATION_MS); } diff --git a/examples/0008-esp8266_esp32_provision_device/0008-esp8266_esp32_provision_device.ino b/examples/0008-esp8266_esp32_provision_device/0008-esp8266_esp32_provision_device.ino index 3f7cc7c9..4fb8f3f3 100644 --- a/examples/0008-esp8266_esp32_provision_device/0008-esp8266_esp32_provision_device.ino +++ b/examples/0008-esp8266_esp32_provision_device/0008-esp8266_esp32_provision_device.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include @@ -22,93 +23,32 @@ #define USE_MAC_FALLBACK false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 256U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 256U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -142,38 +82,17 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif // See https://thingsboard.io/docs/user-guide/device-provisioning/ // to understand how to create a device profile to be able to provision a device -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char PROVISION_DEVICE_KEY[] PROGMEM = "YOUR_PROVISION_DEVICE_KEY"; -constexpr char PROVISION_DEVICE_SECRET[] PROGMEM = "YOUR_PROVISION_DEVICE_SECRET"; -#else constexpr char PROVISION_DEVICE_KEY[] = "YOUR_PROVISION_DEVICE_KEY"; constexpr char PROVISION_DEVICE_SECRET[] = "YOUR_PROVISION_DEVICE_SECRET"; -#endif + // Optionally keep the device name empty and the WiFi mac address of the integrated // wifi chip on ESP32 or ESP8266 will be used as the name instead // Ensuring your device name is unique, even when reusing this code for multiple devices -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char DEVICE_NAME[] PROGMEM = ""; -#else constexpr char DEVICE_NAME[] = ""; -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CREDENTIALS_TYPE[] PROGMEM = "credentialsType"; -constexpr char CREDENTIALS_VALUE[] PROGMEM = "credentialsValue"; -constexpr char CLIENT_ID[] PROGMEM = "clientId"; -constexpr char CLIENT_PASSWORD[] PROGMEM = "password"; -constexpr char CLIENT_USERNAME[] PROGMEM = "userName"; -constexpr char TEMPERATURE_KEY[] PROGMEM = "temperature"; -constexpr char HUMIDITY_KEY[] PROGMEM = "humidity"; -constexpr char ACCESS_TOKEN_CRED_TYPE[] PROGMEM = "ACCESS_TOKEN"; -constexpr char MQTT_BASIC_CRED_TYPE[] PROGMEM = "MQTT_BASIC"; -constexpr char X509_CERTIFICATE_CRED_TYPE[] PROGMEM = "X509_CERTIFICATE"; -#else constexpr char CREDENTIALS_TYPE[] = "credentialsType"; constexpr char CREDENTIALS_VALUE[] = "credentialsValue"; constexpr char CLIENT_ID[] = "clientId"; @@ -184,7 +103,6 @@ constexpr char HUMIDITY_KEY[] = "humidity"; constexpr char ACCESS_TOKEN_CRED_TYPE[] = "ACCESS_TOKEN"; constexpr char MQTT_BASIC_CRED_TYPE[] = "MQTT_BASIC"; constexpr char X509_CERTIFICATE_CRED_TYPE[] = "X509_CERTIFICATE"; -#endif // Initialize underlying client, used to establish a connection @@ -195,8 +113,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +Provision<> prov; +const std::array apis = { + &prov +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); uint32_t previous_processing_time = 0U; @@ -209,34 +132,21 @@ struct Credentials { std::string client_id; std::string username; std::string password; -}; -Credentials credentials; +} credentials; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -264,7 +174,9 @@ void setup() { previous_processing_time = millis(); } -void processProvisionResponse(const JsonObjectConst &data) { +/// @brief Process the provisioning response received from the server +/// @param data Reference to the object containing the provisioning response +void processProvisionResponse(const JsonDocument &data) { const size_t jsonSize = Helper::Measure_Json(data); char buffer[jsonSize]; serializeJson(data, buffer, jsonSize); @@ -314,19 +226,11 @@ void loop() { // Connect to the ThingsBoard server as a client wanting to provision a new device Serial.printf("Connecting to: (%s)\n", THINGSBOARD_SERVER); if (!tb.connect(THINGSBOARD_SERVER, "provision", THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending provisioning request")); -#else Serial.println("Sending provisioning request"); -#endif // Send a claiming request without any device name (random string will be used as the device name) // if the string is empty or null, automatically checked by the sendProvisionRequest method @@ -345,35 +249,23 @@ void loop() { #endif const Provision_Callback provisionCallback(Access_Token(), &processProvisionResponse, PROVISION_DEVICE_KEY, PROVISION_DEVICE_SECRET, device_name.c_str()); - provisionRequestSent = tb.Provision_Request(provisionCallback); + provisionRequestSent = prov.Provision_Request(provisionCallback); } else if (provisionResponseProcessed) { if (!tb.connected()) { // Connect to the ThingsBoard server, as the provisioned client Serial.printf("Connecting to: (%s)\n", THINGSBOARD_SERVER); if (!tb.connect(THINGSBOARD_SERVER, credentials.username.c_str(), THINGSBOARD_PORT, credentials.client_id.c_str(), credentials.password.c_str())) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif Serial.println(credentials.client_id.c_str()); Serial.println(credentials.username.c_str()); Serial.println(credentials.password.c_str()); return; } else { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected!")); -#else Serial.println("Connected!"); -#endif } } else { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Sending telemetry...")); -#else Serial.println("Sending telemetry..."); -#endif tb.sendTelemetryData(TEMPERATURE_KEY, 22); tb.sendTelemetryData(HUMIDITY_KEY, 42.5); } diff --git a/examples/0009-esp8266_esp32_process_OTA_MQTT/0009-esp8266_esp32_process_OTA_MQTT.ino b/examples/0009-esp8266_esp32_process_OTA_MQTT/0009-esp8266_esp32_process_OTA_MQTT.ino index fe65f71a..59400265 100644 --- a/examples/0009-esp8266_esp32_process_OTA_MQTT/0009-esp8266_esp32_process_OTA_MQTT.ino +++ b/examples/0009-esp8266_esp32_process_OTA_MQTT/0009-esp8266_esp32_process_OTA_MQTT.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include #ifdef ESP8266 @@ -37,123 +38,46 @@ // Firmware title and version used to compare with remote version, to check if an update is needed. // Title needs to be the same and version needs to be different --> downgrading is possible -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CURRENT_FIRMWARE_TITLE[] PROGMEM = "TEST"; -constexpr char CURRENT_FIRMWARE_VERSION[] PROGMEM = "1.0.0"; -#else constexpr char CURRENT_FIRMWARE_TITLE[] = "TEST"; constexpr char CURRENT_FIRMWARE_VERSION[] = "1.0.0"; -#endif // Maximum amount of retries we attempt to download each firmware chunck over MQTT -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint8_t FIRMWARE_FAILURE_RETRIES PROGMEM = 12U; -#else constexpr uint8_t FIRMWARE_FAILURE_RETRIES = 12U; -#endif + // Size of each firmware chunck downloaded over MQTT, // increased packet size, might increase download speed -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t FIRMWARE_PACKET_SIZE PROGMEM = 4096U; -#else constexpr uint16_t FIRMWARE_PACKET_SIZE = 4096U; -#endif -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 512U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 512U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -187,13 +111,6 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif - -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char FW_STATE_UPDATED[] PROGMEM = "UPDATED"; -#else -constexpr char FW_STATE_UPDATED[] = "UPDATED"; -#endif // THINGSBOARD_ENABLE_PROGMEM // Initialize underlying client, used to establish a connection @@ -204,8 +121,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +OTA_Firmware_Update<> ota; +const std::array apis = { + &ota +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Initalize the Updater client instance used to flash binary to flash memory #ifdef ESP8266 Arduino_ESP8266_Updater updater; @@ -223,27 +145,15 @@ bool updateRequestSent = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -263,15 +173,21 @@ bool reconnect() { return true; } -/// @brief Updated callback that will be called as soon as the firmware update finishes +/// @brief Update starting callback method that will be called as soon as the shared attribute firmware keys have been received and processed +/// and the moment before we subscribe the necessary topics for the OTA firmware update. +/// Is meant to give a moment were any additional processes or communication with the cloud can be stopped to ensure the update process runs as smooth as possible. +/// To ensure that calling the ThingsBoardSized::Cleanup_Subscriptions() method can be used which stops any receiving of data over MQTT besides the one for the OTA firmware update, +/// if this method is used ensure to call all subscribe methods again so they can be resubscribed, in the method passed to the finished_callback if the update failed and we do not restart the device +void update_starting_callback() { + // Nothing to do +} + +/// @brief End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed. +/// Is meant to allow to either restart the device if the udpate was successfull or to restart any stopped services before the update started in the subscribed update_starting_callback /// @param success Either true (update successful) or false (update failed) -void updatedCallback(const bool& success) { +void finished_callback(const bool & success) { if (success) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Done, Reboot now")); -#else Serial.println("Done, Reboot now"); -#endif #ifdef ESP8266 ESP.restart(); @@ -282,18 +198,16 @@ void updatedCallback(const bool& success) { #endif // ESP8266 return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Downloading firmware failed")); -#else Serial.println("Downloading firmware failed"); -#endif } -/// @brief Progress callback that will be called every time we downloaded a new chunk successfully -/// @param currentChunk -/// @param totalChuncks -void progressCallback(const size_t& currentChunk, const size_t& totalChuncks) { - Serial.printf("Progress %.2f%%\n", static_cast(currentChunk * 100U) / totalChuncks); +/// @brief Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, +/// meaning it will be called if the amount of already downloaded chunks increased. +/// Is meant to allow to display a progress bar or print the current progress of the update into the console with the currently already downloaded amount of chunks and the total amount of chunks +/// @param current Already received and processs amount of chunks +/// @param total Total amount of chunks we need to receive and process until the update has completed +void progress_callback(const size_t & current, const size_t & total) { + Serial.printf("Progress %.2f%%\n", static_cast(current * 100U) / total); } void setup() { @@ -315,33 +229,23 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf("Connecting to: (%s) with token (%s)\n", THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!currentFWSent) { - // Firmware state send at the start of the firmware, to inform the cloud about the current firmware and that it was installed correctly, - // especially important when using OTA update, because the OTA update sends the last firmware state as UPDATING, meaning the device is restarting - // if the device restarted correctly and has the new given firmware title and version it should then send thoose to the cloud with the state UPDATED, - // to inform any end user that the device has successfully restarted and does actually contain the version it was flashed too - currentFWSent = tb.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION) && tb.Firmware_Send_State(FW_STATE_UPDATED); + currentFWSent = ota.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION); } if (!updateRequestSent) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Firwmare Update...")); -#else Serial.println("Firwmare Update..."); -#endif - const OTA_Update_Callback callback(&progressCallback, &updatedCallback, CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); + const OTA_Update_Callback callback(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, &finished_callback, &progress_callback, &update_starting_callback, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); // See https://thingsboard.io/docs/user-guide/ota-updates/ // to understand how to create a new OTA pacakge and assign it to a device so it can download it. - updateRequestSent = tb.Start_Firmware_Update(callback); + // Sending the request again after a successfull update will automatically send the UPDATED firmware state, + // because the assigned firmware title and version on the cloud and the firmware version and title we booted into are the same. + updateRequestSent = ota.Start_Firmware_Update(callback); } tb.loop(); diff --git a/examples/0010-esp8266_esp32_rpc/0010-esp8266_esp32_rpc.ino b/examples/0010-esp8266_esp32_rpc/0010-esp8266_esp32_rpc.ino index 997df7b6..f2dc26b4 100644 --- a/examples/0010-esp8266_esp32_rpc/0010-esp8266_esp32_rpc.ino +++ b/examples/0010-esp8266_esp32_rpc/0010-esp8266_esp32_rpc.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include @@ -18,101 +19,36 @@ #define ENCRYPTED false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 256U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 256U; -#endif // Baud rate for the debugging serial connection. // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -146,21 +82,14 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr const char RPC_JSON_METHOD[] PROGMEM = "example_json"; -constexpr const char RPC_TEMPERATURE_METHOD[] PROGMEM = "example_set_temperature"; -constexpr const char RPC_SWITCH_METHOD[] PROGMEM = "example_set_switch"; -constexpr const char RPC_TEMPERATURE_KEY[] PROGMEM = "temp"; -constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch"; -#else constexpr const char RPC_JSON_METHOD[] = "example_json"; constexpr const char RPC_TEMPERATURE_METHOD[] = "example_set_temperature"; constexpr const char RPC_SWITCH_METHOD[] = "example_set_switch"; constexpr const char RPC_TEMPERATURE_KEY[] = "temp"; -constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch"; -#endif +constexpr const char RPC_SWITCH_KEY[] = "switch"; +constexpr uint8_t MAX_RPC_SUBSCRIPTIONS = 3U; +constexpr uint8_t MAX_RPC_RESPONSE = 5U; // Initialize underlying client, used to establish a connection @@ -171,8 +100,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); -// Initialize ThingsBoard instance with the maximum needed buffer size and the adjusted maximum amount of rpc key value pairs we want to send as a response -ThingsBoardSized tb(mqttClient, MAX_MESSAGE_SIZE); +// Initialize used apis +Server_Side_RPC rpc; +const std::array apis = { + &rpc +}; +// Initialize ThingsBoard instance with the maximum needed buffer size +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Statuses for subscribing to rpc bool subscribed = false; @@ -181,27 +115,15 @@ bool subscribed = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -227,11 +149,7 @@ bool reconnect() { /// @param data Data containing the rpc data that was called and its current value /// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods void processGetJson(const JsonVariantConst &data, JsonDocument &response) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Received the json RPC method")); -#else Serial.println("Received the json RPC method"); -#endif // Size of the response document needs to be configured to the size of the innerDoc + 1. StaticJsonDocument innerDoc; @@ -248,20 +166,12 @@ void processGetJson(const JsonVariantConst &data, JsonDocument &response) { /// @param data Data containing the rpc data that was called and its current value /// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods void processTemperatureChange(const JsonVariantConst &data, JsonDocument &response) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Received the set temperature RPC method")); -#else Serial.println("Received the set temperature RPC method"); -#endif // Process data const float example_temperature = data[RPC_TEMPERATURE_KEY]; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Example temperature: ")); -#else Serial.print("Example temperature: "); -#endif Serial.println(example_temperature); // Ensure to only pass values do not store by copy, or if they do increase the MaxRPC template parameter accordingly to ensure that the value can be deserialized.RPC_Callback. @@ -269,7 +179,7 @@ void processTemperatureChange(const JsonVariantConst &data, JsonDocument &respon response["string"] = "exampleResponseString"; response["int"] = 5; response["float"] = 5.0f; - response["double"] = 10.0d; + response["double"] = 10.0; response["bool"] = true; } @@ -279,20 +189,12 @@ void processTemperatureChange(const JsonVariantConst &data, JsonDocument &respon /// @param data Data containing the rpc data that was called and its current value /// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods void processSwitchChange(const JsonVariantConst &data, JsonDocument &response) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Received the set switch method")); -#else Serial.println("Received the set switch method"); -#endif // Process data const bool switch_state = data[RPC_SWITCH_KEY]; -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F("Example switch state: ")); -#else Serial.print("Example switch state: "); -#endif Serial.println(switch_state); response.set(22.02); @@ -317,22 +219,14 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf("Connecting to: (%s) with token (%s)\n", THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!subscribed) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribing for RPC...")); -#else Serial.println("Subscribing for RPC..."); -#endif - const std::array callbacks = { + const std::array callbacks = { // Requires additional memory in the JsonDocument for the JsonDocument that will be copied into the response RPC_Callback{ RPC_JSON_METHOD, processGetJson }, // Requires additional memory in the JsonDocument for 5 key-value pairs that do not copy their value into the JsonDocument itself @@ -343,20 +237,12 @@ void loop() { // Perform a subscription. All consequent data processing will happen in // processTemperatureChange() and processSwitchChange() functions, // as denoted by callbacks array. - if (!tb.RPC_Subscribe(callbacks.cbegin(), callbacks.cend())) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to subscribe for RPC")); -#else + if (!rpc.RPC_Subscribe(callbacks.cbegin(), callbacks.cend())) { Serial.println("Failed to subscribe for RPC"); -#endif return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Subscribe done")); -#else Serial.println("Subscribe done"); -#endif subscribed = true; } diff --git a/examples/0011-esp8266_esp32_subscribe_OTA_MQTT/0011-esp8266_esp32_subscribe_OTA_MQTT.ino b/examples/0011-esp8266_esp32_subscribe_OTA_MQTT/0011-esp8266_esp32_subscribe_OTA_MQTT.ino index b2557e9f..3002f9ac 100644 --- a/examples/0011-esp8266_esp32_subscribe_OTA_MQTT/0011-esp8266_esp32_subscribe_OTA_MQTT.ino +++ b/examples/0011-esp8266_esp32_subscribe_OTA_MQTT/0011-esp8266_esp32_subscribe_OTA_MQTT.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include #ifdef ESP8266 @@ -37,123 +38,41 @@ // Firmware title and version used to compare with remote version, to check if an update is needed. // Title needs to be the same and version needs to be different --> downgrading is possible -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char CURRENT_FIRMWARE_TITLE[] PROGMEM = "TEST"; -constexpr char CURRENT_FIRMWARE_VERSION[] PROGMEM = "1.0.0"; -#else constexpr char CURRENT_FIRMWARE_TITLE[] = "TEST"; constexpr char CURRENT_FIRMWARE_VERSION[] = "1.0.0"; -#endif // Maximum amount of retries we attempt to download each firmware chunck over MQTT -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint8_t FIRMWARE_FAILURE_RETRIES PROGMEM = 12U; -#else constexpr uint8_t FIRMWARE_FAILURE_RETRIES = 12U; -#endif // Size of each firmware chunck downloaded over MQTT, // increased packet size, might increase download speed -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t FIRMWARE_PACKET_SIZE PROGMEM = 4096U; -#else constexpr uint16_t FIRMWARE_PACKET_SIZE = 4096U; -#endif -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port -#if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else -constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; -#else constexpr uint16_t THINGSBOARD_PORT = 1883U; -#endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 512U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 512U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -187,13 +106,6 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif - -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char FW_STATE_UPDATED[] PROGMEM = "UPDATED"; -#else -constexpr char FW_STATE_UPDATED[] = "UPDATED"; -#endif // THINGSBOARD_ENABLE_PROGMEM // Initialize underlying client, used to establish a connection @@ -204,8 +116,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +OTA_Firmware_Update<> ota; +const std::array apis = { + &ota +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Initalize the Updater client instance used to flash binary to flash memory #ifdef ESP8266 Arduino_ESP8266_Updater updater; @@ -223,27 +140,15 @@ bool updateRequestSent = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -263,15 +168,21 @@ bool reconnect() { return true; } -/// @brief Updated callback that will be called as soon as the firmware update finishes +/// @brief Update starting callback method that will be called as soon as the shared attribute firmware keys have been received and processed +/// and the moment before we subscribe the necessary topics for the OTA firmware update. +/// Is meant to give a moment were any additional processes or communication with the cloud can be stopped to ensure the update process runs as smooth as possible. +/// To ensure that calling the ThingsBoardSized::Cleanup_Subscriptions() method can be used which stops any receiving of data over MQTT besides the one for the OTA firmware update, +/// if this method is used ensure to call all subscribe methods again so they can be resubscribed, in the method passed to the finished_callback if the update failed and we do not restart the device +void update_starting_callback() { + // Nothing to do +} + +/// @brief End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed. +/// Is meant to allow to either restart the device if the udpate was successfull or to restart any stopped services before the update started in the subscribed update_starting_callback /// @param success Either true (update successful) or false (update failed) -void updatedCallback(const bool& success) { +void finished_callback(const bool & success) { if (success) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Done, Reboot now")); -#else Serial.println("Done, Reboot now"); -#endif #ifdef ESP8266 ESP.restart(); @@ -282,18 +193,16 @@ void updatedCallback(const bool& success) { #endif // ESP8266 return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Downloading firmware failed")); -#else Serial.println("Downloading firmware failed"); -#endif } -/// @brief Progress callback that will be called every time we downloaded a new chunk successfully -/// @param currentChunk -/// @param totalChuncks -void progressCallback(const size_t& currentChunk, const size_t& totalChuncks) { - Serial.printf("Progress %.2f%%\n", static_cast(currentChunk * 100U) / totalChuncks); +/// @brief Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, +/// meaning it will be called if the amount of already downloaded chunks increased. +/// Is meant to allow to display a progress bar or print the current progress of the update into the console with the currently already downloaded amount of chunks and the total amount of chunks +/// @param current Already received and processs amount of chunks +/// @param total Total amount of chunks we need to receive and process until the update has completed +void progress_callback(const size_t & current, const size_t & total) { + Serial.printf("Progress %.2f%%\n", static_cast(current * 100U) / total); } void setup() { @@ -315,33 +224,23 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf("Connecting to: (%s) with token (%s)\n", THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!currentFWSent) { - // Firmware state send at the start of the firmware, to inform the cloud about the current firmware and that it was installed correctly, - // especially important when using OTA update, because the OTA update sends the last firmware state as UPDATING, meaning the device is restarting - // if the device restarted correctly and has the new given firmware title and version it should then send thoose to the cloud with the state UPDATED, - // to inform any end user that the device has successfully restarted and does actually contain the version it was flashed too - currentFWSent = tb.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION) && tb.Firmware_Send_State(FW_STATE_UPDATED); + currentFWSent = ota.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION); } if (!updateRequestSent) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Firwmare Update Subscription...")); -#else Serial.println("Firwmare Update Subscription..."); -#endif - const OTA_Update_Callback callback(&progressCallback, &updatedCallback, CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); + const OTA_Update_Callback callback(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, &finished_callback, &progress_callback, &update_starting_callback, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); // See https://thingsboard.io/docs/user-guide/ota-updates/ // to understand how to create a new OTA pacakge and assign it to a device so it can download it. - updateRequestSent = tb.Subscribe_Firmware_Update(callback); + // Sending the request again after a successfull update will automatically send the UPDATED firmware state, + // because the assigned firmware title and version on the cloud and the firmware version and title we booted into are the same. + updateRequestSent = ota.Subscribe_Firmware_Update(callback); } tb.loop(); diff --git a/examples/0012-esp8266_esp32_request_shared_attribute/0012-esp8266_esp32_request_shared_attribute.ino b/examples/0012-esp8266_esp32_request_shared_attribute/0012-esp8266_esp32_request_shared_attribute.ino index 539f340d..14aebeac 100644 --- a/examples/0012-esp8266_esp32_request_shared_attribute/0012-esp8266_esp32_request_shared_attribute.ino +++ b/examples/0012-esp8266_esp32_request_shared_attribute/0012-esp8266_esp32_request_shared_attribute.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include @@ -18,109 +19,40 @@ #define ENCRYPTED false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; -#endif // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 256U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 256U; -#endif // Baud rate for the debugging serial connection // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif // Maximum amount of attributs we can request or subscribe, has to be set both in the ThingsBoard template list and Attribute_Request_Callback template list // and should be the same as the amount of variables in the passed array. If it is less not all variables will be requested or subscribed -#if THINGSBOARD_ENABLE_PROGMEM -constexpr size_t MAX_ATTRIBUTES PROGMEM = 6U; -#else constexpr size_t MAX_ATTRIBUTES = 6U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -154,15 +86,15 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr const char FW_TAG_KEY[] PROGMEM = "fw_tag"; -constexpr const char TEST_KEY[] PROGMEM = "test"; -#else constexpr const char FW_TAG_KEY[] = "fw_tag"; constexpr const char TEST_KEY[] = "test"; -#endif +char constexpr FW_VER_KEY[] = "fw_version"; +char constexpr FW_TITLE_KEY[] = "fw_title"; +char constexpr FW_CHKS_KEY[] = "fw_checksum"; +char constexpr FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm"; +char constexpr FW_SIZE_KEY[] = "fw_size"; +constexpr uint64_t REQUEST_TIMEOUT_MICROSECONDS = 5000U * 1000U; // Initialize underlying client, used to establish a connection @@ -173,8 +105,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +Attribute_Request<2U, MAX_ATTRIBUTES> attr_request; +const std::array apis = { + &attr_request +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoardSized tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Statuses for requesting of attributes bool requestedClient = false; @@ -184,27 +121,15 @@ bool requestedShared = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -224,6 +149,11 @@ bool reconnect() { return true; } +/// @brief Attribute request did not receive a response in the expected amount of microseconds +void requestTimedOut() { + Serial.printf("Attribute request timed out did not receive a response in (%llu) microseconds. Ensure client is connected to the MQTT broker and that the keys actually exist on the target device\n", REQUEST_TIMEOUT_MICROSECONDS); +} + /// @brief Update callback that will be called as soon as the requested shared attributes, have been received. /// The callback will then not be called anymore unless it is reused for another request /// @param data Data containing the shared attributes that were requested and their current value @@ -275,50 +205,30 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf("Connecting to: (%s) with token (%s)\n", THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!requestedShared) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Requesting shared attributes...")); -#else Serial.println("Requesting shared attributes..."); -#endif // Shared attributes we want to request from the server constexpr std::array REQUESTED_SHARED_ATTRIBUTES = {FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TAG_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Attribute_Request_Callback sharedCallback(&processSharedAttributeRequest, REQUESTED_SHARED_ATTRIBUTES.cbegin(), REQUESTED_SHARED_ATTRIBUTES.cend()); - requestedShared = tb.Shared_Attributes_Request(sharedCallback); + const Attribute_Request_Callback sharedCallback(&processSharedAttributeRequest, REQUEST_TIMEOUT_MICROSECONDS, &requestTimedOut, REQUESTED_SHARED_ATTRIBUTES); + requestedShared = attr_request.Shared_Attributes_Request(sharedCallback); if (!requestedShared) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to request shared attributes")); -#else Serial.println("Failed to request shared attributes"); -#endif } } if (!requestedClient) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Requesting client-side attributes...")); -#else Serial.println("Requesting client-side attributes..."); -#endif // Client-side attributes we want to request from the server - constexpr std::array REQUESTED_CLIENT_ATTRIBUTES = {TEST_KEY}; - const Attribute_Request_Callback clientCallback(&processClientAttributeRequest, REQUESTED_CLIENT_ATTRIBUTES.cbegin(), REQUESTED_CLIENT_ATTRIBUTES.cend()); - requestedClient = tb.Client_Attributes_Request(clientCallback); + const std::vector REQUESTED_CLIENT_ATTRIBUTES = {TEST_KEY}; + const Attribute_Request_Callback clientCallback(&processClientAttributeRequest, REQUEST_TIMEOUT_MICROSECONDS, &requestTimedOut, REQUESTED_CLIENT_ATTRIBUTES); + requestedClient = attr_request.Client_Attributes_Request(clientCallback); if (!requestedClient) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to request client-side attributes")); -#else Serial.println("Failed to request client-side attributes"); -#endif } } diff --git a/examples/0013-esp8266_esp32_request_rpc/0013-esp8266_esp32_request_rpc.ino b/examples/0013-esp8266_esp32_request_rpc/0013-esp8266_esp32_request_rpc.ino index 83930da3..f1c0261f 100644 --- a/examples/0013-esp8266_esp32_request_rpc/0013-esp8266_esp32_request_rpc.ino +++ b/examples/0013-esp8266_esp32_request_rpc/0013-esp8266_esp32_request_rpc.ino @@ -8,6 +8,7 @@ #endif // ESP8266 #include +#include #include @@ -18,101 +19,36 @@ #define ENCRYPTED false -// PROGMEM can only be added when using the ESP32 WiFiClient, -// will cause a crash if using the ESP8266WiFiSTAClass instead. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char WIFI_SSID[] PROGMEM = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] PROGMEM = "YOUR_WIFI_PASSWORD"; -#else constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; -#endif // See https://thingsboard.io/docs/getting-started-guides/helloworld/ // to understand how to obtain an access token -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char TOKEN[] PROGMEM = "YOUR_DEVICE_ACCESS_TOKEN"; -#else constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -#endif // Thingsboard we want to establish a connection too -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#else -constexpr char THINGSBOARD_SERVER[] PROGMEM = "demo.thingsboard.io"; -#endif +constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U; -#else constexpr uint16_t THINGSBOARD_PORT = 8883U; -#endif -#else -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif -#endif // Maximum size packets will ever be sent or received by the underlying MQTT client, // if the size is to small messages might not be sent or received messages will be discarded -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 256U; -#else constexpr uint16_t MAX_MESSAGE_SIZE = 256U; -#endif // Baud rate for the debugging serial connection. // If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable -#if THINGSBOARD_ENABLE_PROGMEM -constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U; -#else constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; -#endif #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, // this is needed to establish a secure connection and changes depending on the website. -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)"; -#else constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -146,13 +82,11 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- )"; #endif -#endif -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char RPC_REQUEST_CALLBACK_METHOD_NAME[] PROGMEM = "getCurrentTime"; -#else constexpr char RPC_REQUEST_CALLBACK_METHOD_NAME[] = "getCurrentTime"; -#endif +constexpr uint8_t MAX_RPC_SUBSCRIPTIONS = 3U; +constexpr uint8_t MAX_RPC_REQUEST = 5U; +constexpr uint64_t REQUEST_TIMEOUT_MICROSECONDS = 5000U * 1000U; // Initialize underlying client, used to establish a connection @@ -163,8 +97,13 @@ WiFiClient espClient; #endif // Initalize the Mqtt client instance Arduino_MQTT_Client mqttClient(espClient); +// Initialize used apis +Client_Side_RPC rpc_request; +const std::array apis = { + &rpc_request +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Statuses for subscribing to rpc bool subscribed = false; @@ -173,27 +112,15 @@ bool subscribed = false; /// @brief Initalizes WiFi connection, // will endlessly delay until a connection has been successfully established void InitWiFi() { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connecting to AP ...")); -#else Serial.println("Connecting to AP ..."); -#endif // Attempting to establish a connection to the given WiFi network WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { // Delay 500ms until a connection has been successfully established delay(500); -#if THINGSBOARD_ENABLE_PROGMEM - Serial.print(F(".")); -#else Serial.print("."); -#endif } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Connected to AP")); -#else Serial.println("Connected to AP"); -#endif #if ENCRYPTED espClient.setCACert(ROOT_CERT); #endif @@ -213,10 +140,15 @@ bool reconnect() { return true; } +/// @brief Attribute request did not receive a response in the expected amount of microseconds +void requestTimedOut() { + Serial.printf("RPC request timed out did not receive a response in (%llu) microseconds. Ensure client is connected to the MQTT broker and that the RPC method actually exist on the device Rule chain\n", REQUEST_TIMEOUT_MICROSECONDS); +} + /// @brief Processes function for RPC response of "getCurrentTime". /// If no response is set the callback is called with {"error": "timeout"}, after a few seconds /// @param data Data containing the rpc response that was sent by the cloud -void processTime(const JsonVariantConst &data) { +void processTime(JsonDocument const & data) { serializeJsonPretty(data, Serial); } @@ -239,30 +171,18 @@ void loop() { // if a connection was disrupted or has not yet been established Serial.printf("Connecting to: (%s) with token (%s)\n", THINGSBOARD_SERVER, TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to connect")); -#else Serial.println("Failed to connect"); -#endif return; } } if (!subscribed) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Requesting RPC...")); -#else Serial.println("Requesting RPC..."); -#endif // RPC Request without any parameters - RPC_Request_Callback callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &processTime); + RPC_Request_Callback callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &processTime, nullptr, REQUEST_TIMEOUT_MICROSECONDS, &requestTimedOut); // Perform a request of the given RPC method. Optional responses are handled in processTime - if (!tb.RPC_Request(callback)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to request for RPC without arguments")); -#else + if (!rpc_request.RPC_Request(callback)) { Serial.println("Failed to request for RPC without arguments"); -#endif return; } @@ -272,14 +192,10 @@ void loop() { array.add("example"); array.add(true); array.add(145); - callback = RPC_Request_Callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &array, &processTime); + callback = RPC_Request_Callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &processTime, &array, REQUEST_TIMEOUT_MICROSECONDS, &requestTimedOut); // Perform a request of the given RPC method. Optional responses are handled in processTime - if (!tb.RPC_Request(callback)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to request for RPC with multiple arguments")); -#else + if (!rpc_request.RPC_Request(callback)) { Serial.println("Failed to request for RPC with multiple arguments"); -#endif return; } @@ -289,22 +205,14 @@ void loop() { array = doc2.to(); innerDoc["example"] = "test"; array.add(innerDoc); - callback = RPC_Request_Callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &array, &processTime); + callback = RPC_Request_Callback(RPC_REQUEST_CALLBACK_METHOD_NAME, &processTime, &array, REQUEST_TIMEOUT_MICROSECONDS, &requestTimedOut); // Perform a request of the given RPC method. Optional responses are handled in processTime - if (!tb.RPC_Request(callback)) { -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Failed to request for RPC with one inner json argument")); -#else + if (!rpc_request.RPC_Request(callback)) { Serial.println("Failed to request for RPC with one inner json argument"); -#endif return; } -#if THINGSBOARD_ENABLE_PROGMEM - Serial.println(F("Request done")); -#else Serial.println("Request done"); -#endif subscribed = true; } diff --git a/examples/0014-espressif_esp32_send_data/main/0014-espressif_esp32_send_data.cpp b/examples/0014-espressif_esp32_send_data/main/0014-espressif_esp32_send_data.cpp index c8803b5e..c49b7bbf 100644 --- a/examples/0014-espressif_esp32_send_data/main/0014-espressif_esp32_send_data.cpp +++ b/examples/0014-espressif_esp32_send_data/main/0014-espressif_esp32_send_data.cpp @@ -16,9 +16,6 @@ #include -// Examples using arduino used PROGMEM to save constants into flash memory, -// this is not needed when using Espressif IDF because per default -// all read only variables will be saved into DROM (flash memory). // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#drom-data-stored-in-flash // for more information about the aforementioned feature constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; diff --git a/examples/0014-espressif_esp32_send_data/main/CMakeLists.txt b/examples/0014-espressif_esp32_send_data/main/CMakeLists.txt index f0a11d61..dc09be97 100644 --- a/examples/0014-espressif_esp32_send_data/main/CMakeLists.txt +++ b/examples/0014-espressif_esp32_send_data/main/CMakeLists.txt @@ -5,7 +5,6 @@ set(srcs 0014-espressif_esp32_send_data.cpp ../../../src/Arduino_HTTP_Client.cpp ../../../src/Arduino_MQTT_Client.cpp - ../../../src/Callback_Watchdog.cpp ../../../src/Arduino_ESP32_Updater.cpp ../../../src/Arduino_ESP8266_Updater.cpp ../../../src/Espressif_Updater.cpp diff --git a/examples/0015-espressif_esp32_process_OTA_MQTT/main/0015-espressif_esp32_process_OTA_MQTT.cpp b/examples/0015-espressif_esp32_process_OTA_MQTT/main/0015-espressif_esp32_process_OTA_MQTT.cpp index b8408fd7..a059a9f4 100644 --- a/examples/0015-espressif_esp32_process_OTA_MQTT/main/0015-espressif_esp32_process_OTA_MQTT.cpp +++ b/examples/0015-espressif_esp32_process_OTA_MQTT/main/0015-espressif_esp32_process_OTA_MQTT.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "esp_ota_ops.h" @@ -28,9 +29,6 @@ constexpr uint8_t FIRMWARE_FAILURE_RETRIES = 12U; // increased packet size, might increase download speed constexpr uint16_t FIRMWARE_PACKET_SIZE = 4096U; -// Examples using arduino used PROGMEM to save constants into flash memory, -// this is not needed when using Espressif IDF because per default -// all read only variables will be saved into DROM (flash memory). // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#drom-data-stored-in-flash // for more information about the aforementioned feature constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; @@ -99,14 +97,18 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= )"; #endif -constexpr char FW_STATE_UPDATED[] = "UPDATED"; constexpr char UPDAT_FILE_PATH[] = "/sd/update.bin"; // Initalize the Mqtt client instance Espressif_MQTT_Client mqttClient; +// Initialize used apis +OTA_Firmware_Update<> ota; +const std::array apis = { + &ota +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); // Initalize the Updater client instance used to flash binary to flash memory SDCard_Updater updater(UPDAT_FILE_PATH); @@ -176,9 +178,19 @@ void otaSDToFlashTask(void* pvParameter) { } } -/// @brief Updated callback that will be called as soon as the firmware update finishes +/// @brief Update starting callback method that will be called as soon as the shared attribute firmware keys have been received and processed +/// and the moment before we subscribe the necessary topics for the OTA firmware update. +/// Is meant to give a moment were any additional processes or communication with the cloud can be stopped to ensure the update process runs as smooth as possible. +/// To ensure that calling the ThingsBoardSized::Cleanup_Subscriptions() method can be used which stops any receiving of data over MQTT besides the one for the OTA firmware update, +/// if this method is used ensure to call all subscribe methods again so they can be resubscribed, in the method passed to the finished_callback if the update failed and we do not restart the device +void update_starting_callback() { + // Nothing to do +} + +/// @brief End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed. +/// Is meant to allow to either restart the device if the udpate was successfull or to restart any stopped services before the update started in the subscribed update_starting_callback /// @param success Either true (update successful) or false (update failed) -void updatedCallback(const bool& success) { +void finished_callback(const bool & success) { if (success) { ESP_LOGI("MAIN", "Done updated to sd card. Write from SD card to flash"); xTaskCreate(otaSDToFlashTask, "OTA_SD_TO_FLASH", FIRMWARE_PACKET_SIZE + 1024 * 1, NULL, 16, NULL); @@ -187,11 +199,13 @@ void updatedCallback(const bool& success) { ESP_LOGI("MAIN", "Downloading firmware failed"); } -/// @brief Progress callback that will be called every time we downloaded a new chunk successfully -/// @param currentChunk -/// @param totalChuncks -void progressCallback(const size_t& currentChunk, const size_t& totalChuncks) { - ESP_LOGI("MAIN", "Downwloading firmware progress %.2f%%", static_cast(currentChunk * 100U) / totalChuncks); +/// @brief Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, +/// meaning it will be called if the amount of already downloaded chunks increased. +/// Is meant to allow to display a progress bar or print the current progress of the update into the console with the currently already downloaded amount of chunks and the total amount of chunks +/// @param current Already received and processs amount of chunks +/// @param total Total amount of chunks we need to receive and process until the update has completed +void progress_callback(const size_t & current, const size_t & total) { + ESP_LOGI("MAIN", "Downwloading firmware progress %.2f%%", static_cast(current * 100U) / total); } /// @brief Callback method that is called if we got an ip address from the connected WiFi meaning we successfully established a connection @@ -259,18 +273,16 @@ extern "C" void app_main() { } if (!currentFWSent) { - // Firmware state send at the start of the firmware, to inform the cloud about the current firmware and that it was installed correctly, - // especially important when using OTA update, because the OTA update sends the last firmware state as UPDATING, meaning the device is restarting - // if the device restarted correctly and has the new given firmware title and version it should then send thoose to the cloud with the state UPDATED, - // to inform any end user that the device has successfully restarted and does actually contain the version it was flashed too - currentFWSent = tb.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION) && tb.Firmware_Send_State(FW_STATE_UPDATED); + currentFWSent = ota.Firmware_Send_Info(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION); } if (!updateRequestSent) { - const OTA_Update_Callback callback(&progressCallback, &updatedCallback, CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); + const OTA_Update_Callback callback(CURRENT_FIRMWARE_TITLE, CURRENT_FIRMWARE_VERSION, &updater, &finished_callback, &progress_callback, &update_starting_callback, FIRMWARE_FAILURE_RETRIES, FIRMWARE_PACKET_SIZE); // See https://thingsboard.io/docs/user-guide/ota-updates/ // to understand how to create a new OTA pacakge and assign it to a device so it can download it. - updateRequestSent = tb.Start_Firmware_Update(callback); + // Sending the request again after a successfull update will automatically send the UPDATED firmware state, + // because the assigned firmware title and version on the cloud and the firmware version and title we booted into are the same. + updateRequestSent = ota.Start_Firmware_Update(callback); } tb.loop(); diff --git a/examples/0015-espressif_esp32_process_OTA_MQTT/main/CMakeLists.txt b/examples/0015-espressif_esp32_process_OTA_MQTT/main/CMakeLists.txt index f9b4fcf3..76854676 100644 --- a/examples/0015-espressif_esp32_process_OTA_MQTT/main/CMakeLists.txt +++ b/examples/0015-espressif_esp32_process_OTA_MQTT/main/CMakeLists.txt @@ -5,7 +5,6 @@ set(srcs 0015-espressif_esp32_process_OTA_MQTT.cpp ../../../src/Arduino_HTTP_Client.cpp ../../../src/Arduino_MQTT_Client.cpp - ../../../src/Callback_Watchdog.cpp ../../../src/Arduino_ESP32_Updater.cpp ../../../src/Arduino_ESP8266_Updater.cpp ../../../src/Espressif_Updater.cpp diff --git a/examples/0016-espressif_esp32_rpc/.gitignore b/examples/0016-espressif_esp32_rpc/.gitignore deleted file mode 100644 index 4b7a730c..00000000 --- a/examples/0016-espressif_esp32_rpc/.gitignore +++ /dev/null @@ -1,162 +0,0 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -.config -*.o -*.pyc - -# gtags -GTAGS -GRTAGS -GPATH - -# emacs -.dir-locals.el - -# emacs temp file suffixes -*~ -.#* -\#*# - -# eclipse setting -.settings - -# MacOS directory files -.DS_Store - -# cache dir -.cache/ - -# Components Unit Test Apps files -components/**/build/ -components/**/build_*_*/ -components/**/sdkconfig -components/**/sdkconfig.old - -# Example project files -examples/**/build/ -examples/**/build_esp*_*/ -examples/**/sdkconfig -examples/**/sdkconfig.old - -# Doc build artifacts -docs/_build/ -docs/doxygen_sqlite3.db - -# Downloaded font files -docs/_static/DejaVuSans.ttf -docs/_static/NotoSansSC-Regular.otf - -# sdkconfig -sdkconfig.old -sdkconfig - -# Unit test app files -tools/unit-test-app/sdkconfig -tools/unit-test-app/sdkconfig.old -tools/unit-test-app/build -tools/unit-test-app/build_*_*/ -tools/unit-test-app/output -tools/unit-test-app/test_configs - -# Unit Test CMake compile log folder -log_ut_cmake - -# test application build files -tools/test_apps/**/build/ -tools/test_apps/**/build_*_*/ -tools/test_apps/**/sdkconfig -tools/test_apps/**/sdkconfig.old - -TEST_LOGS - -# gcov coverage reports -*.gcda -*.gcno -coverage.info -coverage_report/ - -test_multi_heap_host - -# VS Code Settings -.vscode/ - -# VIM files -*.swp -*.swo - -# Clion IDE CMake build & config -.idea/ -cmake-build-*/ - -# Results for the checking of the Python coding style and static analysis -.mypy_cache -flake8_output.txt - -# ESP-IDF default build directory name -build - -# lock files for examples and components -dependencies.lock - -# managed_components for examples -managed_components - -# pytest log -pytest_embedded_log/ - -# Squareline Studio -SquarelineStudio/SquarelineStudio_Project/backup/ -SquarelineStudio/SquarelineStudio_Project/autosave/ -SquarelineStudio/SquarelineStudio_Project/cache/ -SquarelineStudio/SquarelineStudio_Project/export/ \ No newline at end of file diff --git a/examples/0016-espressif_esp32_rpc/main/0016-espressif_esp32_rpc.cpp b/examples/0016-espressif_esp32_rpc/main/0016-espressif_esp32_rpc.cpp index b0b2e097..59819e70 100644 --- a/examples/0016-espressif_esp32_rpc/main/0016-espressif_esp32_rpc.cpp +++ b/examples/0016-espressif_esp32_rpc/main/0016-espressif_esp32_rpc.cpp @@ -11,11 +11,10 @@ #define ENCRYPTED false #include +#include #include -// Examples using arduino used PROGMEM to save constants into flash memory, -// this is not needed when using Espressif IDF because per default -// all read only variables will be saved into DROM (flash memory). + // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#drom-data-stored-in-flash // for more information about the aforementioned feature constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; @@ -78,88 +77,159 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= )"; #endif +constexpr const char RPC_JSON_METHOD[] = "example_json"; constexpr char RPC_TEMPERATURE_METHOD[] = "example_set_temperature"; constexpr char RPC_SWITCH_METHOD[] = "example_set_switch"; constexpr char RPC_TEMPERATURE_KEY[] = "temp"; constexpr char RPC_SWITCH_KEY[] = "switch"; constexpr char RPC_RESPONSE_KEY[] = "example_response"; +constexpr uint8_t MAX_RPC_SUBSCRIPTIONS = 3U; +constexpr uint8_t MAX_RPC_RESPONSE = 5U; // Initalize the Mqtt client instance Espressif_MQTT_Client mqttClient; +// Initialize used apis +Server_Side_RPC rpc; +const std::array apis = { + &rpc +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); +// Status for successfully connecting to the given WiFi +bool wifi_connected = false; // Statuses for subscribing to rpc bool subscribed = false; -static void on_got_ip(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - ESP_LOGI("RPC Example", "Got IP"); +/// @brief Callback method that is called if we got an ip address from the connected WiFi meaning we successfully established a connection +/// @param event_handler_arg User data registered to the event +/// @param event_base Event base for the handler +/// @param event_id The id for the received event +/// @param event_data The data for the event, esp_event_handler_t +void on_got_ip(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + wifi_connected = true; } +/// @brief Initalizes WiFi connection, +// will endlessly delay until a connection has been successfully established void InitWiFi() { - ESP_ERROR_CHECK(nvs_flash_init()); - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - esp_netif_create_default_wifi_sta(); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); - - wifi_config_t wifi_config = {}; - strcpy((char*)wifi_config.sta.ssid, WIFI_SSID); - strcpy((char*)wifi_config.sta.password, WIFI_PASSWORD); - - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + const wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_WIFI_STA(); + esp_netif_t *netif = esp_netif_new(&netif_config); + assert(netif); + + ESP_ERROR_CHECK(esp_netif_attach_wifi_station(netif)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ip_event_t::IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_default_wifi_sta_handlers()); + ESP_ERROR_CHECK(esp_wifi_set_storage(wifi_storage_t::WIFI_STORAGE_RAM)); + + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config)); + strncpy(reinterpret_cast(wifi_config.sta.ssid), WIFI_SSID, strlen(WIFI_SSID) + 1); + strncpy(reinterpret_cast(wifi_config.sta.password), WIFI_PASSWORD, strlen(WIFI_PASSWORD) + 1); + + ESP_LOGI("MAIN", "Connecting to %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(wifi_mode_t::WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_connect()); } -RPC_Response processTemperatureChange(const RPC_Data &data) { - float example_temperature = data[RPC_TEMPERATURE_KEY]; +/// @brief Processes function for RPC call "example_json" +/// JsonVariantConst is a JSON variant, that can be queried using operator[] +/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details +/// @param data Data containing the rpc data that was called and its current value +/// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods +void processGetJson(const JsonVariantConst &data, JsonDocument &response) { + ESP_LOGI("RPC Example", "Received the json RPC method"); + + // Size of the response document needs to be configured to the size of the innerDoc + 1. + StaticJsonDocument innerDoc; + innerDoc["string"] = "exampleResponseString"; + innerDoc["int"] = 5; + innerDoc["float"] = 5.0f; + innerDoc["bool"] = true; + response["json_data"] = innerDoc; +} + +/// @brief Processes function for RPC call "example_set_temperature" +/// JsonVariantConst is a JSON variant, that can be queried using operator[] +/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details +/// @param data Data containing the rpc data that was called and its current value +/// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods +void processTemperatureChange(const JsonVariantConst &data, JsonDocument &response) { + const float example_temperature = data[RPC_TEMPERATURE_KEY]; ESP_LOGI("RPC Example", "Received the set temperature RPC method: %f", example_temperature); - StaticJsonDocument doc; - doc[RPC_RESPONSE_KEY] = 42; - return RPC_Response(doc); + // Ensure to only pass values do not store by copy, or if they do increase the MaxRPC template parameter accordingly to ensure that the value can be deserialized. + // See https://arduinojson.org/v6/api/jsondocument/add/ for more information on which variables cause a copy to be created + response["string"] = "exampleResponseString"; + response["int"] = 5; + response["float"] = 5.0f; + response["double"] = 10.0; + response["bool"] = true; } -RPC_Response processSwitchChange(const RPC_Data &data) { - bool switch_state = data[RPC_SWITCH_KEY]; +/// @brief Processes function for RPC call "example_set_switch" +/// JsonVariantConst is a JSON variant, that can be queried using operator[] +/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details +/// @param data Data containing the rpc data that was called and its current value +/// @param response Data containgin the response value, any number, string or json, that should be sent to the cloud. Useful for getMethods +void processSwitchChange(const JsonVariantConst &data, JsonDocument &response) { + // Process data + const bool switch_state = data[RPC_SWITCH_KEY]; ESP_LOGI("RPC Example", "Received the set switch method: %d", switch_state); - - StaticJsonDocument doc; - doc[RPC_RESPONSE_KEY] = 22.02; - return RPC_Response(doc); + response.set(22.02); } extern "C" void app_main(void) { + ESP_LOGI("MAIN", "[APP] Startup.."); + ESP_LOGI("MAIN", "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI("MAIN", "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + InitWiFi(); - const std::array callbacks = { - RPC_Callback{RPC_TEMPERATURE_METHOD, processTemperatureChange}, - RPC_Callback{RPC_SWITCH_METHOD, processSwitchChange} - }; +#if ENCRYPTED + mqttClient.set_server_certificate(ROOT_CERT); +#endif // ENCRYPTED + + for (;;) { + // Wait until we connected to WiFi + if (!wifi_connected) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + continue; + } - while (true) { if (!tb.connected()) { - ESP_LOGI("RPC Example", "Connecting to: %s with token %s", THINGSBOARD_SERVER, TOKEN); - if (tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { - ESP_LOGI("RPC Example", "Successfully connected"); - if (!subscribed && tb.RPC_Subscribe(callbacks.begin(), callbacks.end())) { - ESP_LOGI("RPC Example", "Subscribed to RPC commands"); - subscribed = true; - } - } else { - ESP_LOGE("RPC Example", "Failed to connect"); - } + tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT); + } + + if (!subscribed) { + const std::array callbacks = { + // Requires additional memory in the JsonDocument for the JsonDocument that will be copied into the response + RPC_Callback{ RPC_JSON_METHOD, processGetJson }, + // Requires additional memory in the JsonDocument for 5 key-value pairs that do not copy their value into the JsonDocument itself + RPC_Callback{ RPC_TEMPERATURE_METHOD, processTemperatureChange }, + // Internal size can be 0, because if we use the JsonDocument as a JsonVariant and then set the value we do not require additional memory + RPC_Callback{ RPC_SWITCH_METHOD, processSwitchChange } + }; + // Perform a subscription. All consequent data processing will happen in + // processTemperatureChange() and processSwitchChange() functions, + // as denoted by callbacks array. + subscribed = rpc.RPC_Subscribe(callbacks.begin(), callbacks.end()); } tb.loop(); - vTaskDelay(pdMS_TO_TICKS(1000)); + + vTaskDelay(10000 / portTICK_PERIOD_MS); } } diff --git a/examples/0016-espressif_esp32_rpc/main/CMakeLists.txt b/examples/0016-espressif_esp32_rpc/main/CMakeLists.txt index c8ad39ba..fb073797 100644 --- a/examples/0016-espressif_esp32_rpc/main/CMakeLists.txt +++ b/examples/0016-espressif_esp32_rpc/main/CMakeLists.txt @@ -5,22 +5,17 @@ set(srcs 0016-espressif_esp32_rpc.cpp ../../../src/Arduino_HTTP_Client.cpp ../../../src/Arduino_MQTT_Client.cpp - ../../../src/Attribute_Request_Callback.cpp - ../../../src/Callback_Watchdog.cpp ../../../src/Arduino_ESP32_Updater.cpp ../../../src/Arduino_ESP8266_Updater.cpp ../../../src/Espressif_Updater.cpp + ../../../src/SDCard_Updater.cpp ../../../src/Espressif_MQTT_Client.cpp ../../../src/HashGenerator.cpp ../../../src/Helper.cpp ../../../src/OTA_Update_Callback.cpp ../../../src/Provision_Callback.cpp - ../../../src/RPC_Callback.cpp ../../../src/RPC_Request_Callback.cpp - ../../../src/RPC_Response.cpp - ../../../src/Shared_Attribute_Callback.cpp ../../../src/Telemetry.cpp - ../../../src/ThingsBoardDefaultLogger.cpp ) idf_component_register( diff --git a/examples/0016-espressif_esp32_rpc/sdkconfig b/examples/0016-espressif_esp32_rpc/sdkconfig new file mode 100644 index 00000000..685f270f --- /dev/null +++ b/examples/0016-espressif_esp32_rpc/sdkconfig @@ -0,0 +1,532 @@ +# +# SDK tool configuration +# +CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-" + +# +# Bootloader config +# +CONFIG_BOOTLOADER_LOG_LEVEL_NONE= +CONFIG_BOOTLOADER_LOG_LEVEL_ERROR= +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL_INFO= +CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG= +CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE= +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V= +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y + +# +# Security features +# +CONFIG_SECURE_BOOT= +CONFIG_SECURE_FLASH_ENC_ENABLED= + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_QIO= +CONFIG_ESPTOOLPY_FLASHMODE_QOUT= +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE_DOUT= +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M= +CONFIG_ESPTOOLPY_FLASHFREQ_40M=y +CONFIG_ESPTOOLPY_FLASHFREQ_26M= +CONFIG_ESPTOOLPY_FLASHFREQ_20M= +CONFIG_ESPTOOLPY_FLASHFREQ="40m" +CONFIG_ESPTOOLPY_FLASHSIZE_1MB= +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE_NORESET= +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER_NORESET= +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_SINGLE_APP= +CONFIG_PARTITION_TABLE_TWO_OTA= +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_PARTITION_TABLE_CUSTOM= +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME= +CONFIG_PARTITION_TABLE_FILENAME= +CONFIG_PARTITION_TABLE_MD5=y + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +CONFIG_COMPILER_OPTIMIZATION_SIZE= +CONFIG_COMPILER_OPTIMIZATION_NONE= +CONFIG_COMPILER_OPTIMIZATION_PERF= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE= +CONFIG_COMPILER_CXX_EXCEPTIONS= +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +CONFIG_COMPILER_STACK_CHECK_MODE_NORM= +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG= +CONFIG_COMPILER_STACK_CHECK_MODE_ALL= +CONFIG_COMPILER_STACK_CHECK= +CONFIG_COMPILER_WARN_WRITE_STRINGS= + +# +# Component config +# + +# +# Application Level Tracing +# +CONFIG_ESP32_APPTRACE_DEST_TRAX= +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_ENABLE= +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y + +# +# FreeRTOS SystemView Tracing +# +CONFIG_AWS_IOT_SDK= + +# +# Bluetooth +# +CONFIG_BT_ENABLED= +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_RESERVE_DRAM=0 + +# +# ADC configuration +# +CONFIG_ADC_FORCE_XPD_FSM= +CONFIG_ADC_DISABLE_DAC=y + +# +# ESP32-specific +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SPIRAM= +CONFIG_ESP32_MEMMAP_TRACEMEM= +CONFIG_ESP32_MEMMAP_TRACEMEM_TWOBANKS= +CONFIG_ESP32_TRAX= +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH= +CONFIG_ESP_COREDUMP_ENABLE_TO_UART= +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +CONFIG_ESP_COREDUMP_ENABLE= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2048 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF= +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_LF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT= +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_UART_CUSTOM= +CONFIG_ESP_CONSOLE_UART_NONE= +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ULP_COPROC_ENABLED= +CONFIG_ULP_COPROC_RESERVE_MEM=0 +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT= +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT= +CONFIG_ESP_SYSTEM_PANIC_GDBSTUB= +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +CONFIG_ESP_TASK_WDT_PANIC= +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7= +CONFIG_ESP_BROWNOUT_DET_LVL=0 +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC= +CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT= +CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE= +CONFIG_RTC_CLK_SRC_INT_RC=y +CONFIG_RTC_CLK_SRC_EXT_CRYS= +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_XTAL_BOOTSTRAP_CYCLES=100 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ_26= +CONFIG_XTAL_FREQ_AUTO= +CONFIG_XTAL_FREQ=40 +CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE= +CONFIG_ESP_TIMER_PROFILING= +CONFIG_APP_COMPATIBLE_PRE_V2_1_BOOTLOADERS= +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y + +# +# Wi-Fi +# +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER= +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION= +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 + +# +# Power Management +# +CONFIG_PM_ENABLE= + +# +# ADC-Calibration +# +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y + +# +# Ethernet +# +CONFIG_ETH_DMA_RX_BUF_NUM=10 +CONFIG_ETH_DMA_TX_BUF_NUM=10 +CONFIG_ETH_EMAC_L2_TO_L3_RX_BUF_MODE= +CONFIG_ETH_EMAC_TASK_PRIORITY=20 + +# +# FAT Filesystem support +# +CONFIG_FATFS_CODEPAGE_DYNAMIC= +CONFIG_FATFS_CODEPAGE_437=y +CONFIG_FATFS_CODEPAGE_720= +CONFIG_FATFS_CODEPAGE_737= +CONFIG_FATFS_CODEPAGE_771= +CONFIG_FATFS_CODEPAGE_775= +CONFIG_FATFS_CODEPAGE_850= +CONFIG_FATFS_CODEPAGE_852= +CONFIG_FATFS_CODEPAGE_855= +CONFIG_FATFS_CODEPAGE_857= +CONFIG_FATFS_CODEPAGE_860= +CONFIG_FATFS_CODEPAGE_861= +CONFIG_FATFS_CODEPAGE_862= +CONFIG_FATFS_CODEPAGE_863= +CONFIG_FATFS_CODEPAGE_864= +CONFIG_FATFS_CODEPAGE_865= +CONFIG_FATFS_CODEPAGE_866= +CONFIG_FATFS_CODEPAGE_869= +CONFIG_FATFS_CODEPAGE_932= +CONFIG_FATFS_CODEPAGE_936= +CONFIG_FATFS_CODEPAGE_949= +CONFIG_FATFS_CODEPAGE_950= +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +CONFIG_FATFS_LFN_HEAP= +CONFIG_FATFS_LFN_STACK= +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y + +# +# FreeRTOS +# +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_CORETIMER_0=y +CONFIG_FREERTOS_CORETIMER_1= +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE= +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY= +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK= +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 +CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y +CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE= +CONFIG_FREERTOS_ASSERT_DISABLE= +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024 +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_LEGACY_HOOKS= +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION= +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_USE_TRACE_FACILITY= +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS= +CONFIG_FREERTOS_DEBUG_INTERNALS= + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +CONFIG_HEAP_POISONING_LIGHT= +CONFIG_HEAP_POISONING_COMPREHENSIVE= +CONFIG_HEAP_TRACING= + +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y + +# +# Log output +# +CONFIG_LOG_DEFAULT_LEVEL_NONE= +CONFIG_LOG_DEFAULT_LEVEL_ERROR= +CONFIG_LOG_DEFAULT_LEVEL_WARN= +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_DEFAULT_LEVEL_DEBUG= +CONFIG_LOG_DEFAULT_LEVEL_VERBOSE= +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_COLORS=y + +# +# LWIP +# +CONFIG_LWIP_L2_TO_L3_COPY= +CONFIG_LWIP_IRAM_OPTIMIZATION= +CONFIG_LWIP_MAX_SOCKETS=4 +CONFIG_LWIP_SO_REUSE= +CONFIG_LWIP_SO_RCVBUF= +CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_IP_FRAG= +CONFIG_LWIP_IP_REASSEMBLY= +CONFIG_LWIP_STATS= +CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y + +# +# DHCP server +# +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_AUTOIP= +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=6 +CONFIG_LWIP_TCP_MSS=1436 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS= +CONFIG_LWIP_TCP_OVERSIZE_DISABLE= + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048 +CONFIG_LWIP_PPP_SUPPORT= + +# +# ICMP +# +CONFIG_LWIP_MULTICAST_PING= +CONFIG_LWIP_BROADCAST_PING= + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 + +# +# mbedTLS +# +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_DEBUG= +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_HARDWARE_SHA= +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_HAVE_TIME_DATE= +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +CONFIG_MBEDTLS_TLS_SERVER_ONLY= +CONFIG_MBEDTLS_TLS_CLIENT_ONLY= +CONFIG_MBEDTLS_TLS_DISABLED= +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES= +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_SSL3= +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS= +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +CONFIG_MBEDTLS_CAMELLIA_C= +CONFIG_MBEDTLS_DES_C= +CONFIG_MBEDTLS_RC4_DISABLED=y +CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT= +CONFIG_MBEDTLS_RC4_ENABLED= +CONFIG_MBEDTLS_BLOWFISH_C= +CONFIG_MBEDTLS_XTEA_C= +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +CONFIG_MBEDTLS_RIPEMD160_C= + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y + +# +# OpenSSL +# +CONFIG_OPENSSL_DEBUG= +CONFIG_OPENSSL_ASSERT_DO_NOTHING=y +CONFIG_OPENSSL_ASSERT_EXIT= + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 + +# +# SPI Flash driver +# +CONFIG_SPI_FLASH_VERIFY_WRITE= +CONFIG_SPI_FLASH_ENABLE_COUNTERS= +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS= +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED= + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +CONFIG_SPIFFS_CACHE_STATS= +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +CONFIG_SPIFFS_GC_STATS= +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_FOLLOW_SYMLINKS= +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +CONFIG_SPIFFS_DBG= +CONFIG_SPIFFS_API_DBG= +CONFIG_SPIFFS_GC_DBG= +CONFIG_SPIFFS_CACHE_DBG= +CONFIG_SPIFFS_CHECK_DBG= +CONFIG_SPIFFS_TEST_VISUALISATION= + +# +# esp_netif +# +CONFIG_NETIF_IP_LOST_TIMER_INTERVAL=120 + +# +# Wear Levelling +# +CONFIG_WL_SECTOR_SIZE_512= +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 diff --git a/examples/0017-espressif_esp32_process_shared_attribute_update/.gitignore b/examples/0017-espressif_esp32_process_shared_attribute_update/.gitignore deleted file mode 100644 index 4b7a730c..00000000 --- a/examples/0017-espressif_esp32_process_shared_attribute_update/.gitignore +++ /dev/null @@ -1,162 +0,0 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -.config -*.o -*.pyc - -# gtags -GTAGS -GRTAGS -GPATH - -# emacs -.dir-locals.el - -# emacs temp file suffixes -*~ -.#* -\#*# - -# eclipse setting -.settings - -# MacOS directory files -.DS_Store - -# cache dir -.cache/ - -# Components Unit Test Apps files -components/**/build/ -components/**/build_*_*/ -components/**/sdkconfig -components/**/sdkconfig.old - -# Example project files -examples/**/build/ -examples/**/build_esp*_*/ -examples/**/sdkconfig -examples/**/sdkconfig.old - -# Doc build artifacts -docs/_build/ -docs/doxygen_sqlite3.db - -# Downloaded font files -docs/_static/DejaVuSans.ttf -docs/_static/NotoSansSC-Regular.otf - -# sdkconfig -sdkconfig.old -sdkconfig - -# Unit test app files -tools/unit-test-app/sdkconfig -tools/unit-test-app/sdkconfig.old -tools/unit-test-app/build -tools/unit-test-app/build_*_*/ -tools/unit-test-app/output -tools/unit-test-app/test_configs - -# Unit Test CMake compile log folder -log_ut_cmake - -# test application build files -tools/test_apps/**/build/ -tools/test_apps/**/build_*_*/ -tools/test_apps/**/sdkconfig -tools/test_apps/**/sdkconfig.old - -TEST_LOGS - -# gcov coverage reports -*.gcda -*.gcno -coverage.info -coverage_report/ - -test_multi_heap_host - -# VS Code Settings -.vscode/ - -# VIM files -*.swp -*.swo - -# Clion IDE CMake build & config -.idea/ -cmake-build-*/ - -# Results for the checking of the Python coding style and static analysis -.mypy_cache -flake8_output.txt - -# ESP-IDF default build directory name -build - -# lock files for examples and components -dependencies.lock - -# managed_components for examples -managed_components - -# pytest log -pytest_embedded_log/ - -# Squareline Studio -SquarelineStudio/SquarelineStudio_Project/backup/ -SquarelineStudio/SquarelineStudio_Project/autosave/ -SquarelineStudio/SquarelineStudio_Project/cache/ -SquarelineStudio/SquarelineStudio_Project/export/ \ No newline at end of file diff --git a/examples/0017-espressif_esp32_process_shared_attribute_update/main/0017-espressif_esp32_process_shared_attribute_update.cpp b/examples/0017-espressif_esp32_process_shared_attribute_update/main/0017-espressif_esp32_process_shared_attribute_update.cpp index d16dada6..636c5553 100644 --- a/examples/0017-espressif_esp32_process_shared_attribute_update/main/0017-espressif_esp32_process_shared_attribute_update.cpp +++ b/examples/0017-espressif_esp32_process_shared_attribute_update/main/0017-espressif_esp32_process_shared_attribute_update.cpp @@ -11,11 +11,10 @@ #define ENCRYPTED false #include +#include #include -// Examples using arduino used PROGMEM to save constants into flash memory, -// this is not needed when using Espressif IDF because per default -// all read only variables will be saved into DROM (flash memory). + // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#drom-data-stored-in-flash // for more information about the aforementioned feature constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; @@ -25,16 +24,25 @@ constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; // to understand how to obtain an access token constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; +// Thingsboard we want to establish a connection to constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; +// MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, +// whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED constexpr uint16_t THINGSBOARD_PORT = 8883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif +// Maximum size packets will ever be sent or received by the underlying MQTT client, +// if the size is to small messages might not be sent or received messages will be discarded constexpr uint16_t MAX_MESSAGE_SIZE = 128U; +// Maximum amount of attributs we can request or subscribe, has to be set both in the ThingsBoard template list and Attribute_Request_Callback template list +// and should be the same as the amount of variables in the passed array. If it is less not all variables will be requested or subscribed +constexpr size_t MAX_ATTRIBUTES = 6U; + #if ENCRYPTED // See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/ // on how to get the root certificate of the server we want to communicate with, @@ -73,73 +81,116 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= )"; #endif +constexpr const char FW_TAG_KEY[] = "fw_tag"; +char constexpr FW_VER_KEY[] = "fw_version"; +char constexpr FW_TITLE_KEY[] = "fw_title"; +char constexpr FW_CHKS_KEY[] = "fw_checksum"; +char constexpr FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm"; +char constexpr FW_SIZE_KEY[] = "fw_size"; + // Initalize the Mqtt client instance Espressif_MQTT_Client mqttClient; +// Initialize used apis +Shared_Attribute_Update<1U, MAX_ATTRIBUTES> shared_update; +const std::array apis = { + &shared_update +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); -// Statuses for subscribing to rpc +// Status for successfully connecting to the given WiFi +bool wifi_connected = false; +// Statuses for subscribing to shared attributes bool subscribed = false; -static void on_got_ip(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - ESP_LOGI("Attribute Update", "Got IP address"); + +/// @brief Callback method that is called if we got an ip address from the connected WiFi meaning we successfully established a connection +/// @param event_handler_arg User data registered to the event +/// @param event_base Event base for the handler +/// @param event_id The id for the received event +/// @param event_data The data for the event, esp_event_handler_t +void on_got_ip(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + wifi_connected = true; } +/// @brief Initalizes WiFi connection, +// will endlessly delay until a connection has been successfully established void InitWiFi() { - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - esp_netif_create_default_wifi_sta(); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); - - wifi_config_t wifi_config = {}; - strcpy((char*)wifi_config.sta.ssid, WIFI_SSID); - strcpy((char*)wifi_config.sta.password, WIFI_PASSWORD); - - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + const wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_WIFI_STA(); + esp_netif_t *netif = esp_netif_new(&netif_config); + assert(netif); + + ESP_ERROR_CHECK(esp_netif_attach_wifi_station(netif)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ip_event_t::IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_default_wifi_sta_handlers()); + ESP_ERROR_CHECK(esp_wifi_set_storage(wifi_storage_t::WIFI_STORAGE_RAM)); + + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config)); + strncpy(reinterpret_cast(wifi_config.sta.ssid), WIFI_SSID, strlen(WIFI_SSID) + 1); + strncpy(reinterpret_cast(wifi_config.sta.password), WIFI_PASSWORD, strlen(WIFI_PASSWORD) + 1); + + ESP_LOGI("MAIN", "Connecting to %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(wifi_mode_t::WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_connect()); } -void processSharedAttributeUpdate(const Shared_Attribute_Data& data) { +/// @brief Update callback that will be called as soon as one of the provided shared attributes changes value, +/// if none are provided we subscribe to any shared attribute change instead +/// @param data Data containing the shared attributes that were changed and their current value +void processSharedAttributeUpdate(const JsonObjectConst &data) { for (auto it = data.begin(); it != data.end(); ++it) { - ESP_LOGI("Attribute Update", "Key: %s, Value: %s", it->key().c_str(), it->value().as()); + ESP_LOGI("MAIN", "Key: %s, Value: %s", it->key().c_str(), it->value().as()); } + + const size_t jsonSize = Helper::Measure_Json(data); + char buffer[jsonSize]; + serializeJson(data, buffer, jsonSize); + ESP_LOGI("MAIN", "%s", buffer); } extern "C" void app_main(void) { - InitWiFi(); + ESP_LOGI("MAIN", "[APP] Startup.."); + ESP_LOGI("MAIN", "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI("MAIN", "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); - const std::array attribute_key = { "exampleAttribute1", "exampleAttribute2" }; + InitWiFi(); - const Shared_Attribute_Callback attribute_key_change_callback(&processSharedAttributeUpdate, attribute_key.cbegin(), attribute_key.cend()); +#if ENCRYPTED + mqttClient.set_server_certificate(ROOT_CERT); +#endif // ENCRYPTED + + for (;;) { + // Wait until we connected to WiFi + if (!wifi_connected) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + continue; + } - while (true) { if (!tb.connected()) { - ESP_LOGI("Attribute Update", "Connecting to: %s with token %s", THINGSBOARD_SERVER, TOKEN); - if (tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) { - ESP_LOGI("Attribute Update", "Successfully connected"); - - if (!subscribed) { - const char* subscribed_attributes[] = { "exampleAttribute1", "exampleAttribute2" }; - if (tb.Shared_Attributes_Subscribe(attribute_key_change_callback)) { - ESP_LOGI("Attribute Update", "Subscribed to shared attribute updates"); - subscribed = true; - } else { - ESP_LOGE("Attribute Update", "Failed to subscribe for shared attribute updates"); - } - } - } else { - ESP_LOGE("Attribute Update", "Failed to connect"); - } + tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT); + } + + if (!subscribed) { + // Shared attributes we want to request from the server + constexpr std::array SUBSCRIBED_SHARED_ATTRIBUTES = {FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TAG_KEY, FW_TITLE_KEY, FW_VER_KEY}; + const Shared_Attribute_Callback callback(&processSharedAttributeUpdate, SUBSCRIBED_SHARED_ATTRIBUTES); + subscribed = shared_update.Shared_Attributes_Subscribe(callback); } tb.loop(); - vTaskDelay(pdMS_TO_TICKS(1000)); + + vTaskDelay(10000 / portTICK_PERIOD_MS); } } diff --git a/examples/0017-espressif_esp32_process_shared_attribute_update/main/CMakeLists.txt b/examples/0017-espressif_esp32_process_shared_attribute_update/main/CMakeLists.txt index 8179ffdc..9c19b56a 100644 --- a/examples/0017-espressif_esp32_process_shared_attribute_update/main/CMakeLists.txt +++ b/examples/0017-espressif_esp32_process_shared_attribute_update/main/CMakeLists.txt @@ -5,22 +5,17 @@ set(srcs 0017-espressif_esp32_process_shared_attribute_update.cpp ../../../src/Arduino_HTTP_Client.cpp ../../../src/Arduino_MQTT_Client.cpp - ../../../src/Attribute_Request_Callback.cpp - ../../../src/Callback_Watchdog.cpp ../../../src/Arduino_ESP32_Updater.cpp ../../../src/Arduino_ESP8266_Updater.cpp ../../../src/Espressif_Updater.cpp + ../../../src/SDCard_Updater.cpp ../../../src/Espressif_MQTT_Client.cpp ../../../src/HashGenerator.cpp ../../../src/Helper.cpp ../../../src/OTA_Update_Callback.cpp ../../../src/Provision_Callback.cpp - ../../../src/RPC_Callback.cpp ../../../src/RPC_Request_Callback.cpp - ../../../src/RPC_Response.cpp - ../../../src/Shared_Attribute_Callback.cpp ../../../src/Telemetry.cpp - ../../../src/ThingsBoardDefaultLogger.cpp ) idf_component_register( diff --git a/examples/0017-espressif_esp32_process_shared_attribute_update/sdkconfig b/examples/0017-espressif_esp32_process_shared_attribute_update/sdkconfig new file mode 100644 index 00000000..685f270f --- /dev/null +++ b/examples/0017-espressif_esp32_process_shared_attribute_update/sdkconfig @@ -0,0 +1,532 @@ +# +# SDK tool configuration +# +CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-" + +# +# Bootloader config +# +CONFIG_BOOTLOADER_LOG_LEVEL_NONE= +CONFIG_BOOTLOADER_LOG_LEVEL_ERROR= +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL_INFO= +CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG= +CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE= +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V= +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y + +# +# Security features +# +CONFIG_SECURE_BOOT= +CONFIG_SECURE_FLASH_ENC_ENABLED= + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_QIO= +CONFIG_ESPTOOLPY_FLASHMODE_QOUT= +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE_DOUT= +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M= +CONFIG_ESPTOOLPY_FLASHFREQ_40M=y +CONFIG_ESPTOOLPY_FLASHFREQ_26M= +CONFIG_ESPTOOLPY_FLASHFREQ_20M= +CONFIG_ESPTOOLPY_FLASHFREQ="40m" +CONFIG_ESPTOOLPY_FLASHSIZE_1MB= +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE_NORESET= +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER_NORESET= +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_SINGLE_APP= +CONFIG_PARTITION_TABLE_TWO_OTA= +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_PARTITION_TABLE_CUSTOM= +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME= +CONFIG_PARTITION_TABLE_FILENAME= +CONFIG_PARTITION_TABLE_MD5=y + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +CONFIG_COMPILER_OPTIMIZATION_SIZE= +CONFIG_COMPILER_OPTIMIZATION_NONE= +CONFIG_COMPILER_OPTIMIZATION_PERF= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE= +CONFIG_COMPILER_CXX_EXCEPTIONS= +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +CONFIG_COMPILER_STACK_CHECK_MODE_NORM= +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG= +CONFIG_COMPILER_STACK_CHECK_MODE_ALL= +CONFIG_COMPILER_STACK_CHECK= +CONFIG_COMPILER_WARN_WRITE_STRINGS= + +# +# Component config +# + +# +# Application Level Tracing +# +CONFIG_ESP32_APPTRACE_DEST_TRAX= +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_ENABLE= +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y + +# +# FreeRTOS SystemView Tracing +# +CONFIG_AWS_IOT_SDK= + +# +# Bluetooth +# +CONFIG_BT_ENABLED= +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_RESERVE_DRAM=0 + +# +# ADC configuration +# +CONFIG_ADC_FORCE_XPD_FSM= +CONFIG_ADC_DISABLE_DAC=y + +# +# ESP32-specific +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SPIRAM= +CONFIG_ESP32_MEMMAP_TRACEMEM= +CONFIG_ESP32_MEMMAP_TRACEMEM_TWOBANKS= +CONFIG_ESP32_TRAX= +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH= +CONFIG_ESP_COREDUMP_ENABLE_TO_UART= +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +CONFIG_ESP_COREDUMP_ENABLE= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2048 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF= +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_LF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT= +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_UART_CUSTOM= +CONFIG_ESP_CONSOLE_UART_NONE= +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ULP_COPROC_ENABLED= +CONFIG_ULP_COPROC_RESERVE_MEM=0 +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT= +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT= +CONFIG_ESP_SYSTEM_PANIC_GDBSTUB= +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +CONFIG_ESP_TASK_WDT_PANIC= +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7= +CONFIG_ESP_BROWNOUT_DET_LVL=0 +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC= +CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT= +CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE= +CONFIG_RTC_CLK_SRC_INT_RC=y +CONFIG_RTC_CLK_SRC_EXT_CRYS= +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_XTAL_BOOTSTRAP_CYCLES=100 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ_26= +CONFIG_XTAL_FREQ_AUTO= +CONFIG_XTAL_FREQ=40 +CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE= +CONFIG_ESP_TIMER_PROFILING= +CONFIG_APP_COMPATIBLE_PRE_V2_1_BOOTLOADERS= +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y + +# +# Wi-Fi +# +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER= +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION= +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 + +# +# Power Management +# +CONFIG_PM_ENABLE= + +# +# ADC-Calibration +# +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y + +# +# Ethernet +# +CONFIG_ETH_DMA_RX_BUF_NUM=10 +CONFIG_ETH_DMA_TX_BUF_NUM=10 +CONFIG_ETH_EMAC_L2_TO_L3_RX_BUF_MODE= +CONFIG_ETH_EMAC_TASK_PRIORITY=20 + +# +# FAT Filesystem support +# +CONFIG_FATFS_CODEPAGE_DYNAMIC= +CONFIG_FATFS_CODEPAGE_437=y +CONFIG_FATFS_CODEPAGE_720= +CONFIG_FATFS_CODEPAGE_737= +CONFIG_FATFS_CODEPAGE_771= +CONFIG_FATFS_CODEPAGE_775= +CONFIG_FATFS_CODEPAGE_850= +CONFIG_FATFS_CODEPAGE_852= +CONFIG_FATFS_CODEPAGE_855= +CONFIG_FATFS_CODEPAGE_857= +CONFIG_FATFS_CODEPAGE_860= +CONFIG_FATFS_CODEPAGE_861= +CONFIG_FATFS_CODEPAGE_862= +CONFIG_FATFS_CODEPAGE_863= +CONFIG_FATFS_CODEPAGE_864= +CONFIG_FATFS_CODEPAGE_865= +CONFIG_FATFS_CODEPAGE_866= +CONFIG_FATFS_CODEPAGE_869= +CONFIG_FATFS_CODEPAGE_932= +CONFIG_FATFS_CODEPAGE_936= +CONFIG_FATFS_CODEPAGE_949= +CONFIG_FATFS_CODEPAGE_950= +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +CONFIG_FATFS_LFN_HEAP= +CONFIG_FATFS_LFN_STACK= +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y + +# +# FreeRTOS +# +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_CORETIMER_0=y +CONFIG_FREERTOS_CORETIMER_1= +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE= +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY= +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK= +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 +CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y +CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE= +CONFIG_FREERTOS_ASSERT_DISABLE= +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024 +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_LEGACY_HOOKS= +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION= +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_USE_TRACE_FACILITY= +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS= +CONFIG_FREERTOS_DEBUG_INTERNALS= + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +CONFIG_HEAP_POISONING_LIGHT= +CONFIG_HEAP_POISONING_COMPREHENSIVE= +CONFIG_HEAP_TRACING= + +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y + +# +# Log output +# +CONFIG_LOG_DEFAULT_LEVEL_NONE= +CONFIG_LOG_DEFAULT_LEVEL_ERROR= +CONFIG_LOG_DEFAULT_LEVEL_WARN= +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_DEFAULT_LEVEL_DEBUG= +CONFIG_LOG_DEFAULT_LEVEL_VERBOSE= +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_COLORS=y + +# +# LWIP +# +CONFIG_LWIP_L2_TO_L3_COPY= +CONFIG_LWIP_IRAM_OPTIMIZATION= +CONFIG_LWIP_MAX_SOCKETS=4 +CONFIG_LWIP_SO_REUSE= +CONFIG_LWIP_SO_RCVBUF= +CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_IP_FRAG= +CONFIG_LWIP_IP_REASSEMBLY= +CONFIG_LWIP_STATS= +CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y + +# +# DHCP server +# +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_AUTOIP= +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=6 +CONFIG_LWIP_TCP_MSS=1436 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS= +CONFIG_LWIP_TCP_OVERSIZE_DISABLE= + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048 +CONFIG_LWIP_PPP_SUPPORT= + +# +# ICMP +# +CONFIG_LWIP_MULTICAST_PING= +CONFIG_LWIP_BROADCAST_PING= + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 + +# +# mbedTLS +# +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_DEBUG= +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_HARDWARE_SHA= +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_HAVE_TIME_DATE= +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +CONFIG_MBEDTLS_TLS_SERVER_ONLY= +CONFIG_MBEDTLS_TLS_CLIENT_ONLY= +CONFIG_MBEDTLS_TLS_DISABLED= +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES= +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_SSL3= +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS= +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +CONFIG_MBEDTLS_CAMELLIA_C= +CONFIG_MBEDTLS_DES_C= +CONFIG_MBEDTLS_RC4_DISABLED=y +CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT= +CONFIG_MBEDTLS_RC4_ENABLED= +CONFIG_MBEDTLS_BLOWFISH_C= +CONFIG_MBEDTLS_XTEA_C= +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +CONFIG_MBEDTLS_RIPEMD160_C= + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y + +# +# OpenSSL +# +CONFIG_OPENSSL_DEBUG= +CONFIG_OPENSSL_ASSERT_DO_NOTHING=y +CONFIG_OPENSSL_ASSERT_EXIT= + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 + +# +# SPI Flash driver +# +CONFIG_SPI_FLASH_VERIFY_WRITE= +CONFIG_SPI_FLASH_ENABLE_COUNTERS= +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS= +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED= + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +CONFIG_SPIFFS_CACHE_STATS= +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +CONFIG_SPIFFS_GC_STATS= +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_FOLLOW_SYMLINKS= +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +CONFIG_SPIFFS_DBG= +CONFIG_SPIFFS_API_DBG= +CONFIG_SPIFFS_GC_DBG= +CONFIG_SPIFFS_CACHE_DBG= +CONFIG_SPIFFS_CHECK_DBG= +CONFIG_SPIFFS_TEST_VISUALISATION= + +# +# esp_netif +# +CONFIG_NETIF_IP_LOST_TIMER_INTERVAL=120 + +# +# Wear Levelling +# +CONFIG_WL_SECTOR_SIZE_512= +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 diff --git a/examples/0018-espressif_esp32_provision_device/.gitignore b/examples/0018-espressif_esp32_provision_device/.gitignore deleted file mode 100644 index 4b7a730c..00000000 --- a/examples/0018-espressif_esp32_provision_device/.gitignore +++ /dev/null @@ -1,162 +0,0 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -.config -*.o -*.pyc - -# gtags -GTAGS -GRTAGS -GPATH - -# emacs -.dir-locals.el - -# emacs temp file suffixes -*~ -.#* -\#*# - -# eclipse setting -.settings - -# MacOS directory files -.DS_Store - -# cache dir -.cache/ - -# Components Unit Test Apps files -components/**/build/ -components/**/build_*_*/ -components/**/sdkconfig -components/**/sdkconfig.old - -# Example project files -examples/**/build/ -examples/**/build_esp*_*/ -examples/**/sdkconfig -examples/**/sdkconfig.old - -# Doc build artifacts -docs/_build/ -docs/doxygen_sqlite3.db - -# Downloaded font files -docs/_static/DejaVuSans.ttf -docs/_static/NotoSansSC-Regular.otf - -# sdkconfig -sdkconfig.old -sdkconfig - -# Unit test app files -tools/unit-test-app/sdkconfig -tools/unit-test-app/sdkconfig.old -tools/unit-test-app/build -tools/unit-test-app/build_*_*/ -tools/unit-test-app/output -tools/unit-test-app/test_configs - -# Unit Test CMake compile log folder -log_ut_cmake - -# test application build files -tools/test_apps/**/build/ -tools/test_apps/**/build_*_*/ -tools/test_apps/**/sdkconfig -tools/test_apps/**/sdkconfig.old - -TEST_LOGS - -# gcov coverage reports -*.gcda -*.gcno -coverage.info -coverage_report/ - -test_multi_heap_host - -# VS Code Settings -.vscode/ - -# VIM files -*.swp -*.swo - -# Clion IDE CMake build & config -.idea/ -cmake-build-*/ - -# Results for the checking of the Python coding style and static analysis -.mypy_cache -flake8_output.txt - -# ESP-IDF default build directory name -build - -# lock files for examples and components -dependencies.lock - -# managed_components for examples -managed_components - -# pytest log -pytest_embedded_log/ - -# Squareline Studio -SquarelineStudio/SquarelineStudio_Project/backup/ -SquarelineStudio/SquarelineStudio_Project/autosave/ -SquarelineStudio/SquarelineStudio_Project/cache/ -SquarelineStudio/SquarelineStudio_Project/export/ \ No newline at end of file diff --git a/examples/0018-espressif_esp32_provision_device/main/0018-espressif_esp32_provision_device.cpp b/examples/0018-espressif_esp32_provision_device/main/0018-espressif_esp32_provision_device.cpp index 714117eb..88fb519c 100644 --- a/examples/0018-espressif_esp32_provision_device/main/0018-espressif_esp32_provision_device.cpp +++ b/examples/0018-espressif_esp32_provision_device/main/0018-espressif_esp32_provision_device.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include // Whether the given script is using encryption or not, @@ -10,44 +11,37 @@ #define ENCRYPTED false #include +#include #include -#define TAG "Provision Device" -// Examples using arduino used PROGMEM to save constants into flash memory, -// this is not needed when using Espressif IDF because per default -// all read only variables will be saved into DROM (flash memory). -// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html#drom-data-stored-in-flash -// for more information about the aforementioned feature -constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; -constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; +// Whether the given script is using encryption or not, +// generally recommended as it increases security (communication with the server is not in clear text anymore), +// it does come with an overhead tough as having an encrypted session requires a lot of memory, +// which might not be avaialable on lower end devices. +#define ENCRYPTED false +// Whether the given script generates the deviceName from the included mac address on the WiFi chip, +// as a fallback option, if no other deviceName is given. +// If not a random string generated by the cloud will be used as the device name instead +#define USE_MAC_FALLBACK false -// See https://thingsboard.io/docs/getting-started-guides/helloworld/ -// to understand how to obtain an access token -constexpr char TOKEN[] = "YOUR_DEVICE_ACCESS_TOKEN"; -constexpr char PROVISION_DEVICE_KEY[] = "YOUR_PROVISION_DEVICE_KEY"; -constexpr char PROVISION_DEVICE_SECRET[] = "YOUR_PROVISION_DEVICE_SECRET"; -constexpr char DEVICE_NAME[] = ""; -constexpr char CREDENTIALS_TYPE[] = "credentialsType"; -constexpr char CREDENTIALS_VALUE[] = "credentialsValue"; -constexpr char CLIENT_ID[] = "clientId"; -constexpr char CLIENT_PASSWORD[] = "password"; -constexpr char CLIENT_USERNAME[] = "userName"; -constexpr char TEMPERATURE_KEY[] = "temperature"; -constexpr char HUMIDITY_KEY[] = "humidity"; -constexpr char ACCESS_TOKEN_CRED_TYPE[] = "ACCESS_TOKEN"; -constexpr char MQTT_BASIC_CRED_TYPE[] = "MQTT_BASIC"; -constexpr char X509_CERTIFICATE_CRED_TYPE[] = "X509_CERTIFICATE"; +constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID"; +constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; +// Thingsboard we want to establish a connection too constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io"; +// MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, +// whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED constexpr uint16_t THINGSBOARD_PORT = 8883U; #else constexpr uint16_t THINGSBOARD_PORT = 1883U; #endif +// Maximum size packets will ever be sent or received by the underlying MQTT client, +// if the size is to small messages might not be sent or received messages will be discarded constexpr uint16_t MAX_MESSAGE_SIZE = 256U; #if ENCRYPTED @@ -88,182 +82,161 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= )"; #endif +// See https://thingsboard.io/docs/user-guide/device-provisioning/ +// to understand how to create a device profile to be able to provision a device +constexpr char PROVISION_DEVICE_KEY[] = "YOUR_PROVISION_DEVICE_KEY"; +constexpr char PROVISION_DEVICE_SECRET[] = "YOUR_PROVISION_DEVICE_SECRET"; + +// Optionally keep the device name empty and the WiFi mac address of the integrated +// wifi chip on ESP32 or ESP8266 will be used as the name instead +// Ensuring your device name is unique, even when reusing this code for multiple devices +constexpr char DEVICE_NAME[] = ""; + +constexpr char CREDENTIALS_TYPE[] = "credentialsType"; +constexpr char CREDENTIALS_VALUE[] = "credentialsValue"; +constexpr char CLIENT_ID[] = "clientId"; +constexpr char CLIENT_PASSWORD[] = "password"; +constexpr char CLIENT_USERNAME[] = "userName"; +constexpr char TEMPERATURE_KEY[] = "temperature"; +constexpr char HUMIDITY_KEY[] = "humidity"; +constexpr char ACCESS_TOKEN_CRED_TYPE[] = "ACCESS_TOKEN"; +constexpr char MQTT_BASIC_CRED_TYPE[] = "MQTT_BASIC"; +constexpr char X509_CERTIFICATE_CRED_TYPE[] = "X509_CERTIFICATE"; +constexpr char PROVISION_DEVICE_TASK_NAME[] = "provision_device_task"; + // Initalize the Mqtt client instance Espressif_MQTT_Client mqttClient; +// Initialize used apis +Provision<> prov; +const std::array apis = { + &prov +}; // Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE); +ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE, Default_Max_Stack_Size, apis); -typedef struct { - std::string client_id; - std::string username; - std::string password; -} credentials_t; +uint32_t previous_processing_time = 0U; -credentials_t credentials; +// Status for successfully connecting to the given WiFi +bool wifi_connected = false; +// Statuses for provisioning bool provisionRequestSent = false; bool provisionResponseProcessed = false; -/** - * @brief Event handler for the IP_EVENT_STA_GOT_IP event. - * - * This function is called when the WiFi station (STA) gets an IP address. - * It logs a message indicating that an IP address has been obtained. - * - * @param arg User-defined argument (not used in this handler). - * @param event_base Base ID of the event (should be IP_EVENT for this handler). - * @param event_id ID of the event (should be IP_EVENT_STA_GOT_IP for this handler). - * @param event_data Event-specific data (not used in this handler). - * - * @return void - */ -static void on_got_ip(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - ESP_LOGI(TAG, "Got IP address"); +// Struct for client connecting after provisioning +struct Credentials { + std::string client_id; + std::string username; + std::string password; +}; +Credentials credentials; + + +/// @brief Callback method that is called if we got an ip address from the connected WiFi meaning we successfully established a connection +/// @param event_handler_arg User data registered to the event +/// @param event_base Event base for the handler +/// @param event_id The id for the received event +/// @param event_data The data for the event, esp_event_handler_t +void on_got_ip(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + wifi_connected = true; } -/** - * @brief Initialize WiFi and connect to the specified access point. - * - * This function initializes the WiFi stack, creates a default station (STA) interface, - * configures it with the SSID and password defined in the `WIFI_SSID` and `WIFI_PASSWORD` - * macros, and connects to the access point. It also registers an event handler for the - * `IP_EVENT_STA_GOT_IP` event to handle IP acquisition. - * - * @note This function assumes that `WIFI_SSID` and `WIFI_PASSWORD` are defined as - * macros containing the SSID and password of the target WiFi network. - * - * @return void - */ +/// @brief Initalizes WiFi connection, +// will endlessly delay until a connection has been successfully established void InitWiFi() { - // Initialize the TCP/IP stack - ESP_ERROR_CHECK(esp_netif_init()); - - // Create default event loop - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - // Create default WiFi STA (Station) interface - esp_netif_create_default_wifi_sta(); - - // Initialize WiFi with default configuration - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - - // Register event handler for IP_EVENT_STA_GOT_IP - ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); - - // Configure WiFi connection settings - wifi_config_t wifi_config = {}; - strcpy((char*)wifi_config.sta.ssid, WIFI_SSID); - strcpy((char*)wifi_config.sta.password, WIFI_PASSWORD); - - // Set WiFi mode to STA (Station) - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - - // Set WiFi configuration - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - - // Start WiFi + const wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_WIFI_STA(); + esp_netif_t *netif = esp_netif_new(&netif_config); + assert(netif); + + ESP_ERROR_CHECK(esp_netif_attach_wifi_station(netif)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ip_event_t::IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_default_wifi_sta_handlers()); + ESP_ERROR_CHECK(esp_wifi_set_storage(wifi_storage_t::WIFI_STORAGE_RAM)); + + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config)); + strncpy(reinterpret_cast(wifi_config.sta.ssid), WIFI_SSID, strlen(WIFI_SSID) + 1); + strncpy(reinterpret_cast(wifi_config.sta.password), WIFI_PASSWORD, strlen(WIFI_PASSWORD) + 1); + + ESP_LOGI("MAIN", "Connecting to %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(wifi_mode_t::WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); - - // Connect to the AP ESP_ERROR_CHECK(esp_wifi_connect()); } -/** - * @brief Process the provisioning response received from the server. - * - * This function processes the provisioning response contained in the `Provision_Data` object. - * It serializes the response to JSON, logs the response, checks for success status, and - * extracts the credentials based on the `credentialsType`. It supports `ACCESS_TOKEN` and - * `MQTT_BASIC` credential types. If the device is provisioned successfully, it disconnects - * from the current cloud client and sets the `provisionResponseProcessed` flag to true. - * - * @param data A reference to the `Provision_Data` object containing the provisioning response. - * - * @return void - */ -void processProvisionResponse(const Provision_Data &data) { - // Measure the size of the JSON data +/// @brief Process the provisioning response received from the server +/// @param data Reference to the object containing the provisioning response +void processProvisionResponse(const JsonDocument &data) { const size_t jsonSize = Helper::Measure_Json(data); - - // Buffer to hold the serialized JSON char buffer[jsonSize]; - - // Serialize the JSON data serializeJson(data, buffer, jsonSize); - - // Log the received provision response - ESP_LOGI(TAG, "Received device provision response: %s", buffer); + ESP_LOGI("MAIN", "Received device provision response: %s", buffer); - // Check if the provisioning was successful if (strcmp(data["status"], "SUCCESS") != 0) { - ESP_LOGE(TAG, "Provision response contains the error: %s", data["errorMsg"].as()); + ESP_LOGE("MAIN", "Provision response contains the error: %s", data["errorMsg"].as()); return; } // Process credentials based on the type - if (strcmp(data["credentialsType"], "ACCESS_TOKEN") == 0) { + if (strncmp(data[CREDENTIALS_TYPE], ACCESS_TOKEN_CRED_TYPE, strlen(ACCESS_TOKEN_CRED_TYPE)) == 0) { credentials.client_id = ""; - credentials.username = data["credentialsValue"].as(); + credentials.username = data[CREDENTIALS_VALUE].as(); credentials.password = ""; - } else if (strcmp(data["credentialsType"], "MQTT_BASIC") == 0) { - auto credentials_value = data["credentialsValue"].as(); - credentials.client_id = credentials_value["clientId"].as(); - credentials.username = credentials_value["userName"].as(); - credentials.password = credentials_value["password"].as(); - } else { - ESP_LOGE(TAG, "Unexpected provision credentialsType: %s", data["credentialsType"].as()); + } + else if (strncmp(data[CREDENTIALS_TYPE], MQTT_BASIC_CRED_TYPE, strlen(MQTT_BASIC_CRED_TYPE)) == 0) { + auto credentials_value = data[CREDENTIALS_VALUE].as(); + credentials.client_id = credentials_value[CLIENT_ID].as(); + credentials.username = credentials_value[CLIENT_USERNAME].as(); + credentials.password = credentials_value[CLIENT_PASSWORD].as(); + } + else { + ESP_LOGE("MAIN", "Unexpected provision credentialsType: %s", data[CREDENTIALS_VALUE].as()); return; } - // Disconnect from the cloud client connected to the provision account - // because the device has been provisioned and can reconnect with new credentials + // Disconnect from the cloud client connected to the provision account, because it is no longer needed the device has been provisioned + // and we can reconnect to the cloud with the newly generated credentials. if (tb.connected()) { - tb.disconnect(); + tb.disconnect(); } - - // Set the provision response processed flag to true provisionResponseProcessed = true; } -/** - * @brief Provision a device by connecting to the ThingsBoard server. - * - * This function provisions a new device by connecting to the ThingsBoard server using - * a provision account. If the device name (`DEVICE_NAME`) is empty, it generates a - * device name using the device's MAC address. It sends a provisioning request and waits - * for the provisioning response to be processed. Once the device is provisioned, it - * disconnects from the provision account and reconnects using the newly generated credentials. - * - * @param pvParameters A pointer to the parameters passed to the task (not used in this function). - * - * @return void - */ +/// @brief Attempts to connect as a provision client, provision a device and then reconnect as that provisioned device +/// @param pvParameter Always null void provision_device(void *pvParameters) { - // Get the device name, using MAC address if DEVICE_NAME is empty - std::string dev_name = DEVICE_NAME; - if (dev_name.empty()) { + // Send a claiming request without any device name (random string will be used as the device name) + // if the string is empty or null, automatically checked by the sendProvisionRequest method + std::string device_name = DEVICE_NAME; + +#if USE_MAC_FALLBACK + // Check if passed DEVICE_NAME was empty, + // and if it was get the mac address of the wifi chip as fallback and use that one instead + if ((DEVICE_NAME == nullptr) || (DEVICE_NAME[0] == '\0')) { uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); char mac_str[18]; - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - dev_name = mac_str; + snprintf(mac_str, 18U, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + device_name = mac_str; } +#endif // Connect to the ThingsBoard server as a client wanting to provision a new device if (!tb.connect(THINGSBOARD_SERVER, "provision", THINGSBOARD_PORT)) { - ESP_LOGE(TAG, "Failed to connect to ThingsBoard server with provision account"); + ESP_LOGE("MAIN", "Failed to connect to ThingsBoard server with provision account"); return; } // Prepare and send the provision request - Provision_Callback provisionCallback(Access_Token(), &processProvisionResponse, PROVISION_DEVICE_KEY, PROVISION_DEVICE_SECRET, dev_name.c_str()); - provisionRequestSent = tb.Provision_Request(provisionCallback); + const Provision_Callback provisionCallback(Access_Token(), &processProvisionResponse, PROVISION_DEVICE_KEY, PROVISION_DEVICE_SECRET, device_name.c_str()); + provisionRequestSent = prov.Provision_Request(provisionCallback); // Wait for the provisioning response to be processed while (!provisionResponseProcessed) { vTaskDelay(1000 / portTICK_PERIOD_MS); - tb.loop(); } // Disconnect from the cloud client connected to the provision account @@ -272,20 +245,29 @@ void provision_device(void *pvParameters) { tb.disconnect(); } - // Reconnect to the ThingsBoard server using the new credentials + // Connect to the ThingsBoard server, as the provisioned client tb.connect(THINGSBOARD_SERVER, credentials.username.c_str(), THINGSBOARD_PORT, credentials.client_id.c_str(), credentials.password.c_str()); - // Run the ThingsBoard loop tb.loop(); + vTaskDelete(NULL); } - extern "C" void app_main(void) { + ESP_LOGI("MAIN", "[APP] Startup.."); + ESP_LOGI("MAIN", "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI("MAIN", "[APP] IDF version: %s", esp_get_idf_version()); + + esp_log_level_set("*", ESP_LOG_INFO); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + InitWiFi(); #if ENCRYPTED mqttClient.set_server_certificate(ROOT_CERT); -#endif +#endif // ENCRYPTED - xTaskCreate(&provision_device, "provision_device_task", 1024 * 8, NULL, 5, NULL); + xTaskCreate(&provision_device, PROVISION_DEVICE_TASK_NAME, 1024 * 8, NULL, 5, NULL); } diff --git a/examples/0018-espressif_esp32_provision_device/main/CMakeLists.txt b/examples/0018-espressif_esp32_provision_device/main/CMakeLists.txt index a73846c0..76ab571d 100644 --- a/examples/0018-espressif_esp32_provision_device/main/CMakeLists.txt +++ b/examples/0018-espressif_esp32_provision_device/main/CMakeLists.txt @@ -5,22 +5,17 @@ set(srcs 0018-espressif_esp32_provision_device.cpp ../../../src/Arduino_HTTP_Client.cpp ../../../src/Arduino_MQTT_Client.cpp - ../../../src/Attribute_Request_Callback.cpp - ../../../src/Callback_Watchdog.cpp ../../../src/Arduino_ESP32_Updater.cpp ../../../src/Arduino_ESP8266_Updater.cpp ../../../src/Espressif_Updater.cpp + ../../../src/SDCard_Updater.cpp ../../../src/Espressif_MQTT_Client.cpp ../../../src/HashGenerator.cpp ../../../src/Helper.cpp ../../../src/OTA_Update_Callback.cpp ../../../src/Provision_Callback.cpp - ../../../src/RPC_Callback.cpp ../../../src/RPC_Request_Callback.cpp - ../../../src/RPC_Response.cpp - ../../../src/Shared_Attribute_Callback.cpp ../../../src/Telemetry.cpp - ../../../src/ThingsBoardDefaultLogger.cpp ) idf_component_register( diff --git a/examples/0018-espressif_esp32_provision_device/sdkconfig b/examples/0018-espressif_esp32_provision_device/sdkconfig new file mode 100644 index 00000000..685f270f --- /dev/null +++ b/examples/0018-espressif_esp32_provision_device/sdkconfig @@ -0,0 +1,532 @@ +# +# SDK tool configuration +# +CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-" + +# +# Bootloader config +# +CONFIG_BOOTLOADER_LOG_LEVEL_NONE= +CONFIG_BOOTLOADER_LOG_LEVEL_ERROR= +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_BOOTLOADER_LOG_LEVEL_INFO= +CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG= +CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE= +CONFIG_BOOTLOADER_LOG_LEVEL=2 +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V= +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y + +# +# Security features +# +CONFIG_SECURE_BOOT= +CONFIG_SECURE_FLASH_ENC_ENABLED= + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_QIO= +CONFIG_ESPTOOLPY_FLASHMODE_QOUT= +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE_DOUT= +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M= +CONFIG_ESPTOOLPY_FLASHFREQ_40M=y +CONFIG_ESPTOOLPY_FLASHFREQ_26M= +CONFIG_ESPTOOLPY_FLASHFREQ_20M= +CONFIG_ESPTOOLPY_FLASHFREQ="40m" +CONFIG_ESPTOOLPY_FLASHSIZE_1MB= +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE_NORESET= +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER_NORESET= +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B= +CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER= +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_SINGLE_APP= +CONFIG_PARTITION_TABLE_TWO_OTA= +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_PARTITION_TABLE_CUSTOM= +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME= +CONFIG_PARTITION_TABLE_FILENAME= +CONFIG_PARTITION_TABLE_MD5=y + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +CONFIG_COMPILER_OPTIMIZATION_SIZE= +CONFIG_COMPILER_OPTIMIZATION_NONE= +CONFIG_COMPILER_OPTIMIZATION_PERF= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT= +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE= +CONFIG_COMPILER_CXX_EXCEPTIONS= +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +CONFIG_COMPILER_STACK_CHECK_MODE_NORM= +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG= +CONFIG_COMPILER_STACK_CHECK_MODE_ALL= +CONFIG_COMPILER_STACK_CHECK= +CONFIG_COMPILER_WARN_WRITE_STRINGS= + +# +# Component config +# + +# +# Application Level Tracing +# +CONFIG_ESP32_APPTRACE_DEST_TRAX= +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_ENABLE= +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y + +# +# FreeRTOS SystemView Tracing +# +CONFIG_AWS_IOT_SDK= + +# +# Bluetooth +# +CONFIG_BT_ENABLED= +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_RESERVE_DRAM=0 + +# +# ADC configuration +# +CONFIG_ADC_FORCE_XPD_FSM= +CONFIG_ADC_DISABLE_DAC=y + +# +# ESP32-specific +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160= +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SPIRAM= +CONFIG_ESP32_MEMMAP_TRACEMEM= +CONFIG_ESP32_MEMMAP_TRACEMEM_TWOBANKS= +CONFIG_ESP32_TRAX= +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH= +CONFIG_ESP_COREDUMP_ENABLE_TO_UART= +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +CONFIG_ESP_COREDUMP_ENABLE= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO= +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2048 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF= +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_LF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT= +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +CONFIG_ESP_CONSOLE_UART_CUSTOM= +CONFIG_ESP_CONSOLE_UART_NONE= +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ULP_COPROC_ENABLED= +CONFIG_ULP_COPROC_RESERVE_MEM=0 +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT= +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT= +CONFIG_ESP_SYSTEM_PANIC_GDBSTUB= +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +CONFIG_ESP_TASK_WDT_PANIC= +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_0=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6= +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7= +CONFIG_ESP_BROWNOUT_DET_LVL=0 +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC= +CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT= +CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE= +CONFIG_RTC_CLK_SRC_INT_RC=y +CONFIG_RTC_CLK_SRC_EXT_CRYS= +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_XTAL_BOOTSTRAP_CYCLES=100 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ_26= +CONFIG_XTAL_FREQ_AUTO= +CONFIG_XTAL_FREQ=40 +CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE= +CONFIG_ESP_TIMER_PROFILING= +CONFIG_APP_COMPATIBLE_PRE_V2_1_BOOTLOADERS= +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y + +# +# Wi-Fi +# +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER= +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION= +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 + +# +# Power Management +# +CONFIG_PM_ENABLE= + +# +# ADC-Calibration +# +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y + +# +# Ethernet +# +CONFIG_ETH_DMA_RX_BUF_NUM=10 +CONFIG_ETH_DMA_TX_BUF_NUM=10 +CONFIG_ETH_EMAC_L2_TO_L3_RX_BUF_MODE= +CONFIG_ETH_EMAC_TASK_PRIORITY=20 + +# +# FAT Filesystem support +# +CONFIG_FATFS_CODEPAGE_DYNAMIC= +CONFIG_FATFS_CODEPAGE_437=y +CONFIG_FATFS_CODEPAGE_720= +CONFIG_FATFS_CODEPAGE_737= +CONFIG_FATFS_CODEPAGE_771= +CONFIG_FATFS_CODEPAGE_775= +CONFIG_FATFS_CODEPAGE_850= +CONFIG_FATFS_CODEPAGE_852= +CONFIG_FATFS_CODEPAGE_855= +CONFIG_FATFS_CODEPAGE_857= +CONFIG_FATFS_CODEPAGE_860= +CONFIG_FATFS_CODEPAGE_861= +CONFIG_FATFS_CODEPAGE_862= +CONFIG_FATFS_CODEPAGE_863= +CONFIG_FATFS_CODEPAGE_864= +CONFIG_FATFS_CODEPAGE_865= +CONFIG_FATFS_CODEPAGE_866= +CONFIG_FATFS_CODEPAGE_869= +CONFIG_FATFS_CODEPAGE_932= +CONFIG_FATFS_CODEPAGE_936= +CONFIG_FATFS_CODEPAGE_949= +CONFIG_FATFS_CODEPAGE_950= +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +CONFIG_FATFS_LFN_HEAP= +CONFIG_FATFS_LFN_STACK= +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y + +# +# FreeRTOS +# +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_CORETIMER_0=y +CONFIG_FREERTOS_CORETIMER_1= +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE= +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY= +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK= +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 +CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y +CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE= +CONFIG_FREERTOS_ASSERT_DISABLE= +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024 +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_LEGACY_HOOKS= +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION= +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_USE_TRACE_FACILITY= +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS= +CONFIG_FREERTOS_DEBUG_INTERNALS= + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +CONFIG_HEAP_POISONING_LIGHT= +CONFIG_HEAP_POISONING_COMPREHENSIVE= +CONFIG_HEAP_TRACING= + +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y + +# +# Log output +# +CONFIG_LOG_DEFAULT_LEVEL_NONE= +CONFIG_LOG_DEFAULT_LEVEL_ERROR= +CONFIG_LOG_DEFAULT_LEVEL_WARN= +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_DEFAULT_LEVEL_DEBUG= +CONFIG_LOG_DEFAULT_LEVEL_VERBOSE= +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_COLORS=y + +# +# LWIP +# +CONFIG_LWIP_L2_TO_L3_COPY= +CONFIG_LWIP_IRAM_OPTIMIZATION= +CONFIG_LWIP_MAX_SOCKETS=4 +CONFIG_LWIP_SO_REUSE= +CONFIG_LWIP_SO_RCVBUF= +CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_IP_FRAG= +CONFIG_LWIP_IP_REASSEMBLY= +CONFIG_LWIP_STATS= +CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y + +# +# DHCP server +# +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_AUTOIP= +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=6 +CONFIG_LWIP_TCP_MSS=1436 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS= +CONFIG_LWIP_TCP_OVERSIZE_DISABLE= + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048 +CONFIG_LWIP_PPP_SUPPORT= + +# +# ICMP +# +CONFIG_LWIP_MULTICAST_PING= +CONFIG_LWIP_BROADCAST_PING= + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 + +# +# mbedTLS +# +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_DEBUG= +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_HARDWARE_SHA= +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_HAVE_TIME_DATE= +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +CONFIG_MBEDTLS_TLS_SERVER_ONLY= +CONFIG_MBEDTLS_TLS_CLIENT_ONLY= +CONFIG_MBEDTLS_TLS_DISABLED= +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES= +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_SSL3= +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS= +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +CONFIG_MBEDTLS_CAMELLIA_C= +CONFIG_MBEDTLS_DES_C= +CONFIG_MBEDTLS_RC4_DISABLED=y +CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT= +CONFIG_MBEDTLS_RC4_ENABLED= +CONFIG_MBEDTLS_BLOWFISH_C= +CONFIG_MBEDTLS_XTEA_C= +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +CONFIG_MBEDTLS_RIPEMD160_C= + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y + +# +# OpenSSL +# +CONFIG_OPENSSL_DEBUG= +CONFIG_OPENSSL_ASSERT_DO_NOTHING=y +CONFIG_OPENSSL_ASSERT_EXIT= + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 + +# +# SPI Flash driver +# +CONFIG_SPI_FLASH_VERIFY_WRITE= +CONFIG_SPI_FLASH_ENABLE_COUNTERS= +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS= +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED= + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +CONFIG_SPIFFS_CACHE_STATS= +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +CONFIG_SPIFFS_GC_STATS= +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_FOLLOW_SYMLINKS= +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +CONFIG_SPIFFS_DBG= +CONFIG_SPIFFS_API_DBG= +CONFIG_SPIFFS_GC_DBG= +CONFIG_SPIFFS_CACHE_DBG= +CONFIG_SPIFFS_CHECK_DBG= +CONFIG_SPIFFS_TEST_VISUALISATION= + +# +# esp_netif +# +CONFIG_NETIF_IP_LOST_TIMER_INTERVAL=120 + +# +# Wear Levelling +# +CONFIG_WL_SECTOR_SIZE_512= +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 diff --git a/examples/README.md b/examples/README.md index 47c7c46d..a2ee6f63 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,5 +57,6 @@ The remaining features have to be implemented by hand with the `sendGetRequest` | `0015-espressif_esp32_process_OTA_MQTT` | Process OTA updates via MQTT on ESP32 using ESP-IDF. | ESP32 (ESP-IDF) | | `0016-espressif_esp32_rpc` | Handle RPC on ESP32 using ESP-IDF. | ESP32 (ESP-IDF) | | `0017-espressif_esp32_process_shared_attribute_update` | Process shared attribute updates on ESP32 using ESP-IDF. | ESP32 (ESP-IDF) | +| `0018-espressif_esp32_provision_device` | Device provisioning on ESP32 using ESP-IDF. | ESP32 (ESP-IDF) | -Each folder contains a `README.md` file with more information about the example. Please refer to the specific `README.md` in each folder for more detailed guidance. \ No newline at end of file +Each folder contains a `README.md` file with more information about the example. Please refer to the specific `README.md` in each folder for more detailed guidance. diff --git a/idf_component.yml b/idf_component.yml index 60590019..da4f0697 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,7 +1,7 @@ -version: "0.13.0" +version: "0.14.0" description: Provides access to ThingsBoard platform over the MQTT protocol or alternatively over HTTP/S. url: https://github.com/thingsboard/thingsboard-client-sdk dependencies: - bblanchon/arduinojson: "^6.21.4" + bblanchon/arduinojson: "^6.21.5" idf: version: "*" diff --git a/keywords.txt b/keywords.txt index d00c58ad..3689a9ad 100644 --- a/keywords.txt +++ b/keywords.txt @@ -110,8 +110,6 @@ Measure_Json KEYWORD2 ####################################### THINGSBOARD_ENABLE_STL LITERAL1 -THINGSBOARD_ENABLE_OTA LITERAL1 -THINGSBOARD_ENABLE_PROGMEM LITERAL1 THINGSBOARD_ENABLE_DYNAMIC LITERAL1 THINGSBOARD_ENABLE_DEBUG LITERAL1 THINGSBOARD_ENABLE_STREAM_UTILS LITERAL1 diff --git a/library.json b/library.json index f2adf87f..3459b29c 100644 --- a/library.json +++ b/library.json @@ -7,9 +7,9 @@ "url": "https://github.com/thingsboard/thingsboard-client-sdk" }, "dependencies": { - "bblanchon/ArduinoJson": "^6.21.4" + "bblanchon/ArduinoJson": "^6.21.5" }, - "version": "0.13.0", + "version": "0.14.0", "examples": "examples/*/*.ino", "frameworks": "*", "license": "MIT" diff --git a/library.properties b/library.properties index bc3486fa..46a09119 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ # See https://arduino.github.io/arduino-cli/0.35/library-specification/#libraryproperties-file-format for more information on the formatting name=ThingsBoard -version=0.13.0 +version=0.14.0 author=ThingsBoard Team maintainer=ThingsBoard Team sentence=ThingsBoard library for Arduino. @@ -9,4 +9,4 @@ category=Communication url=https://github.com/thingsboard/thingsboard-client-sdk architectures=* includes=ThingsBoard.h -depends=ArduinoJson (=6.21.4) +depends=ArduinoJson (=6.21.5) diff --git a/src/API_Process_Type.h b/src/API_Process_Type.h new file mode 100644 index 00000000..a0b2b635 --- /dev/null +++ b/src/API_Process_Type.h @@ -0,0 +1,16 @@ +#ifndef API_Process_Type_h +#define API_Process_Type_h + +// Library include. +#include + + +/// @brief Possible processing types an API Implementation uses to handle responses from the server. +/// Only ever uses one at the time, because the response is either unserialized data which we need to process as such (OTA Firmware Update) +/// or actually JSON which needs to be serialized (everything else) +enum class API_Process_Type : const uint8_t { + RAW, ///< Passes the data into the process method as a copy but in its raw uint8_t array form + JSON ///< Passes the data into the process method as a copy and in a serialized manner +}; + +#endif // API_Process_Type_h diff --git a/src/Arduino_ESP32_Updater.cpp b/src/Arduino_ESP32_Updater.cpp index 2cba837f..8541359f 100644 --- a/src/Arduino_ESP32_Updater.cpp +++ b/src/Arduino_ESP32_Updater.cpp @@ -1,8 +1,6 @@ // Header include. #include "Arduino_ESP32_Updater.h" -#if THINGSBOARD_ENABLE_OTA - #if defined(ESP32) && defined(ARDUINO) // Library include. @@ -26,4 +24,3 @@ bool Arduino_ESP32_Updater::end() { #endif // defined(ESP32) && defined(ARDUINO) -#endif // THINGSBOARD_ENABLE_OTA diff --git a/src/Arduino_ESP32_Updater.h b/src/Arduino_ESP32_Updater.h index b157dc3c..341b810f 100644 --- a/src/Arduino_ESP32_Updater.h +++ b/src/Arduino_ESP32_Updater.h @@ -4,8 +4,6 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - #if defined(ESP32) && defined(ARDUINO) // Local include. @@ -27,6 +25,4 @@ class Arduino_ESP32_Updater : public IUpdater { #endif // defined(ESP32) && defined(ARDUINO) -#endif // THINGSBOARD_ENABLE_OTA - #endif // Arduino_ESP32_Updater_h diff --git a/src/Arduino_ESP8266_Updater.cpp b/src/Arduino_ESP8266_Updater.cpp index 45d93ae0..106b993a 100644 --- a/src/Arduino_ESP8266_Updater.cpp +++ b/src/Arduino_ESP8266_Updater.cpp @@ -1,8 +1,6 @@ // Header include. #include "Arduino_ESP8266_Updater.h" -#if THINGSBOARD_ENABLE_OTA - #if defined(ESP8266) && defined(ARDUINO) // Library include. @@ -25,5 +23,3 @@ bool Arduino_ESP8266_Updater::end() { } #endif // defined(ESP8266) && defined(ARDUINO) - -#endif // THINGSBOARD_ENABLE_OTA diff --git a/src/Arduino_ESP8266_Updater.h b/src/Arduino_ESP8266_Updater.h index edc407a4..8e0a05e7 100644 --- a/src/Arduino_ESP8266_Updater.h +++ b/src/Arduino_ESP8266_Updater.h @@ -4,8 +4,6 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - #if defined(ESP8266) && defined(ARDUINO) // Local include. @@ -27,6 +25,4 @@ class Arduino_ESP8266_Updater : public IUpdater { #endif // defined(ESP8266) && defined(ARDUINO) -#endif // THINGSBOARD_ENABLE_OTA - #endif // Arduino_ESP8266_Updater_h diff --git a/src/Arduino_HTTP_Client.cpp b/src/Arduino_HTTP_Client.cpp index 2c6f6568..43f38124 100644 --- a/src/Arduino_HTTP_Client.cpp +++ b/src/Arduino_HTTP_Client.cpp @@ -3,19 +3,19 @@ #ifdef ARDUINO -Arduino_HTTP_Client::Arduino_HTTP_Client(Client& transport_client, char const * const host, uint16_t const & port) : +Arduino_HTTP_Client::Arduino_HTTP_Client(Client& transport_client, char const * const host, uint16_t port) : m_http_client(transport_client, host, port) { // Nothing to do } -void Arduino_HTTP_Client::set_keep_alive(bool const & keep_alive) { +void Arduino_HTTP_Client::set_keep_alive(bool keep_alive) { if (keep_alive) { m_http_client.connectionKeepAlive(); } } -int Arduino_HTTP_Client::connect(char const * const host, uint16_t const & port) { +int Arduino_HTTP_Client::connect(char const * const host, uint16_t port) { return m_http_client.connect(host, port); } diff --git a/src/Arduino_HTTP_Client.h b/src/Arduino_HTTP_Client.h index 09964fc5..51c90506 100644 --- a/src/Arduino_HTTP_Client.h +++ b/src/Arduino_HTTP_Client.h @@ -22,11 +22,11 @@ class Arduino_HTTP_Client : public IHTTP_Client { /// Should be either 80 for unencrypted HTTP or 443 for HTTPS with encryption. /// The latter is recommended if relevant data is sent or if the client receives and handles requests from the server, /// because using an unencrpyted connection, will allow 3rd parties to listen to the communication and impersonate the server sending payloads which might influence the device in unexpected ways - Arduino_HTTP_Client(Client& transport_client, char const * const host, uint16_t const & port); + Arduino_HTTP_Client(Client& transport_client, char const * const host, uint16_t port); - void set_keep_alive(bool const & keep_alive) override; + void set_keep_alive(bool keep_alive) override; - int connect(char const * const host, uint16_t const & port) override; + int connect(char const * const host, uint16_t port) override; void stop() override; diff --git a/src/Arduino_MQTT_Client.cpp b/src/Arduino_MQTT_Client.cpp index 0b41a024..8d344793 100644 --- a/src/Arduino_MQTT_Client.cpp +++ b/src/Arduino_MQTT_Client.cpp @@ -4,7 +4,7 @@ #ifdef ARDUINO Arduino_MQTT_Client::Arduino_MQTT_Client(Client & transport_client) : - m_cb(nullptr), + m_connected_callback(), m_mqtt_client(transport_client) { // Nothing to do @@ -14,15 +14,15 @@ void Arduino_MQTT_Client::set_client(Client & transport_client) { m_mqtt_client.setClient(transport_client); } -void Arduino_MQTT_Client::set_data_callback(data_function cb) { - m_mqtt_client.setCallback(cb); +void Arduino_MQTT_Client::set_data_callback(Callback::function callback) { + m_mqtt_client.setCallback(callback); } -void Arduino_MQTT_Client::set_connect_callback(connect_function cb) { - m_cb = cb; +void Arduino_MQTT_Client::set_connect_callback(Callback::function callback) { + m_connected_callback.Set_Callback(callback); } -bool Arduino_MQTT_Client::set_buffer_size(uint16_t const & buffer_size) { +bool Arduino_MQTT_Client::set_buffer_size(uint16_t buffer_size) { return m_mqtt_client.setBufferSize(buffer_size); } @@ -30,13 +30,13 @@ uint16_t Arduino_MQTT_Client::get_buffer_size() { return m_mqtt_client.getBufferSize(); } -void Arduino_MQTT_Client::set_server(char const * const domain, uint16_t const & port) { +void Arduino_MQTT_Client::set_server(char const * const domain, uint16_t port) { m_mqtt_client.setServer(domain, port); } bool Arduino_MQTT_Client::connect(char const * const client_id, char const * const user_name, char const * const password) { bool const result = m_mqtt_client.connect(client_id, user_name, password); - m_cb(); + m_connected_callback.Call_Callback(); return result; } diff --git a/src/Arduino_MQTT_Client.h b/src/Arduino_MQTT_Client.h index dd279567..874f4367 100644 --- a/src/Arduino_MQTT_Client.h +++ b/src/Arduino_MQTT_Client.h @@ -27,15 +27,15 @@ class Arduino_MQTT_Client : public IMQTT_Client { /// but the actual type of connection does not matter (Ethernet or WiFi) void set_client(Client & transport_client); - void set_data_callback(data_function cb) override; + void set_data_callback(Callback::function callback) override; - void set_connect_callback(connect_function cb) override; + void set_connect_callback(Callback::function callback) override; - bool set_buffer_size(uint16_t const & buffer_size) override; + bool set_buffer_size(uint16_t buffer_size) override; uint16_t get_buffer_size() override; - void set_server(char const * const domain, uint16_t const & port) override; + void set_server(char const * const domain, uint16_t port) override; bool connect(char const * const client_id, char const * const user_name, char const * const password) override; @@ -68,8 +68,8 @@ class Arduino_MQTT_Client : public IMQTT_Client { #endif // THINGSBOARD_ENABLE_STREAM_UTILS private: - connect_function m_cb; // Callback that will be called as soon as the mqtt client has connected - PubSubClient m_mqtt_client; // Underlying MQTT client instance used to send data + Callback m_connected_callback = {}; // Callback that will be called as soon as the mqtt client has connected + PubSubClient m_mqtt_client = {}; // Underlying MQTT client instance used to send data }; #endif // ARDUINO diff --git a/src/Array.h b/src/Array.h index 5223a8af..da556ccc 100644 --- a/src/Array.h +++ b/src/Array.h @@ -17,23 +17,56 @@ class Array { /// @brief Constructor Array(void) = default; - /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - Array(T const * const first, T const * const last) : - m_elements(), - m_size(0U) + /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data between the first and last iterator + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + Array(InputIterator const & first, InputIterator const & last) + : m_elements() + , m_size(0U) { insert(nullptr, first, last); } - /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - void assign(T const * const first, T const * const last) { + /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data from the given container + /// @tparam Container Class that contains the actual data we want to copy into our internal data container, + /// requires access to a begin() and end() method, that point to the first element and one past the last element we want to copy respectively. + /// Both methods need to return an InputIterator, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param container Data container with begin() and end() method that we want to copy fully into our underlying data container + template + Array(Container const & container) + : m_elements() + , m_size(0U) + { + insert(nullptr, container.begin(), container.end()); + } + + /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data between the first and last iterator + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + void assign(InputIterator const & first, InputIterator const & last) { insert(nullptr, first, last); } + /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data from the given container + /// @tparam Container Class that contains the actual data we want to copy into our internal data container, + /// requires access to a begin() and end() method, that point to the first element and one past the last element we want to copy respectively. + /// Both methods need to return an InputIterator, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param container Data container with begin() and end() method that we want to copy fully into our underlying data container + template + void assign(Container const & container) { + insert(nullptr, container.begin(), container.end()); + } + /// @brief Returns whether there are still any element in the underlying data container /// @return Whether the underlying data container is empty or not bool empty() const { @@ -42,7 +75,7 @@ class Array { /// @brief Gets the current amount of elements in the underlying data container /// @return The amount of items currently in the underlying data container - size_t const & size() const { + size_t size() const { return m_size; } @@ -52,20 +85,20 @@ class Array { return Capacity; } - /// @brief Returns a constant pointer to the first element of the vector - /// @return Constant pointer to the first element of the vector + /// @brief Returns a iterator to the first element of the underlying data container + /// @return Iterator pointing to the first element of the underlying data container T * begin() { return m_elements; } - /// @brief Returns a constant pointer to the first element of the vector - /// @return Constant pointer to the first element of the vector + /// @brief Returns a constant iterator to the first element of the underlying data container + /// @return Constant iterator pointing to the first element of the underlying data container T const * begin() const { return m_elements; } - /// @brief Returns a constant pointer to the first element of the vector - /// @return Constant pointer to the first element of the vector + /// @brief Returns a constant iterator to the first element of the underlying data container + /// @return Constant iterator pointing to the first element of the underlying data container T const * cbegin() const { return m_elements; } @@ -77,52 +110,58 @@ class Array { return m_elements[m_size - 1U]; } - /// @brief Returns a pointer to one-past-the-end element of the vector - /// @return Pointer to one-past-the-end element of the vector + /// @brief Returns a iterator to one-past-the-end element of the underlying data container + /// @return Iterator pointing to one-past-the-end element of the underlying data container T * end() { return m_elements + m_size; } - /// @brief Returns a constant pointer to one-past-the-end element of the vector - /// @return Constant pointer to one-past-the-end element of the vector + /// @brief Returns a constantiterator to one-past-the-end element of the underlying data container + /// @return Constant iterator pointing to one-past-the-end element of the underlying data container T const * end() const { return m_elements + m_size; } - /// @brief Returns a constant pointer to one-past-the-end element of the vector - /// @return Constant pointer to one-past-the-end element of the vector + /// @brief Returns a constant iterator to one-past-the-end element of the underlying data container + /// @return Constant iterator pointing to one-past-the-end element of the underlying data container T const * cend() const { return m_elements + m_size; } /// @brief Inserts the given element at the end of the underlying data container, - /// If the interal data structure is full already then this method will assert and stop the application. + /// if the interal data structure is full already then this method will assert and stop the application. /// Because if we do not we could cause an out of bounds write, which could possibly overwrite other memory. /// Causing hard to debug issues, therefore this behaviour is not allowed in the first place /// @param element Element that should be inserted at the end void push_back(T const & element) { assert(m_size < Capacity); - m_elements[m_size] = element; - m_size++; + m_elements[m_size++] = element; } /// @brief Inserts all element from the given start to the given end iterator into the underlying data container. /// Simply calls push_back on each element, meaning if the initally allocated size if not big enough to hold all elements, /// then this method will simply not insert those elements instead + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator /// @param position Attribute is not used and can be left as nullptr, simply there to keep compatibility with std::vector insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - void insert(T const * const position, T const * first, T const * const last) { - while (first < last) { - push_back(*first); - first++; + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + void insert(T const * const position, InputIterator const & first, InputIterator const & last) { + for (auto it = first; it != last; ++it) { + push_back(*it); } } - /// @brief Removes the element at the given iterator, has to move all element one to the left if the index is not at the end of the array - /// @param iterator Iterator the element should be removed at from the underlying data container - void erase(T const * const iterator) { - size_t const index = Helper::distance(cbegin(), iterator); + /// @brief Removes the element at the given position, has to move all element one to the left if the index is not at the end of the array + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param position Iterator pointing to the element, that should be removed from the underlying data container + template + void erase(InputIterator const & position) { + size_t const index = Helper::distance(begin(), position); // Check if the given index is bigger or equal than the actual amount of elements if it is we can not erase that element because it does not exist if (index < m_size) { // Move all elements after the index one position to the left @@ -137,7 +176,7 @@ class Array { /// @brief Method to access an element at a given index, /// ensures the device crashes if we attempted to access in an invalid location /// @param index Index we want to get the corresponding element for - T& at(size_t const & index) { + T & at(size_t const & index) { assert(index < m_size); return m_elements[index]; } @@ -145,7 +184,7 @@ class Array { /// @brief Bracket operator to access an element at a given index. /// Does not do any bounds checks, meaning the access is more efficient but it is possible to read out of bounds data /// @param index Index we want to get the corresponding element for - T& operator[](size_t const & index) { + T & operator[](size_t const & index) { return m_elements[index]; } @@ -163,8 +202,8 @@ class Array { } private: - T m_elements[Capacity]; // Underlying c-array holding our data - size_t m_size; // Used size that shows how many elements we entered + T m_elements[Capacity] = {}; // Underlying c-array holding our data + size_t m_size = {}; // Used size that shows how many elements we entered }; #endif // Array_h diff --git a/src/Attribute_Request.h b/src/Attribute_Request.h new file mode 100644 index 00000000..6a7747db --- /dev/null +++ b/src/Attribute_Request.h @@ -0,0 +1,300 @@ +#ifndef Attribute_Request_h +#define Attribute_Request_h + +// Local includes. +#include "Attribute_Request_Callback.h" +#include "IAPI_Implementation.h" + + +// Attribute request API topics. +char constexpr ATTRIBUTE_REQUEST_TOPIC[] = "v1/devices/me/attributes/request/%u"; +char constexpr ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/attributes/response/+"; +char constexpr ATTRIBUTE_RESPONSE_TOPIC[] = "v1/devices/me/attributes/response/"; +// Client side attribute request keys. +char constexpr CLIENT_REQUEST_KEYS[] = "clientKeys"; +char constexpr CLIENT_RESPONSE_KEY[] = "client"; +// Shared attribute request keys. +char constexpr SHARED_REQUEST_KEY[] = "sharedKeys"; +// Log messages. +#if THINGSBOARD_ENABLE_DEBUG +char constexpr NO_KEYS_TO_REQUEST[] = "No keys to request were given"; +char constexpr ATT_KEY_NOT_FOUND[] = "Attribute key in Attribute_Request_Callback is NULL"; +char constexpr ATT_KEY_IS_NULL[] = "Requested attribute key is NULL"; +#endif // THINGSBOARD_ENABLE_DEBUG +#if !THINGSBOARD_ENABLE_DYNAMIC +char constexpr CLIENT_SHARED_ATTRIBUTE_SUBSCRIPTIONS[] = "client or shared attribute request"; +#endif // THINGSBOARD_ENABLE_DYNAMIC + + +/// @brief Handles the internal implementation of the ThingsBoard shared and server-side Attribute API. +/// More specifically it handles the part for both types, where we can request the current value from the cloud +/// See https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +#if THINGSBOARD_ENABLE_DYNAMIC +template +#else +/// @tparam MaxSubscriptions Maximum amount of simultaneous server side rpc subscriptions. +/// Once the maximum amount has been reached it is not possible to increase the size, this is done because it allows to allcoate the memory on the stack instead of the heap, default = Default_Subscriptions_Amount (1) +/// @tparam MaxAttributes Maximum amount of attributes that will ever be requested with the Attribute_Request_Callback, allows to use an array on the stack in the background, default = Default_Attributes_Amount (5) +template +#endif // THINGSBOARD_ENABLE_DYNAMIC +class Attribute_Request : public IAPI_Implementation { + public: + /// @brief Constructor + Attribute_Request() = default; + + /// @brief Requests one client-side attribute calllback, + /// that will be called if the key-value pair from the server for the given client-side attributes is received. + /// Because the client-side attribute request is a single event subscription, meaning we only ever receive a response to our request once, + /// we automatically unsubscribe and delete the internal allocated data for the request as soon as the response has been received and handled by the subscribed callback. + /// See https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server for more information + /// @param callback Callback method that will be called + /// @return Whether requesting the given callback was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC + bool Client_Attributes_Request(Attribute_Request_Callback const & callback) { +#else + bool Client_Attributes_Request(Attribute_Request_Callback const & callback) { +#endif // THINGSBOARD_ENABLE_DYNAMIC + return Attributes_Request(callback, CLIENT_REQUEST_KEYS, CLIENT_RESPONSE_KEY); + } + + /// @brief Requests one shared attribute calllback, + /// that will be called if the key-value pair from the server for the given shared attributes is received. + /// Because the shared attribute request is a single event subscription, meaning we only ever receive a response to our request once, + /// we automatically unsubscribe and delete the internal allocated data for the request as soon as the response has been received and handled by the subscribed callback. + /// See https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server for more information + /// @param callback Callback method that will be called + /// @return Whether requesting the given callback was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC + bool Shared_Attributes_Request(Attribute_Request_Callback const & callback) { +#else + bool Shared_Attributes_Request(Attribute_Request_Callback const & callback) { +#endif // THINGSBOARD_ENABLE_DYNAMIC + return Attributes_Request(callback, SHARED_REQUEST_KEY, SHARED_RESPONSE_KEY); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + size_t const request_id = Helper::parseRequestId(ATTRIBUTE_RESPONSE_TOPIC, topic); + JsonObjectConst object = data.template as(); + + for (auto it = m_attribute_request_callbacks.begin(); it != m_attribute_request_callbacks.end(); ++it) { + auto & attribute_request = *it; + + if (attribute_request.Get_Request_ID() != request_id) { + continue; + } + char const * const attribute_response_key = attribute_request.Get_Attribute_Key(); + if (attribute_response_key == nullptr) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(ATT_KEY_NOT_FOUND); +#endif // THINGSBOARD_ENABLE_DEBUG + goto delete_callback; + } + + if (object.containsKey(attribute_response_key)) { + object = object[attribute_response_key]; + } + + attribute_request.Stop_Timeout_Timer(); + attribute_request.Call_Callback(object); + + delete_callback: + // Delete callback because the changes have been requested and the callback is no longer needed + Helper::remove(m_attribute_request_callbacks, it); + break; + } + + // Unsubscribe from the shared attribute request topic, + // if we are not waiting for any further responses with shared attributes from the server. + // Will be resubscribed if another request is sent anyway + if (m_attribute_request_callbacks.empty()) { + (void)Attributes_Request_Unsubscribe(); + } + } + + char const * Get_Response_Topic_String() const override { + return ATTRIBUTE_RESPONSE_TOPIC; + } + + bool Unsubscribe() override { + return Attributes_Request_Unsubscribe(); + } + + bool Resubscribe_Topic() override { + return Unsubscribe(); + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + for (auto & attribute_request : m_attribute_request_callbacks) { + attribute_request.Update_Timeout_Timer(); + } + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_send_json_callback.Set_Callback(send_json_callback); + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + m_get_request_id_callback.Set_Callback(get_request_id_callback); + } + + private: + /// @brief Requests one client-side or shared attribute calllback, + /// that will be called if the key-value pair from the server for the given client-side or shared attributes is received + /// @param callback Callback method that will be called + /// @param attribute_request_key Key of the key-value pair that will contain the attributes we want to request + /// @param attribute_response_key Key of the key-value pair that will contain the attributes we got as a response + /// @return Whether requesting the given callback was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC + bool Attributes_Request(Attribute_Request_Callback const & callback, char const * const attribute_request_key, char const * const attribute_response_key) { +#else + bool Attributes_Request(Attribute_Request_Callback const & callback, char const * const attribute_request_key, char const * const attribute_response_key) { +#endif // THINGSBOARD_ENABLE_DYNAMIC + auto const & attributes = callback.Get_Attributes(); + + // Check if any sharedKeys were requested + if (attributes.empty()) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(NO_KEYS_TO_REQUEST); +#endif // THINGSBOARD_ENABLE_DEBUG + return false; + } + else if (attribute_request_key == nullptr || attribute_response_key == nullptr) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(ATT_KEY_NOT_FOUND); +#endif // THINGSBOARD_ENABLE_DEBUG + return false; + } + +#if THINGSBOARD_ENABLE_DYNAMIC + Attribute_Request_Callback * registered_callback = nullptr; +#else + Attribute_Request_Callback * registered_callback = nullptr; +#endif // THINGSBOARD_ENABLE_DYNAMIC + if (!Attributes_Request_Subscribe(callback, registered_callback)) { + return false; + } + else if (registered_callback == nullptr) { + return false; + } + + // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, + // Data structure size depends on the amount of key value pairs passed + the default clientKeys or sharedKeys + // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument + StaticJsonDocument request_buffer; + + // Calculate the size required for the char buffer containing all the attributes seperated by a comma, + // before initalizing it so it is possible to allocate it on the stack + size_t size = 0U; + for (const auto & att : attributes) { + if (Helper::stringIsNullorEmpty(att)) { + continue; + } + + size += strlen(att); + size += strlen(","); + } + + // Initalizes complete array to 0, required because strncat needs both destination and source to contain proper null terminated strings + char request[size] = {}; + for (const auto & att : attributes) { + if (Helper::stringIsNullorEmpty(att)) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(ATT_KEY_IS_NULL); +#endif // THINGSBOARD_ENABLE_DEBUG + continue; + } + + strncat(request, att, size); + size -= strlen(att); + strncat(request, ",", size); + size -= strlen(","); + } + + // Ensure to cast to const, this is done so that ArduinoJson does not copy the value but instead simply store the pointer, which does not require any more memory, + // besides the base size needed to allocate one key-value pair. Because if we don't the char array would be copied + // and because there is not enough space the value would simply be "undefined" instead. Which would cause the request to not be sent correctly + request_buffer[attribute_request_key] = static_cast(request); + + size_t * p_request_id = m_get_request_id_callback.Call_Callback(); + if (p_request_id == nullptr) { + Logger::println(REQUEST_ID_NULL); + return false; + } + auto & request_id = *p_request_id; + + registered_callback->Set_Request_ID(++request_id); + registered_callback->Set_Attribute_Key(attribute_response_key); + registered_callback->Start_Timeout_Timer(); + + char topic[Helper::detectSize(ATTRIBUTE_REQUEST_TOPIC, request_id)] = {}; + (void)snprintf(topic, sizeof(topic), ATTRIBUTE_REQUEST_TOPIC, request_id); + return m_send_json_callback.Call_Callback(topic, request_buffer, Helper::Measure_Json(request_buffer)); + } + + /// @brief Subscribes to attribute response topic + /// @param callback Callback method that will be called + /// @param registered_callback Editable pointer to a reference of the local version that was copied from the passed callback + /// @return Whether requesting the given callback was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC + bool Attributes_Request_Subscribe(Attribute_Request_Callback const & callback, Attribute_Request_Callback * & registered_callback) { +#else + bool Attributes_Request_Subscribe(Attribute_Request_Callback const & callback, Attribute_Request_Callback * & registered_callback) { +#endif // THINGSBOARD_ENABLE_DYNAMIC +#if !THINGSBOARD_ENABLE_DYNAMIC + if (m_attribute_request_callbacks.size() + 1 > m_attribute_request_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, CLIENT_SHARED_ATTRIBUTE_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + if (!m_subscribe_topic_callback.Call_Callback(ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC)) { + Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC); + return false; + } + m_attribute_request_callbacks.push_back(callback); + registered_callback = &m_attribute_request_callbacks.back(); + return true; + } + + /// @brief Unsubscribes all client-side or shared attributes request callbacks + /// @return Whether unsubcribing the previously subscribed callbacks + /// and from the attribute response topic, was successful or not + bool Attributes_Request_Unsubscribe() { + m_attribute_request_callbacks.clear(); + return m_unsubscribe_topic_callback.Call_Callback(ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC); + } + + Callback m_send_json_callback = {}; // Send json document callback + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + Callback m_get_request_id_callback = {}; // Get internal request id callback + + // Vectors or array (depends on wheter if THINGSBOARD_ENABLE_DYNAMIC is set to 1 or 0), hold copy of the actual passed data, this is to ensure they stay valid, + // even if the user only temporarily created the object before the method was called. + // This can be done because all Callback methods mostly consists of pointers to actual object so copying them + // does not require a huge memory overhead and is acceptable especially in comparsion to possible problems that could + // arise if references were used and the end user does not take care to ensure the Callbacks live on for the entirety + // of its usage, which will lead to dangling references and undefined behaviour. + // Therefore copy-by-value has been choosen as for this specific use case it is more advantageous, + // especially because at most we copy internal vectors or array, that will only ever contain a few pointers +#if THINGSBOARD_ENABLE_DYNAMIC + Vector m_attribute_request_callbacks = {}; // Client-side or shared attribute request callback vector +#else + Array, MaxSubscriptions> m_attribute_request_callbacks = {}; // Client-side or shared attribute request callback array +#endif // THINGSBOARD_ENABLE_DYNAMIC +}; + +#endif // Attribute_Request_h diff --git a/src/Attribute_Request_Callback.h b/src/Attribute_Request_Callback.h index d8c29b40..09492896 100644 --- a/src/Attribute_Request_Callback.h +++ b/src/Attribute_Request_Callback.h @@ -2,19 +2,12 @@ #define Attribute_Request_Callback_h // Local includes. -#include "Callback.h" +#include "Callback_Watchdog.h" #if !THINGSBOARD_ENABLE_DYNAMIC #include "Constants.h" #endif // !THINGSBOARD_ENABLE_DYNAMIC -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char ATT_REQUEST_CB_IS_NULL[] PROGMEM = "Client-side or shared attribute request callback is NULL"; -#else -constexpr char ATT_REQUEST_CB_IS_NULL[] = "Client-side or shared attribute request callback is NULL"; -#endif // THINGSBOARD_ENABLE_PROGMEM - - /// @brief Client-side or shared attributes request callback wrapper, /// contains the needed configuration settings to create the request that should be sent to the server. /// Which attribute scope will be requested from either client-side or shared, is decided depending on which method the class instance is passed to as an argument. @@ -45,13 +38,19 @@ class Attribute_Request_Callback : public Callback - Attribute_Request_Callback(function callback, Args const &... args) - : Callback(callback, ATT_REQUEST_CB_IS_NULL) + Attribute_Request_Callback(function callback, uint64_t const & timeout_microseconds = 0U, Callback_Watchdog::function timeout_callback = nullptr, Args const &... args) + : Callback(callback) , m_attributes(args...) , m_request_id(0U) , m_attribute_key(nullptr) + , m_timeout_microseconds(timeout_microseconds) + , m_timeout_callback(timeout_callback) { // Nothing to do } @@ -60,11 +59,10 @@ class Attribute_Request_Callback : public Callback m_attributes; // Attribute we want to request + Vector m_attributes = {}; // Attribute we want to request #else - Array m_attributes; // Attribute we want to request + Array m_attributes = {}; // Attribute we want to request #endif // THINGSBOARD_ENABLE_DYNAMIC - size_t m_request_id; // Id the request was called with - char const *m_attribute_key; // Attribute key that we wil receive the response on ("client" or "shared") + size_t m_request_id = {}; // Id the request was called with + char const *m_attribute_key = {}; // Attribute key that we wil receive the response on ("client" or "shared") + uint64_t m_timeout_microseconds = {}; // Timeout time until we expect response to request + Callback_Watchdog m_timeout_callback = {}; // Handles callback that will be called if request times out }; #endif // Attribute_Request_Callback_h diff --git a/src/Callback.h b/src/Callback.h index 0e67a208..b31be5ea 100644 --- a/src/Callback.h +++ b/src/Callback.h @@ -15,9 +15,6 @@ #include #include #endif // THINGSBOARD_ENABLE_STL -#if THINGSBOARD_ENABLE_PROGMEM -#include -#endif // THINGSBOARD_ENABLE_PROGMEM #if THINGSBOARD_ENABLE_STL && THINGSBOARD_ENABLE_DYNAMIC @@ -27,44 +24,42 @@ using Vector = std::vector; #endif // THINGSBOARD_ENABLE_STL && THINGSBOARD_ENABLE_DYNAMIC -/// @brief General purpose callback wrapper. Expects either c-style or c++ style function pointer, -/// depending on if the C++ STL has been implemented on the given device or not -/// @tparam returnType Type the given callback method should return -/// @tparam argumentTypes Types the given callback method should receive -template +/// @brief General purpose safe callback wrapper. Expects either c-style or c++ style function pointer, +/// depending on if the C++ STL has been implemented on the given device or not. +/// Simply wraps that function pointer and before calling it ensures it actually exists +/// @tparam return_typ Type the given callback method should return +/// @tparam argument_types Types the given callback method should receive +template class Callback { public: /// @brief Callback signature #if THINGSBOARD_ENABLE_STL - using function = std::function; + using function = std::function; #else - using function = returnType (*)(argumentTypes... arguments); + using function = return_typ (*)(argument_types... arguments); #endif // THINGSBOARD_ENABLE_STL /// @brief Constructs empty callback, will result in never being called. Internals are simply default constructed as nullptr Callback() = default; - /// @brief Constructs base callback, will be called upon specific arrival of json message - /// where the requested data was sent by the cloud and received by the client - /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into the given arguemnt types - /// @param message Message that is logged if the callback given initally is a nullptr and can therefore not be called, - /// used to ensure users are informed that the initalization of the child class is invalid - Callback(function callback, char const * const message) + /// @brief Constructor + /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into the given arguemnt types. + /// If nullptr is passed the callback will never be called and return with a defaulted instance of the requested return variable + explicit Callback(function callback) : m_callback(callback) - , m_message(message) { // Nothing to do } - /// @brief Calls the callback that was subscribed, when this class instance was initally created - /// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set - /// @param ...arguments Received client-side or shared attribute request data that include - /// the client-side or shared attributes that were requested and their current values - template - returnType Call_Callback(argumentTypes const &... arguments) const { + /// @brief Calls the callback that was subscribed, when this class instance was initally created. + /// If the default constructor was used or a nullptr was passed instead of a valid function pointer, + /// this method will check beforehand and simply return with a defaulted instance of the requested return variable + /// @param ...arguments Optional additional arguments that are simply formwarded to the subscribed callback if it exists + /// @return Argument returned by the previously subscribed callback or if none or nullptr is subscribed + /// we instead return a defaulted instance of the requested return variable + return_typ Call_Callback(argument_types const &... arguments) const { if (!m_callback) { - Logger::println(m_message); - return returnType(); + return return_typ(); } return m_callback(arguments...); } @@ -77,8 +72,7 @@ class Callback { } private: - function m_callback; // Callback to call - char const *m_message; // Message to print if callback is a nullptr + function m_callback = {}; // Callback to call }; #endif // Callback_h diff --git a/src/Callback_Watchdog.cpp b/src/Callback_Watchdog.cpp deleted file mode 100644 index 6ba2c031..00000000 --- a/src/Callback_Watchdog.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Header include. -#include "Callback_Watchdog.h" - -#if THINGSBOARD_ENABLE_OTA - -#if THINGSBOARD_USE_ESP_TIMER -// Library includes. -#include - -constexpr char WATCHDOG_TIMER_NAME[] = "watchdog_timer"; -#endif // THINGSBOARD_USE_ESP_TIMER - -Callback_Watchdog *Callback_Watchdog::m_instance = nullptr; - -Callback_Watchdog::Callback_Watchdog(std::function callback) - : m_callback(callback) -#if THINGSBOARD_USE_ESP_TIMER - , m_oneshot_timer(nullptr) -#else - , m_oneshot_timer() -#endif // THINGSBOARD_USE_ESP_TIMER -{ - m_instance = this; -} - -Callback_Watchdog::~Callback_Watchdog() { -#if THINGSBOARD_USE_ESP_TIMER - // Timer only has to deleted at the end of the lifetime of this object, to ensure no memory leak occurs. - // But besides that the same timer can simply be stopped and restarted without needing to delete and create the timer again everytime. - (void)esp_timer_delete(static_cast(m_oneshot_timer)); - m_oneshot_timer = nullptr; -#else - m_oneshot_timer.detach(); -#endif // THINGSBOARD_USE_ESP_TIMER - m_instance = nullptr; -} - -void Callback_Watchdog::once(uint64_t const & timeout_microseconds) { -#if THINGSBOARD_USE_ESP_TIMER - create_timer(); - (void)esp_timer_start_once(static_cast(m_oneshot_timer), timeout_microseconds); -#else - const uint32_t timeout_millis = timeout_microseconds / 1000U; - m_oneshot_timer.once_ms(timeout_millis, &Callback_Watchdog::oneshot_timer_callback); -#endif // THINGSBOARD_USE_ESP_TIMER -} - -void Callback_Watchdog::detach() { -#if THINGSBOARD_USE_ESP_TIMER - (void)esp_timer_stop(static_cast(m_oneshot_timer)); -#else - m_oneshot_timer.detach(); -#endif // THINGSBOARD_USE_ESP_TIMER -} - -#if THINGSBOARD_USE_ESP_TIMER -void Callback_Watchdog::create_timer() { - // Timer has already been created previously there is no need to create it again - if (m_oneshot_timer != nullptr) { - return; - } - - const esp_timer_create_args_t oneshot_timer_args = { - .callback = &oneshot_timer_callback, - .arg = nullptr, - .dispatch_method = esp_timer_dispatch_t::ESP_TIMER_TASK, - .name = WATCHDOG_TIMER_NAME, - .skip_unhandled_events = false - }; - - // Temporary handle is used, because it allows using a void* as the actual oneshot_timer, - // allowing us to only include the esp_timer header in the defintion (.cpp) file, - // instead of also needing to declare it in the declaration (.h) header file - esp_timer_handle_t temp_handle; - const esp_err_t error = esp_timer_create(&oneshot_timer_args, &temp_handle); - if (error != ESP_OK) { - return; - } - - m_oneshot_timer = temp_handle; -} -#endif // THINGSBOARD_USE_ESP_TIMER - -#if THINGSBOARD_USE_ESP_TIMER -void Callback_Watchdog::oneshot_timer_callback(void *arg) { -#else -void Callback_Watchdog::oneshot_timer_callback() { -#endif // THINGSBOARD_USE_ESP_TIMER - if (m_instance == nullptr) { - return; - } - - m_instance->m_callback(); -} - -#endif // THINGSBOARD_ENABLE_OTA diff --git a/src/Callback_Watchdog.h b/src/Callback_Watchdog.h index 4f8bc05b..9070a965 100644 --- a/src/Callback_Watchdog.h +++ b/src/Callback_Watchdog.h @@ -1,75 +1,144 @@ #ifndef Callback_Watchdog_h #define Callback_Watchdog_h -// Local include. -#include "Configuration.h" - -#if THINGSBOARD_ENABLE_OTA +// Local includes. +#include "Callback.h" // Library includes. -#include -#if !THINGSBOARD_USE_ESP_TIMER -#include -#endif // !THINGSBOARD_USE_ESP_TIMER +#if THINGSBOARD_USE_ESP_TIMER +#include +#else +#include +#endif // THINGSBOARD_USE_ESP_TIMER + + +#if THINGSBOARD_USE_ESP_TIMER +constexpr char WATCHDOG_TIMER_NAME[] = "watchdog_timer"; +#endif // THINGSBOARD_USE_ESP_TIMER /// @brief Wrapper class which allows to start a timer and if it is not stopped in the given time then the callback that was passed will be called, /// which informs the user of the failure to stop the timer in time, meaning a timeout has occured. -/// The class wraps around either the Arduino Ticker class from Arduino (https://github.com/sstaub/Ticker) or the offical ESP Timer implementation from Espressif (https://github.com/espressif/esp-idf/tree/2bc1f2f574/examples/system/esp_timer), the latter takes precendence if it exists. -/// This is done because the usage is more efficient, under the hood we simply start and then stop and if needed start a oneshot timer again. -/// When using the esp timer directly the same timer can be reused to stop and start and therefore does not need to create a new timer everytime, -/// the Ticker in comparsion automatically deletes a timer once it has been stopped and we therefore need to create it again once the timer is started. -/// This causes a lot of overhead therefore if the esp timer exists we do not use the Ticker class but instead the esp timer directly. -/// For all other use cases where the esp timer does not exists we instead use the Ticker as a fallback, because in that case its implementation is completly different anyway. -/// The class instance is meant to be started with once() which will then call the registered callback after the timeout has passed, +/// The class wraps around either the Arduino timer class from Arduino (https://github.com/contrem/arduino-timer) or the offical ESP Timer implementation from Espressif (https://github.com/espressif/esp-idf/tree/master/examples/system/esp_timer), the latter takes precendence if it exists. +/// This is done because it uses FreeRTOS to start the actual timer in the background, which removes the need for a Hardware Timer with Interrupts but still achieve the advantage of accurate timings and no need for active polling. +/// For all other use cases where the esp timer does not exists we instead use the Arduino timer as a fallback, because is is a simple software timer with active polling that works on all Arduino based devices, +/// because it simply uses the millis() method per default but can be configured over template arguments to use other methods that return the current time. +/// The class instance is meant to be started with once() which will then call the registered callback after the timeout has passed. /// if the detach() method has not been called yet. /// This results in behaviour similair to a esp task watchdog but without as high of an accuracy and without restarting the device, /// allowing to let it fail and handle the error case silently by the user in the callback method. /// Documentation about the specific use and caviates of the ESP Timer implementation can be found here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html -class Callback_Watchdog { +class Callback_Watchdog : public Callback { public: - /// @brief Constructor - /// @param callback Callback method that will be called if the timeout time passes without detach() being called - Callback_Watchdog(std::function callback); + /// @brief Constructs empty timeout timer callback, will result in never being called. Internals are simply default constructed as nullptr + Callback_Watchdog() = default; + /// @brief Constructs callback, will be called if the timeout time passes without detach() being called + /// @param callback Callback method that will be called as soon as the internal software timers have processed that the given timeout time passed + explicit Callback_Watchdog(function callback) + : Callback(callback) +#if THINGSBOARD_USE_ESP_TIMER + , m_oneshot_timer(nullptr) +#else + , m_oneshot_timer() +#endif // THINGSBOARD_USE_ESP_TIMER + { + // Nothing to do + } + +#if THINGSBOARD_USE_ESP_TIMER /// @brief Destructor - ~Callback_Watchdog(); + ~Callback_Watchdog() { + // Timer only has to deleted at the end of the lifetime of this object, to ensure no memory leak occurs. + // But besides that the same timer can simply be stopped and restarted without needing to delete and create the timer again everytime. + (void)esp_timer_delete(m_oneshot_timer); + m_oneshot_timer = nullptr; + } +#endif // THINGSBOARD_USE_ESP_TIMER /// @brief Starts the watchdog timer once for the given timeout /// @param timeout_microseconds Amount of microseconds until the detach() method is excpected to have been called or the initally given callback method will be called - void once(uint64_t const & timeout_microseconds); - - /// @brief Stops the currently ongoing watchdog timer and ensures the callback is not called. Timer can simply be restarted with calling once() again. - void detach(); + void once(uint64_t const & timeout_microseconds) { +#if THINGSBOARD_USE_ESP_TIMER + create_timer(); + (void)esp_timer_start_once(m_oneshot_timer, timeout_microseconds); +#else + m_oneshot_timer.in(timeout_microseconds, &Callback_Watchdog::oneshot_timer_callback, this); +#endif // THINGSBOARD_USE_ESP_TIMER + } - private: - std::function m_callback; // Callback that should be called if the watchdog timeout expired without calling detach first + /// @brief Stops the currently ongoing watchdog timer and ensures the callback is not called. Timer can simply be restarted with calling once() again + void detach() { #if THINGSBOARD_USE_ESP_TIMER - void *m_oneshot_timer; // ESP Timer handle that is used to start and stop the oneshot timer + (void)esp_timer_stop(m_oneshot_timer); #else - Ticker m_oneshot_timer; // Ticker instance that handles the timer under the hood, if possible we directly use esp timer instead because it is more efficient + m_oneshot_timer.cancel(); #endif // THINGSBOARD_USE_ESP_TIMER + } - static Callback_Watchdog *m_instance; // Instance to the created class, will be set once the constructor has been called and reset once the destructor has been called, used to call private member method from static callback +#if !THINGSBOARD_USE_ESP_TIMER + /// @brief Internally checks if the time already passed, has to be done because we are using a simple software timer. + /// Indirectly called from the interal processing loop of this library, so we expect the user to recently often call the library loop() function. + /// In the worst case the actuall call of the callback might be massively delayed compared to the original timer time + void update() { + m_oneshot_timer.tick(); + } +#endif // !THINGSBOARD_USE_ESP_TIMER + private: #if THINGSBOARD_USE_ESP_TIMER - /// @brief Creates and initally configures the timer, has to be done once before either esp_timer_start_once or esp_timer_stop is called /// It can not be created in the constructor, because that would possibly be called before we have executed the main app code, meaning the esp timer base is not initalized yet. /// This would result in an invalid configuration which would cause crashes when used in combination with once() or detach() - void create_timer(); - + void create_timer() { + // Timer has already been created previously there is no need to create it again + if (m_oneshot_timer != nullptr) { + return; + } + + const esp_timer_create_args_t oneshot_timer_args = { + .callback = &oneshot_timer_callback, + .arg = this, + .dispatch_method = esp_timer_dispatch_t::ESP_TIMER_TASK, + .name = WATCHDOG_TIMER_NAME, + .skip_unhandled_events = false + }; + + const esp_err_t error = esp_timer_create(&oneshot_timer_args, &m_oneshot_timer); + if (error != ESP_OK) { + return; + } + } #endif // THINGSBOARD_USE_ESP_TIMER /// @brief Static callback used to call the initally subscribed callback, if the internal watchdog has not been reset in time with detach() #if THINGSBOARD_USE_ESP_TIMER - /// @param arg Possible argument passed to the timer callback is always nullptr, because we simply forward the call to an internal member method instead - static void oneshot_timer_callback(void *arg); + static void #else - static void oneshot_timer_callback(); + /// @return Whether we want to simply reset the internal time and start the timer once again, always false because we only use oneshot timers internally + static bool #endif // THINGSBOARD_USE_ESP_TIMER -}; + oneshot_timer_callback(void *arg) { + if (arg == nullptr) { +#if THINGSBOARD_USE_ESP_TIMER + return; +#else + return false; +#endif // THINGSBOARD_USE_ESP_TIMER + } + + auto instance = static_cast(arg); + instance->Call_Callback(); +#if !THINGSBOARD_USE_ESP_TIMER + return false; +#endif // !THINGSBOARD_USE_ESP_TIMER + } -#endif // THINGSBOARD_ENABLE_OTA +#if THINGSBOARD_USE_ESP_TIMER + esp_timer_handle_t m_oneshot_timer = {}; // ESP Timer handle that is used to start and stop the oneshot timer +#else + Timer<1, micros> m_oneshot_timer = {}; // Ticker instance that handles the timer under the hood, if possible we directly use esp timer instead because it is more efficient +#endif // THINGSBOARD_USE_ESP_TIMER +}; #endif // Argument_Cache_h diff --git a/src/Client_Side_RPC.h b/src/Client_Side_RPC.h new file mode 100644 index 00000000..d8e17df7 --- /dev/null +++ b/src/Client_Side_RPC.h @@ -0,0 +1,221 @@ +#ifndef Client_Side_RPC_h +#define Client_Side_RPC_h + +// Local includes. +#include "RPC_Request_Callback.h" +#include "IAPI_Implementation.h" + + +// Client side RPC topics. +char constexpr RPC_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/response/+"; +char constexpr RPC_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/"; +char constexpr RPC_SEND_REQUEST_TOPIC[] = "v1/devices/me/rpc/request/%u"; +// Log messages. +char constexpr CLIENT_RPC_METHOD_NULL[] = "Client-side RPC method name is NULL"; +#if !THINGSBOARD_ENABLE_DYNAMIC +char constexpr RPC_REQUEST_OVERFLOWED[] = "Client-side RPC request overflowed, increase MaxRequestRPC (%u)"; +char constexpr CLIENT_SIDE_RPC_SUBSCRIPTIONS[] = "client-side RPC"; +#endif // !THINGSBOARD_ENABLE_DYNAMIC +char constexpr RPC_EMPTY_PARAMS_VALUE[] = "{}"; + + +/// @brief Handles the internal implementation of the ThingsBoard client side RPC API. +/// See https://thingsboard.io/docs/user-guide/rpc/#client-side-rpc for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +#if THINGSBOARD_ENABLE_DYNAMIC +template +#else +/// @tparam MaxSubscriptions Maximum amount of simultaneous client side rpc requests. +/// Once the maximum amount has been reached it is not possible to increase the size, this is done because it allows to allcoate the memory on the stack instead of the heap, default = Default_Subscriptions_Amount (1) +/// @tparam MaxRequestRPC Maximum amount of key-value pairs that will ever be sent as parameters to the requests client side rpc method, allows to use a StaticJsonDocument on the stack in the background. +/// Is expected to only request client side rpc requests, that do not additionally send any parameters. If we attempt to send parameters, we have to adjust the size accordingly. +/// Default value is big enough to hold no parameters, but simply the default method name and params key needed for the request, if additional parameters are sent with the request the size has to be increased by one for each key-value pair. +/// See https://arduinojson.org/v6/assistant/ for more information on how to estimate the required size and divide the result by 16 and add 2 to receive the required MaxRequestRPC value, default = Default_Request_RPC_Amount (2) +template +#endif // THINGSBOARD_ENABLE_DYNAMIC +class Client_Side_RPC : public IAPI_Implementation { + public: + /// @brief Constructor + Client_Side_RPC() = default; + + /// @brief Requests one client-side RPC callback, + /// that will be called if a response from the server for the method with the given name is received. + /// Because the client-side RPC request is a single event subscription, meaning we only ever receive a response to our request once, + /// we automatically unsubscribe and delete the internal allocated data for the request as soon as the response has been received and handled by the subscribed callback. + /// See https://thingsboard.io/docs/user-guide/rpc/#client-side-rpc for more information + /// @param callback Callback method that will be called + /// @return Whether requesting the given callback was successful or not + bool RPC_Request(RPC_Request_Callback const & callback) { + char const * method_name = callback.Get_Name(); + + if (Helper::stringIsNullorEmpty(method_name)) { + Logger::println(CLIENT_RPC_METHOD_NULL); + return false; + } + RPC_Request_Callback * registered_callback = nullptr; + if (!RPC_Request_Subscribe(callback, registered_callback)) { + return false; + } + else if (registered_callback == nullptr) { + return false; + } + + JsonArray const * const parameters = callback.Get_Parameters(); + +#if THINGSBOARD_ENABLE_DYNAMIC + // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, + // Data structure size depends on the amount of key value pairs passed + the default method name and params key needed for the request. + // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument + TBJsonDocument request_buffer(JSON_OBJECT_SIZE(parameters != nullptr ? parameters->size() + 2U : 2U)); +#else + // Ensure to have enough size for the infinite amount of possible parameters that could be sent to the cloud + StaticJsonDocument request_buffer; +#endif // THINGSBOARD_ENABLE_DYNAMIC + + request_buffer[RPC_METHOD_KEY] = method_name; + + if (parameters != nullptr && !parameters->isNull()) { + request_buffer[RPC_PARAMS_KEY] = *parameters; + } + else { + request_buffer[RPC_PARAMS_KEY] = RPC_EMPTY_PARAMS_VALUE; + } + +#if !THINGSBOARD_ENABLE_DYNAMIC + if (request_buffer.overflowed()) { + Logger::printfln(RPC_REQUEST_OVERFLOWED, MaxRequestRPC); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + + size_t * p_request_id = m_get_request_id_callback.Call_Callback(); + if (p_request_id == nullptr) { + Logger::println(REQUEST_ID_NULL); + return false; + } + auto & request_id = *p_request_id; + + registered_callback->Set_Request_ID(++request_id); + registered_callback->Start_Timeout_Timer(); + + char topic[Helper::detectSize(RPC_SEND_REQUEST_TOPIC, request_id)] = {}; + (void)snprintf(topic, sizeof(topic), RPC_SEND_REQUEST_TOPIC, request_id); + return m_send_json_callback.Call_Callback(topic, request_buffer, Helper::Measure_Json(request_buffer)); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + size_t const request_id = Helper::parseRequestId(RPC_RESPONSE_TOPIC, topic); + + for (auto it = m_rpc_request_callbacks.begin(); it != m_rpc_request_callbacks.end(); ++it) { + auto & rpc_request = *it; + + if (rpc_request.Get_Request_ID() != request_id) { + continue; + } + rpc_request.Stop_Timeout_Timer(); + rpc_request.Call_Callback(data); + + // Delete callback because the changes have been requested and the callback is no longer needed + Helper::remove(m_rpc_request_callbacks, it); + break; + } + + // Attempt to unsubscribe from the shared attribute request topic, + // if we are not waiting for any further responses with shared attributes from the server. + // Will be resubscribed if another request is sent anyway + if (m_rpc_request_callbacks.empty()) { + (void)RPC_Request_Unsubscribe(); + } + } + + char const * Get_Response_Topic_String() const override { + return RPC_RESPONSE_TOPIC; + } + + bool Unsubscribe() override { + return RPC_Request_Unsubscribe(); + } + + bool Resubscribe_Topic() override { + return Unsubscribe(); + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + for (auto & rpc_request : m_rpc_request_callbacks) { + rpc_request.Update_Timeout_Timer(); + } + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_send_json_callback.Set_Callback(send_json_callback); + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + m_get_request_id_callback.Set_Callback(get_request_id_callback); + } + + private: + /// @brief Subscribes to the client-side RPC response topic, + /// that will be called if a reponse from the server for the method with the given name is received. + /// See https://thingsboard.io/docs/user-guide/rpc/#client-side-rpc for more information + /// @param callback Callback method that will be called + /// @param registered_callback Editable pointer to a reference of the local version that was copied from the passed callback + /// @return Whether requesting the given callback was successful or not + bool RPC_Request_Subscribe(RPC_Request_Callback const & callback, RPC_Request_Callback * & registered_callback) { +#if !THINGSBOARD_ENABLE_DYNAMIC + if (m_rpc_request_callbacks.size() + 1 > m_rpc_request_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, CLIENT_SIDE_RPC_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + if (!m_subscribe_topic_callback.Call_Callback(RPC_RESPONSE_SUBSCRIBE_TOPIC)) { + Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_RESPONSE_SUBSCRIBE_TOPIC); + return false; + } + m_rpc_request_callbacks.push_back(callback); + registered_callback = &m_rpc_request_callbacks.back(); + return true; + } + + /// @brief Unsubscribes all client-side RPC request callbacks + /// @return Whether unsubcribing the previously subscribed callbacks + /// and from the client-side RPC response topic, was successful or not + bool RPC_Request_Unsubscribe() { + m_rpc_request_callbacks.clear(); + return m_unsubscribe_topic_callback.Call_Callback(RPC_RESPONSE_SUBSCRIBE_TOPIC); + } + + Callback m_send_json_callback = {}; // Send json document callback + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + Callback m_get_request_id_callback = {}; // Get internal request id callback + + // Vectors or array (depends on wheter if THINGSBOARD_ENABLE_DYNAMIC is set to 1 or 0), hold copy of the actual passed data, this is to ensure they stay valid, + // even if the user only temporarily created the object before the method was called. + // This can be done because all Callback methods mostly consists of pointers to actual object so copying them + // does not require a huge memory overhead and is acceptable especially in comparsion to possible problems that could + // arise if references were used and the end user does not take care to ensure the Callbacks live on for the entirety + // of its usage, which will lead to dangling references and undefined behaviour. + // Therefore copy-by-value has been choosen as for this specific use case it is more advantageous, + // especially because at most we copy internal vectors or array, that will only ever contain a few pointers +#if THINGSBOARD_ENABLE_DYNAMIC + Vector m_rpc_request_callbacks = {}; // Server side RPC callbacks vector +#else + Array m_rpc_request_callbacks = {}; // Server side RPC callbacks array +#endif // THINGSBOARD_ENABLE_DYNAMIC +}; + +#endif // Client_Side_RPC_h diff --git a/src/Configuration.h b/src/Configuration.h index b5b117f9..579af931 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -42,17 +42,12 @@ # endif # endif -// Enable the usage of OTA (Over the air) updates, only possible with STL base functionality, theoretically possible without STL support, -// but the code would have to be adjusted at compile time depending on if the C++ STL is supported or not and that has not been implemented for OTA yet. -# ifndef THINGSBOARD_ENABLE_OTA -# define THINGSBOARD_ENABLE_OTA THINGSBOARD_ENABLE_STL -# endif - // Use the esp_timer header internally for handling timeouts and callbacks, as long as the header exists, because it is more efficient than the Arduino Ticker implementation, // because we can stop the timer without having to delete it, removing the need to create a new timer to restart it. Because instead we can simply stop and start again. +// Only exists following major version 3 minor version 0 on ESP32 (https://github.com/espressif/esp-idf/releases/tag/v3.0-rc1)and major version 3 minor version 1 on ESP8266 (https://github.com/espressif/ESP8266_RTOS_SDK/releases/tag/v3.1-rc1) # ifndef THINGSBOARD_USE_ESP_TIMER # ifdef __has_include -# if __has_include() && ESP_IDF_VERSION_MAJOR >= 4 +# if __has_include() # define THINGSBOARD_USE_ESP_TIMER 1 # else # define THINGSBOARD_USE_ESP_TIMER 0 @@ -64,9 +59,10 @@ // Use the mqtt_client header internally for handling the sending and receiving of MQTT data, as long as the header exists, // to allow users that do have the needed component to use the Espressif_MQTT_Client instead of only the Arduino_MQTT_Client. +// Only exists following major version 3 minor version 2 on ESP32 (https://github.com/espressif/esp-idf/releases/tag/v3.2) and major version 3 minor version 4 on ESP8266 (https://github.com/espressif/ESP8266_RTOS_SDK/releases/tag/v3.4). # ifndef THINGSBOARD_USE_ESP_MQTT # ifdef __has_include -# if __has_include() && ESP_IDF_VERSION_MAJOR >= 4 +# if __has_include() # define THINGSBOARD_USE_ESP_MQTT 1 # else # define THINGSBOARD_USE_ESP_MQTT 0 @@ -78,9 +74,10 @@ // Use the mbed_tls header internally for handling the creation of hashes from binary data, as long as the header exists, // because if it is already included we do not need to rely on and incude external lbiraries like Seeed_mbedtls.h, which implements the same features. +// Only exists following major version 0 minor version 9 on ESP32 (https://github.com/espressif/esp-idf/releases/v0.9) and major version 3 minor version 3 on ESP8266 (https://github.com/espressif/ESP8266_RTOS_SDK/releases/tag/v3.3-rc1). # ifndef THINGSBOARD_USE_MBED_TLS # ifdef __has_include -# if __has_include() && ESP_IDF_VERSION_MAJOR >= 4 +# if __has_include() # define THINGSBOARD_USE_MBED_TLS 1 # else # define THINGSBOARD_USE_MBED_TLS 0 @@ -92,9 +89,11 @@ // Use the esp_ota_ops header internally for handling the writing of ota update data, as long as the header exists, // to allow users that do have the needed component to use the Espressif_Updater instead of only the Arduino_ESP32_Updater. +// Only exists following major version 1 minor version 0 on ESP32 (https://github.com/espressif/esp-idf/releases/v0.9) and major version 3 minor version 0 on ESP8266 (https://github.com/espressif/ESP8266_RTOS_SDK/releases/tag/v3.0-rc1). +// Additionally, for all the expected API calls to be implemented atleast, major version 2 minor version 1 on ESP32 and major version 3 minor version 0 on ESP8266 is required. # ifndef THINGSBOARD_USE_ESP_PARTITION # ifdef __has_include -# if __has_include() && ESP_IDF_VERSION_MAJOR >= 4 +# if __has_include() && (!defined(ESP32) || ((ESP_IDF_VERSION_MAJOR == 2 && ESP_IDF_VERSION_MINOR >= 1) || ESP_IDF_VERSION_MAJOR > 2)) && (!defined(ESP8266) || ESP_IDF_VERSION_MAJOR >= 3) # define THINGSBOARD_USE_ESP_PARTITION 1 # else # define THINGSBOARD_USE_ESP_PARTITION 0 @@ -104,20 +103,6 @@ # endif # endif -// Use the pgmspace header internally for enabling the usage of the PROGMEM header for constant variables, as long as the header exists, -// to allow variables to be placed into flash memory instead of sram, meaning the sram can be allocated for other things. -# ifndef THINGSBOARD_ENABLE_PROGMEM -# ifdef __has_include -# if __has_include() && !defined(ESP8266) -# define THINGSBOARD_ENABLE_PROGMEM 1 -# else -# define THINGSBOARD_ENABLE_PROGMEM 0 -# endif -# else -# define THINGSBOARD_ENABLE_PROGMEM 0 -# endif -# endif - // Enables the ThingsBoard class to be fully dynamic instead of requiring template arguments to statically allocate memory. // If enabled the program might be slightly slower and all the memory will be placed onto the heap instead of the stack. // See https://arduinojson.org/v6/api/dynamicjsondocument/ for the main difference in the underlying code. diff --git a/src/Constants.h b/src/Constants.h index 029f6e66..088de32c 100644 --- a/src/Constants.h +++ b/src/Constants.h @@ -5,42 +5,35 @@ #include "Configuration.h" // Library includes. -#if THINGSBOARD_ENABLE_PROGMEM -#include -#endif // THINGSBOARD_ENABLE_PROGMEM #if THINGSBOARD_ENABLE_PSRAM || THINGSBOARD_ENABLE_DYNAMIC #include #endif // THINGSBOARD_ENABLE_PSRAM || THINGSBOARD_ENABLE_DYNAMIC -#define Default_Fields_Amount 8 -#define Default_Subscriptions_Amount 2 -#define Default_Attributes_Amount 5 +#define Default_Endpoints_Amount 7 +#define Default_Response_Amount 8 +#define Default_Subscriptions_Amount 1 +#define Default_Attributes_Amount 1 #define Default_RPC_Amount 0 -#define Default_Payload 64 +#define Default_Request_RPC_Amount 2 +#define Default_Payload_Size 64 #define Default_Max_Stack_Size 1024 #if THINGSBOARD_ENABLE_STREAM_UTILS #define Default_Buffering_Size 64 #endif // THINGSBOARD_ENABLE_STREAM_UTILS +#if THINGSBOARD_ENABLE_DYNAMIC +#define Default_Max_Response_Size 0 +#endif // THINGSBOARD_ENABLE_DYNAMIC // Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr UNABLE_TO_SERIALIZE[] PROGMEM = "Unable to serialize key-value json"; #if !THINGSBOARD_ENABLE_DYNAMIC -char constexpr TOO_MANY_JSON_FIELDS[] PROGMEM = "Too many JSON fields passed (%u), increase MaxFieldsAmount (%u) accordingly"; +char constexpr TOO_MANY_JSON_FIELDS[] = "Attempt to enter to many JSON fields into StaticJsonDocument (%u), increase (%s) (%u) accordingly"; #endif // !THINGSBOARD_ENABLE_DYNAMIC -char constexpr CONNECT_FAILED[] PROGMEM = "Connecting to server failed"; -char constexpr UNABLE_TO_SERIALIZE_JSON[] PROGMEM = "Unable to serialize json data"; -char constexpr UNABLE_TO_ALLOCATE_JSON[] PROGMEM = "Allocating memory for the JsonDocument failed, passed JsonObject or JsonVariant is NULL"; -#else char constexpr UNABLE_TO_SERIALIZE[] = "Unable to serialize key-value json"; -#if !THINGSBOARD_ENABLE_DYNAMIC -char constexpr TOO_MANY_JSON_FIELDS[] = "Too many JSON fields passed (%u), increase MaxFieldsAmount (%u) accordingly"; -#endif // !THINGSBOARD_ENABLE_DYNAMIC char constexpr CONNECT_FAILED[] = "Connecting to server failed"; char constexpr UNABLE_TO_SERIALIZE_JSON[] = "Unable to serialize json data"; -char constexpr UNABLE_TO_ALLOCATE_JSON[] = "Allocating memory for the JsonDocument failed, passed JsonObject or JsonVariant is NULL"; -#endif // THINGSBOARD_ENABLE_PROGMEM +char constexpr UNABLE_TO_ALLOCATE_JSON[] = "Allocating memory for the JsonDocument failed, passed JsonDocument is NULL"; +char constexpr JSON_SIZE_TO_SMALL[] = "JsonDocument too small to store all values. Ensure every key value pair gets JSON_OBJECT_SIZE(1) capacity + size required by value / key that is inserted"; #if THINGSBOARD_ENABLE_PSRAM diff --git a/src/DefaultLogger.h b/src/DefaultLogger.h index 6eef8d05..b6ea9e1c 100644 --- a/src/DefaultLogger.h +++ b/src/DefaultLogger.h @@ -6,13 +6,8 @@ // Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr FAILED_MESSAGE[] PROGMEM = "Invalid arguments passed to format specifiers (%) in Logger::printfln"; -char constexpr LOG_MESSAGE_FORMAT[] PROGMEM = "[TB] %s\n"; -#else char constexpr FAILED_MESSAGE[] = "Invalid arguments passed to format specifiers (%) in Logger::printfln"; char constexpr LOG_MESSAGE_FORMAT[] = "[TB] %s\n"; -#endif // THINGSBOARD_ENABLE_PROGMEM /// @brief Default logger class used by the ThingsBoard class to log messages into the console output diff --git a/src/Espressif_MQTT_Client.cpp b/src/Espressif_MQTT_Client.cpp index 77ad9511..26a6671c 100644 --- a/src/Espressif_MQTT_Client.cpp +++ b/src/Espressif_MQTT_Client.cpp @@ -9,21 +9,18 @@ // to ensure other errors are indentified as well constexpr int MQTT_FAILURE_MESSAGE_ID = -1; -Espressif_MQTT_Client *Espressif_MQTT_Client::m_instance = nullptr; - Espressif_MQTT_Client::Espressif_MQTT_Client() : - m_received_data_callback(nullptr), - m_connected_callback(nullptr), + m_received_data_callback(), + m_connected_callback(), m_connected(false), m_enqueue_messages(false), m_mqtt_configuration(), m_mqtt_client(nullptr) { - m_instance = this; + // Nothing to do } Espressif_MQTT_Client::~Espressif_MQTT_Client() { - m_instance = nullptr; (void)esp_mqtt_client_destroy(m_mqtt_client); } @@ -115,15 +112,15 @@ void Espressif_MQTT_Client::set_enqueue_messages(const bool& enqueue_messages) { m_enqueue_messages = enqueue_messages; } -void Espressif_MQTT_Client::set_data_callback(data_function callback) { - m_received_data_callback = callback; +void Espressif_MQTT_Client::set_data_callback(Callback::function callback) { + m_received_data_callback.Set_Callback(callback); } -void Espressif_MQTT_Client::set_connect_callback(connect_function callback) { - m_connected_callback = callback; +void Espressif_MQTT_Client::set_connect_callback(Callback::function callback) { + m_connected_callback.Set_Callback(callback); } -bool Espressif_MQTT_Client::set_buffer_size(const uint16_t& buffer_size) { +bool Espressif_MQTT_Client::set_buffer_size(uint16_t buffer_size) { // ESP_IDF_VERSION_MAJOR Version 5 is a major breaking changes were the complete esp_mqtt_client_config_t structure changed completely #if ESP_IDF_VERSION_MAJOR < 5 m_mqtt_configuration.buffer_size = buffer_size; @@ -149,7 +146,7 @@ uint16_t Espressif_MQTT_Client::get_buffer_size() { #endif // ESP_IDF_VERSION_MAJOR < 5 } -void Espressif_MQTT_Client::set_server(const char *domain, const uint16_t& port) { +void Espressif_MQTT_Client::set_server(const char *domain, uint16_t port) { // ESP_IDF_VERSION_MAJOR Version 5 is a major breaking changes were the complete esp_mqtt_client_config_t structure changed completely #if ESP_IDF_VERSION_MAJOR < 5 m_mqtt_configuration.host = domain; @@ -200,10 +197,7 @@ bool Espressif_MQTT_Client::connect(const char *client_id, const char *user_name // The client is first initalized once the connect has actually been called, this is done because the passed setting are required for the client inizialitation structure, // additionally before we attempt to connect with the client we have to ensure it is configued by then. m_mqtt_client = esp_mqtt_client_init(&m_mqtt_configuration); - - // The last argument may be used to pass data to the event handler, here that would be the static_mqtt_event_handler. But for our use case this is not needed, - // because the static_mqtt_event_handler calls a private method on this class again anyway, meaning we already have access to all private member variables that are required - esp_err_t error = esp_mqtt_client_register_event(m_mqtt_client, esp_mqtt_event_id_t::MQTT_EVENT_ANY, Espressif_MQTT_Client::static_mqtt_event_handler, nullptr); + esp_err_t error = esp_mqtt_client_register_event(m_mqtt_client, esp_mqtt_event_id_t::MQTT_EVENT_ANY, Espressif_MQTT_Client::static_mqtt_event_handler, this); if (error != ESP_OK) { return false; @@ -276,13 +270,13 @@ bool Espressif_MQTT_Client::update_configuration() { return error == ESP_OK; } -void Espressif_MQTT_Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, const esp_mqtt_event_id_t& event_id, void *event_data) { +void Espressif_MQTT_Client::mqtt_event_handler(esp_event_base_t base, const esp_mqtt_event_id_t& event_id, void *event_data) { const esp_mqtt_event_handle_t event = static_cast(event_data); switch (event_id) { case esp_mqtt_event_id_t::MQTT_EVENT_CONNECTED: m_connected = true; - m_connected_callback(); + m_connected_callback.Call_Callback(); break; case esp_mqtt_event_id_t::MQTT_EVENT_DISCONNECTED: m_connected = false; @@ -302,10 +296,7 @@ void Espressif_MQTT_Client::mqtt_event_handler(void *handler_args, esp_event_bas if (event->data_len != event->total_data_len) { break; } - - if (m_received_data_callback != nullptr) { - m_received_data_callback(event->topic, reinterpret_cast(event->data), event->data_len); - } + m_received_data_callback.Call_Callback(event->topic, reinterpret_cast(event->data), event->data_len); break; case esp_mqtt_event_id_t::MQTT_EVENT_ERROR: // Nothing to do @@ -320,11 +311,11 @@ void Espressif_MQTT_Client::mqtt_event_handler(void *handler_args, esp_event_bas } void Espressif_MQTT_Client::static_mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { - if (m_instance == nullptr) { + if (handler_args == nullptr) { return; } - - m_instance->mqtt_event_handler(handler_args, base, static_cast(event_id), event_data); + auto instance = static_cast(handler_args); + instance->mqtt_event_handler(base, static_cast(event_id), event_data); } #endif // THINGSBOARD_USE_ESP_MQTT diff --git a/src/Espressif_MQTT_Client.h b/src/Espressif_MQTT_Client.h index 02999089..e11ac980 100644 --- a/src/Espressif_MQTT_Client.h +++ b/src/Espressif_MQTT_Client.h @@ -93,15 +93,15 @@ class Espressif_MQTT_Client : public IMQTT_Client { /// @param enqueue_messages Whether to enqueue published messages or not, where setting the value to true means that the messages are enqueued and therefor non blocking on the called from task void set_enqueue_messages(bool const & enqueue_messages); - void set_data_callback(data_function callback) override; + void set_data_callback(Callback::function callback) override; - void set_connect_callback(connect_function cb) override; + void set_connect_callback(Callback::function callback) override; - bool set_buffer_size(uint16_t const & buffer_size) override; + bool set_buffer_size(uint16_t buffer_size) override; uint16_t get_buffer_size() override; - void set_server(char const * const domain, uint16_t const & port) override; + void set_server(char const * const domain, uint16_t port) override; bool connect(char const * const client_id, char const * const user_name, char const * const password) override; @@ -118,15 +118,6 @@ class Espressif_MQTT_Client : public IMQTT_Client { bool connected() override; private: - data_function m_received_data_callback; // Callback that will be called as soon as the mqtt client receives any data - connect_function m_connected_callback; // Callback that will be called as soon as the mqtt client has connected - bool m_connected; // Whether the client has received the connected or disconnected event - bool m_enqueue_messages; // Whether we enqueue messages making nearly all ThingsBoard calls non blocking or wheter we publish instead - esp_mqtt_client_config_t m_mqtt_configuration; // Configuration of the underlying mqtt client, saved as a private variable to allow changes after inital configuration with the same options for all non changed settings - esp_mqtt_client_handle_t m_mqtt_client; // Handle to the underlying mqtt client, used to establish the communication - - static Espressif_MQTT_Client *m_instance; // Instance to the created class, will be set once the constructor has been called and reset once the destructor has been called, used to call private member method from static callback - /// @brief Is internally used to allow changes to the underlying configuration of the esp_mqtt_client_handle_t after it has connected, /// to for example increase the buffer size or increase the timeouts or stack size, allows to change the underlying client configuration, /// without the need to completly disconnect and reconnect the client @@ -134,13 +125,19 @@ class Espressif_MQTT_Client : public IMQTT_Client { bool update_configuration(); /// @brief Event handler registered to receive MQTT events. Is called by the MQTT client event loop, whenever a new event occurs - /// @param handler_args User data registered to the event /// @param base Event base for the handler /// @param event_id The id for the received event /// @param event_data The data for the event, esp_mqtt_event_handle_t - void mqtt_event_handler(void *handler_args, esp_event_base_t base, esp_mqtt_event_id_t const & event_id, void *event_data); + void mqtt_event_handler(esp_event_base_t base, esp_mqtt_event_id_t const & event_id, void *event_data); static void static_mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + + Callback m_received_data_callback = {}; // Callback that will be called as soon as the mqtt client receives any data + Callback m_connected_callback = {}; // Callback that will be called as soon as the mqtt client has connected + bool m_connected = {}; // Whether the client has received the connected or disconnected event + bool m_enqueue_messages = {}; // Whether we enqueue messages making nearly all ThingsBoard calls non blocking or wheter we publish instead + esp_mqtt_client_config_t m_mqtt_configuration = {}; // Configuration of the underlying mqtt client, saved as a private variable to allow changes after inital configuration with the same options for all non changed settings + esp_mqtt_client_handle_t m_mqtt_client = {}; // Handle to the underlying mqtt client, used to establish the communication }; #endif // THINGSBOARD_USE_ESP_MQTT diff --git a/src/Espressif_Updater.cpp b/src/Espressif_Updater.cpp index 79f610bf..a22b0443 100644 --- a/src/Espressif_Updater.cpp +++ b/src/Espressif_Updater.cpp @@ -1,7 +1,7 @@ // Header include. #include "Espressif_Updater.h" -#if THINGSBOARD_ENABLE_OTA && THINGSBOARD_USE_ESP_PARTITION +#if THINGSBOARD_USE_ESP_PARTITION // Library include. #include @@ -42,7 +42,11 @@ size_t Espressif_Updater::write(uint8_t * payload, size_t const & total_bytes) { } void Espressif_Updater::reset() { +#if defined(ESP8266) || (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR < 3) || ESP_IDF_VERSION_MAJOR < 4 + (void)end(); +#else (void)esp_ota_abort(m_ota_handle); +#endif } bool Espressif_Updater::end() { @@ -55,4 +59,4 @@ bool Espressif_Updater::end() { return error == ESP_OK; } -#endif // THINGSBOARD_ENABLE_OTA && THINGSBOARD_USE_ESP_PARTITION +#endif // THINGSBOARD_USE_ESP_PARTITION diff --git a/src/Espressif_Updater.h b/src/Espressif_Updater.h index bf9cb546..7e57b32f 100644 --- a/src/Espressif_Updater.h +++ b/src/Espressif_Updater.h @@ -4,7 +4,7 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA && THINGSBOARD_USE_ESP_PARTITION +#if THINGSBOARD_USE_ESP_PARTITION // Local include. #include "IUpdater.h" @@ -25,10 +25,10 @@ class Espressif_Updater : public IUpdater { bool end() override; private: - uint32_t m_ota_handle; // ESP OTA hanle that is used to to access the underlying updater - void const * m_update_partition; // Non active OTA partition that we write our data into + uint32_t m_ota_handle = {}; // ESP OTA hanle that is used to to access the underlying updater + void const * m_update_partition = {}; // Non active OTA partition that we write our data into }; -#endif // THINGSBOARD_ENABLE_OTA && THINGSBOARD_USE_ESP_PARTITION +#endif // THINGSBOARD_USE_ESP_PARTITION #endif // Espressif_Updater_h diff --git a/src/HashGenerator.cpp b/src/HashGenerator.cpp index 53959537..44bc902b 100644 --- a/src/HashGenerator.cpp +++ b/src/HashGenerator.cpp @@ -1,8 +1,6 @@ // Header include. #include "HashGenerator.h" -#if THINGSBOARD_ENABLE_OTA - HashGenerator::~HashGenerator(void) { free(); } @@ -38,5 +36,3 @@ void HashGenerator::free() { mbedtls_md_free(&m_ctx); } } - -#endif // THINGSBOARD_ENABLE_OTA diff --git a/src/HashGenerator.h b/src/HashGenerator.h index 9753c6ce..e454cd43 100644 --- a/src/HashGenerator.h +++ b/src/HashGenerator.h @@ -4,8 +4,6 @@ // Local includes. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - // Library includes. #if THINGSBOARD_USE_MBED_TLS #include @@ -51,13 +49,11 @@ class HashGenerator { bool finish(unsigned char * hash); private: - mbedtls_md_context_t m_ctx; // Context used to access the already written bytes and update them latter - /// @brief Frees all internally allocated memory to ensure no memory leak occurs, additionally check if a hash calculation was ever started, /// before freeing, because freeing without having started a hash calculation causes a crash. void free(); -}; -#endif // THINGSBOARD_ENABLE_OTA + mbedtls_md_context_t m_ctx = {}; // Context used to access the already written bytes and update them latter +}; #endif // Hash_Generator_h diff --git a/src/Helper.cpp b/src/Helper.cpp index ef31462d..6be8d16c 100644 --- a/src/Helper.cpp +++ b/src/Helper.cpp @@ -7,16 +7,15 @@ // Library includes. #include -size_t Helper::getOccurences(char const * const str, char symbol) { +size_t Helper::getOccurences(uint8_t const * const bytes, char symbol, unsigned int length) { size_t count = 0; - if (str == nullptr) { - return count; + if (bytes == nullptr) { + return count; } - for (size_t i = 0; i < strlen(str); ++i) { - if (str[i] != symbol) { - continue; - } - count++; + for (size_t i = 0; i < length; ++i) { + if (bytes[i] == symbol) { + count++; + } } return count; } @@ -27,8 +26,7 @@ bool Helper::stringIsNullorEmpty(char const * const str) { size_t Helper::parseRequestId(char const * const base_topic, char const * const received_topic) { // Remove the not needed part of the received topic string, which is everything before the request id, - // therefore we ignore the section before that which is the base topic + an additional "/" character, that seperates the topic from the request id. - // Meaning the index we attempt to parse is the length of the base topic + 1 for the additonal "/" character - size_t const index = strlen(base_topic) + 1U; - return atoi(received_topic + index); + // therefore we ignore the section before that which is the base topic, that seperates the topic from the request id. + // Meaning the index we attempt to parse at, is simply the length of the base topic + return atoi(received_topic + strlen(base_topic)); } diff --git a/src/Helper.h b/src/Helper.h index a417b29d..686e0bb0 100644 --- a/src/Helper.h +++ b/src/Helper.h @@ -32,11 +32,13 @@ class Helper { return result; } - /// @brief Returns the amount of occurences of the given smybol in the given string - /// @param str String that we want to check the symbol in - /// @param symbol Symbols we want to search for + /// @brief Returns the amount of occurences of the given smybol in the given byte payload + /// @param bytes Byte payload that we want to check the symbol for + /// @param symbol Symbol we want to search for + /// @param length Length of the byte payload, meaning if we reach the given length and have not found any occurence of the symbol we return 0. + /// Ensure to never pass a length that is longer than the actualy payload, because this will cause this method to read outside of the bounds of the buffer /// @return Amount of occurences of the given symbol - static size_t getOccurences(char const * const str, char symbol); + static size_t getOccurences(uint8_t const * const bytes, char symbol, unsigned int length); /// @brief Returns wheter the given string is either a nullptr or is an empty string, /// meaning it only contains a null terminator and no other characters @@ -45,12 +47,11 @@ class Helper { static bool stringIsNullorEmpty(char const * const str); /// @brief Returns the portion of the received topic after the base topic as an integer. - /// Should contain the request id that the original request was sent with. - /// Is used to know which received response is connected to which inital request. - /// @param base_topic Base portion of the topic that does not contain any parameters, - /// should not contain trailing '/' character because the implementation already adds +1 to exclude that character as well (v1/devices/me/attributes/response) + /// Should contain the request id that the original request was sent with + /// Is used to know which received response is connected to which inital request + /// @param base_topic Base portion of the topic that does not contain any parameters (v1/devices/me/attributes/response/) /// @param received_topic Received topic that contains the base topic as well as the request id parameter (v1/devices/me/rpc/response/$request_id) - /// @return Converted integral request id if possible or 0 if parsing as an integer failed. + /// @return Converted integral request id if possible or 0 if parsing as an integer failed static size_t parseRequestId(char const * const base_topic, char const * const received_topic); /// @brief Calculates the total size of the string the serializeJson method would produce including the null end terminator. @@ -96,10 +97,7 @@ class Helper { // This allows the edge case where an end-user uses this method themselves in the code with their own implemented list data type. size_t size = 0U; auto it = first; - while (it != last) { - ++it; - ++size; - } + for (auto it = first; it != last; ++it, ++size) {} return size; #endif // THINGSBOARD_ENABLE_STL } diff --git a/src/IAPI_Implementation.h b/src/IAPI_Implementation.h new file mode 100644 index 00000000..a83572f6 --- /dev/null +++ b/src/IAPI_Implementation.h @@ -0,0 +1,93 @@ +#ifndef IAPI_Implementation_h +#define IAPI_Implementation_h + +// Local include. +#include "Callback.h" +#include "Constants.h" +#include "DefaultLogger.h" +#include "API_Process_Type.h" + + +// Log messages. +#if !THINGSBOARD_ENABLE_DYNAMIC +char constexpr MAX_SUBSCRIPTIONS_EXCEEDED[] = "Too many (%s) subscriptions, increase (%s) or unsubscribe"; +#endif // !THINGSBOARD_ENABLE_DYNAMIC +char constexpr MAX_SUBSCRIPTIONS_TEMPLATE_NAME[] = "MaxSubscriptions"; +char constexpr SUBSCRIBE_TOPIC_FAILED[] = "Subscribing the given topic (%s) failed"; +char constexpr REQUEST_ID_NULL[] = "Internal request id is NULL"; +// RPC data keys. +char constexpr RPC_METHOD_KEY[] = "method"; +char constexpr RPC_PARAMS_KEY[] = "params"; +// Shared attribute update API topics. +char constexpr ATTRIBUTE_TOPIC[] = "v1/devices/me/attributes"; +// Shared attribute request keys. +char constexpr SHARED_RESPONSE_KEY[] = "shared"; +// Publish data topics. +char constexpr TELEMETRY_TOPIC[] = "v1/devices/me/telemetry"; + + +/// @brief Base functionality required by all API implementation +class IAPI_Implementation { + public: + /// @brief Returns the way the server response should be processed. + /// Only ever uses one at the time, because the response is either unserialized data which we need to process as such (OTA Firmware Update) + /// or actually JSON which needs to be serialized (everything else) + /// @return How the API implementation should be passed the response + virtual API_Process_Type Get_Process_Type() const = 0; + + /// @brief Process callback that will be called upon response arrival + /// and is responsible for handling the payload before serialization and calling the appropriate previously subscribed callbacks + /// @param topic Previously subscribed topic, we got the response over + /// @param payload Payload that was sent over the cloud and received over the given topic + /// @param length Total length of the received payload + virtual void Process_Response(char * const topic, uint8_t * payload, unsigned int length) = 0; + + /// @brief Process callback that will be called upon response arrival + /// and is responsible for handling the alredy serialized payload and calling the appropriate previously subscribed callbacks + /// @param topic Previously subscribed topic, we got the response over + /// @param data Payload sent by the server over our given topic, that contains our key value pairs + virtual void Process_Json_Response(char * const topic, JsonDocument const & data) = 0; + + /// @brief Returns a non-owning pointer to the respone topic string, that we should have received the actual data on. + /// Used to check, which API Implementation needs to handle the current response to a previously sent request + /// @return Response topic null-terminated string + virtual char const * Get_Response_Topic_String() const = 0; + + /// @brief Unsubcribes all callbacks, to clear up any ongoing subscriptions and stop receiving information over the previously subscribed topic + /// @return Whether unsubcribing all the previously subscribed callbacks + /// and from the previously subscribed topic, was successful or not + virtual bool Unsubscribe() = 0; + + /// @brief Forwards the call to let the API clear up any ongoing single-event subscriptions (Provision, Attribute Request, RPC Request) + /// and simply resubscribes the topic for all permanent subscriptions (RPC, Shared Attribute Update) + /// @return Whether resubscribing was successfull or not + virtual bool Resubscribe_Topic() = 0; + +#if !THINGSBOARD_USE_ESP_TIMER + /// @brief Internal loop method to update inernal timers for API calls that can timeout. + /// Only exists on boards that can not use the ESP Timer, because that one uses the FreeRTOS timer in the background instead + /// and therefore does not require calling a loop method + virtual void loop() = 0; +#endif // !THINGSBOARD_USE_ESP_TIMER + + /// @brief Method that allows to construct internal objects, after the required callback member methods have been set already. + /// Required for API Implementations that subscribe further API calls, because immediately calling in the constructor can lead, + /// to attempted subscriptions before the m_subscribe_api_callback is actually subscribed. Therefore we have to call methods like that, + /// in this method instead, because it ensures all member methods are instantiated already + virtual void Initialize() = 0; + + /// @brief Sets the underlying callbacks that are required for the different API Implementation to communicate with the cloud. + /// Directly set by the used ThingsBoard client to its internal methods, therefore calling again and overriding + /// as a user ist not recommended, unless you know what you are doing + /// @param subscribe_api_callback Method which allows to subscribe additional API endpoints, points to Subscribe_API_Implementation per default + /// @param send_json_callback Method which allows to send arbitrary JSON payload, points to Send_Json per default + /// @param send_json_string_callback Method which allows to send arbitrary JSON string payload, points to Send_Json_String per default + /// @param subscribe_topic_callback Method which allows to subscribe to arbitrary topics, points to m_client.subscribe per default + /// @param unsubscribe_topic_callback Method which allows to unsubscribe from arbitrary topics, points to m_client.unsubscribe per default + /// @param get_size_callback Method which allows to get the current underlying size of the buffer, points to m_client.get_buffer_size per default + /// @param set_buffer_size_callback Method which allows to set the current underlying size of the buffer, points to m_client.set_buffer_size per default + /// @param get_request_id_callback Method which allows to get the current request id as a mutable reference, points to getRequestID per default + virtual void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) = 0; +}; + +#endif // IAPI_Implementation_h diff --git a/src/IHTTP_Client.h b/src/IHTTP_Client.h index efc054d5..9acbcdd4 100644 --- a/src/IHTTP_Client.h +++ b/src/IHTTP_Client.h @@ -24,7 +24,7 @@ class IHTTP_Client { /// @brief Sets whether to close the HTTP connection for every single request and reconnect once a new request is sent /// @param keep_alive Enable or disable to keep the connection alive after a message has been sent. /// Is recommended to be always enabled to improve performance and speed, because opening a new connection takes a while especially when using HTTPS - virtual void set_keep_alive(bool const & keep_alive) = 0; + virtual void set_keep_alive(bool keep_alive) = 0; /// @brief Connects to the given server instance that should be connected to over the defined port /// @param host Server instance name the client should connect too @@ -33,7 +33,7 @@ class IHTTP_Client { /// The latter is recommended if relevant data is sent or if the client receives and handles requests from the server, /// because using an unencrpyted connection, will allow 3rd parties to listen to the communication and impersonate the server sending payloads which might influence the device in unexpected ways. /// @return Whether the client could establish the connection successfully with return code 0 or failed with error code otherwise - virtual int connect(char const * const host, uint16_t const & port) = 0; + virtual int connect(char const * const host, uint16_t port) = 0; /// @brief Disconnects the given device from the current host and clears about any remaining bytes still in the reponse body virtual void stop() = 0; diff --git a/src/IMQTT_Client.h b/src/IMQTT_Client.h index 43fd8dfd..a415167d 100644 --- a/src/IMQTT_Client.h +++ b/src/IMQTT_Client.h @@ -2,17 +2,12 @@ #define IMQTT_Client_h // Local include. -#include "Configuration.h" +#include "Callback.h" // Library include. -#if THINGSBOARD_ENABLE_STL -#include -#endif // THINGSBOARD_ENABLE_STL #if THINGSBOARD_ENABLE_STREAM_UTILS #include #endif // THINGSBOARD_ENABLE_STREAM_UTILS -#include -#include /// @brief MQTT Client interface that contains the method that a class that can be used to send and receive data over an MQTT connection should implement. @@ -34,23 +29,16 @@ class IMQTT_Client : public Print { class IMQTT_Client { #endif // THINGSBOARD_ENABLE_STREAM_UTILS public: - /// @brief Callback signature -#if THINGSBOARD_ENABLE_STL - using data_function = std::function; - using connect_function = std::function; -#else - using data_function = void (*)(char * topic, uint8_t * payload, unsigned int length); - using connect_function = void (*)(void); -#endif // THINGSBOARD_ENABLE_STL - /// @brief Sets the callback that is called, if any message is received by the MQTT broker, including the topic string that the message was received over, - /// as well as the payload data and the size of that payload data + /// as well as the payload data and the size of that payload data. Directly set by the used ThingsBoard client to its internal methods, + /// therefore calling again and overriding as a user ist not recommended, unless you know what you are doing /// @param callback Method that should be called on received MQTT response - virtual void set_data_callback(data_function callback) = 0; + virtual void set_data_callback(Callback::function callback) = 0; - /// @brief Sets the callback that is called, if we have successfully established a connection with the MQTT broker + /// @brief Sets the callback that is called, if we have successfully established a connection with the MQTT broker. + /// Directly set by the used ThingsBoard client to its internal methods, therefore calling again and overriding as a user ist not recommended, unless you know what you are doing /// @param callback Method that should be called on established MQTT connection - virtual void set_connect_callback(connect_function callback) = 0; + virtual void set_connect_callback(Callback::function callback) = 0; /// @brief Changes the size of the buffer for sent and received MQTT messages, /// using a bigger value than uint16_t for passing the buffer size does not make any sense because the maximum message size received @@ -59,7 +47,7 @@ class IMQTT_Client { /// expected behaviour is that, if bigger packets are received they are discarded and a warning is printed to the console /// and if we attempt to send data that is bigger, it will simply not be sent and a message is printed to the console instead /// @return Whether allocating the needed memory for the given buffer_size was successful or not - virtual bool set_buffer_size(uint16_t const & buffer_size) = 0; + virtual bool set_buffer_size(uint16_t buffer_size) = 0; /// @brief Gets the previously set size of the internal buffer size for sent and received MQTT /// @return Internal size of the buffer @@ -75,7 +63,7 @@ class IMQTT_Client { /// However if Over the Air udpates are enabled secure communication should definetly be enabled, because if that is not done a 3rd party might impersonate the server sending a malicious payload, /// which is then flashed onto the device instead of the real firmware. Which depeding on the payload might even be able to destroy the device or make it otherwise unusable. /// See https://stackoverflow.blog/2020/12/14/security-considerations-for-ota-software-updates-for-iot-gateway-devices/ for more information on the aforementioned security risk - virtual void set_server(char const * const domain, uint16_t const & port) = 0; + virtual void set_server(char const * const domain, uint16_t port) = 0; /// @brief Connects to the previously with set_server configured server instance that should be connected to over the previously defined port /// @param client_id Client identification code, that allows to differentiate which MQTT device is sending the traffic to the MQTT broker diff --git a/src/IUpdater.h b/src/IUpdater.h index 79cc1085..1fc730f3 100644 --- a/src/IUpdater.h +++ b/src/IUpdater.h @@ -4,8 +4,6 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - // Library include. #include #include @@ -33,6 +31,4 @@ class IUpdater { virtual bool end() = 0; }; -#endif // THINGSBOARD_ENABLE_OTA - #endif // IUpdater_h diff --git a/src/OTA_Failure_Response.h b/src/OTA_Failure_Response.h index 32064cb5..c60bec73 100644 --- a/src/OTA_Failure_Response.h +++ b/src/OTA_Failure_Response.h @@ -9,9 +9,9 @@ /// allows to react to certain issues in the most appropriate way, because some of them require us to restart the complete update, /// whereas other issues can be solved if we simply attempt to refetch the current chunk enum class OTA_Failure_Response : const uint8_t { - RETRY_CHUNK, // Fetching the current chunk failed somehow, but we can still continue the update we just have to refetch the current chunk, mainly occurs from timeouts with requesting the chunks - RETRY_UPDATE, // Internal process failed in the OTA that makes the complete already downloaded data not recoverable anymore, hashing or writing to flash memory failed, requires to restart the update from the first chunk and reinitalize the needed components - RETRY_NOTHING // Initally passed arguments are invalid and would cause crashes or the update was forcefully stopped by the user, therefore we immediately stop the update and do not restart it + RETRY_CHUNK, ///< Fetching the current chunk failed somehow, but we can still continue the update we just have to refetch the current chunk, mainly occurs from timeouts with requesting the chunks + RETRY_UPDATE, ///< Internal process failed in the OTA that makes the complete already downloaded data not recoverable anymore, hashing or writing to flash memory failed, requires to restart the update from the first chunk and reinitalize the needed components + RETRY_NOTHING ///< Initally passed arguments are invalid and would cause crashes or the update was forcefully stopped by the user, therefore we immediately stop the update and do not restart it }; -#endif // OTA_Failure_Response +#endif // OTA_Failure_Response_h diff --git a/src/OTA_Firmware_Update.h b/src/OTA_Firmware_Update.h new file mode 100644 index 00000000..a16c1fca --- /dev/null +++ b/src/OTA_Firmware_Update.h @@ -0,0 +1,476 @@ +#ifndef OTA_Firmware_Update_h +#define OTA_Firmware_Update_h + +// Local includes. +#include "Attribute_Request.h" +#include "Shared_Attribute_Update.h" +#include "OTA_Handler.h" +#include "IAPI_Implementation.h" + + +uint8_t constexpr MAX_FW_TOPIC_SIZE = 33U; +uint8_t constexpr OTA_ATTRIBUTE_KEYS_AMOUNT = 5U; +char constexpr NO_FW_REQUEST_RESPONSE[] = "Did not receive requested shared attribute firmware keys. Ensure keys exist and device is connected"; +// Firmware topics. +char constexpr FIRMWARE_RESPONSE_TOPIC[] = "v2/fw/response/%u/chunk/"; +char constexpr FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] = "v2/fw/response/+"; +char constexpr FIRMWARE_REQUEST_TOPIC[] = "v2/fw/request/%u/chunk/%u"; +// Firmware data keys. +char constexpr CURR_FW_TITLE_KEY[] = "current_fw_title"; +char constexpr CURR_FW_VER_KEY[] = "current_fw_version"; +char constexpr FW_ERROR_KEY[] = "fw_error"; +char constexpr FW_STATE_KEY[] = "fw_state"; +char constexpr FW_VER_KEY[] = "fw_version"; +char constexpr FW_TITLE_KEY[] = "fw_title"; +char constexpr FW_CHKS_KEY[] = "fw_checksum"; +char constexpr FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm"; +char constexpr FW_SIZE_KEY[] = "fw_size"; +char constexpr CHECKSUM_AGORITM_MD5[] = "MD5"; +char constexpr CHECKSUM_AGORITM_SHA256[] = "SHA256"; +char constexpr CHECKSUM_AGORITM_SHA384[] = "SHA384"; +char constexpr CHECKSUM_AGORITM_SHA512[] = "SHA512"; +// Log messages. +char constexpr NUMBER_PRINTF[] = "%u"; +char constexpr NO_FW[] = "Missing shared attribute firmware keys. Ensure you assigned an OTA update with binary"; +char constexpr EMPTY_FW[] = "Received shared attribute firmware keys were NULL"; +char constexpr FW_NOT_FOR_US[] = "Received firmware title (%s) is different and not meant for this device (%s)"; +char constexpr FW_CHKS_ALGO_NOT_SUPPORTED[] = "Received checksum algorithm (%s) is not supported"; +char constexpr NOT_ENOUGH_RAM[] = "Temporary allocating more internal client buffer failed, decrease OTA chunk size or decrease overall heap usage"; +char constexpr RESETTING_FAILED[] = "Preparing for OTA firmware updates failed, attributes might be NULL"; +#if THINGSBOARD_ENABLE_DEBUG +char constexpr PAGE_BREAK[] = "================================="; +char constexpr NEW_FW[] = "A new Firmware is available:"; +char constexpr FROM_TOO[] = "(%s) => (%s)"; +char constexpr DOWNLOADING_FW[] = "Attempting to download over MQTT..."; +#endif // THINGSBOARD_ENABLE_DEBUG + + +/// @brief Handles the internal implementation of the ThingsBoard over the air firmware update API. +/// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +template +class OTA_Firmware_Update : public IAPI_Implementation { + public: + /// @brief Constructor + OTA_Firmware_Update() + : m_subscribe_api_callback() + , m_send_json_callback() + , m_send_json_string_callback() + , m_subscribe_topic_callback() + , m_unsubscribe_topic_callback() + , m_get_size_callback() + , m_set_buffer_size_callback() + , m_get_request_id_callback() + , m_fw_callback() + , m_previous_buffer_size(0U) + , m_changed_buffer_size(false) +#if THINGSBOARD_ENABLE_STL + , m_ota(std::bind(&OTA_Firmware_Update::Publish_Chunk_Request, this, std::placeholders::_1, std::placeholders::_2), std::bind(&OTA_Firmware_Update::Firmware_Send_State, this, std::placeholders::_1, std::placeholders::_2), std::bind(&OTA_Firmware_Update::Firmware_OTA_Unsubscribe, this)) +#else + , m_ota(OTA_Firmware_Update::staticPublishChunk, OTA_Firmware_Update::staticFirmwareSend, OTA_Firmware_Update::staticUnsubscribe) +#endif // THINGSBOARD_ENABLE_STL + , m_response_topic() + , m_fw_attribute_update() + , m_fw_attribute_request() + { +#if !THINGSBOARD_ENABLE_STL + m_subscribedInstance = nullptr; +#endif // !THINGSBOARD_ENABLE_STL + } + + /// @brief Checks if firmware settings are assigned to the connected device and if they are attempts to use those settings to start a firmware update. + /// Will only be checked once and if there is no firmware assigned or if the assigned firmware is already installed this method will not update. + /// This firmware status is only checked once, meaning to recheck the status either call this method again or use the Subscribe_Firmware_Update method. + /// to be automatically informed and start the update if firmware has been assigned and it is not already installed. + /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information + /// @param callback Callback method that will be called + /// @return Whether subscribing the given callback was successful or not + bool Start_Firmware_Update(OTA_Update_Callback const & callback) { + if (!Prepare_Firmware_Settings(callback)) { + Logger::println(RESETTING_FAILED); + return false; + } + + // Request the firmware information + constexpr char const * const array[OTA_ATTRIBUTE_KEYS_AMOUNT] = {FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; +#if THINGSBOARD_ENABLE_DYNAMIC +#if THINGSBOARD_ENABLE_STL + const Attribute_Request_Callback fw_request_callback(std::bind(&OTA_Firmware_Update::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), callback.Get_Timeout(), std::bind(&OTA_Firmware_Update::Request_Timeout, this), array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#else + const Attribute_Request_Callback fw_request_callback(OTA_Firmware_Update::onStaticFirmwareReceived, callback.Get_Timeout(), OTA_Firmware_Update::onStaticRequestTimeout, array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#endif // THINGSBOARD_ENABLE_STL +#else +#if THINGSBOARD_ENABLE_STL + const Attribute_Request_Callback fw_request_callback(std::bind(&OTA_Firmware_Update::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), callback.Get_Timeout(), std::bind(&OTA_Firmware_Update::Request_Timeout, this), array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#else + const Attribute_Request_Callback fw_request_callback(OTA_Firmware_Update::onStaticFirmwareReceived, callback.Get_Timeout(), OTA_Firmware_Update::onStaticRequestTimeout, array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#endif // THINGSBOARD_ENABLE_STL +#endif //THINGSBOARD_ENABLE_DYNAMIC + return m_fw_attribute_request.Shared_Attributes_Request(fw_request_callback); + } + + /// @brief Stops the currently ongoing firmware update, calls the subscribed user finish callback with a failure if any update was stopped. + /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information + void Stop_Firmware_Update() { + m_ota.Stop_Firmware_Update(); + } + + /// @brief Subscribes to any changes of the assigned firmware information on the connected device, + /// meaning once we subscribed if we register any changes we will start the update if the given firmware is not already installed. + /// Unlike Start_Firmware_Update this method only registers changes to the firmware information, + /// meaning if the change occured while this device was asleep or turned off we will not update, + /// to achieve that, it is instead recommended to call the Start_Firmware_Update method when the device has started once to check for that edge case. + /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information + /// @param callback Callback method that will be called + /// @return Whether subscribing the given callback was successful or not + bool Subscribe_Firmware_Update(OTA_Update_Callback const & callback) { + if (!Prepare_Firmware_Settings(callback)) { + Logger::println(RESETTING_FAILED); + return false; + } + + // Subscribes to changes of the firmware information + char const * const array[OTA_ATTRIBUTE_KEYS_AMOUNT] = {FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; +#if THINGSBOARD_ENABLE_DYNAMIC +#if THINGSBOARD_ENABLE_STL + const Shared_Attribute_Callback fw_update_callback(std::bind(&OTA_Firmware_Update::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#else + const Shared_Attribute_Callback fw_update_callback(OTA_Firmware_Update::onStaticFirmwareReceived, array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#endif // THINGSBOARD_ENABLE_STL +#else +#if THINGSBOARD_ENABLE_STL + const Shared_Attribute_Callback fw_update_callback(std::bind(&OTA_Firmware_Update::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#else + const Shared_Attribute_Callback fw_update_callback(OTA_Firmware_Update::onStaticFirmwareReceived, array + 0U, array + OTA_ATTRIBUTE_KEYS_AMOUNT); +#endif // THINGSBOARD_ENABLE_STL +#endif //THINGSBOARD_ENABLE_DYNAMIC + return m_fw_attribute_update.Shared_Attributes_Subscribe(fw_update_callback); + } + + /// @brief Sends the given firmware title and firmware version to the cloud. + /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information + /// @param current_fw_title Current device firmware title + /// @param current_fw_version Current device firmware version + /// @return Whether sending the current device firmware information was successful or not + bool Firmware_Send_Info(char const * const current_fw_title, char const * const current_fw_version) { + StaticJsonDocument current_firmware_info; + current_firmware_info[CURR_FW_TITLE_KEY] = current_fw_title; + current_firmware_info[CURR_FW_VER_KEY] = current_fw_version; + return m_send_json_callback.Call_Callback(TELEMETRY_TOPIC, current_firmware_info, Helper::Measure_Json(current_firmware_info)); + } + + /// @brief Sends the given firmware state to the cloud. + /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information + /// @param current_fw_state Current firmware download state + /// @param fw_error Firmware error message that describes the current firmware state, + /// simply do not enter a value and the default value will be used which overwrites the firmware error messages, default = "" + /// @return Whether sending the current firmware download state was successful or not + bool Firmware_Send_State(char const * const current_fw_state, char const * const fw_error = "") { + StaticJsonDocument current_firmware_state; + current_firmware_state[FW_ERROR_KEY] = fw_error; + current_firmware_state[FW_STATE_KEY] = current_fw_state; + return m_send_json_callback.Call_Callback(TELEMETRY_TOPIC, current_firmware_state, Helper::Measure_Json(current_firmware_state)); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::RAW; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + size_t const & request_id = m_fw_callback.Get_Request_ID(); + char response_topic[Helper::detectSize(FIRMWARE_RESPONSE_TOPIC, request_id)] = {}; + (void)snprintf(response_topic, sizeof(response_topic), FIRMWARE_RESPONSE_TOPIC, request_id); + size_t const chunk = Helper::parseRequestId(response_topic, topic); + m_ota.Process_Firmware_Packet(chunk, payload, length); + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + // Nothing to do + } + + char const * Get_Response_Topic_String() const override { + return m_response_topic; + } + + bool Unsubscribe() override { + Stop_Firmware_Update(); + return true; + } + + bool Resubscribe_Topic() override { + return Firmware_OTA_Subscribe(); + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + m_ota.update(); + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + m_subscribe_api_callback.Call_Callback(m_fw_attribute_update); + m_subscribe_api_callback.Call_Callback(m_fw_attribute_request); + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_subscribe_api_callback.Set_Callback(subscribe_api_callback); + m_send_json_callback.Set_Callback(send_json_callback); + m_send_json_string_callback.Set_Callback(send_json_string_callback); + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + m_get_size_callback.Set_Callback(get_size_callback); + m_set_buffer_size_callback.Set_Callback(set_buffer_size_callback); + m_get_request_id_callback.Set_Callback(get_request_id_callback); + } + + private: + /// @brief Checks the included information in the callback, + /// and attempts to sends the current device firmware information to the cloud + /// @param callback Callback method that will be called + /// @return Whether checking and sending the current device firmware information was successful or not + bool Prepare_Firmware_Settings(OTA_Update_Callback const & callback) { + char const * const current_fw_title = callback.Get_Firmware_Title(); + char const * const current_fw_version = callback.Get_Firmware_Version(); + + if (Helper::stringIsNullorEmpty(current_fw_title) || Helper::stringIsNullorEmpty(current_fw_version)) { + return false; + } + else if (!Firmware_Send_Info(current_fw_title, current_fw_version)) { + return false; + } + + size_t * p_request_id = m_get_request_id_callback.Call_Callback(); + if (p_request_id == nullptr) { + Logger::println(REQUEST_ID_NULL); + return false; + } + auto & request_id = *p_request_id; + + m_fw_callback = callback; + m_fw_callback.Set_Request_ID(++request_id); + (void)snprintf(m_response_topic, sizeof(m_response_topic), FIRMWARE_RESPONSE_TOPIC, request_id); + return true; + } + + /// @brief Subscribes to the firmware response topic + /// @return Whether subscribing to the firmware response topic was successful or not + bool Firmware_OTA_Subscribe() { + if (!m_subscribe_topic_callback.Call_Callback(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC)) { + char message[JSON_STRING_SIZE(strlen(SUBSCRIBE_TOPIC_FAILED)) + JSON_STRING_SIZE(strlen(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC))] = {}; + (void)snprintf(message, sizeof(message), SUBSCRIBE_TOPIC_FAILED, FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC); + Logger::println(message); + Firmware_Send_State(FW_STATE_FAILED, message); + return false; + } + return true; + } + + /// @brief Unsubscribes from the firmware response topic and clears any memory associated with the firmware update, + /// should not be called before actually fully completing the firmware update. + /// @return Whether unsubscribing from the firmware response topic was successful or not + bool Firmware_OTA_Unsubscribe() { + // Buffer size has been set to another value before the update, + // to allow to receive ota chunck packets that might be much bigger than the normal + // buffer size would allow, therefore we return to the previous value to decrease overall memory usage + if (m_changed_buffer_size) { + (void)m_set_buffer_size_callback.Call_Callback(m_previous_buffer_size); + } + // Reset now not needed private member variables + m_fw_callback = OTA_Update_Callback(); + // Unsubscribe from the topic + return m_unsubscribe_topic_callback.Call_Callback(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC); + } + + /// @brief Publishes a request for the given firmware chunk + /// @param request_id Request ID corresponding to the extact OTA update package we want to request chunks from + /// @param request_chunck Chunk index that should be requested from the server + /// @return Whether publishing the message was successful or not + bool Publish_Chunk_Request(size_t const & request_id, size_t const & request_chunck) { + // Calculate the number of chuncks we need to request, + // in order to download the complete firmware binary + uint16_t const & chunk_size = m_fw_callback.Get_Chunk_Size(); + + // Convert the interger size into a readable string + char size[Helper::detectSize(NUMBER_PRINTF, chunk_size)] = {}; + (void)snprintf(size, sizeof(size), NUMBER_PRINTF, chunk_size); + + char topic[Helper::detectSize(FIRMWARE_REQUEST_TOPIC, request_id, request_chunck)] = {}; + (void)snprintf(topic, sizeof(topic), FIRMWARE_REQUEST_TOPIC, request_id, request_chunck); + return m_send_json_string_callback.Call_Callback(topic, size); + } + + /// @brief Handler if the firmware shared attribute request times out without getting a response. + /// Is used to signal that the update could not be started, because the current firmware information could not be fetched + void Request_Timeout() { + Logger::println(NO_FW_REQUEST_RESPONSE); + Firmware_Send_State(FW_STATE_FAILED, NO_FW_REQUEST_RESPONSE); + } + + /// @brief Callback that will be called upon firmware shared attribute arrival + /// @param data Json data containing key-value pairs for the needed firmware information, + /// to ensure we have a firmware assigned and can start the update over MQTT + void Firmware_Shared_Attribute_Received(JsonObjectConst const & data) { + // Check if firmware is available for our device + if (!data.containsKey(FW_VER_KEY) || !data.containsKey(FW_TITLE_KEY) || !data.containsKey(FW_CHKS_KEY) || !data.containsKey(FW_CHKS_ALGO_KEY) || !data.containsKey(FW_SIZE_KEY)) { + Logger::println(NO_FW); + Firmware_Send_State(FW_STATE_FAILED, NO_FW); + return; + } + + char const * const fw_title = data[FW_TITLE_KEY]; + char const * const fw_version = data[FW_VER_KEY]; + char const * const fw_checksum = data[FW_CHKS_KEY]; + char const * const fw_algorithm = data[FW_CHKS_ALGO_KEY]; + size_t const fw_size = data[FW_SIZE_KEY]; + + char const * const curr_fw_title = m_fw_callback.Get_Firmware_Title(); + char const * const curr_fw_version = m_fw_callback.Get_Firmware_Version(); + + if (fw_title == nullptr || fw_version == nullptr || curr_fw_title == nullptr || curr_fw_version == nullptr || fw_algorithm == nullptr || fw_checksum == nullptr) { + Logger::println(EMPTY_FW); + Firmware_Send_State(FW_STATE_FAILED, EMPTY_FW); + return; + } + // If firmware version and title is the same, we do not initiate an update, because we expect the type of binary to be the same one we are currently using + // and therefore updating would be useless as we have already updated previously + else if (strncmp(curr_fw_title, fw_title, strlen(curr_fw_title)) == 0 && strncmp(curr_fw_version, fw_version, strlen(curr_fw_version)) == 0) { + Firmware_Send_State(FW_STATE_UPDATED); + return; + } + // If firmware title is not the same, we do not initiate an update, because we expect the binary to be for another type of device + // and downloading it on this device could possibly cause hardware issues or even destroy the device + else if (strncmp(curr_fw_title, fw_title, strlen(curr_fw_title)) != 0) { + char message[JSON_STRING_SIZE(strlen(FW_NOT_FOR_US)) + JSON_STRING_SIZE(strlen(fw_title)) + JSON_STRING_SIZE(strlen(curr_fw_title))] = {}; + (void)snprintf(message, sizeof(message), FW_NOT_FOR_US, fw_title, curr_fw_title); + Logger::println(message); + Firmware_Send_State(FW_STATE_FAILED, message); + return; + } + + mbedtls_md_type_t fw_checksum_algorithm = mbedtls_md_type_t{}; + + if (strncmp(CHECKSUM_AGORITM_MD5, fw_algorithm, strlen(CHECKSUM_AGORITM_MD5)) == 0) { + fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_MD5; + } + else if (strncmp(CHECKSUM_AGORITM_SHA256, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA256)) == 0) { + fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA256; + } + else if (strncmp(CHECKSUM_AGORITM_SHA384, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA384)) == 0) { + fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA384; + } + else if (strncmp(CHECKSUM_AGORITM_SHA512, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA512)) == 0) { + fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA512; + } + else { + char message[JSON_STRING_SIZE(strlen(FW_CHKS_ALGO_NOT_SUPPORTED)) + JSON_STRING_SIZE(strlen(fw_algorithm))] = {}; + (void)snprintf(message, sizeof(message), FW_CHKS_ALGO_NOT_SUPPORTED, fw_algorithm); + Logger::println(message); + Firmware_Send_State(FW_STATE_FAILED, message); + return; + } + + m_fw_callback.Call_Update_Starting_Callback(); + bool const result = Firmware_OTA_Subscribe(); + if (!result) { + m_fw_callback.Call_Callback(result); + return; + } + +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(PAGE_BREAK); + Logger::println(NEW_FW); + char firmware[JSON_STRING_SIZE(strlen(FROM_TOO)) + JSON_STRING_SIZE(strlen(curr_fw_version)) + JSON_STRING_SIZE(strlen(fw_version))] = {}; + (void)snprintf(firmware, sizeof(firmware), FROM_TOO, curr_fw_version, fw_version); + Logger::println(firmware); + Logger::println(DOWNLOADING_FW); +#endif // THINGSBOARD_ENABLE_DEBUG + + // Calculate the number of chuncks we need to request, + // in order to download the complete firmware binary + const uint16_t& chunk_size = m_fw_callback.Get_Chunk_Size(); + + // Get the previous buffer size and cache it so the previous settings can be restored. + m_previous_buffer_size = m_get_size_callback.Call_Callback(); + m_changed_buffer_size = m_previous_buffer_size < (chunk_size + 50U); + + // Increase size of receive buffer + if (m_changed_buffer_size && !m_set_buffer_size_callback.Call_Callback(chunk_size + 50U)) { + Logger::println(NOT_ENOUGH_RAM); + Firmware_Send_State(FW_STATE_FAILED, NOT_ENOUGH_RAM); + m_fw_callback.Call_Callback(false); + return; + } + + m_ota.Start_Firmware_Update(m_fw_callback, fw_size, fw_checksum, fw_checksum_algorithm); + } + +#if !THINGSBOARD_ENABLE_STL + static void onStaticFirmwareReceived(JsonDocument const & data) { + if (m_subscribedInstance == nullptr) { + return; + } + m_subscribedInstance->Firmware_Shared_Attribute_Received(data); + } + + static void onStaticRequestTimeout() { + if (m_subscribedInstance == nullptr) { + return; + } + m_subscribedInstance->Request_Timeout(); + } + + static bool staticPublishChunk(size_t const & request_id, size_t const & request_chunck) { + if (m_subscribedInstance == nullptr) { + return false; + } + return m_subscribedInstance->Publish_Chunk_Request(request_id, request_chunck); + } + + static bool staticFirmwareSend(char const * const current_fw_state, char const * const fw_error = nullptr) { + if (m_subscribedInstance == nullptr) { + return false; + } + return m_subscribedInstance->Firmware_Send_State(current_fw_state, fw_error); + } + + static bool staticUnsubscribe() { + if (m_subscribedInstance == nullptr) { + return false; + } + return m_subscribedInstance->Firmware_OTA_Unsubscribe(); + } + + // Used API Implementation cannot call a instanced method when message arrives on subscribed topic. + // Only free-standing function is allowed. + // To be able to forward event to an instance, rather than to a function, this pointer exists. + static OTA_Firmware_Update *m_subscribedInstance; +#endif // !THINGSBOARD_ENABLE_STL + + Callback m_subscribe_api_callback = {}; // Subscribe additional api callback + Callback m_send_json_callback = {}; // Send json document callback + Callback m_send_json_string_callback = {}; // Send json string callback + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + Callback m_get_size_callback = {}; // Get client buffer size callback + Callback m_set_buffer_size_callback = {}; // Set client buffer size callback + Callback m_get_request_id_callback = {}; // Get internal request id callback + + OTA_Update_Callback m_fw_callback = {}; // OTA update response callback + uint16_t m_previous_buffer_size = {}; // Previous buffer size of the underlying client, used to revert to the previously configured buffer size if it was temporarily increased by the OTA update + bool m_changed_buffer_size = {}; // Whether the buffer size had to be changed, because the previous internal buffer size was to small to hold the firmware chunks + OTA_Handler m_ota = {}; // Class instance that handles the flashing and creating a hash from the given received binary firmware data + char m_response_topic[MAX_FW_TOPIC_SIZE] = {}; // Firmware response topic that contains the specific request ID of the firmware we actually want to download +#if !THINGSBOARD_ENABLE_DYNAMIC + Shared_Attribute_Update<1U, OTA_ATTRIBUTE_KEYS_AMOUNT, Logger> m_fw_attribute_update = {}; // API implementation to be informed if needed fw attributes have been updated + Attribute_Request<1U, OTA_ATTRIBUTE_KEYS_AMOUNT, Logger> m_fw_attribute_request = {}; // API implementation to request the needed fw attributes to start updating +#else + Shared_Attribute_Update m_fw_attribute_update = {}; // API implementation to be informed if needed fw attributes have been updated + Attribute_Request m_fw_attribute_request = {}; // API implementation to request the needed fw attributes to start updating +#endif // !THINGSBOARD_ENABLE_DYNAMIC +}; + +#if !THINGSBOARD_ENABLE_STL +OTA_Firmware_Update *OTA_Firmware_Update::m_subscribedInstance = nullptr; +#endif + +#endif // OTA_Firmware_Update_h diff --git a/src/OTA_Handler.h b/src/OTA_Handler.h index 73489b37..76fb0331 100644 --- a/src/OTA_Handler.h +++ b/src/OTA_Handler.h @@ -4,8 +4,6 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - // Local include. #include "Callback_Watchdog.h" #include "HashGenerator.h" @@ -18,36 +16,14 @@ // Firmware data keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr FW_STATE_DOWNLOADING[] PROGMEM = "DOWNLOADING"; -char constexpr FW_STATE_DOWNLOADED[] PROGMEM = "DOWNLOADED"; -char constexpr FW_STATE_UPDATING[] PROGMEM = "UPDATING"; -char constexpr FW_STATE_FAILED[] PROGMEM = "FAILED"; -#else char constexpr FW_STATE_DOWNLOADING[] = "DOWNLOADING"; char constexpr FW_STATE_DOWNLOADED[] = "DOWNLOADED"; char constexpr FW_STATE_UPDATING[] = "UPDATING"; char constexpr FW_STATE_FAILED[] = "FAILED"; -#endif // THINGSBOARD_ENABLE_PROGMEM +char constexpr FW_STATE_UPDATED[] = "UPDATED"; // Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr UNABLE_TO_REQUEST_CHUNCKS[] PROGMEM = "Unable to request firmware chunk"; -char constexpr RECEIVED_UNEXPECTED_CHUNK[] PROGMEM = "Received chunk (%u), not the same as requested chunk (%u)"; -char constexpr RECEIVED_UNEXPECTED_CHUNK_SIZE[] PROGMEM = "Received chunk size (%u), not the same as expected chunk size (%u)"; -char constexpr ERROR_UPDATE_BEGIN[] = "Failed to initalize flash updater, ensure that the partition scheme has two app sections"; -char constexpr ERROR_UPDATE_WRITE[] PROGMEM = "Only wrote (%u) bytes of binary data instead of expected (%u)"; -char constexpr ERROR_UPDATE_END[] PROGMEM = "Error (%u) during flash updater not all bytes written"; -char constexpr CHECKSUM_VERIFICATION_FAILED[] PROGMEM = "Calculated checksum (%s), not the same as expected checksum (%s)"; -char constexpr FW_UPDATE_ABORTED[] PROGMEM = "Firmware update aborted"; -char constexpr CHUNK_REQUEST_TIMED_OUT[] PROGMEM = "Failed to receive requested chunk (%u) in (%llu) us. Internet connection might have been lost"; -#if THINGSBOARD_ENABLE_DEBUG -char constexpr FW_CHUNK[] PROGMEM = "Receive chunk (%u), with size (%u) bytes"; -char constexpr HASH_EXPECTED[] PROGMEM = "(%s) expected checksum: (%s)"; -char constexpr CHECKSUM_VERIFICATION_SUCCESS[] PROGMEM = "Checksum is the same as expected"; -char constexpr FW_UPDATE_SUCCESS[] PROGMEM = "Update success"; -#endif // THINGSBOARD_ENABLE_DEBUG -#else +char constexpr OTA_CB_IS_NULL[] = "OTA update callback is NULL, has it been deleted"; char constexpr UNABLE_TO_REQUEST_CHUNCKS[] = "Unable to request firmware chunk"; char constexpr RECEIVED_UNEXPECTED_CHUNK[] = "Received chunk (%u), not the same as requested chunk (%u)"; char constexpr RECEIVED_UNEXPECTED_CHUNK_SIZE[] = "Received chunk size (%u), not the same as expected chunk size (%u)"; @@ -63,7 +39,6 @@ char constexpr HASH_EXPECTED[] = "Expected checksum: (%s)"; char constexpr CHECKSUM_VERIFICATION_SUCCESS[] = "Checksum is the same as expected"; char constexpr FW_UPDATE_SUCCESS[] = "Update success"; #endif // THINGSBOARD_ENABLE_DEBUG -#endif // THINGSBOARD_ENABLE_PROGMEM /// @brief Handles the complete processing of received binary firmware data, including flashing it onto the device, @@ -76,7 +51,7 @@ class OTA_Handler { /// @param publish_callback Callback that is used to request the firmware chunk of the firmware binary with the given chunk number /// @param send_fw_state_callback Callback that is used to send information about the current state of the over the air update /// @param finish_callback Callback that is called once the update has been finished and the user should be informed of the failure or success of the over the air update - OTA_Handler(std::function publish_callback, std::function send_fw_state_callback, std::function finish_callback) + OTA_Handler(Callback::function publish_callback, Callback::function send_fw_state_callback, Callback::function finish_callback) : m_fw_callback(nullptr) , m_publish_callback(publish_callback) , m_send_fw_state_callback(send_fw_state_callback) @@ -105,13 +80,8 @@ class OTA_Handler { (void)strncpy(m_fw_checksum, fw_checksum, sizeof(m_fw_checksum)); m_fw_checksum_algorithm = fw_checksum_algorithm; m_fw_updater = m_fw_callback->Get_Updater(); - - if (!m_publish_callback || !m_send_fw_state_callback || !m_finish_callback || !m_fw_updater) { - Logger::println(OTA_CB_IS_NULL); - return Handle_Failure(OTA_Failure_Response::RETRY_NOTHING, OTA_CB_IS_NULL); - } Request_First_Firmware_Packet(); - (void)m_send_fw_state_callback(FW_STATE_DOWNLOADING, nullptr); + (void)m_send_fw_state_callback.Call_Callback(FW_STATE_DOWNLOADING, nullptr); } /// @brief Stops the firmware update completly and informs that user that the update has failed because it has been aborted, ongoing communication is discarded. @@ -168,7 +138,7 @@ class OTA_Handler { (void)m_hash.update(payload, total_bytes); m_requested_chunks = current_chunk + 1; - m_fw_callback->Call_Progress_Callback(m_requested_chunks, m_total_chunks); + m_fw_callback->Call_Progress_Callback(m_requested_chunks, m_total_chunks); // Ensure to check if the update was cancelled during the progress callback, // if it was the callback variable was reset and there is no need to request the next firmware packet @@ -182,21 +152,15 @@ class OTA_Handler { Request_Next_Firmware_Packet(); } - private: - const OTA_Update_Callback *m_fw_callback; // Callback method that contains configuration information, about the over the air update - std::function m_publish_callback; // Callback that is used to request the firmware chunk of the firmware binary with the given chunk number - std::function m_send_fw_state_callback; // Callback that is used to send information about the current state of the over the air update - std::function m_finish_callback; // Callback that is called once the update has been finished and the user should be informed of the failure or success of the over the air update - size_t m_fw_size; // Total size of the firmware binary we will receive. Allows for a binary size of up to theoretically 4 GB - char m_fw_checksum[MBEDTLS_MD_MAX_SIZE]; // Checksum of the complete firmware binary, should be the same as the actually written data in the end - mbedtls_md_type_t m_fw_checksum_algorithm; // Algorithm type used to hash the firmware binary - IUpdater *m_fw_updater; // Interface implementation that writes received firmware binary data onto the given device - HashGenerator m_hash; // Class instance that allows to generate a hash from received firmware binary data - size_t m_total_chunks; // Total amount of chunks that need to be received to get the complete firmware binary - size_t m_requested_chunks; // Amount of successfully requested and received firmware binary chunks - uint8_t m_retries; // Amount of request retries we attempt for each chunk, increasing makes the connection more stable - Callback_Watchdog m_watchdog; // Class instances that allows to timeout if we do not receive a response for a requested chunk in the given time +#if !THINGSBOARD_USE_ESP_TIMER + /// @brief Used to update the watchdog timer which uses a simple software time in the background. Ensure to call recently often for higher precision. + /// Meaning the timer is actually triggered closer to the specified waiting time + void update() { + m_watchdog.update(); + } +#endif // !THINGSBOARD_USE_ESP_TIMER + private: /// @brief Checks whether the received chunk size matches the expected chunk size, should be the configured chunk size of the OTA_Update_Callback, CHUNK_SIZE (4096) per default /// and it should be the remaining bytes to fill the total firmware size with the last received chunk. If that is not the case then something went wrong with the request and we have to rerequest that specific chunk, /// because if we do not do that we would write missing or only partial binary data to flash and into the hash, meaning the complete OTA update will be invalidated at the end and has to be restarted @@ -234,7 +198,7 @@ class OTA_Handler { return; } - if (!m_publish_callback(m_requested_chunks)) { + if (!m_publish_callback.Call_Callback(m_fw_callback->Get_Request_ID(), m_requested_chunks)) { Logger::println(UNABLE_TO_REQUEST_CHUNCKS); } @@ -249,7 +213,7 @@ class OTA_Handler { /// both should be the same and if that is not the case that means that we received invalid firmware binary data and have to restart the update. /// If checking the hash was successfull we attempt to finish flashing the ota partition and then inform the user that the update was successfull void Finish_Firmware_Update() { - (void)m_send_fw_state_callback(FW_STATE_DOWNLOADED, nullptr); + (void)m_send_fw_state_callback.Call_Callback(FW_STATE_DOWNLOADED, nullptr); unsigned char calculated_hash[MBEDTLS_MD_MAX_SIZE] = {}; // Calculating final hash result is ignored, because it can only fail if the input parameters are invalid @@ -277,9 +241,9 @@ class OTA_Handler { Logger::println(FW_UPDATE_SUCCESS); #endif // THINGSBOARD_ENABLE_DEBUG - (void)m_send_fw_state_callback(FW_STATE_UPDATING, nullptr); - m_fw_callback->Call_Callback(true); - (void)m_finish_callback(); + (void)m_send_fw_state_callback.Call_Callback(FW_STATE_UPDATING, nullptr); + m_fw_callback->Call_Callback(true); + (void)m_finish_callback.Call_Callback(); } /// @brief Handles errors with the received failure response so that the firmware update can regenerate from any possible issue. @@ -288,9 +252,9 @@ class OTA_Handler { /// @param error_message Error message that should be printed if we abort the update void Handle_Failure(OTA_Failure_Response const & failure_response, char const * const error_message = nullptr) { if (m_retries <= 0) { - (void)m_send_fw_state_callback(FW_STATE_FAILED, error_message); - m_fw_callback->Call_Callback(false); - (void)m_finish_callback(); + (void)m_send_fw_state_callback.Call_Callback(FW_STATE_FAILED, error_message); + m_fw_callback->Call_Callback(false); + (void)m_finish_callback.Call_Callback(); return; } @@ -306,9 +270,9 @@ class OTA_Handler { Request_First_Firmware_Packet(); break; case OTA_Failure_Response::RETRY_NOTHING: - (void)m_send_fw_state_callback(FW_STATE_FAILED, error_message); - m_fw_callback->Call_Callback(false); - (void)m_finish_callback(); + (void)m_send_fw_state_callback.Call_Callback(FW_STATE_FAILED, error_message); + m_fw_callback->Call_Callback(false); + (void)m_finish_callback.Call_Callback(); break; default: // Nothing to do @@ -324,8 +288,20 @@ class OTA_Handler { Logger::println(message); Handle_Failure(OTA_Failure_Response::RETRY_CHUNK, message); } -}; -#endif // THINGSBOARD_ENABLE_OTA + const OTA_Update_Callback *m_fw_callback = {}; // Callback method that contains configuration information, about the over the air update + Callback m_publish_callback = {}; // Callback that is used to request the firmware chunk of the firmware binary with the given chunk number + Callback m_send_fw_state_callback = {}; // Callback that is used to send information about the current state of the over the air update + Callback m_finish_callback = {}; // Callback that is called once the update has been finished and the user should be informed of the failure or success of the over the air update + size_t m_fw_size = {}; // Total size of the firmware binary we will receive. Allows for a binary size of up to theoretically 4 GB + char m_fw_checksum[MBEDTLS_MD_MAX_SIZE] = {}; // Checksum of the complete firmware binary, should be the same as the actually written data in the end + mbedtls_md_type_t m_fw_checksum_algorithm = {}; // Algorithm type used to hash the firmware binary + IUpdater *m_fw_updater = {}; // Interface implementation that writes received firmware binary data onto the given device + HashGenerator m_hash = {}; // Class instance that allows to generate a hash from received firmware binary data + size_t m_total_chunks = {}; // Total amount of chunks that need to be received to get the complete firmware binary + size_t m_requested_chunks = {}; // Amount of successfully requested and received firmware binary chunks + uint8_t m_retries = {}; // Amount of request retries we attempt for each chunk, increasing makes the connection more stable + Callback_Watchdog m_watchdog = {}; // Class instances that allows to timeout if we do not receive a response for a requested chunk in the given time +}; #endif // OTA_Handler_h diff --git a/src/OTA_Update_Callback.cpp b/src/OTA_Update_Callback.cpp index becfce3d..bc2ae5fa 100644 --- a/src/OTA_Update_Callback.cpp +++ b/src/OTA_Update_Callback.cpp @@ -1,45 +1,34 @@ // Header include. #include "OTA_Update_Callback.h" -#if THINGSBOARD_ENABLE_OTA - -OTA_Update_Callback::OTA_Update_Callback(function endCb, char const * const currFwTitle, char const * const currFwVersion, IUpdater * updater, uint8_t const & chunkRetries, uint16_t const & chunkSize, uint64_t const & timeout) - : OTA_Update_Callback(nullptr, endCb, currFwTitle, currFwVersion, updater, chunkRetries, chunkSize, timeout) -{ - // Nothing to do -} - -OTA_Update_Callback::OTA_Update_Callback(progressFn progressCb, function endCb, char const * const currFwTitle, char const * const currFwVersion, IUpdater * updater, uint8_t const & chunkRetries, uint16_t const & chunkSize, uint64_t const & timeout) - : Callback(endCb, OTA_CB_IS_NULL) - , m_progressCb(progressCb) - , m_fwTitel(currFwTitle) - , m_fwVersion(currFwVersion) +OTA_Update_Callback::OTA_Update_Callback(char const * const current_fw_title, char const * const current_fw_version, IUpdater * const updater, function finished_callback, Callback::function progress_callback, Callback::function update_starting_callback, uint8_t chunk_retries, uint16_t chunk_size, uint64_t const & timeout_microseconds) + : Callback(finished_callback) + , m_current_fw_title(current_fw_title) + , m_current_fw_version(current_fw_version) , m_updater(updater) - , m_retries(chunkRetries) - , m_size(chunkSize) - , m_timeout(timeout) + , m_progress_callback(progress_callback) + , m_update_starting_callback(update_starting_callback) + , m_chunk_retries(chunk_retries) + , m_chunk_size(chunk_size) + , m_timeout_microseconds(timeout_microseconds) { // Nothing to do } -void OTA_Update_Callback::Set_Progress_Callback(progressFn progressCb) { - m_progressCb = progressCb; -} - char const * OTA_Update_Callback::Get_Firmware_Title() const { - return m_fwTitel; + return m_current_fw_title; } -void OTA_Update_Callback::Set_Firmware_Title(char const * const currFwTitle) { - m_fwTitel = currFwTitle; +void OTA_Update_Callback::Set_Firmware_Title(char const * const current_fw_title) { + m_current_fw_title = current_fw_title; } char const * OTA_Update_Callback::Get_Firmware_Version() const { - return m_fwVersion; + return m_current_fw_version; } -void OTA_Update_Callback::Set_Firmware_Version(char const * const currFwVersion) { - m_fwVersion = currFwVersion; +void OTA_Update_Callback::Set_Firmware_Version(char const * const current_fw_version) { + m_current_fw_version = current_fw_version; } IUpdater * OTA_Update_Callback::Get_Updater() const { @@ -50,28 +39,50 @@ void OTA_Update_Callback::Set_Updater(IUpdater* updater) { m_updater = updater; } -uint8_t const & OTA_Update_Callback::Get_Chunk_Retries() const { - return m_retries; +size_t const & OTA_Update_Callback::Get_Request_ID() const { + return m_request_id; } -void OTA_Update_Callback::Set_Chunk_Retries(const uint8_t &chunkRetries) { - m_retries = chunkRetries; +void OTA_Update_Callback::Set_Request_ID(size_t const & request_id) { + m_request_id = request_id; } -uint16_t const & OTA_Update_Callback::Get_Chunk_Size() const { - return m_size; +void OTA_Update_Callback::Call_Progress_Callback(size_t const & current, size_t const & total) const { + return m_progress_callback.Call_Callback(current, total); } -void OTA_Update_Callback::Set_Chunk_Size(const uint16_t &chunkSize) { - m_size = chunkSize; +void OTA_Update_Callback::Set_Progress_Callback(Callback::function progress_callback) { + m_progress_callback.Set_Callback(progress_callback); } -uint64_t const & OTA_Update_Callback::Get_Timeout() const { - return m_timeout; +void OTA_Update_Callback::Call_Update_Starting_Callback() const { + return m_update_starting_callback.Call_Callback(); +} + +void OTA_Update_Callback::Set_Update_Starting_Callback(Callback::function update_starting_callback) { + m_update_starting_callback.Set_Callback(update_starting_callback); } -void OTA_Update_Callback::Set_Timeout(const uint64_t &timeout_microseconds) { - m_timeout = timeout_microseconds; +uint8_t OTA_Update_Callback::Get_Chunk_Retries() const { + return m_chunk_retries; } -#endif // THINGSBOARD_ENABLE_OTA +void OTA_Update_Callback::Set_Chunk_Retries(uint8_t chunk_retries) { + m_chunk_retries = chunk_retries; +} + +uint16_t OTA_Update_Callback::Get_Chunk_Size() const { + return m_chunk_size; +} + +void OTA_Update_Callback::Set_Chunk_Size(uint16_t chunk_size) { + m_chunk_size = chunk_size; +} + +uint64_t const & OTA_Update_Callback::Get_Timeout() const { + return m_timeout_microseconds; +} + +void OTA_Update_Callback::Set_Timeout(const uint64_t & timeout_microseconds) { + m_timeout_microseconds = timeout_microseconds; +} diff --git a/src/OTA_Update_Callback.h b/src/OTA_Update_Callback.h index 0b8bdd13..1059f298 100644 --- a/src/OTA_Update_Callback.h +++ b/src/OTA_Update_Callback.h @@ -4,28 +4,14 @@ // Local includes. #include "Callback.h" -#if THINGSBOARD_ENABLE_OTA - // Local includes. #include "IUpdater.h" -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr OTA_CB_IS_NULL[] PROGMEM = "OTA update callback is NULL"; -#else -char constexpr OTA_CB_IS_NULL[] = "OTA update callback is NULL"; -#endif // THINGSBOARD_ENABLE_PROGMEM - // OTA default values. -#if THINGSBOARD_ENABLE_PROGMEM -uint8_t constexpr CHUNK_RETRIES PROGMEM = 12U; -uint16_t constexpr CHUNK_SIZE PROGMEM = (4U * 1024U); -uint64_t constexpr REQUEST_TIMEOUT PROGMEM = (5U * 1000U * 1000U); -#else uint8_t constexpr CHUNK_RETRIES = 12U; uint16_t constexpr CHUNK_SIZE = (4U * 1024U); uint64_t constexpr REQUEST_TIMEOUT = (5U * 1000U * 1000U); -#endif // THINGSBOARD_ENABLE_PROGMEM /// @brief Over the air firmware update callback wrapper, @@ -33,64 +19,33 @@ uint64_t constexpr REQUEST_TIMEOUT = (5U * 1000U * 1000U); /// Documentation about the specific use of Over the air updates in ThingsBoard can be found here https://thingsboard.io/docs/user-guide/ota-updates/ class OTA_Update_Callback : public Callback { public: - /// @brief OTA firmware update callback signature - using returnType = void; - using progressArgumentType = size_t const &; - using progressFn = std::function; - /// @brief Constructs empty callback, will result in never being called. Internals are simply default constructed as nullptr OTA_Update_Callback() = default; - /// @brief Constructs callbacks that will be called when the OTA firmware data, - /// has been completly sent by the cloud, received by the client and written to the flash partition - /// @param endCb End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed - /// @param currFwTitle Firmware title the device has choosen, is used to only allow updates with the same given title, other updates will be canceled - /// @param currFwVersion Firmware version the device is currently on, is usded to only allow updates with a different version, other updates will be canceled - /// @param updater Updater implementation that writes the given firmware data - /// @param chunkRetries Amount of retries the OTA firmware update has to download each seperate chunk with a given size, - /// before the complete download is stopped and registered as failed - /// @param chunkSize Size of the chunks that the firmware binary data will be split into, - /// increased chunkSize might speed up the process by a little bit, but requires more heap memory, - // because the whole chunk is saved into the heap before it can be processed and is then erased again after it has been used - /// @param timeout Maximum amount of time in microseconds for the OTA firmware update for each seperate chunk, - /// until that chunk counts as a timeout, retries is then subtraced by one and the download is retried - OTA_Update_Callback(function endCb, char const * const currFwTitle, char const * const currFwVersion, IUpdater * const updater, uint8_t const & chunkRetries = CHUNK_RETRIES, uint16_t const & chunkSize = CHUNK_SIZE, uint64_t const & timeout = REQUEST_TIMEOUT); - /// @brief Constructs callbacks that will be called when the OTA firmware data, /// has been completly sent by the cloud, received by the client and written to the flash partition as well as callback // that will be called every time the current progress of the firmware update changes - /// @param progressCb Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, - /// this makes it possible to display a progress bar or signal easily how far we are in the current downloading process - /// @param endCb End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed - /// @param currFwTitle Firmware title the device has choosen, is used to only allow updates with the same given title, other updates will be canceled - /// @param currFwVersion Firmware version the device is currently on, is usded to only allow updates with a different version, other updates will be canceled + /// @param current_fw_title Firmware title the device has choosen, is used to only allow updates with the same given title, other updates will be canceled + /// @param current_fw_version Firmware version the device is currently on, is usded to only allow updates with a different version, other updates will be canceled /// @param updater Updater implementation that writes the given firmware data - /// @param chunkRetries Amount of retries the OTA firmware update has to download each seperate chunk with a given size, - /// before the complete download is stopped and registered as failed - /// @param chunkSize Size of the chunks that the firmware binary data will be split into, - /// increased chunkSize might speed up the process by a little bit, but requires more heap memory, - // because the whole chunk is saved into the heap before it can be processed and is then erased again after it has been used + /// @param finished_callback End callback method that will be called as soon as the OTA firmware update, either finished successfully or failed. + /// Is meant to allow to either restart the device if the udpate was successfull or to restart any stopped services before the update started in the subscribed update_starting_callback + /// @param progress_callback Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, + /// meaning it will be called if the amount of already downloaded chunks increased. + /// Is meant to allow to display a progress bar or print the current progress of the update into the console with the currently already downloaded amount of chunks and the total amount of chunks, default = nullptr + /// @param update_starting_callback Update starting callback method that will be called as soon as the shared attribute firmware keys have been received and processed + /// and the moment before we subscribe the necessary topics for the OTA firmware update. + /// Is meant to give a moment were any additional processes or communication with the cloud can be stopped to ensure the update process runs as smooth as possible. + /// To ensure that calling the ThingsBoardSized::Cleanup_Subscriptions() method can be used which stops any receiving of data over MQTT besides the one for the OTA firmware update, + /// if this method is used ensure to call all subscribe methods again so they can be resubscribed, in the method passed to the finished_callback if the update failed and we do not restart the device, default = nullptr + /// @param chunk_retries Amount of retries the OTA firmware update has to download each seperate chunk with a given size, + /// before the complete download is stopped and registered as failed, default = CHUNK_RETRIES + /// @param chunk_size Size of the chunks that the firmware binary data will be split into, + /// increased chunk size might speed up the process by a little bit, but requires more heap memory, + // because the whole chunk is saved into the heap before it can be processed and is then erased again after it has been used, default = CHUNK_SIZE /// @param timeout Maximum amount of time in microseconds for the OTA firmware update for each seperate chunk, - /// until that chunk counts as a timeout, retries is then subtraced by one and the download is retried - OTA_Update_Callback(progressFn progressCb, function endCb, char const * const currFwTitle, char const * const currFwVersion, IUpdater * const updater, uint8_t const & chunkRetries = CHUNK_RETRIES, uint16_t const & chunkSize = CHUNK_SIZE, uint64_t const & timeout = REQUEST_TIMEOUT); - - /// @brief Calls the progress callback that was subscribed, when this class instance was initally created - /// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set - /// @param current Already received and processs amount of chunks - /// @param total Total amount of chunks we need to receive and process until the update has completed - template - returnType Call_Progress_Callback(progressArgumentType current, progressArgumentType total) const { - if (!m_progressCb) { - Logger::println(OTA_CB_IS_NULL); - return returnType(); - } - return m_progressCb(current, total); - } - - /// @brief Sets the progress callback method that will be called every time our current progress of downloading the complete firmware data changed, - /// this makes it possible to display a progress bar or signal easily how far we are in the current downloading process - /// @param progressCb Progress callback method that will be called - void Set_Progress_Callback(progressFn progressCb); + /// until that chunk counts as a timeout, retries is then subtraced by one and the download is retried, default = REQUEST_TIMEOUT + OTA_Update_Callback(char const * const current_fw_title, char const * const current_fw_version, IUpdater * const updater, function finished_callback, Callback::function progress_callback = nullptr, Callback::function update_starting_callback = nullptr, uint8_t chunk_retries = CHUNK_RETRIES, uint16_t chunk_size = CHUNK_SIZE, uint64_t const & timeout_microseconds = REQUEST_TIMEOUT); /// @brief Gets the current firmware title, used to decide if an OTA firmware update is already installed and therefore should not be downladed, /// this is only done if the title of the update and the current firmware title are the same because if they are not then this firmware is meant for another device type @@ -99,8 +54,8 @@ class OTA_Update_Callback : public Callback { /// @brief Sets the current firmware title, used to decide if an OTA firmware update is already installed and therefore should not be downladed, /// this is only done if the title of the update and the current firmware title are the same because if they are not then this firmware is meant for another device type - /// @param currFwTitle Current firmware title of the device - void Set_Firmware_Title(const char *currFwTitle); + /// @param current_fw_title Current firmware title of the device + void Set_Firmware_Title(const char *current_fw_title); /// @brief Gets the current firmware version, used to decide if an OTA firmware update is already installed and therefore should not be downladed, /// this is only done if the version of the update and the current firmware version are different, because if they are not then we would download the same firmware as is already on the device @@ -109,8 +64,8 @@ class OTA_Update_Callback : public Callback { /// @brief Sets the current firmware version, used to decide if an OTA firmware update is already installed and therefore should not be downladed, /// this is only done if the version of the update and the current firmware version are different, because if they are not then we would download the same firmware as is already on the device - /// @param currFwVersion Current firmware version of the device - void Set_Firmware_Version(const char *currFwVersion); + /// @param current_fw_version Current firmware version of the device + void Set_Firmware_Version(const char *current_fw_version); /// @brief Gets the updater implementation, used to write the actual firmware data into the needed memory location, /// so it can be used to reboot the given device with that new flashed firmware @@ -122,29 +77,63 @@ class OTA_Update_Callback : public Callback { /// @param updater Updater implementation that writes the given firmware data void Set_Updater(IUpdater *updater); + /// @brief Gets the unique request identifier that is connected to the original request, + /// and will be later used to verifiy which OTA_Update_Callback + /// is connected to which received OTA firmware chunk update + /// @return Unique identifier connected to the request for OTA firmware update + size_t const & Get_Request_ID() const; + + /// @brief Sets the unique request identifier that is connected to the original request, + /// and will be later used to verifiy which OTA_Update_Callback + /// is connected to which received OTA firmware chunk update + /// @param request_id Unique identifier connected to the request for OTA firmware update + void Set_Request_ID(size_t const & request_id); + + /// @brief Calls the progress callback that was subscribed, when this class instance was initally created + /// @param current Already received and processs amount of chunks + /// @param total Total amount of chunks we need to receive and process until the update has completed + void Call_Progress_Callback(size_t const & current, size_t const & total) const; + + /// @brief Sets the progress callback method. + /// Is meant to allow to display a progress bar or print the current progress of the update into the console with the currently already downloaded amount of chunks and the total amount of chunks + /// @param progress_callback Progress callback method that will be called every time our current progress of downloading the complete firmware data changed, + /// meaning it will be called if the amount of already downloaded chunks increased + void Set_Progress_Callback(Callback::function progress_callback); + + /// @brief Calls the update starting callback that was subscribed, when this class instance was initally created + void Call_Update_Starting_Callback() const; + + /// @brief Sets the update starting callback method. + /// Is meant to give a moment were any additional processes or communication with the cloud can be stopped to ensure the update process runs as smooth as possible. + /// To ensure that calling the ThingsBoardSized::Cleanup_Subscriptions() method can be used which stops any receiving of data over MQTT besides the one for the OTA firmware update, + /// if this method is used ensure to call all subscribe methods again so they can be resubscribed, in the method passed to the finished_callback if the update failed and we do not restart the device + /// @param update_starting_callback Update starting callback method that will be called as soon as the shared attribute firmware keys have been received and processed + /// and the moment before we subscribe the necessary topics for the OTA firmware update + void Set_Update_Starting_Callback(Callback::function update_starting_callback); + /// @brief Gets the amount of times we attempt to download each chunk of the OTA firmware binary file, /// if the download fails because it times out, doesn't let itself write into flash memory, ... /// the retries are decreased by 1 until we hit 0, if that is the case then we instead stop the OTA firmware update completely /// @return Amount of retries for each single chunk before we abort the update - uint8_t const & Get_Chunk_Retries() const; + uint8_t Get_Chunk_Retries() const; /// @brief Sets the amount of times we attempt to download each chunk of the OTA firmware binary file, /// if the download fails because it times out, doesn't let itself write into flash memory, ... /// the retries are decreased by 1 until we hit 0, if that is the case then we instead stop the OTA firmware update completely - /// @param chunkRetries Amount of retries for each single chunk before we abort the update - void Set_Chunk_Retries(uint8_t const & chunkRetries); + /// @param chunk_retries Amount of retries for each single chunk before we abort the update + void Set_Chunk_Retries(uint8_t chunk_retries); /// @brief Gets the size of the chunks that the firmware binary data will be split into, - /// increased chunkSize might speed up the process by a little bit, but requires more heap memory, + /// increased chunk size might speed up the process by a little bit, but requires more heap memory, // because the whole chunk is saved into the heap before it can be processed and is then erased again after it has been used /// @return Size of each single chunk to be downloaded - uint16_t const & Get_Chunk_Size() const; + uint16_t Get_Chunk_Size() const; /// @brief Sets the size of the chunks that the firmware binary data will be split into, - /// increased chunkSize might speed up the process by a little bit, but requires more heap memory, + /// increased chunk size might speed up the process by a little bit, but requires more heap memory, // because the whole chunk is saved into the heap before it can be processed and is then erased again after it has been used - /// @param chunkSize Size of each single chunk to be downloaded - void Set_Chunk_Size(uint16_t const & chunkSize); + /// @param chunk_size Size of each single chunk to be downloaded + void Set_Chunk_Size(uint16_t chunk_size); /// @brief Gets the time in microseconds we wait until we declare a single chunk we attempted to download as a failure /// @return Timeout time until we expect a response from the server @@ -155,15 +144,15 @@ class OTA_Update_Callback : public Callback { void Set_Timeout(uint64_t const & timeout_microseconds); private: - progressFn m_progressCb; // Progress callback to call - char const *m_fwTitel; // Current firmware title of device - char const *m_fwVersion; // Current firmware version of device - IUpdater *m_updater; // Updater implementation used to write firmware data - uint8_t m_retries; // Maximum amount of retries for a single chunk to be downloaded and flashes successfully - uint16_t m_size; // Size of chunks the firmware data will be split into - uint64_t m_timeout; // How long we wait for each chunck to arrive before declaring it as failed + char const *m_current_fw_title = {}; // Current firmware title of device + char const *m_current_fw_version = {}; // Current firmware version of device + IUpdater *m_updater = {}; // Updater implementation used to write firmware data + size_t m_request_id = {}; // Id the request was called with + Callback m_progress_callback = {}; // Callback called when amount of downloaded chunks increased + Callback m_update_starting_callback = {}; // Callback called when update is about to start (moment before topic subscription) + uint8_t m_chunk_retries = {}; // Maximum amount of retries for a single chunk to be downloaded and flashed successfully + uint16_t m_chunk_size = {}; // Size of chunks the firmware data will be split into + uint64_t m_timeout_microseconds = {}; // How long we wait for each chunck to arrive before declaring it as failed }; -#endif // THINGSBOARD_ENABLE_OTA - #endif // OTA_Update_Callback_h diff --git a/src/Provision.h b/src/Provision.h new file mode 100644 index 00000000..56d60f62 --- /dev/null +++ b/src/Provision.h @@ -0,0 +1,165 @@ +#ifndef Provision_h +#define Provision_h + +// Local includes. +#include "Provision_Callback.h" +#include "IAPI_Implementation.h" + + +// Provision topics. +char constexpr PROV_RESPONSE_TOPIC[] = "/provision/response/"; +char constexpr PROV_REQUEST_TOPIC[] = "/provision/request/"; +// Provision data keys. +char constexpr DEVICE_NAME_KEY[] = "deviceName"; +char constexpr PROV_DEVICE_KEY[] = "provisionDeviceKey"; +char constexpr PROV_DEVICE_SECRET_KEY[] = "provisionDeviceSecret"; +char constexpr PROV_CRED_TYPE_KEY[] = "credentialsType"; +char constexpr PROV_TOKEN[] = "token"; +char constexpr PROV_CRED_USERNAME[] = "username"; +char constexpr PROV_CRED_PASSWORD[] = "password"; +char constexpr PROV_CRED_CLIENT_ID[] = "clientId"; +char constexpr PROV_CRED_HASH[] = "hash"; + + +/// @brief Handles the internal implementation of the ThingsBoard provision API. +/// See https://thingsboard.io/docs/user-guide/device-provisioning/ for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +template +class Provision : public IAPI_Implementation { + public: + /// @brief Constructor + Provision() = default; + + /// @brief Sends provisioning request for a new device, meaning we want to create a device that we can then connect over, + /// where the given provision device key / secret decide which device profile is used to create the given device with. + /// Optionally a device name can be passed or be left empty (cloud will use a random string as the name instead). + /// The cloud then sends back json data containing our credentials, that will call the given callback, if creating the device was successful. + /// The data contained in that callbackcan then be used to disconnect and reconnect to the ThingsBoard server as our newly created device. + /// that will be called if a response from the server for the method with the given name is received. + /// Because the provision request is a single event subscription, meaning we only ever receive a response to our request once, + /// we automatically unsubscribe and delete the internal allocated data for the request as soon as the response has been received and handled by the subscribed callback. + /// See https://thingsboard.io/docs/user-guide/device-provisioning/ for more information + /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument + /// @return Whether sending the provisioning request was successful or not + bool Provision_Request(Provision_Callback const & callback) { + char const * provision_device_key = callback.Get_Device_Key(); + char const * provision_device_secret = callback.Get_Device_Secret(); + + if (Helper::stringIsNullorEmpty(provision_device_key) || Helper::stringIsNullorEmpty(provision_device_secret)) { + return false; + } + else if (!Provision_Subscribe(callback)) { + return false; + } + + StaticJsonDocument request_buffer; + char const * device_name = callback.Get_Device_Name(); + char const * access_token = callback.Get_Device_Access_Token(); + char const * cred_username = callback.Get_Credentials_Username(); + char const * cred_password = callback.Get_Credentials_Password(); + char const * cred_client_id = callback.Get_Credentials_Client_ID(); + char const * hash = callback.Get_Certificate_Hash(); + char const * credentials_type = callback.Get_Credentials_Type(); + + // Deciding which underlying provisioning method is restricted, by the Provision_Callback class. + // Meaning only the key-value pairs that are needed for the given provisioning method are set, + // resulting in the rest not being sent and therefore the provisioning request having the correct formatting + if (!Helper::stringIsNullorEmpty(device_name)) { + request_buffer[DEVICE_NAME_KEY] = device_name; + } + if (!Helper::stringIsNullorEmpty(access_token)) { + request_buffer[PROV_TOKEN] = access_token; + } + if (!Helper::stringIsNullorEmpty(cred_username)) { + request_buffer[PROV_CRED_USERNAME] = cred_username; + } + if (!Helper::stringIsNullorEmpty(cred_password)) { + request_buffer[PROV_CRED_PASSWORD] = cred_password; + } + if (!Helper::stringIsNullorEmpty(cred_client_id)) { + request_buffer[PROV_CRED_CLIENT_ID] = cred_client_id; + } + if (!Helper::stringIsNullorEmpty(hash)) { + request_buffer[PROV_CRED_HASH] = hash; + } + if (!Helper::stringIsNullorEmpty(credentials_type)) { + request_buffer[PROV_CRED_TYPE_KEY] = credentials_type; + } + request_buffer[PROV_DEVICE_KEY] = provision_device_key; + request_buffer[PROV_DEVICE_SECRET_KEY] = provision_device_secret; + return m_send_json_callback.Call_Callback(PROV_REQUEST_TOPIC, request_buffer, Helper::Measure_Json(request_buffer)); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + m_provision_callback.Call_Callback(data); + // Unsubscribe from the provision response topic, + // Will be resubscribed if another request is sent anyway + (void)Provision_Unsubscribe(); + } + + char const * Get_Response_Topic_String() const override { + return PROV_RESPONSE_TOPIC; + } + + bool Unsubscribe() override { + return Provision_Unsubscribe(); + } + + bool Resubscribe_Topic() override { + return Unsubscribe(); + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + // Nothing to do + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_send_json_callback.Set_Callback(send_json_callback); + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + } + +private: + /// @brief Subscribes one provision callback, + /// that will be called if a provision response from the server is received + /// @param callback Callback method that will be called + /// @return Whether requesting the given callback was successful or not + bool Provision_Subscribe(Provision_Callback const & callback) { + if (!m_subscribe_topic_callback.Call_Callback(PROV_RESPONSE_TOPIC)) { + Logger::printfln(SUBSCRIBE_TOPIC_FAILED, PROV_RESPONSE_TOPIC); + return false; + } + m_provision_callback = callback; + return true; + } + + /// @brief Unsubcribes the provision callback + /// @return Whether unsubcribing the previously subscribed callback + /// and from the provision response topic, was successful or not + bool Provision_Unsubscribe() { + m_provision_callback = Provision_Callback(); + return m_unsubscribe_topic_callback.Call_Callback(PROV_RESPONSE_TOPIC); + } + + Callback m_send_json_callback = {}; // Send json document callback + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + + Provision_Callback m_provision_callback = {}; // Provision response callback +}; + +#endif // Provision_h diff --git a/src/Provision_Callback.cpp b/src/Provision_Callback.cpp index 62eb246e..6a7b8666 100644 --- a/src/Provision_Callback.cpp +++ b/src/Provision_Callback.cpp @@ -1,132 +1,124 @@ // Header include. #include "Provision_Callback.h" -#if THINGSBOARD_ENABLE_PROGMEM -constexpr char PROVISION_CB_IS_NULL[] PROGMEM = "Provisioning callback is NULL"; -constexpr char ACCESS_TOKEN_CRED_TYPE[] PROGMEM = "ACCESS_TOKEN"; -constexpr char MQTT_BASIC_CRED_TYPE[] PROGMEM = "MQTT_BASIC"; -constexpr char X509_CERTIFICATE_CRED_TYPE[] PROGMEM = "X509_CERTIFICATE"; -#else -constexpr char PROVISION_CB_IS_NULL[] = "Provisioning callback is NULL"; constexpr char ACCESS_TOKEN_CRED_TYPE[] = "ACCESS_TOKEN"; constexpr char MQTT_BASIC_CRED_TYPE[] = "MQTT_BASIC"; constexpr char X509_CERTIFICATE_CRED_TYPE[] = "X509_CERTIFICATE"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -Provision_Callback::Provision_Callback(Access_Token, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const deviceName) - : Callback(callback, PROVISION_CB_IS_NULL) - , m_deviceKey(provisionDeviceKey) - , m_deviceSecret(provisionDeviceSecret) - , m_deviceName(deviceName) - , m_accessToken(nullptr) - , m_credUsername(nullptr) - , m_credPassword(nullptr) - , m_credClientID(nullptr) + +Provision_Callback::Provision_Callback(Access_Token, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const device_name) + : Callback(callback) + , m_device_key(provision_device_key) + , m_device_secret(provision_device_secret) + , m_device_name(device_name) + , m_access_token(nullptr) + , m_cred_username(nullptr) + , m_cred_password(nullptr) + , m_cred_client_id(nullptr) , m_hash(nullptr) - , m_credentialsType(nullptr) + , m_credentials_type(nullptr) { // Nothing to do } -Provision_Callback::Provision_Callback(Device_Access_Token, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const accessToken, char const * const deviceName) - : Callback(callback, PROVISION_CB_IS_NULL) - , m_deviceKey(provisionDeviceKey) - , m_deviceSecret(provisionDeviceSecret) - , m_deviceName(deviceName) - , m_accessToken(accessToken) - , m_credUsername(nullptr) - , m_credPassword(nullptr) - , m_credClientID(nullptr) +Provision_Callback::Provision_Callback(Device_Access_Token, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const access_token, char const * const device_name) + : Callback(callback) + , m_device_key(provision_device_key) + , m_device_secret(provision_device_secret) + , m_device_name(device_name) + , m_access_token(access_token) + , m_cred_username(nullptr) + , m_cred_password(nullptr) + , m_cred_client_id(nullptr) , m_hash(nullptr) - , m_credentialsType(ACCESS_TOKEN_CRED_TYPE) + , m_credentials_type(ACCESS_TOKEN_CRED_TYPE) { // Nothing to do } -Provision_Callback::Provision_Callback(Basic_MQTT_Credentials, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const username, char const * const password, char const * const clientID, char const * const deviceName) - : Callback(callback, PROVISION_CB_IS_NULL) - , m_deviceKey(provisionDeviceKey) - , m_deviceSecret(provisionDeviceSecret) - , m_deviceName(deviceName) - , m_accessToken(nullptr) - , m_credUsername(username) - , m_credPassword(password) - , m_credClientID(clientID) +Provision_Callback::Provision_Callback(Basic_MQTT_Credentials, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const username, char const * const password, char const * const client_id, char const * const device_name) + : Callback(callback) + , m_device_key(provision_device_key) + , m_device_secret(provision_device_secret) + , m_device_name(device_name) + , m_access_token(nullptr) + , m_cred_username(username) + , m_cred_password(password) + , m_cred_client_id(client_id) , m_hash(nullptr) - , m_credentialsType(MQTT_BASIC_CRED_TYPE) + , m_credentials_type(MQTT_BASIC_CRED_TYPE) { // Nothing to do } -Provision_Callback::Provision_Callback(X509_Certificate, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const hash, char const * const deviceName) - : Callback(callback, PROVISION_CB_IS_NULL) - , m_deviceKey(provisionDeviceKey) - , m_deviceSecret(provisionDeviceSecret) - , m_deviceName(deviceName) - , m_accessToken(nullptr) - , m_credUsername(nullptr) - , m_credPassword(nullptr) - , m_credClientID(nullptr) +Provision_Callback::Provision_Callback(X509_Certificate, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const hash, char const * const device_name) + : Callback(callback) + , m_device_key(provision_device_key) + , m_device_secret(provision_device_secret) + , m_device_name(device_name) + , m_access_token(nullptr) + , m_cred_username(nullptr) + , m_cred_password(nullptr) + , m_cred_client_id(nullptr) , m_hash(hash) - , m_credentialsType(X509_CERTIFICATE_CRED_TYPE) + , m_credentials_type(X509_CERTIFICATE_CRED_TYPE) { // Nothing to do } const char* Provision_Callback::Get_Device_Key() const { - return m_deviceKey; + return m_device_key; } -void Provision_Callback::Set_Device_Key(char const * const provisionDeviceKey) { - m_deviceKey = provisionDeviceKey; +void Provision_Callback::Set_Device_Key(char const * const provision_device_key) { + m_device_key = provision_device_key; } const char* Provision_Callback::Get_Device_Secret() const { - return m_deviceSecret; + return m_device_secret; } -void Provision_Callback::Set_Device_Secret(char const * const provisionDeviceSecret) { - m_deviceSecret = provisionDeviceSecret; +void Provision_Callback::Set_Device_Secret(char const * const provision_device_secret) { + m_device_secret = provision_device_secret; } const char* Provision_Callback::Get_Device_Name() const { - return m_deviceName; + return m_device_name; } -void Provision_Callback::Set_Device_Name(char const * const deviceName) { - m_deviceName = deviceName; +void Provision_Callback::Set_Device_Name(char const * const device_name) { + m_device_name = device_name; } const char* Provision_Callback::Get_Device_Access_Token() const { - return m_accessToken; + return m_access_token; } -void Provision_Callback::Set_Device_Access_Token(char const * const accessToken) { - m_accessToken = accessToken; +void Provision_Callback::Set_Device_Access_Token(char const * const access_token) { + m_access_token = access_token; } const char* Provision_Callback::Get_Credentials_Username() const { - return m_credUsername; + return m_cred_username; } void Provision_Callback::Set_Credentials_Username(char const * const username) { - m_credUsername = username; + m_cred_username = username; } const char* Provision_Callback::Get_Credentials_Password() const { - return m_credPassword; + return m_cred_password; } void Provision_Callback::Set_Credentials_Password(char const * const password) { - m_credPassword = password; + m_cred_password = password; } const char* Provision_Callback::Get_Credentials_Client_ID() const { - return m_credClientID; + return m_cred_client_id; } -void Provision_Callback::Set_Credentials_Client_ID(char const * const clientID) { - m_credClientID = clientID; +void Provision_Callback::Set_Credentials_Client_ID(char const * const client_id) { + m_cred_client_id = client_id; } const char* Provision_Callback::Get_Certificate_Hash() const { @@ -138,5 +130,5 @@ void Provision_Callback::Set_Certificate_Hash(char const * const hash) { } const char* Provision_Callback::Get_Credentials_Type() const { - return m_credentialsType; + return m_credentials_type; } diff --git a/src/Provision_Callback.h b/src/Provision_Callback.h index ca7dadd4..9aaf3272 100644 --- a/src/Provision_Callback.h +++ b/src/Provision_Callback.h @@ -15,7 +15,7 @@ struct X509_Certificate{}; /// @brief Provisioning callback wrapper, /// contains the needed configuration settings to create the request that should be sent to the server. /// Documentation about the specific use of Provisioning devices in ThingsBoard can be found here https://thingsboard.io/docs/user-guide/device-provisioning/ -class Provision_Callback : public Callback { +class Provision_Callback : public Callback { public: /// @brief Constructs empty callback, will result in never being called. Internals are simply default constructed as nullptr Provision_Callback() = default; @@ -24,46 +24,46 @@ class Provision_Callback : public Callback { /// where the requested credentials were sent by the cloud and received by the client. /// Using the credentials generated by the ThingsBoard server method. See https://thingsboard.io/docs/user-guide/device-provisioning/?mqttprovisioning=without#mqtt-device-apis /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - /// @param provisionDeviceKey Device profile provisioning key of the device profile that should be used to create the device under - /// @param provisionDeviceSecret Device profile provisioning secret of the device profile that should be used to create the device under - /// @param deviceName Name the created device should have on the cloud, + /// @param provision_device_key Device profile provisioning key of the device profile that should be used to create the device under + /// @param provision_device_secret Device profile provisioning secret of the device profile that should be used to create the device under + /// @param device_name Name the created device should have on the cloud, /// pass nullptr or an empty string if a random string should be used as a name instead - Provision_Callback(Access_Token, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const deviceName = nullptr); + Provision_Callback(Access_Token, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const device_name = nullptr); /// @brief Constructs callback that will be fired upon a provision request arrival, /// where the requested credentials were sent by the cloud and received by the client. /// Using the device supplies access token method. See https://thingsboard.io/docs/user-guide/device-provisioning/?mqttprovisioning=without#mqtt-device-apis /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - /// @param provisionDeviceKey Device profile provisioning key of the device profile that should be used to create the device under - /// @param provisionDeviceSecret Device profile provisioning secret of the device profile that should be used to create the device under - /// @param accessToken Access token generated by the device, that will be used by the provisioned device, alternative to letting the access token be generated by the cloud instead - /// @param deviceName Name the created device should have on the cloud, + /// @param provision_device_key Device profile provisioning key of the device profile that should be used to create the device under + /// @param provision_device_secret Device profile provisioning secret of the device profile that should be used to create the device under + /// @param access_token Access token generated by the device, that will be used by the provisioned device, alternative to letting the access token be generated by the cloud instead + /// @param device_name Name the created device should have on the cloud, /// pass nullptr or an empty string if a random string should be used as a name instead - Provision_Callback(Device_Access_Token, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const accessToken, char const * const deviceName = nullptr); + Provision_Callback(Device_Access_Token, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const access_token, char const * const device_name = nullptr); /// @brief Constructs callback that will be fired upon a provision request arrival, /// where the requested credentials were sent by the cloud and received by the client. /// Using the device supplies basic MQTT credentials method. See https://thingsboard.io/docs/user-guide/device-provisioning/?mqttprovisioning=without#mqtt-device-apis /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - /// @param provisionDeviceKey Device profile provisioning key of the device profile that should be used to create the device under - /// @param provisionDeviceSecret Device profile provisioning secret of the device profile that should be used to create the device under + /// @param provision_device_key Device profile provisioning key of the device profile that should be used to create the device under + /// @param provision_device_secret Device profile provisioning secret of the device profile that should be used to create the device under /// @param username Basic MQTT credentials username, that will be used by the provisioned device /// @param password Basic MQTT credentials password, that will be used by the provisioned device - /// @param clientID Basic MQTT credentials clientID, that will be used by the provisioned device - /// @param deviceName Name the created device should have on the cloud, + /// @param client_id Basic MQTT credentials client_id, that will be used by the provisioned device + /// @param device_name Name the created device should have on the cloud, /// pass nullptr or an empty string if a random string should be used as a name instead - Provision_Callback(Basic_MQTT_Credentials, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const username, char const * const password, char const * const clientID, char const * const deviceName = nullptr); + Provision_Callback(Basic_MQTT_Credentials, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const username, char const * const password, char const * const client_id, char const * const device_name = nullptr); /// @brief Constructs callback that will be fired upon a provision request arrival, /// where the requested credentials were sent by the cloud and received by the client. /// Using the device supplies X.509 certificate method. See https://thingsboard.io/docs/user-guide/device-provisioning/?mqttprovisioning=without#mqtt-device-apis /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - /// @param provisionDeviceKey Device profile provisioning key of the device profile that should be used to create the device under - /// @param provisionDeviceSecret Device profile provisioning secret of the device profile that should be used to create the device under + /// @param provision_device_key Device profile provisioning key of the device profile that should be used to create the device under + /// @param provision_device_secret Device profile provisioning secret of the device profile that should be used to create the device under /// @param hash Public X.509 certificate hash, that will be used by the provisioned device - /// @param deviceName Name the created device should have on the cloud, + /// @param device_name Name the created device should have on the cloud, /// pass nullptr or an empty string if a random string should be used as a name instead - Provision_Callback(X509_Certificate, function callback, char const * const provisionDeviceKey, char const * const provisionDeviceSecret, char const * const hash, char const * const deviceName = nullptr); + Provision_Callback(X509_Certificate, function callback, char const * const provision_device_key, char const * const provision_device_secret, char const * const hash, char const * const device_name = nullptr); /// @brief Gets the device profile provisioning key of the device profile, /// that should be used to create the device under @@ -72,8 +72,8 @@ class Provision_Callback : public Callback { /// @brief Sets the device profile provisioning key of the device profile, /// that should be used to create the device under - /// @param provisionDeviceKey Device profile provisioning key - void Set_Device_Key(char const * const provisionDeviceKey); + /// @param provision_device_key Device profile provisioning key + void Set_Device_Key(char const * const provision_device_key); /// @brief Gets the device profile provisioning secret of the device profile, /// that should be used to create the device under @@ -82,8 +82,8 @@ class Provision_Callback : public Callback { /// @brief Gets the device profile provisioning secret of the device profile, /// that should be used to create the device under - /// @param provisionDeviceSecret Device profile provisioning secret - void Set_Device_Secret(char const * const provisionDeviceSecret); + /// @param provision_device_secret Device profile provisioning secret + void Set_Device_Secret(char const * const provision_device_secret); /// @brief Gets the name the created device should have on the cloud, /// is a nullptr or an empty string if a random string should be used as a name instead @@ -92,8 +92,8 @@ class Provision_Callback : public Callback { /// @brief Sets the name the created device should have on the cloud, /// is a nullptr or an empty string if a random string should be used as a name instead - /// @param deviceName Name the created device should have on the cloud - void Set_Device_Name(char const * const deviceName) ; + /// @param device_name Name the created device should have on the cloud + void Set_Device_Name(char const * const device_name) ; /// @brief Gets the access token generated by the device, /// that will be used by the provisioned device, @@ -104,8 +104,8 @@ class Provision_Callback : public Callback { /// @brief Sets the access token generated by the device, /// that will be used by the provisioned device, /// alternative to letting the access token be generated by the cloud instead - /// @param accessToken Access token generated by the device - void Set_Device_Access_Token(char const * const accessToken); + /// @param access_token Access token generated by the device + void Set_Device_Access_Token(char const * const access_token); /// @brief Gets the basic MQTT credentials username, that will be used by the provisioned device /// @return Basic MQTT credentials username @@ -123,13 +123,13 @@ class Provision_Callback : public Callback { /// @param password Basic MQTT credentials password void Set_Credentials_Password(char const * const password); - /// @brief Gets the basic MQTT credentials clientID, that will be used by the provisioned device - /// @return Basic MQTT credentials clientID + /// @brief Gets the basic MQTT credentials client_id, that will be used by the provisioned device + /// @return Basic MQTT credentials client_id char const * Get_Credentials_Client_ID() const; - /// @brief Sets the basic MQTT credentials clientID, that will be used by the provisioned device - /// @param clientID Basic MQTT credentials clientID - void Set_Credentials_Client_ID(char const * const clientID); + /// @brief Sets the basic MQTT credentials client_id, that will be used by the provisioned device + /// @param client_id Basic MQTT credentials client_id + void Set_Credentials_Client_ID(char const * const client_id); /// @brief Gets the public X.509 certificate hash, that will be used by the provisioned device /// @return Public X.509 certificate hash @@ -145,15 +145,15 @@ class Provision_Callback : public Callback { char const * Get_Credentials_Type() const; private: - char const *m_deviceKey; // Device profile provisioning key - char const *m_deviceSecret; // Device profile provisioning secret - char const *m_deviceName; // Device name the provisioned device should have - char const *m_accessToken; // Access token supplied by the device, if it should not be generated by the server instead - char const *m_credUsername; // MQTT credential username, if the MQTT basic credentials method is used - char const *m_credPassword; // MQTT credential password, if the MQTT basic credentials method is used - char const *m_credClientID; // MQTT credential clientID, if Mthe QTT basic credentials method is used - char const *m_hash; // X.509 certificate hash, if the X.509 certificate authentication method is used - char const *m_credentialsType; // Credentials type we are requesting from the server, nullptr for the default option (Credentials generated by the ThingsBoard server) + char const *m_device_key = {}; // Device profile provisioning key + char const *m_device_secret = {}; // Device profile provisioning secret + char const *m_device_name = {}; // Device name the provisioned device should have + char const *m_access_token = {}; // Access token supplied by the device, if it should not be generated by the server instead + char const *m_cred_username = {}; // MQTT credential username, if the MQTT basic credentials method is used + char const *m_cred_password = {}; // MQTT credential password, if the MQTT basic credentials method is used + char const *m_cred_client_id = {}; // MQTT credential client_id, if Mthe QTT basic credentials method is used + char const *m_hash = {}; // X.509 certificate hash, if the X.509 certificate authentication method is used + char const *m_credentials_type = {}; // Credentials type we are requesting from the server, nullptr for the default option (Credentials generated by the ThingsBoard server) }; #endif // Provision_Callback_h diff --git a/src/RPC_Callback.h b/src/RPC_Callback.h index ed227502..db990b73 100644 --- a/src/RPC_Callback.h +++ b/src/RPC_Callback.h @@ -6,13 +6,6 @@ #include "Constants.h" -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr RPC_CB_NULL[] PROGMEM = "Server-side RPC callback is NULL"; -#else -char constexpr RPC_CB_NULL[] = "Server-side RPC callback is NULL"; -#endif // THINGSBOARD_ENABLE_PROGMEM - - /// @brief Server-side RPC callback wrapper, /// contains the needed configuration settings to create the request that should be sent to the server. /// Documentation about the specific use of Server-side RPC in ThingsBoard can be found here https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc @@ -21,58 +14,58 @@ class RPC_Callback : public Callback { /// @brief Constructs empty callback, will result in never being called. Internals are simply default constructed as nullptr RPC_Request_Callback() = default; - /// @brief Constructs callback, will be called upon client-side RPC response arrival originating - /// from the original client side RPC request without any parameters - /// @param methodName Name of the client side RPC method we want to call on the cloud - /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - RPC_Request_Callback(char const * const methodName, function callback); - /// @brief Constructs callback, will be called upon RPC response arrival originating - /// from the original client side RPC request with additional parameters that should be passed to the method - /// @param methodName Name of the client side RPC method we want to call on the cloud - /// @param parameteres Parameters that will be passed to the client side RPC, - /// Optional, pass NULL if there are no argument for the given method - /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - RPC_Request_Callback(char const * const methodName, JsonArray const * const parameteres, function callback); + /// from the original client side RPC request with any optional additional parameters that should be passed to the method + /// @param method_name Name of the client side RPC method we want to call on the cloud + /// @param received_callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument + /// @param parameters Optional parameters that will be passed with the client side RPC call, use nullptr if there are no arguments for the given method, default = nullptr + /// @param timeout_microseconds Optional amount of microseconds until we expect to have received a response and if we didn't, we call the previously subscribed callback. + /// If the value is 0 we will not start the timer and therefore never call the timeout callback method, default = 0 + /// @param timeout_callback Optional callback method that will be called upon request timeout (did not receive a response in the given timeout time). Can happen if the requested method does not exist on the cloud, + /// or if the connection could not be established, default = nullptr + RPC_Request_Callback(char const * const method_name, function received_callback, JsonArray const * const parameters = nullptr, uint64_t const & timeout_microseconds = 0U, Callback_Watchdog::function timeout_callback = nullptr); /// @brief Gets the unique request identifier that is connected to the original request, /// and will be later used to verifiy which RPC_Request_Callback @@ -40,25 +37,52 @@ class RPC_Request_Callback : public Callback { void Set_Request_ID(size_t const & request_id); /// @brief Gets the poiner to the underlying name of the client side RPC method we want to call on the cloud - /// @return Pointer to the passed methodName + /// @return Pointer to the passed method name char const * Get_Name() const; /// @brief Sets the poiner to the underlying name of the client side RPC method we want to call on the cloud - /// @param methodName Pointer to the passed methodName - void Set_Name(char const * const methodName); + /// @param method_name Pointer to the passed method name + void Set_Name(char const * const method_name); /// @brief Gets the pointer to the underlying paramaters we want to call the client side RPC method on the cloud with /// @return Pointer to the passed parameters JsonArray const * Get_Parameters() const; /// @brief Sets the pointer to the underlying paramaters we want to call the client side RPC method on the cloud with - /// @param parameteres Pointer to the passed parameters - void Set_Parameters(JsonArray const * const parameteres); + /// @param parameters Pointer to the passed parameters + void Set_Parameters(JsonArray const * const parameters); + + /// @brief Gets the amount of microseconds until we expect to have received a response + /// @return Timeout time until timeout callback is called + uint64_t const & Get_Timeout() const; + + /// @brief Sets the amount of microseconds until we expect to have received a response + /// @param timeout_microseconds Timeout time until timeout callback is called + void Set_Timeout(uint64_t const & timeout_microseconds); + +#if !THINGSBOARD_USE_ESP_TIMER + /// @brief Updates the internal timeout timer + void Update_Timeout_Timer(); +#endif // !THINGSBOARD_USE_ESP_TIMER + + /// @brief Starts the internal timeout timer if we actually received a configured valid timeout time and a valid callback. + /// Is called as soon as the request is actually sent + void Start_Timeout_Timer(); + + /// @brief Stops the internal timeout timer, is called as soon as an answer is received from the cloud + /// if it isn't we call the previously subscribed callback instead + void Stop_Timeout_Timer(); + + /// @brief Sets the callback method that will be called upon request timeout (did not receive a response in the given timeout time) + /// @param timeout_callback Callback function that will be called + void Set_Timeout_Callback(Callback_Watchdog::function timeout_callback); private: - char const *m_methodName; // Method name - JsonArray const *m_parameters; // Parameter json - size_t m_request_id; // Id the request was called with + char const *m_method_name = {}; // Method name + JsonArray const *m_parameters = {}; // Parameter json + size_t m_request_id = {}; // Id the request was called with + uint64_t m_timeout_microseconds = {}; // Timeout time until we expect response to request + Callback_Watchdog m_timeout_callback = {}; // Handles callback that will be called if request times out }; #endif // RPC_Request_Callback_h diff --git a/src/SDCard_Updater.cpp b/src/SDCard_Updater.cpp index 9fdcf2e6..22a039fa 100644 --- a/src/SDCard_Updater.cpp +++ b/src/SDCard_Updater.cpp @@ -1,10 +1,8 @@ // Header include. #include "SDCard_Updater.h" -#if THINGSBOARD_ENABLE_OTA - // Library include. -#include +#include SDCard_Updater::SDCard_Updater(char const * const file_path) @@ -39,5 +37,3 @@ void SDCard_Updater::reset() { bool SDCard_Updater::end() { return remove(m_path) == 0; } - -#endif // THINGSBOARD_ENABLE_OTA diff --git a/src/SDCard_Updater.h b/src/SDCard_Updater.h index c4f3cac7..c16d6421 100644 --- a/src/SDCard_Updater.h +++ b/src/SDCard_Updater.h @@ -4,8 +4,6 @@ // Local include. #include "Configuration.h" -#if THINGSBOARD_ENABLE_OTA - // Local include. #include @@ -24,9 +22,7 @@ class SDCard_Updater : public IUpdater { bool end() override; private: - char const * const m_path; + char const * const m_path = {}; // Path to the file the binary data is written into }; -#endif // THINGSBOARD_ENABLE_OTA - #endif // SDCard_Updater_h diff --git a/src/Server_Side_RPC.h b/src/Server_Side_RPC.h new file mode 100644 index 00000000..9a83b4c4 --- /dev/null +++ b/src/Server_Side_RPC.h @@ -0,0 +1,227 @@ +#ifndef Server_Side_RPC_h +#define Server_Side_RPC_h + +// Local includes. +#include "RPC_Callback.h" +#include "IAPI_Implementation.h" + + +// Server side RPC topics. +char constexpr RPC_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/request/+"; +char constexpr RPC_REQUEST_TOPIC[] = "v1/devices/me/rpc/request/"; +char constexpr RPC_SEND_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/%u"; +// Log messages. +char constexpr SERVER_RPC_METHOD_NULL[] = "Server-side RPC method name is NULL"; +char constexpr RPC_RESPONSE_OVERFLOWED[] = "Server-side RPC response overflowed, increase MaxRPC (%u)"; +#if !THINGSBOARD_ENABLE_DYNAMIC +char constexpr SERVER_SIDE_RPC_SUBSCRIPTIONS[] = "server-side RPC"; +#endif // !THINGSBOARD_ENABLE_DYNAMIC +#if THINGSBOARD_ENABLE_DEBUG +char constexpr RPC_RESPONSE_NULL[] = "Response JsonDocument is NULL, skipping sending"; +char constexpr NO_RPC_PARAMS_PASSED[] = "No parameters passed with RPC, passing null JSON"; +char constexpr CALLING_RPC_CB[] = "Calling subscribed callback for rpc with methodname (%s)"; +#endif // THINGSBOARD_ENABLE_DEBUG + + +/// @brief Handles the internal implementation of the ThingsBoard server side RPC API. +/// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +#if THINGSBOARD_ENABLE_DYNAMIC +template +#else +/// @tparam MaxSubscriptions Maximum amount of simultaneous server side rpc subscriptions. +/// Once the maximum amount has been reached it is not possible to increase the size, this is done because it allows to allcoate the memory on the stack instead of the heap, default = Default_Subscriptions_Amount (1) +/// @tparam MaxRPC Maximum amount of key-value pairs that will ever be sent in the subscribed callback method of an RPC_Callback, allows to use a StaticJsonDocument on the stack in the background. +/// If we simply use .to(); on the received document and use .set() to change the internal value then the size requirements are 0. +/// However if we attempt to send multiple key-value pairs, we have to adjust the size accordingly. See https://arduinojson.org/v6/assistant/ for more information on how to estimate the required size and divide the result by 16 to receive the required MaxRPC value, default = Default_RPC_Amount (0) +template +#endif // THINGSBOARD_ENABLE_DYNAMIC +class Server_Side_RPC : public IAPI_Implementation { + public: + /// @brief Constructor + Server_Side_RPC() = default; + + /// @brief Subscribes multiple server side RPC callbacks, + /// that will be called if a request from the server for the method with the given name is received. + /// Can be called even if we are currently not connected to the cloud, + /// this is the case because the only interaction that requires an active connection is the subscription of the topic that we receive the response on + /// and that subscription is also done automatically by the library once the device has established a connection to the cloud. + /// Therefore this method can simply be called once at startup before a connection has been established + /// and will then automatically handle the subscription of the topic once the connection has been established. + /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element in the data container + /// @param last Iterator pointing to the end of the data container (last element + 1) + /// @return Whether subscribing the given callbacks was successful or not + template + bool RPC_Subscribe(InputIterator const & first, InputIterator const & last) { +#if !THINGSBOARD_ENABLE_DYNAMIC + size_t const size = Helper::distance(first, last); + if (m_rpc_callbacks.size() + size > m_rpc_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, SERVER_SIDE_RPC_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + (void)m_subscribe_topic_callback.Call_Callback(RPC_SUBSCRIBE_TOPIC); + // Push back complete vector into our local m_rpc_callbacks vector. + m_rpc_callbacks.insert(m_rpc_callbacks.end(), first, last); + return true; + } + + /// @brief Subscribe one server side RPC callback, + /// that will be called if a request from the server for the method with the given name is received. + /// Can be called even if we are currently not connected to the cloud, + /// this is the case because the only interaction that requires an active connection is the subscription of the topic that we receive the response on + /// and that subscription is also done automatically by the library once the device has established a connection to the cloud. + /// Therefore this method can simply be called once at startup before a connection has been established + /// and will then automatically handle the subscription of the topic once the connection has been established. + /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information + /// @param callback Callback method that will be called + /// @return Whether subscribing the given callback was successful or not + bool RPC_Subscribe(RPC_Callback const & callback) { +#if !THINGSBOARD_ENABLE_DYNAMIC + if (m_rpc_callbacks.size() + 1 > m_rpc_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, SERVER_SIDE_RPC_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + (void)m_subscribe_topic_callback.Call_Callback(RPC_SUBSCRIBE_TOPIC); + m_rpc_callbacks.push_back(callback); + return true; + } + + /// @brief Unsubcribes all server side RPC callbacks. + /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information + /// @return Whether unsubcribing all the previously subscribed callbacks + /// and from the rpc topic, was successful or not + bool RPC_Unsubscribe() { + m_rpc_callbacks.clear(); + return m_unsubscribe_topic_callback.Call_Callback(RPC_SUBSCRIBE_TOPIC); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + char const * const method_name = data[RPC_METHOD_KEY]; + + if (method_name == nullptr) { + Logger::println(SERVER_RPC_METHOD_NULL); + return; + } + + for (auto const & rpc : m_rpc_callbacks) { + char const * const subscribedMethodName = rpc.Get_Name(); + if (Helper::stringIsNullorEmpty(subscribedMethodName)) { + Logger::println(SERVER_RPC_METHOD_NULL); + continue; + } + // Strncmp returns the ascii value difference of the ascii characters that are different, + // meaning 0 is the same string and less and more than 0 is the difference in ascci values between the 2 chararacters. + else if (strncmp(subscribedMethodName, method_name, strlen(subscribedMethodName)) != 0) { + continue; + } + + // Do not inform client, if parameter field is missing for some reason + if (!data.containsKey(RPC_PARAMS_KEY)) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(NO_RPC_PARAMS_PASSED); +#endif // THINGSBOARD_ENABLE_DEBUG + } + +#if THINGSBOARD_ENABLE_DEBUG + Logger::printfln(CALLING_RPC_CB, method_name); +#endif // THINGSBOARD_ENABLE_DEBUG + + JsonVariantConst const param = data[RPC_PARAMS_KEY]; +#if THINGSBOARD_ENABLE_DYNAMIC + size_t const & rpc_response_size = rpc.Get_Response_Size(); + // String are char const * and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, + // Data structure size depends on the amount of key value pairs passed. + // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument + TBJsonDocument json_buffer(rpc_response_size); +#else + size_t constexpr rpc_response_size = MaxRPC; + StaticJsonDocument json_buffer; +#endif // THINGSBOARD_ENABLE_DYNAMIC + rpc.Call_Callback(param, json_buffer); + + if (json_buffer.isNull()) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(RPC_RESPONSE_NULL); +#endif // THINGSBOARD_ENABLE_DEBUG + break; + } + else if (json_buffer.overflowed()) { + Logger::printfln(RPC_RESPONSE_OVERFLOWED, rpc_response_size); + break; + } + + size_t const request_id = Helper::parseRequestId(RPC_REQUEST_TOPIC, topic); + char responseTopic[Helper::detectSize(RPC_SEND_RESPONSE_TOPIC, request_id)] = {}; + (void)snprintf(responseTopic, sizeof(responseTopic), RPC_SEND_RESPONSE_TOPIC, request_id); + (void)m_send_json_callback.Call_Callback(responseTopic, json_buffer, Helper::Measure_Json(json_buffer)); + break; + } + } + + char const * Get_Response_Topic_String() const override { + return RPC_REQUEST_TOPIC; + } + + bool Unsubscribe() override { + return RPC_Unsubscribe(); + } + + bool Resubscribe_Topic() override { + if (!m_rpc_callbacks.empty() && !m_subscribe_topic_callback.Call_Callback(RPC_SUBSCRIBE_TOPIC)) { + Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_SUBSCRIBE_TOPIC); + return false; + } + return true; + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + // Nothing to do + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_send_json_callback.Set_Callback(send_json_callback); + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + } + + private: + Callback m_send_json_callback = {}; // Send json document callback + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + + // Vectors or array (depends on wheter if THINGSBOARD_ENABLE_DYNAMIC is set to 1 or 0), hold copy of the actual passed data, this is to ensure they stay valid, + // even if the user only temporarily created the object before the method was called. + // This can be done because all Callback methods mostly consists of pointers to actual object so copying them + // does not require a huge memory overhead and is acceptable especially in comparsion to possible problems that could + // arise if references were used and the end user does not take care to ensure the Callbacks live on for the entirety + // of its usage, which will lead to dangling references and undefined behaviour. + // Therefore copy-by-value has been choosen as for this specific use case it is more advantageous, + // especially because at most we copy internal vectors or array, that will only ever contain a few pointers +#if THINGSBOARD_ENABLE_DYNAMIC + Vector m_rpc_callbacks = {}; // Server side RPC callbacks vector +#else + Array m_rpc_callbacks = {}; // Server side RPC callbacks array +#endif // THINGSBOARD_ENABLE_DYNAMIC +}; + +#endif // Server_Side_RPC_h diff --git a/src/Shared_Attribute_Callback.h b/src/Shared_Attribute_Callback.h index 42d4fada..4eba9ef1 100644 --- a/src/Shared_Attribute_Callback.h +++ b/src/Shared_Attribute_Callback.h @@ -8,13 +8,6 @@ #endif // !THINGSBOARD_ENABLE_DYNAMIC -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr ATT_CB_IS_NULL[] PROGMEM = "Shared attribute update callback is NULL"; -#else -char constexpr ATT_CB_IS_NULL[] = "Shared attribute update callback is NULL"; -#endif // THINGSBOARD_ENABLE_PROGMEM - - /// @brief Shared attribute update callback wrapper, /// contains the needed configuration settings to create the request that should be sent to the server. /// Documentation about the specific use of shared attribute update in ThingsBoard can be found here https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server @@ -28,16 +21,6 @@ class Shared_Attribute_Callback : public Callback /// @brief Constructs empty callback, will result in never being called. Internals are simply default constructed as nullptr Shared_Attribute_Callback() = default; - /// @brief Constructs callback, will be called upon shared attribute update arrival, - /// of any existing or new shared attribute on the given device - /// @param cb Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - explicit Shared_Attribute_Callback(function cb) - : Callback(cb, ATT_CB_IS_NULL) - , m_attributes() - { - // Nothing to do - } - /// @brief Constructs callback, will be called upon shared attribute update arrival, /// where atleast one of the given multiple shared attributes passed was updated by the cloud. /// If the update does not include any of the given shared attributes the callback is not called. @@ -54,7 +37,7 @@ class Shared_Attribute_Callback : public Callback /// @param ...args Arguments that will be forwarded into the overloaded vector constructor see https://en.cppreference.com/w/cpp/container/vector/vector for more information template Shared_Attribute_Callback(function callback, Args const &... args) - : Callback(callback, ATT_CB_IS_NULL) + : Callback(callback) , m_attributes(args...) { // Nothing to do @@ -92,9 +75,9 @@ class Shared_Attribute_Callback : public Callback private: #if THINGSBOARD_ENABLE_DYNAMIC - Vector m_attributes; // Shared attribute we want to subscribe to receive a message if they change + Vector m_attributes = {}; // Shared attribute we want to subscribe to receive a message if they change #else - Array m_attributes; // Shared attribute we want to subscribe to receive a message if they change + Array m_attributes = {}; // Shared attribute we want to subscribe to receive a message if they change #endif // THINGSBOARD_ENABLE_DYNAMIC }; diff --git a/src/Shared_Attribute_Update.h b/src/Shared_Attribute_Update.h new file mode 100644 index 00000000..a6645190 --- /dev/null +++ b/src/Shared_Attribute_Update.h @@ -0,0 +1,208 @@ +#ifndef Shared_Attribute_Update_h +#define Shared_Attribute_Update_h + +// Local includes. +#include "Shared_Attribute_Callback.h" +#include "IAPI_Implementation.h" + + +// Log messages. +#if THINGSBOARD_ENABLE_DEBUG +char constexpr ATT_CB_NO_KEYS[] = "No keys subscribed. Calling subscribed callback for any updated attributes, assumed to be subscribed to every possible key"; +char constexpr ATT_NO_CHANGE[] = "No keys that we subscribed too were changed, skipping callback"; +char constexpr SHARED_KEY_IS_NULL[] = "Subscribed shared attribute update key is NULL"; +char constexpr CALLING_ATT_CB[] = "Calling subscribed callback for updated shared attribute (%s)"; +#endif // THINGSBOARD_ENABLE_DEBUG +#if !THINGSBOARD_ENABLE_DYNAMIC +char constexpr SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS[] = "shared attribute update"; +#endif // !THINGSBOARD_ENABLE_DYNAMIC + + +/// @brief Handles the internal implementation of the ThingsBoard shared attribute update API. +/// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information +/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger +#if THINGSBOARD_ENABLE_DYNAMIC +template +#else +/// @tparam MaxSubscriptions Maximum amount of simultaneous server side rpc subscriptions. +/// Once the maximum amount has been reached it is not possible to increase the size, this is done because it allows to allcoate the memory on the stack instead of the heap, default = Default_Subscriptions_Amount (1) +/// @tparam MaxAttributes Maximum amount of attributes that will ever be requested with the Shared_Attribute_Callback, allows to use an array on the stack in the background, default = Default_Attributes_Amount (5) +template +#endif // THINGSBOARD_ENABLE_DYNAMIC +class Shared_Attribute_Update : public IAPI_Implementation { + public: + /// @brief Constructor + Shared_Attribute_Update() = default; + + /// @brief Subscribes multiple shared attribute callbacks, + /// that will be called if the key-value pair from the server for the given shared attributes is received. + /// Can be called even if we are currently not connected to the cloud, + /// this is the case because the only interaction that requires an active connection is the subscription of the topic that we receive the response on + /// and that subscription is also done automatically by the library once the device has established a connection to the cloud. + /// Therefore this method can simply be called once at startup before a connection has been established + /// and will then automatically handle the subscription of the topic once the connection has been established. + /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element in the data container + /// @param last Iterator pointing to the end of the data container (last element + 1) + /// @return Whether subscribing the given callbacks was successful or not + template + bool Shared_Attributes_Subscribe(InputIterator const & first, InputIterator const & last) { +#if !THINGSBOARD_ENABLE_DYNAMIC + size_t const size = Helper::distance(first, last); + if (m_shared_attribute_update_callbacks.size() + size > m_shared_attribute_update_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + (void)m_subscribe_topic_callback.Call_Callback(ATTRIBUTE_TOPIC); + // Push back complete vector into our local m_shared_attribute_update_callbacks vector. + m_shared_attribute_update_callbacks.insert(m_shared_attribute_update_callbacks.end(), first, last); + return true; + } + + /// @brief Subscribe one shared attribute callback, + /// that will be called if the key-value pair from the server for the given shared attributes is received. + /// Can be called even if we are currently not connected to the cloud, + /// this is the case because the only interaction that requires an active connection is the subscription of the topic that we receive the response on + /// and that subscription is also done automatically by the library once the device has established a connection to the cloud. + /// Therefore this method can simply be called once at startup before a connection has been established + /// and will then automatically handle the subscription of the topic once the connection has been established. + /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information + /// @param callback Callback method that will be called + /// @return Whether subscribing the given callback was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC + bool Shared_Attributes_Subscribe(Shared_Attribute_Callback const & callback) { +#else + bool Shared_Attributes_Subscribe(Shared_Attribute_Callback const & callback) { +#endif // THINGSBOARD_ENABLE_DYNAMIC +#if !THINGSBOARD_ENABLE_DYNAMIC + if (m_shared_attribute_update_callbacks.size() + 1U > m_shared_attribute_update_callbacks.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_SUBSCRIPTIONS_TEMPLATE_NAME, SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS); + return false; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + (void)m_subscribe_topic_callback.Call_Callback(ATTRIBUTE_TOPIC); + m_shared_attribute_update_callbacks.push_back(callback); + return true; + } + + /// @brief Unsubcribes all shared attribute callbacks. + /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information + /// @return Whether unsubcribing all the previously subscribed callbacks + /// and from the attribute topic, was successful or not + bool Shared_Attributes_Unsubscribe() { + m_shared_attribute_update_callbacks.clear(); + return m_unsubscribe_topic_callback.Call_Callback(ATTRIBUTE_TOPIC); + } + + API_Process_Type Get_Process_Type() const override { + return API_Process_Type::JSON; + } + + void Process_Response(char * const topic, uint8_t * payload, unsigned int length) override { + // Nothing to do + } + + void Process_Json_Response(char * const topic, JsonDocument const & data) override { + JsonObjectConst object = data.template as(); + if (object.containsKey(SHARED_RESPONSE_KEY)) { + object = object[SHARED_RESPONSE_KEY]; + } + + for (auto const & shared_attribute : m_shared_attribute_update_callbacks) { + if (shared_attribute.Get_Attributes().empty()) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(ATT_CB_NO_KEYS); +#endif // THINGSBOARD_ENABLE_DEBUG + // No specifc keys were subscribed so we call the callback anyway, assumed to be subscribed to any update + shared_attribute.Call_Callback(object); + continue; + } + + char const * requested_att = nullptr; + + for (auto const & att : shared_attribute.Get_Attributes()) { + if (Helper::stringIsNullorEmpty(att)) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(SHARED_KEY_IS_NULL); +#endif // THINGSBOARD_ENABLE_DEBUG + continue; + } + // Check if the request contained any of our requested keys and + // break early if the key was requested from this callback. + if (object.containsKey(att)) { + requested_att = att; + break; + } + } + + // Check if this callback did not request any keys that were in this response, + // if there were not we simply continue with the next subscribed callback. + if (requested_att == nullptr) { +#if THINGSBOARD_ENABLE_DEBUG + Logger::println(ATT_NO_CHANGE); +#endif // THINGSBOARD_ENABLE_DEBUG + continue; + } + +#if THINGSBOARD_ENABLE_DEBUG + Logger::printfln(CALLING_ATT_CB, requested_att); +#endif // THINGSBOARD_ENABLE_DEBUG + shared_attribute.Call_Callback(object); + } + } + + char const * Get_Response_Topic_String() const override { + return ATTRIBUTE_TOPIC; + } + + bool Unsubscribe() override { + return Shared_Attributes_Unsubscribe(); + } + + bool Resubscribe_Topic() override { + if (!m_shared_attribute_update_callbacks.empty() && !m_subscribe_topic_callback.Call_Callback(ATTRIBUTE_TOPIC)) { + Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_TOPIC); + return false; + } + return true; + } + +#if !THINGSBOARD_USE_ESP_TIMER + void loop() override { + // Nothing to do + } +#endif // !THINGSBOARD_USE_ESP_TIMER + + void Initialize() override { + // Nothing to do + } + + void Set_Client_Callbacks(Callback::function subscribe_api_callback, Callback::function send_json_callback, Callback::function send_json_string_callback, Callback::function subscribe_topic_callback, Callback::function unsubscribe_topic_callback, Callback::function get_size_callback, Callback::function set_buffer_size_callback, Callback::function get_request_id_callback) override { + m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback); + m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback); + } + + private: + Callback m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback + Callback m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback + + // Vectors or array (depends on wheter if THINGSBOARD_ENABLE_DYNAMIC is set to 1 or 0), hold copy of the actual passed data, this is to ensure they stay valid, + // even if the user only temporarily created the object before the method was called. + // This can be done because all Callback methods mostly consists of pointers to actual object so copying them + // does not require a huge memory overhead and is acceptable especially in comparsion to possible problems that could + // arise if references were used and the end user does not take care to ensure the Callbacks live on for the entirety + // of its usage, which will lead to dangling references and undefined behaviour. + // Therefore copy-by-value has been choosen as for this specific use case it is more advantageous, + // especially because at most we copy internal vectors or array, that will only ever contain a few pointers +#if THINGSBOARD_ENABLE_DYNAMIC + Vector m_shared_attribute_update_callbacks = {}; // Shared attribute update callbacks vector +#else + Array, MaxSubscriptions> m_shared_attribute_update_callbacks = {}; // Shared attribute update callbacks array +#endif // THINGSBOARD_ENABLE_DYNAMIC +}; + +#endif // Shared_Attribute_Update_h diff --git a/src/Telemetry.h b/src/Telemetry.h index 88ed32ba..cae3646a 100644 --- a/src/Telemetry.h +++ b/src/Telemetry.h @@ -123,16 +123,16 @@ class Telemetry { /// @brief Data type that the data container currently holds enum class DataType: const uint8_t { - TYPE_NONE, // Telemetry instance is empty and has not been assigned a value - TYPE_BOOL, // Telemetry instance is a key value-pair with a boolean value - TYPE_INT, // Telemetry instance is a key value-pair with an integral value - TYPE_REAL, // Telemetry instance is a key value-pair with a real (float, double) value - TYPE_STR // Telemetry isntance is a key value-pair with a string value + TYPE_NONE, ///< Telemetry instance is empty and has not been assigned a value + TYPE_BOOL, ///< Telemetry instance is a key value-pair with a boolean value + TYPE_INT, ///< Telemetry instance is a key value-pair with an integral value + TYPE_REAL, ///< Telemetry instance is a key value-pair with a real (float, double) value + TYPE_STR ///< Telemetry isntance is a key value-pair with a string value }; - DataType m_type; // Data type flag, showing which value is saved in the class instance - const char *m_key; // Data key of the key-value pair - Data m_value; // Data value of the key-value pair + DataType m_type = {}; // Data type flag, showing which value is saved in the class instance + const char *m_key = {}; // Data key of the key-value pair + Data m_value = {}; // Data value of the key-value pair }; /// @brief Telemetry and attributes are only different on the database side (one has a history the other one does not), but both are simply key-value pairs diff --git a/src/ThingsBoard.h b/src/ThingsBoard.h index d64a8298..cf39f58d 100644 --- a/src/ThingsBoard.h +++ b/src/ThingsBoard.h @@ -2,12 +2,8 @@ #define ThingsBoard_h // Local includes. -#include "Shared_Attribute_Callback.h" -#include "Attribute_Request_Callback.h" -#include "RPC_Callback.h" -#include "RPC_Request_Callback.h" -#include "Provision_Callback.h" -#include "OTA_Handler.h" +#include "Constants.h" +#include "IAPI_Implementation.h" #include "IMQTT_Client.h" #include "DefaultLogger.h" #include "Telemetry.h" @@ -16,283 +12,30 @@ #if THINGSBOARD_ENABLE_STREAM_UTILS #include #endif // THINGSBOARD_ENABLE_STREAM_UTILS -#if THINGSBOARD_ENABLE_OTA -#include -#endif // THINGSBOARD_ENABLE_OTA -// Publish data topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr ATTRIBUTE_TOPIC[] PROGMEM = "v1/devices/me/attributes"; -char constexpr TELEMETRY_TOPIC[] PROGMEM = "v1/devices/me/telemetry"; -#else -char constexpr ATTRIBUTE_TOPIC[] = "v1/devices/me/attributes"; -char constexpr TELEMETRY_TOPIC[] = "v1/devices/me/telemetry"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// RPC topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr RPC_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/rpc/request/+"; -char constexpr RPC_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/rpc/response/+"; -char constexpr RPC_SEND_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/rpc/request/%u"; -char constexpr RPC_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/rpc/request"; -char constexpr RPC_RESPONSE_TOPIC[] PROGMEM = "v1/devices/me/rpc/response"; -char constexpr RPC_SEND_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/%u"; -#else -char constexpr RPC_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/request/+"; -char constexpr RPC_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/response/+"; -char constexpr RPC_SEND_REQUEST_TOPIC[] = "v1/devices/me/rpc/request/%u"; -char constexpr RPC_REQUEST_TOPIC[] = "v1/devices/me/rpc/request"; -char constexpr RPC_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response"; -char constexpr RPC_SEND_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/%u"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Firmware topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr FIRMWARE_RESPONSE_TOPIC[] PROGMEM = "v2/fw/response/0/chunk"; -#else -char constexpr FIRMWARE_RESPONSE_TOPIC[] = "v2/fw/response/0/chunk"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Shared attribute topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr ATTRIBUTE_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/attributes/request/%u"; -char constexpr ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/attributes/response/+"; -char constexpr ATTRIBUTE_RESPONSE_TOPIC[] PROGMEM = "v1/devices/me/attributes/response"; -#else -char constexpr ATTRIBUTE_REQUEST_TOPIC[] = "v1/devices/me/attributes/request/%u"; -char constexpr ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/attributes/response/+"; -char constexpr ATTRIBUTE_RESPONSE_TOPIC[] = "v1/devices/me/attributes/response"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Provision topics. -#if THINGSBOARD_ENABLE_PROGMEM -uint16_t constexpr PROGMEM DEFAULT_MQTT_PORT = 1883U; -char constexpr PROV_ACCESS_TOKEN[] PROGMEM = "provision"; -char constexpr PROV_RESPONSE_TOPIC[] PROGMEM = "/provision/response"; -#else uint16_t constexpr DEFAULT_MQTT_PORT = 1883U; char constexpr PROV_ACCESS_TOKEN[] = "provision"; -char constexpr PROV_RESPONSE_TOPIC[] = "/provision/response"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Shared attribute request keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr SHARED_REQUEST_KEY[] PROGMEM = "sharedKeys"; -char constexpr SHARED_RESPONSE_KEY[] PROGMEM = "shared"; -#else -char constexpr SHARED_REQUEST_KEY[] = "sharedKeys"; -char constexpr SHARED_RESPONSE_KEY[] = "shared"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Client side attribute request keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr CLIENT_REQUEST_KEYS[] PROGMEM = "clientKeys"; -char constexpr CLIENT_RESPONSE_KEY[] PROGMEM = "client"; -#else -char constexpr CLIENT_REQUEST_KEYS[] = "clientKeys"; -char constexpr CLIENT_RESPONSE_KEY[] = "client"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// RPC data keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr RPC_METHOD_KEY[] PROGMEM = "method"; -char constexpr RPC_PARAMS_KEY[] PROGMEM = "params"; -char constexpr RPC_EMPTY_PARAMS_VALUE[] PROGMEM = "{}"; -#else -char constexpr RPC_METHOD_KEY[] = "method"; -char constexpr RPC_PARAMS_KEY[] = "params"; -char constexpr RPC_EMPTY_PARAMS_VALUE[] = "{}"; -#endif // THINGSBOARD_ENABLE_PROGMEM - // Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr UNABLE_TO_DE_SERIALIZE_JSON[] PROGMEM = "Unable to de-serialize received json data with error (DeserializationError::%s)"; -char constexpr INVALID_BUFFER_SIZE[] PROGMEM = "Buffer size (%u) to small for the given payloads size (%u), increase with setBufferSize accordingly or set THINGSBOARD_ENABLE_STREAM_UTILS to 1 before including ThingsBoard"; -char constexpr UNABLE_TO_ALLOCATE_BUFFER[] PROGMEM = "Allocating memory for the internal MQTT buffer failed"; -#if THINGSBOARD_ENABLE_OTA -char constexpr NUMBER_PRINTF[] PROGMEM = "%u"; -#endif // THINGSBOARD_ENABLE_OTA -#if !THINGSBOARD_ENABLE_DYNAMIC -char constexpr RPC_RESPONSE_OVERFLOWED[] PROGMEM = "Server-side RPC response overflowed, increase MaxRPC (%u)"; -char constexpr SERVER_SIDE_RPC_SUBSCRIPTIONS[] PROGMEM = "server-side RPC"; -char constexpr CLIENT_SIDE_RPC_SUBSCRIPTIONS[] PROGMEM = "client-side RPC"; -char constexpr SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS[] PROGMEM = "shared attribute update"; -char constexpr CLIENT_SHARED_ATTRIBUTE_SUBSCRIPTIONS[] PROGMEM = "client or shared attribute request"; -char constexpr MAX_SUBSCRIPTIONS_EXCEEDED[] PROGMEM = "Too many (%s) subscriptions, increase MaxSubscribtions or unsubscribe"; -#else -char constexpr RPC_RESPONSE_OVERFLOWED[] PROGMEM = "Server-side RPC response overflowed, increase responseSize (%u)"; -char constexpr COLON PROGMEM = ':'; -#endif // !THINGSBOARD_ENABLE_DYNAMIC -char constexpr COMMA[] PROGMEM = ","; -char constexpr NO_KEYS_TO_REQUEST[] PROGMEM = "No keys to request were given"; -char constexpr RPC_METHOD_NULL[] PROGMEM = "RPC methodName is NULL"; -char constexpr SUBSCRIBE_TOPIC_FAILED[] PROGMEM = "Subscribing the given topic (%s) failed"; -#if THINGSBOARD_ENABLE_DEBUG -char constexpr NO_RPC_PARAMS_PASSED[] PROGMEM = "No parameters passed with RPC, passing null JSON"; -char constexpr NOT_FOUND_ATT_UPDATE[] PROGMEM = "Shared attribute update key not found"; -char constexpr ATT_KEY_NOT_FOUND[] PROGMEM = "Attribute key not found"; -char constexpr ATT_CB_NO_KEYS[] PROGMEM = "No keys subscribed. Calling subscribed callback for any updated attributes, assumed to be subscribed to every possible key"; -char constexpr ATT_IS_NULL[] PROGMEM = "Subscribed shared attribute update key is NULL"; -char constexpr ATT_NO_CHANGE[] PROGMEM = "No keys that we subscribed too were changed, skipping callback"; -char constexpr CALLING_RPC_CB[] PROGMEM = "Calling subscribed callback for rpc with methodname (%s)"; -char constexpr CALLING_ATT_CB[] PROGMEM = "Calling subscribed callback for updated shared attribute (%s)"; -char constexpr CALLING_REQUEST_CB[] PROGMEM = "Calling subscribed callback for request with response id (%u)"; -char constexpr RECEIVE_MESSAGE[] PROGMEM = "Received data from server over topic (%s)"; -char constexpr SEND_MESSAGE[] PROGMEM = "Sending data to server over topic (%s) with data (%s)"; -char constexpr SEND_SERIALIZED[] PROGMEM = "Hidden, because json data is bigger than buffer, therefore showing in console is skipped"; -#endif // THINGSBOARD_ENABLE_DEBUG -#else char constexpr UNABLE_TO_DE_SERIALIZE_JSON[] = "Unable to de-serialize received json data with error (DeserializationError::%s)"; char constexpr INVALID_BUFFER_SIZE[] = "Buffer size (%u) to small for the given payloads size (%u), increase with setBufferSize accordingly or set THINGSBOARD_ENABLE_STREAM_UTILS to 1 before including ThingsBoard"; char constexpr UNABLE_TO_ALLOCATE_BUFFER[] = "Allocating memory for the internal MQTT buffer failed"; -#if THINGSBOARD_ENABLE_OTA -char constexpr NUMBER_PRINTF[] = "%u"; -#endif // THINGSBOARD_ENABLE_OTA -#if !THINGSBOARD_ENABLE_DYNAMIC -char constexpr RPC_RESPONSE_OVERFLOWED[] = "Server-side RPC response overflowed, increase MaxRPC (%u)"; -char constexpr SERVER_SIDE_RPC_SUBSCRIPTIONS[] = "server-side RPC"; -char constexpr CLIENT_SIDE_RPC_SUBSCRIPTIONS[] = "client-side RPC"; -char constexpr SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS[] = "shared attribute update"; -char constexpr CLIENT_SHARED_ATTRIBUTE_SUBSCRIPTIONS[] = "client or shared attribute request"; -char constexpr MAX_SUBSCRIPTIONS_EXCEEDED[] = "Too many (%s) subscriptions, increase MaxSubscribtions or unsubscribe"; -#else -char constexpr RPC_RESPONSE_OVERFLOWED[] = "Server-side RPC response overflowed, increase responseSize (%u)"; -char constexpr COLON = ':'; -#endif // !THINGSBOARD_ENABLE_DYNAMIC -char constexpr COMMA[] = ","; -char constexpr NO_KEYS_TO_REQUEST[] = "No keys to request were given"; -char constexpr RPC_METHOD_NULL[] = "RPC methodName is NULL"; -char constexpr SUBSCRIBE_TOPIC_FAILED[] = "Subscribing the given topic (%s) failed"; +char constexpr MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME[] = "MaxEndpointsAmount"; +#if THINGSBOARD_ENABLE_DYNAMIC +char constexpr MAXIMUM_RESPONSE_EXCEEDED[] = "Prevented allocation on the heap (%u) for JsonDocument. Discarding message that is bigger than maximum response size (%u)"; +char constexpr HEAP_ALLOCATION_FAILED[] = "Failed allocating required size (%u) for JsonDocument. Ensure there is enough heap memory left"; +#endif // THINGSBOARD_ENABLE_DYNAMIC #if THINGSBOARD_ENABLE_DEBUG -char constexpr NO_RPC_PARAMS_PASSED[] = "No parameters passed with RPC, passing null JSON"; -char constexpr NOT_FOUND_ATT_UPDATE[] = "Shared attribute update key not found"; -char constexpr ATT_KEY_NOT_FOUND[] = "Attribute key not found"; -char constexpr ATT_CB_NO_KEYS[] = "No keys subscribed. Calling subscribed callback for any updated attributes, assumed to be subscribed to every possible key"; -char constexpr ATT_IS_NULL[] = "Subscribed shared attribute update key is NULL"; -char constexpr ATT_NO_CHANGE[] = "No keys that we subscribed too were changed, skipping callback"; -char constexpr CALLING_RPC_CB[] = "Calling subscribed callback for rpc with methodname (%s)"; -char constexpr CALLING_ATT_CB[] = "Calling subscribed callback for updated shared attribute (%s)"; -char constexpr CALLING_REQUEST_CB[] = "Calling subscribed callback for request with response id (%u)"; -char constexpr RECEIVE_MESSAGE[] = "Received data from server over topic (%s)"; +char constexpr RECEIVE_MESSAGE[] = "Received (%u) bytes of data from server over topic (%s)"; +char constexpr ALLOCATING_JSON[] = "Allocated internal JsonDocument for MQTT server response with size (%u)"; char constexpr SEND_MESSAGE[] = "Sending data to server over topic (%s) with data (%s)"; char constexpr SEND_SERIALIZED[] = "Hidden, because json data is bigger than buffer, therefore showing in console is skipped"; #endif // THINGSBOARD_ENABLE_DEBUG -#endif // THINGSBOARD_ENABLE_PROGMEM - // Claim topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr CLAIM_TOPIC[] PROGMEM = "v1/devices/me/claim"; -#else char constexpr CLAIM_TOPIC[] = "v1/devices/me/claim"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Provision topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr PROV_REQUEST_TOPIC[] PROGMEM = "/provision/request"; -#else -char constexpr PROV_REQUEST_TOPIC[] = "/provision/request"; -#endif // THINGSBOARD_ENABLE_PROGMEM - // Claim data keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr SECRET_KEY[] PROGMEM = "secretKey"; -char constexpr DURATION_KEY[] PROGMEM = "durationMs"; -char constexpr DEVICE_NAME_KEY[] PROGMEM = "deviceName"; -char constexpr PROV_DEVICE_KEY[] PROGMEM = "provisionDeviceKey"; -char constexpr PROV_DEVICE_SECRET_KEY[] PROGMEM = "provisionDeviceSecret"; -char constexpr PROV_CRED_TYPE_KEY[] PROGMEM = "credentialsType"; -char constexpr PROV_TOKEN[] PROGMEM = "token"; -char constexpr PROV_CRED_USERNAME[] PROGMEM = "username"; -char constexpr PROV_CRED_PASSWORD[] PROGMEM = "password"; -char constexpr PROV_CRED_CLIENT_ID[] PROGMEM = "clientId"; -char constexpr PROV_CRED_HASH[] PROGMEM = "hash"; -#else char constexpr SECRET_KEY[] = "secretKey"; char constexpr DURATION_KEY[] = "durationMs"; -char constexpr DEVICE_NAME_KEY[] = "deviceName"; -char constexpr PROV_DEVICE_KEY[] = "provisionDeviceKey"; -char constexpr PROV_DEVICE_SECRET_KEY[] = "provisionDeviceSecret"; -char constexpr PROV_CRED_TYPE_KEY[] = "credentialsType"; -char constexpr PROV_TOKEN[] = "token"; -char constexpr PROV_CRED_USERNAME[] = "username"; -char constexpr PROV_CRED_PASSWORD[] = "password"; -char constexpr PROV_CRED_CLIENT_ID[] = "clientId"; -char constexpr PROV_CRED_HASH[] = "hash"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -#if THINGSBOARD_ENABLE_OTA -// Firmware topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v2/fw/response/#"; -char constexpr FIRMWARE_REQUEST_TOPIC[] PROGMEM = "v2/fw/request/0/chunk/%u"; -#else -char constexpr FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] = "v2/fw/response/#"; -char constexpr FIRMWARE_REQUEST_TOPIC[] = "v2/fw/request/0/chunk/%u"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Firmware data keys. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr CURR_FW_TITLE_KEY[] PROGMEM = "current_fw_title"; -char constexpr CURR_FW_VER_KEY[] PROGMEM = "current_fw_version"; -char constexpr FW_ERROR_KEY[] PROGMEM = "fw_error"; -char constexpr FW_STATE_KEY[] PROGMEM = "fw_state"; -char constexpr FW_VER_KEY[] PROGMEM = "fw_version"; -char constexpr FW_TITLE_KEY[] PROGMEM = "fw_title"; -char constexpr FW_CHKS_KEY[] PROGMEM = "fw_checksum"; -char constexpr FW_CHKS_ALGO_KEY[] PROGMEM = "fw_checksum_algorithm"; -char constexpr FW_SIZE_KEY[] PROGMEM = "fw_size"; -char constexpr CHECKSUM_AGORITM_MD5[] PROGMEM = "MD5"; -char constexpr CHECKSUM_AGORITM_SHA256[] PROGMEM = "SHA256"; -char constexpr CHECKSUM_AGORITM_SHA384[] PROGMEM = "SHA384"; -char constexpr CHECKSUM_AGORITM_SHA512[] PROGMEM = "SHA512"; -#else -char constexpr CURR_FW_TITLE_KEY[] = "current_fw_title"; -char constexpr CURR_FW_VER_KEY[] = "current_fw_version"; -char constexpr FW_ERROR_KEY[] = "fw_error"; -char constexpr FW_STATE_KEY[] = "fw_state"; -char constexpr FW_VER_KEY[] = "fw_version"; -char constexpr FW_TITLE_KEY[] = "fw_title"; -char constexpr FW_CHKS_KEY[] = "fw_checksum"; -char constexpr FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm"; -char constexpr FW_SIZE_KEY[] = "fw_size"; -char constexpr CHECKSUM_AGORITM_MD5[] = "MD5"; -char constexpr CHECKSUM_AGORITM_SHA256[] = "SHA256"; -char constexpr CHECKSUM_AGORITM_SHA384[] = "SHA384"; -char constexpr CHECKSUM_AGORITM_SHA512[] = "SHA512"; -#endif // THINGSBOARD_ENABLE_PROGMEM - -// Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr NO_FW[] PROGMEM = "No new firmware assigned on the given device"; -char constexpr EMPTY_FW[] PROGMEM = "Given firmware was NULL"; -char constexpr FW_UP_TO_DATE[] PROGMEM = "Firmware version (%s) already up to date"; -char constexpr FW_NOT_FOR_US[] PROGMEM = "Firmware title (%s) not same as received title (%s)"; -char constexpr FW_CHKS_ALGO_NOT_SUPPORTED[] PROGMEM = "Checksum algorithm (%s) is not supported"; -char constexpr NOT_ENOUGH_RAM[] PROGMEM = "Temporary allocating more internal client buffer failed, decrease OTA chunk size or decrease overall heap usage"; -char constexpr RESETTING_FAILED[] PROGMEM = "Preparing for OTA firmware updates failed, attributes might be NULL"; -#if THINGSBOARD_ENABLE_DEBUG -char constexpr PAGE_BREAK[] PROGMEM = "================================="; -char constexpr NEW_FW[] PROGMEM = "A new Firmware is available:"; -char constexpr FROM_TOO[] PROGMEM = "(%s) => (%s)"; -char constexpr DOWNLOADING_FW[] PROGMEM = "Attempting to download over MQTT..."; -#endif // THINGSBOARD_ENABLE_DEBUG -#else -char constexpr NO_FW[] = "No new firmware assigned on the given device"; -char constexpr EMPTY_FW[] = "Given firmware was NULL"; -char constexpr FW_UP_TO_DATE[] = "Firmware version (%s) already up to date"; -char constexpr FW_NOT_FOR_US[] = "Firmware title (%s) not same as received title (%s)"; -char constexpr FW_CHKS_ALGO_NOT_SUPPORTED[] = "Checksum algorithm (%s) is not supported"; -char constexpr NOT_ENOUGH_RAM[] = "Temporary allocating more internal client buffer failed, decrease OTA chunk size or decrease overall heap usage"; -char constexpr RESETTING_FAILED[] = "Preparing for OTA firmware updates failed, attributes might be NULL"; -#if THINGSBOARD_ENABLE_DEBUG -char constexpr PAGE_BREAK[] = "================================="; -char constexpr NEW_FW[] = "A new Firmware is available:"; -char constexpr FROM_TOO[] = "(%s) => (%s)"; -char constexpr DOWNLOADING_FW[] = "Attempting to download over MQTT..."; -#endif // THINGSBOARD_ENABLE_DEBUG -#endif // THINGSBOARD_ENABLE_PROGMEM -#endif // THINGSBOARD_ENABLE_OTA #if THINGSBOARD_ENABLE_DYNAMIC @@ -313,26 +56,22 @@ template /// Setting a fixed size, allows to allocate the variables in the container on the stack, which can also be set once as a template argument. /// Changing is only possible if a new instance of this class is created. If these values should be automatically deduced at runtime instead then, and then dynamically allocated on the heap, /// simply set THINGSBOARD_ENABLE_DYNAMIC to 1, before including ThingsBoard.h -/// @tparam MaxFieldsAmount Maximum amount of key value pair that we will be able to sent or received by ThingsBoard in one call, default = Default_Fields_Amount (8) -/// @tparam MaxSubscribtions Maximum amount of simultaneous shared attribute or server side rpc subscriptions and shared or client scope attribute or client side rpc requests. -/// Each aforementioned type has its own maximum and does not count to the max allowed simultaneous subscriptions or requests of any other. -/// Once the maximum amount has been reached it is not possible to increase the size, this is done because it allows to allcoate the memory on the stack instead of the heap, default = Default_Subscriptions_Amount (2) -/// @tparam MaxAttributes Maximum amount of attributes that will ever be requested with the Attribute_Request_Callback or the Shared_Attribute_Callback, allows to use an array on the stack in the background. -/// Be aware though the size set in this template and the size passed to the ThingsBoard MaxAttributes template need to be the same or the value in this class lower, if not some of the requested keys may be lost. -/// Furthermore, if OTA Updates are utilized the size should never be decreased to less than 5, because that amount of attributes needs to be requested or updated to start the OTA update. -/// Meaning if the number is decreased the OTA update will not work correctly anymore and will not be able to be started anymore, default = Default_Attributes_Amount (5) -/// @tparam MaxRPC Maximum amount of key-value pairs that will ever be sent in the subscribed callback method of an RPC_Callback, allows to use a StaticJsonDocument on the stack in the background. -/// If we simply use .to(); on the received document and use .set() to change the internal value then the size requirements are 0. -/// However if we attempt to send multiple key-value pairs, we have to adjust the size accordingly. See https://arduinojson.org/v6/assistant/ for more information on how to estimate the required size and divide the reulst by 16 to receive the required MaxRPC value, default = Default_RPC_Amount (0) +/// @tparam MaxResponse Maximum amount of key value pair that will ever be received by ThingsBoard in one call, default = Default_Response_Amount (8) +/// @tparam MaxEndpointsAmount Maximum amount of subscribed API endpoints, Default_Endpoints_Amount is used as the default value because it is big enough to hold one instance of every possible API Implementation, default = Default_Endpoints_Amount (7) /// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger -template +template #endif // THINGSBOARD_ENABLE_DYNAMIC class ThingsBoardSized { public: - /// @brief Constructs a ThingsBoardSized instance with the given network client that should be used to establish the connection to ThingsBoard + /// @brief Constructs a ThingsBoardSized instance with the given network client that should be used to establish the connection to ThingsBoard. + /// Directly forwards the last given arguments to the overloaded Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor, + /// meaning all combinatons of arguments that would initalize the Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) can be used to call this constructor. + /// The possibilites mainly consist out of the default constructor which creates an empty internal buffer with no data + /// or out of the range constructor where we can pass an interator the start of another data container + /// and to the end of the data container (last element + 1) and then every element between those iteratos will be copied, in the same order as in the original data container + /// @tparam ...Args Holds the multiple arguments that will simply be forwarded to the Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor and therefore allow to use every overloaded vector constructor without having to implement them /// @param client MQTT Client implementation that should be used to establish the connection to ThingsBoard - /// @param logger Logger implementation that should be used to print messages generated by internal processes - /// @param bufferSize Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded + /// @param buffer_size Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded /// and if we attempt to send data that is bigger, it will not be sent, the internal value can be changed later at any time with the setBufferSize() method /// alternatively setting THINGSBOARD_ENABLE_STREAM_UTILS to 1 allows to send arbitrary size payloads if that is done the internal buffer of the MQTT Client implementation /// can be theoretically set to only be as big as the biggest message we should every receive from ThingsBoard, @@ -343,40 +82,67 @@ class ThingsBoardSized { /// that size can vary but if all ThingsBoard features are used a buffer size of 256 bytes should suffice for receiving most responses. /// If the aforementioned feature is not enabled the buffer size might need to be much bigger though, /// but in that case if a message was too big to be sent the user will be informed with a message to the Logger. - /// The aforementioned options can only be enabled if Arduino is used to build this library, because the StreamUtils library requires it, default = Default_Payload (64) - /// @param maxStackSize Maximum amount of bytes we want to allocate on the stack, default = Default_Max_Stack_Size (1024) -#if !THINGSBOARD_ENABLE_STREAM_UTILS - ThingsBoardSized(IMQTT_Client& client, uint16_t const & bufferSize = Default_Payload, size_t const & maxStackSize = Default_Max_Stack_Size) + /// The aforementioned options can only be enabled if Arduino is used to build this library, because the StreamUtils library requires it, default = Default_Payload_Size (64) + /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack, default = Default_Max_Stack_Size (1024) + /// @param ...args Arguments that will be forwarded into the overloaded Array or Vector (THINGSBOARD_ENABLE_DYNAMIC) constructor + template +#if THINGSBOARD_ENABLE_DYNAMIC +#if THINGSBOARD_ENABLE_STREAM_UTILS + /// @param buffering_size Amount of bytes allocated to speed up serialization, default = Default_Buffering_Size (64) + /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload. + /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}. + /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem, + /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32, + /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc. + /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0) + ThingsBoardSized(IMQTT_Client & client, uint16_t buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & buffering_size = Default_Buffering_Size, size_t const & max_response_size = Default_Max_Response_Size, Args const &... args) +#else + /// @param max_response_size Amount of bytes allocated to speed up serialization, default = Default_Max_Response_Size (0) + /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload. + /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}. + /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem, + /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32, + /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc. + /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0) + ThingsBoardSized(IMQTT_Client & client, uint16_t buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & max_response_size = Default_Max_Response_Size, Args const &... args) +#endif // THINGSBOARD_ENABLE_STREAM_UTILS +#else +#if THINGSBOARD_ENABLE_STREAM_UTILS + /// @param buffering_size Amount of bytes allocated to speed up serialization, default = Default_Buffering_Size (64) + ThingsBoardSized(IMQTT_Client & client, uint16_t buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, size_t const & buffering_size = Default_Buffering_Size, Args const &... args) #else - /// @param bufferingSize Amount of bytes allocated to speed up serialization, default = Default_Buffering_Size (64) - ThingsBoardSized(IMQTT_Client& client, uint16_t const & bufferSize = Default_Payload, size_t const & maxStackSize = Default_Max_Stack_Size, size_t const & bufferingSize = Default_Buffering_Size) + ThingsBoardSized(IMQTT_Client & client, uint16_t buffer_size = Default_Payload_Size, size_t const & max_stack_size = Default_Max_Stack_Size, Args const &... args) #endif // THINGSBOARD_ENABLE_STREAM_UTILS +#endif // THINGSBOARD_ENABLE_DYNAMIC : m_client(client) - , m_max_stack(maxStackSize) + , m_max_stack(max_stack_size) #if THINGSBOARD_ENABLE_STREAM_UTILS - , m_buffering_size(bufferingSize) + , m_buffering_size(buffering_size) #endif // THINGSBOARD_ENABLE_STREAM_UTILS - , m_rpc_callbacks() - , m_rpc_request_callbacks() - , m_shared_attribute_update_callbacks() - , m_attribute_request_callbacks() - , m_provision_callback() - , m_request_id(0U) -#if THINGSBOARD_ENABLE_OTA - , m_fw_callback() - , m_previous_buffer_size(0U) - , m_change_buffer_size(false) - , m_ota(std::bind(&ThingsBoardSized::Publish_Chunk_Request, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Firmware_Send_State, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::Firmware_OTA_Unsubscribe, this)) -#endif // THINGSBOARD_ENABLE_OTA +#if THINGSBOARD_ENABLE_DYNAMIC + , m_max_response_size(max_response_size) +#endif // THINGSBOARD_ENABLE_DYNAMIC + , m_api_implementations(args...) { - (void)setBufferSize(bufferSize); - // Initalize callback. + for (auto & api : m_api_implementations) { + if (api == nullptr) { + continue; + } +#if THINGSBOARD_ENABLE_STL + api->Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getRequestID, this)); +#else + api->Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID); +#endif // THINGSBOARD_ENABLE_STL + api->Initialize(); + } + (void)setBufferSize(buffer_size); + // Initialize callback. #if THINGSBOARD_ENABLE_STL m_client.set_data_callback(std::bind(&ThingsBoardSized::onMQTTMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); m_client.set_connect_callback(std::bind(&ThingsBoardSized::Resubscribe_Topics, this)); #else m_client.set_data_callback(ThingsBoardSized::onStaticMQTTMessage); - m_client.set_connect_callback(ThingsBoardSized::onStaticMQTTConnect); + m_client.set_connect_callback(ThingsBoardSized::staticMQTTConnect); m_subscribedInstance = this; #endif // THINGSBOARD_ENABLE_STL } @@ -391,22 +157,35 @@ class ThingsBoardSized { } /// @brief Sets the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead - /// @param maxStackSize Maximum amount of bytes we want to allocate on the stack - void setMaximumStackSize(size_t const & maxStackSize) { - m_max_stack = maxStackSize; + /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack + void setMaximumStackSize(size_t const & max_stack_size) { + m_max_stack = max_stack_size; } #if THINGSBOARD_ENABLE_STREAM_UTILS /// @brief Sets the amount of bytes that can be allocated to speed up fall back serialization with the StreamUtils class /// See https://github.com/bblanchon/ArduinoStreamUtils for more information on the underlying class used - /// @param bufferingSize Amount of bytes allocated to speed up serialization - void setBufferingSize(size_t const & bufferingSize) { - m_buffering_size = bufferingSize; + /// @param buffering_size Amount of bytes allocated to speed up serialization + void setBufferingSize(size_t const & buffering_size) { + m_buffering_size = buffering_size; } #endif // THINGSBOARD_ENABLE_STREAM_UTILS +#if THINGSBOARD_ENABLE_DYNAMIC + /// @brief Sets the maximum amount of bytes allocated for internal JsonDocument holding received payload from server responses by attribute requests, shared attribute updates, server-side or client-side rpc + /// @param max_response_size Maximum amount of bytes allocated for the interal JsonDocument structure that holds the received payload. + /// Size is calculated automatically from certain characters in the received payload (',', '{', '[') but if we receive a malicious payload that contains these symbols in a string {"example":",,,,,,..."}. + /// It is possible to cause huge allocations, nut because the memory only lives for as long as the subscribed callback methods it should not be a problem, + /// especially because attempting to allocate too much memory, will cause the allocation to fail, which is checked. But if the failure of that heap allocation is subscribed for example with the heap_caps_register_failed_alloc_callback method on the ESP32, + /// then that subscribed callback will be called and could theoretically restart the device. To circumvent that we can simply set the size of this variable to a value that should never be exceeded by a non malicious json payload, received by attribute requests, shared attribute updates, server-side or client-side rpc. + /// If this safety feature is not required, because the heap allocation failure callback is not subscribed, then the value of the variable can simply be kept as 0, which means we will not check the received payload for its size before the allocation happens, default = Default_Max_Response_Size (0) + void setMaxResponseSize(size_t const & max_response_size) { + m_max_response_size = max_response_size; + } +#endif // THINGSBOARD_ENABLE_DYNAMIC + /// @brief Sets the size of the buffer for the underlying network client that will be used to establish the connection to ThingsBoard - /// @param bufferSize Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded + /// @param buffer_size Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded /// and if we attempt to send data that is bigger, it will not be sent, the internal value can be changed later at any time with the setBufferSize() method /// alternatively setting THINGSBOARD_ENABLE_STREAM_UTILS to 1 allows to send arbitrary size payloads if that is done the internal buffer of the MQTT Client implementation /// can be theoretically set to only be as big as the biggest message we should every receive from ThingsBoard, @@ -418,9 +197,9 @@ class ThingsBoardSized { /// If the aforementioned feature is not enabled the buffer size might need to be much bigger though, /// but in that case if a message was too big to be sent the user will be informed with a message to the logger implementation. /// The aforementioned options can only be enabled if Arduino is used to build this library, because the StreamUtils library requires it - /// @return Whether allocating the needed memory for the given bufferSize was successful or not - bool setBufferSize(uint16_t const & bufferSize) { - bool const result = m_client.set_buffer_size(bufferSize); + /// @return Whether allocating the needed memory for the given buffer size was successful or not + bool setBufferSize(uint16_t buffer_size) { + bool const result = m_client.set_buffer_size(buffer_size); if (!result) { Logger::println(UNABLE_TO_ALLOCATE_BUFFER); } @@ -434,24 +213,13 @@ class ThingsBoardSized { /// because connect() method now reconencts to all previously subscribed MQTT topics instead, /// therefore there is no need anymore to discard all previously subscribed callbacks and letting the user resubscribe void Cleanup_Subscriptions() { - // Results are ignored, because the important part of clearing internal data structures always succeeds. - // Cleanup all server-side RPC subscriptions - (void)RPC_Unsubscribe(); - // Cleanup all client-side RPC requests - (void)RPC_Request_Unsubscribe(); - // Cleanup all shared attributes subscriptions - (void)Shared_Attributes_Unsubscribe(); - // Cleanup all client-side or shared attributes requests - (void)Attributes_Request_Unsubscribe(); - // Cleanup all provision requests - (void)Provision_Unsubscribe(); -#if THINGSBOARD_ENABLE_OTA - // Stop any ongoing Firmware update, - // which will in turn cleanup the internal member variables of the OTAHandler class - // as well as all firmware subscriptions - // and inform the user of the failed firmware update - Stop_Firmware_Update(); -#endif // THINGSBOARD_ENABLE_OTA + // Results are ignored, because the important part of clearing internal data structures always succeeds + for (auto & api : m_api_implementations) { + if (api == nullptr) { + continue; + } + (void)api->Unsubscribe(); + } } /// @brief Connects to the specified ThingsBoard server over the given port as the given device. @@ -465,12 +233,12 @@ class ThingsBoardSized { /// so it possible to discern which device is communicating, default = Value of passed access token /// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard, default = nullptr /// @return Whether connecting to ThingsBoard was successful or not - bool connect(char const * const host, char const * const access_token = PROV_ACCESS_TOKEN, uint16_t const & port = DEFAULT_MQTT_PORT, char const * const client_id = nullptr, char const * const password = nullptr) { + bool connect(char const * const host, char const * const access_token = PROV_ACCESS_TOKEN, uint16_t port = DEFAULT_MQTT_PORT, char const * const client_id = nullptr, char const * const password = nullptr) { if (host == nullptr) { return false; } m_client.set_server(host, port); - return connect_to_host(access_token, (client_id == nullptr) ? access_token : client_id, password); + return connectToHost(access_token, (client_id == nullptr) ? access_token : client_id, password); } /// @brief Disconnects any connection that has been established already @@ -485,51 +253,58 @@ class ThingsBoardSized { return m_client.connected(); } - /// @brief Receives / sends any outstanding messages from and to the MQTT broker + /// @brief Receives / sends any outstanding messages from and to the MQTT broker. + /// Additionally when not being able to use the ESP Timer, it updates the internal timeout timers /// @return Whether sending or receiving the oustanding the messages was successful or not bool loop() { +#if !THINGSBOARD_USE_ESP_TIMER + for (auto & api : m_api_implementations) { + if (api == nullptr) { + continue; + } + api->loop(); + } +#endif // !THINGSBOARD_USE_ESP_TIMER return m_client.loop(); } /// @brief Attempts to send key value pairs from custom source over the given topic to the server - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server /// @param topic Topic we want to send the data over - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool Send_Json(char const * const topic, TSource const & source, size_t const & jsonSize) { - // Check if allocating needed memory failed when trying to create the JsonObject, + bool Send_Json(char const * const topic, JsonDocument const & source, size_t const & json_size) { + // Check if allocating needed memory failed when trying to create the JsonDocument, // if it did the isNull() method will return true. See https://arduinojson.org/v6/api/jsonvariant/isnull/ for more information if (source.isNull()) { Logger::println(UNABLE_TO_ALLOCATE_JSON); return false; } -#if !THINGSBOARD_ENABLE_DYNAMIC - size_t const amount = source.size(); - if (MaxFieldsAmount < amount) { - Logger::printfln(TOO_MANY_JSON_FIELDS, amount, MaxFieldsAmount); + // Check if inserting any of the internal values failed because the JsonDocument was too small, + // if it did the overflowed() method will return true. See https://arduinojson.org/v6/api/jsondocument/overflowed/ for more information + if (source.overflowed()) { + Logger::println(JSON_SIZE_TO_SMALL); return false; } -#endif // !THINGSBOARD_ENABLE_DYNAMIC bool result = false; #if THINGSBOARD_ENABLE_STREAM_UTILS // Check if the size of the given message would be too big for the actual client, // if it is utilize the serialize json work around, so that the internal client buffer can be circumvented - if (m_client.get_buffer_size() < jsonSize) { + if (m_client.get_buffer_size() < json_size) { #if THINGSBOARD_ENABLE_DEBUG Logger::printfln(SEND_MESSAGE, topic, SEND_SERIALIZED); #endif // THINGSBOARD_ENABLE_DEBUG - result = Serialize_Json(topic, source, jsonSize - 1); + result = Serialize_Json(topic, source, json_size - 1); } // Check if the remaining stack size of the current task would overflow the stack, // if it would allocate the memory on the heap instead to ensure no stack overflow occurs else #endif // THINGSBOARD_ENABLE_STREAM_UTILS - if (getMaximumStackSize() < jsonSize) { - char* json = new char[jsonSize](); - if (serializeJson(source, json, jsonSize) < jsonSize - 1) { + if (json_size > getMaximumStackSize()) { + char* json = new char[json_size](); + if (serializeJson(source, json, json_size) < json_size - 1) { Logger::println(UNABLE_TO_SERIALIZE_JSON); } else { @@ -541,8 +316,8 @@ class ThingsBoardSized { json = nullptr; } else { - char json[jsonSize] = {}; - if (serializeJson(source, json, jsonSize) < jsonSize - 1) { + char json[json_size] = {}; + if (serializeJson(source, json, json_size) < json_size - 1) { Logger::println(UNABLE_TO_SERIALIZE_JSON); return result; } @@ -561,18 +336,69 @@ class ThingsBoardSized { return false; } - uint16_t const & currentBufferSize = m_client.get_buffer_size(); - size_t const jsonSize = strlen(json); + uint16_t currentBufferSize = m_client.get_buffer_size(); + size_t const json_size = strlen(json); - if (currentBufferSize < jsonSize) { - Logger::printfln(INVALID_BUFFER_SIZE, currentBufferSize, jsonSize); + if (currentBufferSize < json_size) { + Logger::printfln(INVALID_BUFFER_SIZE, currentBufferSize, json_size); return false; } #if THINGSBOARD_ENABLE_DEBUG Logger::printfln(SEND_MESSAGE, topic, json); #endif // THINGSBOARD_ENABLE_DEBUG - return m_client.publish(topic, reinterpret_cast(json), jsonSize); + return m_client.publish(topic, reinterpret_cast(json), json_size); + } + + /// @brief Copies a non-owning pointer to the given API implementation, into the local data container. + /// Ensure the actual variable is kept alive for as long as the instance of this class + /// @param api Additional API that we want to be handled + void Subscribe_API_Implementation(IAPI_Implementation & api) { +#if !THINGSBOARD_ENABLE_DYNAMIC + if (m_api_implementations.size() + 1 > m_api_implementations.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME, MaxEndpointsAmount); + return; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC +#if THINGSBOARD_ENABLE_STL + api.Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getRequestID, this)); +#else + api.Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID); +#endif // THINGSBOARD_ENABLE_STL + api.Initialize(); + m_api_implementations.push_back(&api); + } + + /// @brief Copies the non-owning pointers to the given API implementations, into the local data container. + /// Expects iterators to a container containing API implementations instances. + /// Ensure the actual memory of the API implementations inside the data container are kept alive for as long as the instance of this class + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element in the data container + /// @param last Iterator pointing to the end of the data container (last element + 1) + template + void Subscribe_API_Implementations(InputIterator const & first, InputIterator const & last) { +#if !THINGSBOARD_ENABLE_DYNAMIC + size_t const size = Helper::distance(first, last); + if (m_api_implementations.size() + size > m_api_implementations.capacity()) { + Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, MAX_ENDPOINTS_AMOUNT_TEMPLATE_NAME, MaxEndpointsAmount); + return; + } +#endif // !THINGSBOARD_ENABLE_DYNAMIC + for (auto it = first; it != last; ++it) { + auto & api = *it; + if (api == nullptr) { + continue; + } +#if THINGSBOARD_ENABLE_STL + api->Set_Client_Callbacks(std::bind(&ThingsBoardSized::Subscribe_API_Implementation, this, std::placeholders::_1), std::bind(&ThingsBoardSized::Send_Json, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&ThingsBoardSized::Send_Json_String, this, std::placeholders::_1, std::placeholders::_2), std::bind(&ThingsBoardSized::clientSubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::clientUnsubscribe, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getClientBufferSize, this), std::bind(&ThingsBoardSized::setBufferSize, this, std::placeholders::_1), std::bind(&ThingsBoardSized::getRequestID, this)); +#else + api->Set_Client_Callbacks(ThingsBoardSized::staticSubscribeImplementation, ThingsBoardSized::staticSendJson, ThingsBoardSized::staticSendJsonString, ThingsBoardSized::staticClientSubscribe, ThingsBoardSized::staticClientUnsubscribe, ThingsBoardSized::staticGetClientBufferSize, ThingsBoardSized::staticSetBufferSize, ThingsBoardSized::staticGetRequestID); +#endif // THINGSBOARD_ENABLE_STL + api->Initialize(); + } + m_api_implementations.insert(m_api_implementations.end(), first, last); } //---------------------------------------------------------------------------- @@ -582,82 +408,18 @@ class ThingsBoardSized { /// as long as they enter the given device name and secret key in the given amount of time. /// Optionally a secret key can be passed or be left empty (cloud will allow any user to claim the device for the given amount of time). /// See https://thingsboard.io/docs/user-guide/claiming-devices/ for more information - /// @param secretKey Password the user additionaly to the device name needs to enter to claim it as their own, + /// @param secret_key Password the user additionaly to the device name needs to enter to claim it as their own, /// pass nullptr or an empty string if the user should be able to claim the device without any password - /// @param durationMs Total time in milliseconds the user has to claim their device as their own + /// @param duration_ms Total time in milliseconds the user has to claim their device as their own /// @return Whether sending the claiming request was successful or not - bool Claim_Request(char const * const secretKey, size_t const & durationMs) { - StaticJsonDocument requestBuffer; - - if (!Helper::stringIsNullorEmpty(secretKey)) { - requestBuffer[SECRET_KEY] = secretKey; - } - requestBuffer[DURATION_KEY] = durationMs; - - size_t const objectSize = Helper::Measure_Json(requestBuffer); - return Send_Json(CLAIM_TOPIC, requestBuffer, objectSize); - } - - //---------------------------------------------------------------------------- - // Provisioning API - - /// @brief Sends provisioning request for a new device, meaning we want to create a device that we can then connect over, - /// where the given provision device key / secret decide which device profile is used to create the given device with. - /// Optionally a device name can be passed or be left empty (cloud will use a random string as the name instead). - /// The cloud then sends back json data containing our credentials, that will call the given callback, if creating the device was successful. - /// The data contained in that callbackcan then be used to disconnect and reconnect to the ThingsBoard server as our newly created device. - /// See https://thingsboard.io/docs/user-guide/device-provisioning/ for more information - /// @param callback Callback method that will be called upon data arrival with the given data that was received serialized into a JsonDocument - /// @return Whether sending the provisioning request was successful or not - bool Provision_Request(Provision_Callback const & callback) { - char const * provisionDeviceKey = callback.Get_Device_Key(); - char const * provisionDeviceSecret = callback.Get_Device_Secret(); - - if (Helper::stringIsNullorEmpty(provisionDeviceKey) || Helper::stringIsNullorEmpty(provisionDeviceSecret)) { - return false; - } - else if (!Provision_Subscribe(callback)) { - return false; - } + bool Claim_Request(char const * const secret_key, size_t const & duration_ms) { + StaticJsonDocument request_buffer; - StaticJsonDocument requestBuffer; - char const * deviceName = callback.Get_Device_Name(); - char const * accessToken = callback.Get_Device_Access_Token(); - char const * credUsername = callback.Get_Credentials_Username(); - char const * credPassword = callback.Get_Credentials_Password(); - char const * credClientID = callback.Get_Credentials_Client_ID(); - char const * hash = callback.Get_Certificate_Hash(); - char const * credentialsType = callback.Get_Credentials_Type(); - - // Deciding which underlying provisioning method is restricted, by the Provision_Callback class. - // Meaning only the key-value pairs that are needed for the given provisioning method are set, - // resulting in the rest not being sent and therefore the provisioning request having the correct formatting - if (!Helper::stringIsNullorEmpty(deviceName)) { - requestBuffer[DEVICE_NAME_KEY] = deviceName; - } - if (!Helper::stringIsNullorEmpty(accessToken)) { - requestBuffer[PROV_TOKEN] = accessToken; - } - if (!Helper::stringIsNullorEmpty(credUsername)) { - requestBuffer[PROV_CRED_USERNAME] = credUsername; - } - if (!Helper::stringIsNullorEmpty(credPassword)) { - requestBuffer[PROV_CRED_PASSWORD] = credPassword; - } - if (!Helper::stringIsNullorEmpty(credClientID)) { - requestBuffer[PROV_CRED_CLIENT_ID] = credClientID; + if (!Helper::stringIsNullorEmpty(secret_key)) { + request_buffer[SECRET_KEY] = secret_key; } - if (!Helper::stringIsNullorEmpty(hash)) { - requestBuffer[PROV_CRED_HASH] = hash; - } - if (!Helper::stringIsNullorEmpty(credentialsType)) { - requestBuffer[PROV_CRED_TYPE_KEY] = credentialsType; - } - requestBuffer[PROV_DEVICE_KEY] = provisionDeviceKey; - requestBuffer[PROV_DEVICE_SECRET_KEY] = provisionDeviceSecret; - - size_t const objectSize = Helper::Measure_Json(requestBuffer); - return Send_Json(PROV_REQUEST_TOPIC, requestBuffer, objectSize); + request_buffer[DURATION_KEY] = duration_ms; + return Send_Json(CLAIM_TOPIC, request_buffer, Helper::Measure_Json(request_buffer)); } //---------------------------------------------------------------------------- @@ -682,28 +444,37 @@ class ThingsBoardSized { /// @param first Iterator pointing to the first element in the data container /// @param last Iterator pointing to the end of the data container (last element + 1) /// @return Whether sending the aggregated telemetry data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC bool sendTelemetry(InputIterator const & first, InputIterator const & last) { +#if THINGSBOARD_ENABLE_DYNAMIC return sendDataArray(first, last, true); +#else + return sendDataArray(first, last, true); +#endif // THINGSBOARD_ENABLE_DYNAMIC } /// @brief Attempts to send custom json telemetry string. /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information /// @param json String containing our json key value pairs we want to attempt to send /// @return Whether sending the data was successful or not - bool sendTelemetryJson(char const * const json) { + bool sendTelemtryString(char const * const json) { return Send_Json_String(TELEMETRY_TOPIC, json); } /// @brief Attempts to send telemetry key value pairs from custom source to the server. /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool sendTelemetryJson(TSource const & source, size_t const & jsonSize) { - return Send_Json(TELEMETRY_TOPIC, source, jsonSize); + bool sendTelemetryJson(JsonDocument const & source, size_t const & json_size) { + return Send_Json(TELEMETRY_TOPIC, source, json_size); } //---------------------------------------------------------------------------- @@ -728,1173 +499,368 @@ class ThingsBoardSized { /// @param first Iterator pointing to the first element in the data container /// @param last Iterator pointing to the end of the data container (last element + 1) /// @return Whether sending the aggregated attribute data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC bool sendAttributes(InputIterator const & first, InputIterator const & last) { +#if THINGSBOARD_ENABLE_DYNAMIC return sendDataArray(first, last, false); +#else + return sendDataArray(first, last, false); +#endif // THINGSBOARD_ENABLE_DYNAMIC } /// @brief Attempts to send custom json attribute string. /// See https://thingsboard.io/docs/user-guide/attributes/ for more information /// @param json String containing our json key value pairs we want to attempt to send /// @return Whether sending the data was successful or not - bool sendAttributeJson(char const * const json) { + bool sendAttributeString(char const * const json) { return Send_Json_String(ATTRIBUTE_TOPIC, json); } /// @brief Attempts to send attribute key value pairs from custom source to the server. /// See https://thingsboard.io/docs/user-guide/attributes/ for more information - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool sendAttributeJson(TSource const & source, size_t const & jsonSize) { - return Send_Json(ATTRIBUTE_TOPIC, source, jsonSize); - } - - /// @brief Requests one client-side attribute calllback, - /// that will be called if the key-value pair from the server for the given client-side attributes is received. - /// See https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server for more information - /// @param callback Callback method that will be called - /// @return Whether requesting the given callback was successful or not -#if THINGSBOARD_ENABLE_DYNAMIC - bool Client_Attributes_Request(Attribute_Request_Callback const & callback) { -#else - bool Client_Attributes_Request(Attribute_Request_Callback const & callback) { -#endif // THINGSBOARD_ENABLE_DYNAMIC - return Attributes_Request(callback, CLIENT_REQUEST_KEYS, CLIENT_RESPONSE_KEY); + bool sendAttributeJson(JsonDocument const & source, size_t const & json_size) { + return Send_Json(ATTRIBUTE_TOPIC, source, json_size); } - //---------------------------------------------------------------------------- - // Server-side RPC API - - /// @brief Subscribes multiple server-side RPC callbacks, - /// that will be called if a request from the server for the method with the given name is received. - /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information - /// @tparam InputIterator Class that points to the begin and end iterator - /// of the given data container, allows for using / passing either std::vector or std::array. - /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator - /// @param first Iterator pointing to the first element in the data container - /// @param last Iterator pointing to the end of the data container (last element + 1) - /// @return Whether subscribing the given callbacks was successful or not - template - bool RPC_Subscribe(InputIterator const & first, InputIterator const & last) { -#if !THINGSBOARD_ENABLE_DYNAMIC - size_t const size = Helper::distance(first, last); - if (m_rpc_callbacks.size() + size > m_rpc_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, SERVER_SIDE_RPC_SUBSCRIPTIONS); + private: +#if THINGSBOARD_ENABLE_STREAM_UTILS + /// @brief Serialize the custom attribute source into the underlying client. + /// Sends the given bytes to the client without requiring any temporary buffer at the cost of hugely increased send times + /// @param topic Topic we want to send the data over + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source + /// @return Whether sending the data was successful or not + bool Serialize_Json(char const * const topic, JsonDocument const & source, size_t const & json_size) { + if (!m_client.begin_publish(topic, json_size)) { + Logger::println(UNABLE_TO_SERIALIZE_JSON); return false; } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_SUBSCRIBE_TOPIC); + BufferingPrint buffered_print(m_client, getBufferingSize()); + size_t const bytes_serialized = serializeJson(source, buffered_print); + if (bytes_serialized < json_size) { + Logger::println(UNABLE_TO_SERIALIZE_JSON); return false; } - - // Push back complete vector into our local m_rpc_callbacks vector. - m_rpc_callbacks.insert(m_rpc_callbacks.end(), first, last); - return true; + buffered_print.flush(); + return m_client.end_publish(); } +#endif // THINGSBOARD_ENABLE_STREAM_UTILS - /// @brief Subscribe one server-side RPC callback, - /// that will be called if a request from the server for the method with the given name is received. - /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information - /// @param callback Callback method that will be called - /// @return Whether subscribing the given callback was successful or not - bool RPC_Subscribe(RPC_Callback const & callback) { -#if !THINGSBOARD_ENABLE_DYNAMIC - if (m_rpc_callbacks.size() + 1 > m_rpc_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, SERVER_SIDE_RPC_SUBSCRIPTIONS); - return false; - } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_SUBSCRIBE_TOPIC); - return false; - } - - // Push back given callback into our local vector - m_rpc_callbacks.push_back(callback); - return true; + /// @brief Returns the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead + /// @return Maximum amount of bytes we want to allocate on the stack + size_t const & getMaximumStackSize() const { + return m_max_stack; } - /// @brief Unsubcribes all server-side RPC callbacks. - /// See https://thingsboard.io/docs/user-guide/rpc/#server-side-rpc for more information - /// @return Whether unsubcribing all the previously subscribed callbacks - /// and from the rpc topic, was successful or not - bool RPC_Unsubscribe() { - m_rpc_callbacks.clear(); - return m_client.unsubscribe(RPC_SUBSCRIBE_TOPIC); + /// @brief Returns the current buffer size of the underlying client interface + /// @return Current internal buffer size + uint16_t getClientBufferSize() { + return m_client.get_buffer_size(); } - //---------------------------------------------------------------------------- - // Client-side RPC API - - /// @brief Requests one client-side RPC callback, - /// that will be called if a response from the server for the method with the given name is received. - /// See https://thingsboard.io/docs/user-guide/rpc/#client-side-rpc for more information - /// @param callback Callback method that will be called - /// @return Whether requesting the given callback was successful or not - bool RPC_Request(RPC_Request_Callback const & callback) { - char const * methodName = callback.Get_Name(); - - if (Helper::stringIsNullorEmpty(methodName)) { - Logger::println(RPC_METHOD_NULL); - return false; - } - RPC_Request_Callback * registeredCallback = nullptr; - if (!RPC_Request_Subscribe(callback, registeredCallback)) { - return false; - } - else if (registeredCallback == nullptr) { - return false; - } - - JsonArray const * const parameters = callback.Get_Parameters(); - -#if THINGSBOARD_ENABLE_DYNAMIC - // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, - // Data structure size depends on the amount of key value pairs passed + the default methodName and params key needed for the request. - // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - TBJsonDocument requestBuffer(JSON_OBJECT_SIZE(parameters != nullptr ? parameters->size() + 2U : 2U)); -#else - // Ensure to have enough size for the infinite amount of possible parameters that could be sent to the cloud, - // therefore we set the size to the MaxFieldsAmount instead of JSON_OBJECT_SIZE(1), which will result in a JsonDocument with a size of 16 bytes - StaticJsonDocument requestBuffer; -#endif // THINGSBOARD_ENABLE_DYNAMIC - - requestBuffer[RPC_METHOD_KEY] = methodName; - - if (parameters != nullptr && !parameters->isNull()) { - requestBuffer[RPC_PARAMS_KEY] = *parameters; - } - else { - requestBuffer[RPC_PARAMS_KEY] = RPC_EMPTY_PARAMS_VALUE; - } - - m_request_id++; - registeredCallback->Set_Request_ID(m_request_id); - - char topic[Helper::detectSize(RPC_SEND_REQUEST_TOPIC, m_request_id)] = {}; - (void)snprintf(topic, sizeof(topic), RPC_SEND_REQUEST_TOPIC, m_request_id); - - size_t const objectSize = Helper::Measure_Json(requestBuffer); - return Send_Json(topic, requestBuffer, objectSize); + /// @brief Subscribes the given topic with the underlying client interface + /// @param topic Topic that should be subscribed + /// @return Whether subscribing was successfull or not + bool clientSubscribe(char const * const topic) { + return m_client.subscribe(topic); } - //---------------------------------------------------------------------------- - // Firmware OTA API - -#if THINGSBOARD_ENABLE_OTA - - /// @brief Checks if firmware settings are assigned to the connected device and if they are attempts to use those settings to start a firmware update. - /// Will only be checked once and if there is no firmware assigned or if the assigned firmware is already installed this method will not update. - /// This firmware status is only checked once, meaning to recheck the status either call this method again or use the Subscribe_Firmware_Update method. - /// to be automatically informed and start the update if firmware has been assigned and it is not already installed. - /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information - /// @param callback Callback method that will be called - /// @return Whether subscribing the given callback was successful or not - bool Start_Firmware_Update(OTA_Update_Callback const & callback) { - if (!Prepare_Firmware_Settings(callback)) { - Logger::println(RESETTING_FAILED); - return false; - } - - // Request the firmware information -#if THINGSBOARD_ENABLE_DYNAMIC - constexpr std::array fw_shared_keys{FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Attribute_Request_Callback fw_request_callback(std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), fw_shared_keys.cbegin(), fw_shared_keys.cend()); -#else - constexpr std::array fw_shared_keys{FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Attribute_Request_Callback fw_request_callback(std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), fw_shared_keys.cbegin(), fw_shared_keys.cend()); -#endif //THINGSBOARD_ENABLE_DYNAMIC - return Shared_Attributes_Request(fw_request_callback); + /// @brief Unsubscribes the given topic with the underlying client interface + /// @param topic Topic that should be unsubscribed + /// @return Whether unsubscribing was successfull or not + bool clientUnsubscribe(char const * const topic) { + return m_client.unsubscribe(topic); } - /// @brief Stops the currently ongoing firmware update, calls the subscribed user finish callback with a failure if any update was stopped. - /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information - void Stop_Firmware_Update() { - m_ota.Stop_Firmware_Update(); + /// @brief Gets a mutable pointer to the request id, the current value is the id of the last sent request. + /// Is used because each request to the cloud of the same type (attribute request, rpc request, over the air firmware update), has to use a different id to differentiate request and response. + /// To ensure that we therefore simply provide a global request id that can be used and incremented by all request types + /// @return Mutable reference to the request id + size_t * getRequestID() { + return &m_request_id; } - /// @brief Subscribes to any changes of the assigned firmware information on the connected device, - /// meaning once we subscribed if we register any changes we will start the update if the given firmware is not already installed. - /// Unlike Start_Firmware_Update this method only registers changes to the firmware information, - /// meaning if the change occured while this device was asleep or turned off we will not update, - /// to achieve that, it is instead recommended to call the Start_Firmware_Update method when the device has started once to check for that edge case. - /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information - /// @param callback Callback method that will be called - /// @return Whether subscribing the given callback was successful or not - bool Subscribe_Firmware_Update(OTA_Update_Callback const & callback) { - if (!Prepare_Firmware_Settings(callback)) { - Logger::println(RESETTING_FAILED); - return false; - } - - // Subscribes to changes of the firmware information -#if THINGSBOARD_ENABLE_DYNAMIC - constexpr std::array fw_shared_keys{FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Shared_Attribute_Callback fw_update_callback(std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), fw_shared_keys.cbegin(), fw_shared_keys.cend()); -#else - constexpr std::array fw_shared_keys{FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}; - const Shared_Attribute_Callback fw_update_callback(std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1), fw_shared_keys.cbegin(), fw_shared_keys.cend()); -#endif //THINGSBOARD_ENABLE_DYNAMIC - return Shared_Attributes_Subscribe(fw_update_callback); +#if THINGSBOARD_ENABLE_STREAM_UTILS + /// @brief Returns the amount of bytes that can be allocated to speed up fall back serialization with the StreamUtils class + /// See https://github.com/bblanchon/ArduinoStreamUtils for more information on the underlying class used + /// @return Amount of bytes allocated to speed up serialization + size_t const & getBufferingSize() const { + return m_buffering_size; } +#endif // THINGSBOARD_ENABLE_STREAM_UTILS - /// @brief Sends the given firmware title and firmware version to the cloud. - /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information - /// @param currFwTitle Current device firmware title - /// @param currFwVersion Current device firmware version - /// @return Whether sending the current device firmware information was successful or not - bool Firmware_Send_Info(char const * const currFwTitle, char const * const currFwVersion) { - StaticJsonDocument currentFirmwareInfo; - currentFirmwareInfo[CURR_FW_TITLE_KEY] = currFwTitle; - currentFirmwareInfo[CURR_FW_VER_KEY] = currFwVersion; - return sendTelemetryJson(currentFirmwareInfo, Helper::Measure_Json(currentFirmwareInfo)); + /// @brief Connects to the previously set ThingsBoard server, as the given client with the given access token + /// @param access_token Access token that connects this device with a created device on the ThingsBoard server, + /// can be "provision", if the device creates itself instead + /// @param client_id Client username that can be used to differentiate the user that is connecting the given device to ThingsBoard + /// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard + /// @return Whether connecting to ThingsBoard was successful or not + bool connectToHost(char const * const access_token, char const * const client_id, char const * const password) { + bool const connection_result = m_client.connect(client_id, access_token, password); + if (!connection_result) { + Logger::println(CONNECT_FAILED); + } + return connection_result; } - /// @brief Sends the given firmware state to the cloud. - /// See https://thingsboard.io/docs/user-guide/ota-updates/ for more information - /// @param currFwState Current firmware download state - /// @param fwError Firmware error message that describes the current firmware state, - /// pass nullptr or an empty string if the current state is not a failure state - /// and therefore does not require any firmware error messsages, default = nullptr - /// @return Whether sending the current firmware download state was successful or not - bool Firmware_Send_State(char const * const currFwState, char const * const fwError = nullptr) { - StaticJsonDocument currentFirmwareState; - if (!Helper::stringIsNullorEmpty(fwError)) { - currentFirmwareState[FW_ERROR_KEY] = fwError; + /// @brief Resubscribes to topics that establish a permanent connection with MQTT, meaning they may receive more than one event over their lifetime, + /// whereas other events that are only ever called once and then deleted after they have been handled are not resubscribed. + /// Only the topics that establish a permanent connection are resubscribed, because all not yet received data is discard on the MQTT broker, + // once we establish a connection again. This is the case because we connect with the cleanSession attribute set to true. + // Therefore we can also clear the buffer of all non-permanent topics. + void Resubscribe_Topics() { + // Results are ignored, because the important part of clearing internal data structures always succeeds + for (auto & api : m_api_implementations) { + if (api == nullptr) { + continue; + } + (void)api->Resubscribe_Topic(); } - currentFirmwareState[FW_STATE_KEY] = currFwState; - return sendTelemetryJson(currentFirmwareState, Helper::Measure_Json(currentFirmwareState)); } -#endif // THINGSBOARD_ENABLE_OTA - - //---------------------------------------------------------------------------- - // Shared attributes API + /// @brief Attempts to send a single key-value pair with the given key and value of the given type + /// @tparam T Type of the passed value + /// @param key Key of the key value pair we want to send + /// @param value Value of the key value pair we want to send + /// @param telemetry Whether the data we want to send should be sent as an attribute or telemetry data value + /// @return Whether sending the data was successful or not + template + bool sendKeyValue(char const * const key, T const & value, bool telemetry = true) { + const Telemetry t(key, value); + if (t.IsEmpty()) { + return false; + } - /// @brief Requests one shared attribute calllback, - /// that will be called if the key-value pair from the server for the given shared attributes is received. - /// See https://thingsboard.io/docs/reference/mqtt-api/#request-attribute-values-from-the-server for more information - /// @param callback Callback method that will be called - /// @return Whether requesting the given callback was successful or not -#if THINGSBOARD_ENABLE_DYNAMIC - bool Shared_Attributes_Request(Attribute_Request_Callback const & callback) { -#else - bool Shared_Attributes_Request(Attribute_Request_Callback const & callback) { -#endif // THINGSBOARD_ENABLE_DYNAMIC - return Attributes_Request(callback, SHARED_REQUEST_KEY, SHARED_RESPONSE_KEY); + StaticJsonDocument json_buffer; + if (!t.SerializeKeyValue(json_buffer)) { + Logger::println(UNABLE_TO_SERIALIZE); + return false; + } + return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer)); } - /// @brief Subscribes multiple shared attribute callbacks, - /// that will be called if the key-value pair from the server for the given shared attributes is received. - /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information + /// @brief Attempts to send aggregated attribute or telemetry data /// @tparam InputIterator Class that points to the begin and end iterator /// of the given data container, allows for using / passing either std::vector or std::array. /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator /// @param first Iterator pointing to the first element in the data container /// @param last Iterator pointing to the end of the data container (last element + 1) - /// @return Whether subscribing the given callbacks was successful or not + /// @param telemetry Whether the data we want to send should be sent over the attribute or telemtry topic + /// @return Whether sending the aggregated data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template - bool Shared_Attributes_Subscribe(InputIterator const & first, InputIterator const & last) { -#if !THINGSBOARD_ENABLE_DYNAMIC +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC + bool sendDataArray(InputIterator const & first, InputIterator const & last, bool telemetry) { size_t const size = Helper::distance(first, last); - if (m_shared_attribute_update_callbacks.size() + size > m_shared_attribute_update_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS); - return false; - } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(ATTRIBUTE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_TOPIC); +#if THINGSBOARD_ENABLE_DYNAMIC + // String are char const * and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, + // Data structure size depends on the amount of key value pairs passed. + // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument + TBJsonDocument json_buffer(JSON_OBJECT_SIZE(size)); +#else + if (size > MaxKeyValuePairAmount) { + Logger::printfln(TOO_MANY_JSON_FIELDS, size, "MaxKeyValuePairAmount", MaxKeyValuePairAmount); return false; } + StaticJsonDocument json_buffer; +#endif // THINGSBOARD_ENABLE_DYNAMIC - // Push back complete vector into our local m_shared_attribute_update_callbacks vector. - m_shared_attribute_update_callbacks.insert(m_shared_attribute_update_callbacks.end(), first, last); - return true; - } - - /// @brief Subscribe one shared attribute callback, - /// that will be called if the key-value pair from the server for the given shared attributes is received. - /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information - /// @param callback Callback method that will be called - /// @return Whether subscribing the given callback was successful or not -#if THINGSBOARD_ENABLE_DYNAMIC - bool Shared_Attributes_Subscribe(Shared_Attribute_Callback const & callback) { -#else - bool Shared_Attributes_Subscribe(Shared_Attribute_Callback const & callback) { -#endif // THINGSBOARD_ENABLE_DYNAMIC -#if !THINGSBOARD_ENABLE_DYNAMIC - if (m_shared_attribute_update_callbacks.size() + 1U > m_shared_attribute_update_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, SHARED_ATTRIBUTE_UPDATE_SUBSCRIPTIONS); - return false; - } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(ATTRIBUTE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_TOPIC); - return false; - } - - // Push back given callback into our local vector - m_shared_attribute_update_callbacks.push_back(callback); - return true; - } - - /// @brief Unsubcribes all shared attribute callbacks. - /// See https://thingsboard.io/docs/reference/mqtt-api/#subscribe-to-attribute-updates-from-the-server for more information - /// @return Whether unsubcribing all the previously subscribed callbacks - /// and from the attribute topic, was successful or not - bool Shared_Attributes_Unsubscribe() { - m_shared_attribute_update_callbacks.clear(); - return m_client.unsubscribe(ATTRIBUTE_TOPIC); - } - - private: - IMQTT_Client& m_client; // MQTT client instance. - size_t m_max_stack; // Maximum stack size we allocate at once. -#if THINGSBOARD_ENABLE_STREAM_UTILS - size_t m_buffering_size; // Buffering size used to serialize directly into client. -#endif // THINGSBOARD_ENABLE_STREAM_UTILS - - // Vectors or array (depends on wheter if THINGSBOARD_ENABLE_DYNAMIC is set to 1 or 0), hold copy of the actual passed data, this is to ensure they stay valid, - // even if the user only temporarily created the object before the method was called. - // This can be done because all Callback methods mostly consists of pointers to actual object so copying them - // does not require a huge memory overhead and is acceptable especially in comparsion to possible problems that could - // arise if references were used and the end user does not take care to ensure the Callbacks live on for the entirety - // of its usage, which will lead to dangling references and undefined behaviour. - // Therefore copy-by-value has been choosen as for this specific use case it is more advantageous, - // especially because at most we copy internal vectors or array, that will only ever contain a few pointers -#if THINGSBOARD_ENABLE_DYNAMIC - Vector m_rpc_callbacks; // Server side RPC callbacks vector - Vector m_rpc_request_callbacks; // Client side RPC callbacks vector - Vector m_shared_attribute_update_callbacks; // Shared attribute update callbacks vector - Vector m_attribute_request_callbacks; // Client-side or shared attribute request callback vector -#else - Array m_rpc_callbacks; // Server side RPC callbacks array - Array m_rpc_request_callbacks; // Client side RPC callbacks array - Array, MaxSubscribtions> m_shared_attribute_update_callbacks; // Shared attribute update callbacks array - Array, MaxSubscribtions> m_attribute_request_callbacks; // Client-side or shared attribute request callback array -#endif // THINGSBOARD_ENABLE_DYNAMIC - - Provision_Callback m_provision_callback; // Provision response callback - size_t m_request_id; // Allows nearly 4.3 million requests before wrapping back to 0 - -#if THINGSBOARD_ENABLE_OTA - OTA_Update_Callback m_fw_callback; // Ota update response callback - uint16_t m_previous_buffer_size; // Previous buffer size of the underlying client, used to revert to the previously configured buffer size if it was temporarily increased by the OTA update - bool m_change_buffer_size; // Whether the buffer size had to be changed, because the previous internal buffer size was to small to hold the firmware chunks - OTA_Handler m_ota; // Class instance that handles the flashing and creating a hash from the given received binary firmware data -#endif // THINGSBOARD_ENABLE_OTA - -#if THINGSBOARD_ENABLE_STREAM_UTILS - /// @brief Serialize the custom attribute source into the underlying client. - /// Sends the given bytes to the client without requiring any temporary buffer at the cost of hugely increased send times - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server - /// @param topic Topic we want to send the data over - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source - /// @return Whether sending the data was successful or not - template - bool Serialize_Json(char const * const topic, TSource const & source, size_t const & jsonSize) { - if (!m_client.begin_publish(topic, jsonSize)) { - Logger::println(UNABLE_TO_SERIALIZE_JSON); - return false; - } - BufferingPrint buffered_print(m_client, getBufferingSize()); - size_t const bytes_serialized = serializeJson(source, buffered_print); - if (bytes_serialized < jsonSize) { - Logger::println(UNABLE_TO_SERIALIZE_JSON); - return false; + for (auto it = first; it != last; ++it) { + auto const & data = *it; + if (!data.SerializeKeyValue(json_buffer)) { + Logger::println(UNABLE_TO_SERIALIZE); + return false; + } } - buffered_print.flush(); - return m_client.end_publish(); - } -#endif // THINGSBOARD_ENABLE_STREAM_UTILS - -#if THINGSBOARD_ENABLE_OTA - /// @brief Publishes a request via MQTT to request the given firmware chunk - /// @param request_chunck Chunk index that should be requested from the server - /// @return Whether publishing the message was successful or not - bool Publish_Chunk_Request(size_t const & request_chunck) { - // Calculate the number of chuncks we need to request, - // in order to download the complete firmware binary - uint16_t const & chunk_size = m_fw_callback.Get_Chunk_Size(); - - // Convert the interger size into a readable string - char size[Helper::detectSize(NUMBER_PRINTF, chunk_size)] = {}; - (void)snprintf(size, sizeof(size), NUMBER_PRINTF, chunk_size); - size_t const jsonSize = strlen(size); - - // Size adjuts dynamically to the current length of the currChunk number to ensure we don't cut it out of the topic string. - char topic[Helper::detectSize(FIRMWARE_REQUEST_TOPIC, request_chunck)] = {}; - (void)snprintf(topic, sizeof(topic), FIRMWARE_REQUEST_TOPIC, request_chunck); - - return m_client.publish(topic, reinterpret_cast(size), jsonSize); - } -#endif // THINGSBOARD_ENABLE_OTA - - /// @brief Returns the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead - /// @return Maximum amount of bytes we want to allocate on the stack - size_t const & getMaximumStackSize() const { - return m_max_stack; + return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer)); } -#if THINGSBOARD_ENABLE_STREAM_UTILS - /// @brief Returns the amount of bytes that can be allocated to speed up fall back serialization with the StreamUtils class - /// See https://github.com/bblanchon/ArduinoStreamUtils for more information on the underlying class used - /// @return Amount of bytes allocated to speed up serialization - size_t const & getBufferingSize() const { - return m_buffering_size; - } -#endif // THINGSBOARD_ENABLE_STREAM_UTILS - - /// @brief Requests one client-side or shared attribute calllback, - /// that will be called if the key-value pair from the server for the given client-side or shared attributes is received - /// @param callback Callback method that will be called - /// @param attributeRequestKey Key of the key-value pair that will contain the attributes we want to request - /// @param attributeResponseKey Key of the key-value pair that will contain the attributes we got as a response - /// @return Whether requesting the given callback was successful or not -#if THINGSBOARD_ENABLE_DYNAMIC - bool Attributes_Request(Attribute_Request_Callback const & callback, char const * const attributeRequestKey, char const * const attributeResponseKey) { -#else - bool Attributes_Request(Attribute_Request_Callback const & callback, char const * const attributeRequestKey, char const * const attributeResponseKey) { -#endif // THINGSBOARD_ENABLE_DYNAMIC - auto const & attributes = callback.Get_Attributes(); - - // Check if any sharedKeys were requested - if (attributes.empty()) { - Logger::println(NO_KEYS_TO_REQUEST); - return false; - } - else if (attributeRequestKey == nullptr || attributeResponseKey == nullptr) { + /// @brief MQTT callback that will be called if a publish message is received from the server + /// Payload contains data from the internal buffer of the MQTT client, + /// therefore the buffer and the specific memory region the payload points too and the following length bytes need to live on for as long as this method has not finished. + /// This could be a problem if the system uses FreeRTOS or another tasking system and the processing of the data is interrupted. + /// Because if this happens and we then send data it is possible for the system to overwrite the memory region that contained the previous response. + /// Therefore we simply assume that either the used MQTT client, has seperate input and output buffers + /// or that the receiving of data is not executed on a seperate FreeRTOS tasks to other sends + /// @param topic Previously subscribed topic, we got the response over + /// @param payload Payload that was sent over the cloud and received over the given topic + /// @param length Total length of the received payload + void onMQTTMessage(char * topic, uint8_t * payload, unsigned int length) { #if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_KEY_NOT_FOUND); + Logger::printfln(RECEIVE_MESSAGE, length, topic); #endif // THINGSBOARD_ENABLE_DEBUG - return false; - } -#if THINGSBOARD_ENABLE_DYNAMIC - Attribute_Request_Callback * registeredCallback = nullptr; -#else - Attribute_Request_Callback * registeredCallback = nullptr; -#endif // THINGSBOARD_ENABLE_DYNAMIC - if (!Attributes_Request_Subscribe(callback, registeredCallback)) { - return false; - } - else if (registeredCallback == nullptr) { - return false; - } - - // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, - // Data structure size depends on the amount of key value pairs passed + the default clientKeys or sharedKeys - // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - StaticJsonDocument requestBuffer; - - // Calculate the size required for the char buffer containing all the attributes seperated by a comma, - // before initalizing it so it is possible to allocate it on the stack - size_t size = 0U; - for (const auto & att : attributes) { - if (Helper::stringIsNullorEmpty(att)) { - continue; - } - - size += strlen(att); - size += strlen(COMMA); - } - - // Initalizes complete array to 0, required because strncat needs both destination and source to contain proper null terminated strings - char request[size] = {}; - for (const auto & att : attributes) { - if (Helper::stringIsNullorEmpty(att)) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_IS_NULL); -#endif // THINGSBOARD_ENABLE_DEBUG + for (auto & api : m_api_implementations) { + if (api == nullptr || api->Get_Process_Type() != API_Process_Type::RAW || api->Get_Response_Topic_String() == nullptr || strncmp(api->Get_Response_Topic_String(), topic, strlen(api->Get_Response_Topic_String())) != 0) { continue; } - - strncat(request, att, size); - size -= strlen(att); - strncat(request, COMMA, size); - size -= strlen(COMMA); - } - - // Ensure to cast to const, this is done so that ArduinoJson does not copy the value but instead simply store the pointer, which does not require any more memory, - // besides the base size needed to allocate one key-value pair. Because if we don't the char array would be copied - // and because there is not enough space the value would simply be "undefined" instead. Which would cause the request to not be sent correctly - requestBuffer[attributeRequestKey] = static_cast(request); - - m_request_id++; - registeredCallback->Set_Request_ID(m_request_id); - registeredCallback->Set_Attribute_Key(attributeResponseKey); - - char topic[Helper::detectSize(ATTRIBUTE_REQUEST_TOPIC, m_request_id)] = {}; - (void)snprintf(topic, sizeof(topic), ATTRIBUTE_REQUEST_TOPIC, m_request_id); - - size_t const objectSize = Helper::Measure_Json(requestBuffer); - return Send_Json(topic, requestBuffer, objectSize); - } - - /// @brief Subscribes one provision callback, - /// that will be called if a provision response from the server is received - /// @param callback Callback method that will be called - /// @return Whether requesting the given callback was successful or not - bool Provision_Subscribe(Provision_Callback const & callback) { - if (!m_client.subscribe(PROV_RESPONSE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, PROV_RESPONSE_TOPIC); - return false; - } - m_provision_callback = callback; - return true; - } - - /// @brief Unsubcribes the provision callback - /// @return Whether unsubcribing the previously subscribed callback - /// and from the provision response topic, was successful or not - bool Provision_Unsubscribe() { - m_provision_callback = Provision_Callback(); - return m_client.unsubscribe(PROV_RESPONSE_TOPIC); - } - -#if THINGSBOARD_ENABLE_OTA - /// @brief Checks the included information in the callback, - /// and attempts to sends the current device firmware information to the cloud - /// @param callback Callback method that will be called - /// @return Whether checking and sending the current device firmware information was successful or not - bool Prepare_Firmware_Settings(OTA_Update_Callback const & callback) { - char const * const currFwTitle = callback.Get_Firmware_Title(); - char const * const currFwVersion = callback.Get_Firmware_Version(); - - if (Helper::stringIsNullorEmpty(currFwTitle) || Helper::stringIsNullorEmpty(currFwVersion)) { - return false; - } - else if (!Firmware_Send_Info(currFwTitle, currFwVersion)) { - return false; - } - - m_fw_callback = callback; - return true; - } - - /// @brief Subscribes to the firmware response topic - /// @return Whether subscribing to the firmware response topic was successful or not - bool Firmware_OTA_Subscribe() { - if (!m_client.subscribe(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC)) { - char message[JSON_STRING_SIZE(strlen(SUBSCRIBE_TOPIC_FAILED)) + JSON_STRING_SIZE(strlen(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC))] = {}; - (void)snprintf(message, sizeof(message), SUBSCRIBE_TOPIC_FAILED, FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC); - Logger::println(message); - Firmware_Send_State(FW_STATE_FAILED, message); - return false; - } - return true; - } - - /// @brief Unsubscribes from the firmware response topic and clears any memory associated with the firmware update, - /// should not be called before actually fully completing the firmware update. - /// @return Whether unsubscribing from the firmware response topic was successful or not - bool Firmware_OTA_Unsubscribe() { - // Buffer size has been set to another value before the update, - // to allow to receive ota chunck packets that might be much bigger than the normal - // buffer size would allow, therefore we return to the previous value to decrease overall memory usage - if (m_change_buffer_size) { - (void)setBufferSize(m_previous_buffer_size); - } - // Reset now not needed private member variables - m_fw_callback = OTA_Update_Callback(); - // Unsubscribe from the topic - return m_client.unsubscribe(FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC); - } - - /// @brief Callback that will be called upon firmware shared attribute arrival - /// @param data Json data containing key-value pairs for the needed firmware information, - /// to ensure we have a firmware assigned and can start the update over MQTT - void Firmware_Shared_Attribute_Received(JsonObjectConst const & data) { - // Check if firmware is available for our device - if (!data.containsKey(FW_VER_KEY) || !data.containsKey(FW_TITLE_KEY) || !data.containsKey(FW_CHKS_KEY) || !data.containsKey(FW_CHKS_ALGO_KEY) || !data.containsKey(FW_SIZE_KEY)) { - Logger::println(NO_FW); - Firmware_Send_State(FW_STATE_FAILED, NO_FW); + api->Process_Response(topic, payload, length); return; } - char const * const fw_title = data[FW_TITLE_KEY]; - char const * const fw_version = data[FW_VER_KEY]; - char const * const fw_checksum = data[FW_CHKS_KEY]; - char const * const fw_algorithm = data[FW_CHKS_ALGO_KEY]; - size_t const fw_size = data[FW_SIZE_KEY]; - - char const * const curr_fw_title = m_fw_callback.Get_Firmware_Title(); - char const * const curr_fw_version = m_fw_callback.Get_Firmware_Version(); - - if (fw_title == nullptr || fw_version == nullptr || curr_fw_title == nullptr || curr_fw_version == nullptr || fw_algorithm == nullptr || fw_checksum == nullptr) { - Logger::println(EMPTY_FW); - Firmware_Send_State(FW_STATE_FAILED, EMPTY_FW); - return; - } - // If firmware version and title is the same, we do not initiate an update, because we expect the type of binary to be the same one we are currently using and therefore updating would be useless - else if (strncmp(curr_fw_title, fw_title, strlen(curr_fw_title)) == 0 && strncmp(curr_fw_version, fw_version, strlen(curr_fw_version)) == 0) { - char message[JSON_STRING_SIZE(strlen(FW_UP_TO_DATE)) + JSON_STRING_SIZE(strlen(curr_fw_version))] = {}; - (void)snprintf(message, sizeof(message), FW_UP_TO_DATE, curr_fw_version); - Logger::println(message); - Firmware_Send_State(FW_STATE_FAILED, message); - return; - } - // If firmware title is not the same, we do not initiate an update, because we expect the binary to be for another type of device and downloading it on this device could possibly cause hardware issues - else if (strncmp(curr_fw_title, fw_title, strlen(curr_fw_title)) != 0) { - char message[JSON_STRING_SIZE(strlen(FW_NOT_FOR_US)) + JSON_STRING_SIZE(strlen(curr_fw_title)) + JSON_STRING_SIZE(strlen(fw_title))] = {}; - (void)snprintf(message, sizeof(message), FW_NOT_FOR_US, curr_fw_title, fw_title); - Logger::println(message); - Firmware_Send_State(FW_STATE_FAILED, message); + // Calculate size with the total amount of commas, always denotes the end of a key-value pair besides for the last element in an array or in an object where the comma is not permitted, + // therfore we have to add the space for another key-value pair for all the occurences of thoose symbols as well + size_t const size = Helper::getOccurences(payload, ',', length) + Helper::getOccurences(payload, '{', length) + Helper::getOccurences(payload, '[', length); +#if THINGSBOARD_ENABLE_DYNAMIC + // Buffer that we deserialize is writeable and not read only --> zero copy, meaning the size for the data is 0 bytes,1 + // Data structure size depends on the amount of key value pairs received. + // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument + size_t const document_size = JSON_OBJECT_SIZE(size); + if (m_max_response_size != 0U && document_size > m_max_response_size) { + Logger::printfln(MAXIMUM_RESPONSE_EXCEEDED, document_size, m_max_response_size); return; } - - mbedtls_md_type_t fw_checksum_algorithm = mbedtls_md_type_t{}; - - if (strncmp(CHECKSUM_AGORITM_MD5, fw_algorithm, strlen(CHECKSUM_AGORITM_MD5)) == 0) { - fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_MD5; - } - else if (strncmp(CHECKSUM_AGORITM_SHA256, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA256)) == 0) { - fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA256; - } - else if (strncmp(CHECKSUM_AGORITM_SHA384, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA384)) == 0) { - fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA384; - } - else if (strncmp(CHECKSUM_AGORITM_SHA512, fw_algorithm, strlen(CHECKSUM_AGORITM_SHA512)) == 0) { - fw_checksum_algorithm = mbedtls_md_type_t::MBEDTLS_MD_SHA512; - } - else { - char message[JSON_STRING_SIZE(strlen(FW_CHKS_ALGO_NOT_SUPPORTED)) + JSON_STRING_SIZE(strlen(fw_algorithm))] = {}; - (void)snprintf(message, sizeof(message), FW_CHKS_ALGO_NOT_SUPPORTED, fw_algorithm); - Logger::println(message); - Firmware_Send_State(FW_STATE_FAILED, message); + TBJsonDocument json_buffer(document_size); + // Because we calcualte the allocation dynamically fromt he payload, which is user input, it could theoretically be malicious ({ "malicious" : "{{{{{{{{{..."}) and contain a lot of the symbols used to calculate the size. + // But if that is the case adn the allocation still succeeds we delete the allocated memory relatively fast again so it shouldn't be a problem and if the allocation fails we simply return at this point with an appropriate error message + if (json_buffer.capacity() != document_size) { + Logger::printfln(HEAP_ALLOCATION_FAILED, document_size); return; } - - if (!Firmware_OTA_Subscribe()) { +#else + if (size > MaxResponse) { + Logger::printfln(TOO_MANY_JSON_FIELDS, size, "MaxResponse", MaxResponse); return; } - + size_t const document_size = JSON_OBJECT_SIZE(MaxResponse); + StaticJsonDocument json_buffer; +#endif // THINGSBOARD_ENABLE_DYNAMIC #if THINGSBOARD_ENABLE_DEBUG - Logger::println(PAGE_BREAK); - Logger::println(NEW_FW); - char firmware[JSON_STRING_SIZE(strlen(FROM_TOO)) + JSON_STRING_SIZE(strlen(curr_fw_version)) + JSON_STRING_SIZE(strlen(fw_version))] = {}; - (void)snprintf(firmware, sizeof(firmware), FROM_TOO, curr_fw_version, fw_version); - Logger::println(firmware); - Logger::println(DOWNLOADING_FW); + Logger::printfln(ALLOCATING_JSON, document_size); #endif // THINGSBOARD_ENABLE_DEBUG - // Calculate the number of chuncks we need to request, - // in order to download the complete firmware binary - const uint16_t& chunk_size = m_fw_callback.Get_Chunk_Size(); - - // Get the previous buffer size and cache it so the previous settings can be restored. - m_previous_buffer_size = m_client.get_buffer_size(); - m_change_buffer_size = m_previous_buffer_size < (chunk_size + 50U); - - // Increase size of receive buffer - if (m_change_buffer_size && !setBufferSize(chunk_size + 50U)) { - Logger::println(NOT_ENOUGH_RAM); - Firmware_Send_State(FW_STATE_FAILED, NOT_ENOUGH_RAM); + // The deserializeJson method we use, can use the zero copy mode because a writeable input was passed, + // if that were not the case the needed allocated memory would drastically increase, because the keys would need to be copied as well. + // See https://arduinojson.org/v6/doc/deserialization/ for more info on ArduinoJson deserialization + DeserializationError const error = deserializeJson(json_buffer, payload, length); + if (error) { + Logger::printfln(UNABLE_TO_DE_SERIALIZE_JSON, error.c_str()); return; } - m_ota.Start_Firmware_Update(m_fw_callback, fw_size, fw_checksum, fw_checksum_algorithm); - } -#endif // THINGSBOARD_ENABLE_OTA - - /// @brief Connects to the previously set ThingsBoard server, as the given client with the given access token - /// @param access_token Access token that connects this device with a created device on the ThingsBoard server, - /// can be "provision", if the device creates itself instead - /// @param client_id Client username that can be used to differentiate the user that is connecting the given device to ThingsBoard - /// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard - /// @return Whether connecting to ThingsBoard was successful or not - bool connect_to_host(char const * const access_token, char const * const client_id, char const * const password) { - bool const connection_result = m_client.connect(client_id, access_token, password); - if (!connection_result) { - Logger::println(CONNECT_FAILED); + for (auto & api : m_api_implementations) { + if (api == nullptr || api->Get_Process_Type() != API_Process_Type::JSON || api->Get_Response_Topic_String() == nullptr || strncmp(api->Get_Response_Topic_String(), topic, strlen(api->Get_Response_Topic_String())) != 0) { + continue; + } + api->Process_Json_Response(topic, json_buffer); } - return connection_result; } - /// @brief Resubscribes to topics that establish a permanent connection with MQTT, meaning they may receive more than one event over their lifetime, - /// whereas other events that are only ever called once and then deleted after they have been handled are not resubscribed. - /// Only the topics that establish a permanent connection are resubscribed, because all not yet received data is discard on the MQTT broker, - // once we establish a connection again. This is the case because we connect with the cleanSession attribute set to true. - // Therefore we can also clear the buffer of all non-permanent topics. - void Resubscribe_Topics() { - if (!m_rpc_callbacks.empty() && !m_client.subscribe(RPC_SUBSCRIBE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_SUBSCRIBE_TOPIC); - } - if (!m_shared_attribute_update_callbacks.empty() && !m_client.subscribe(ATTRIBUTE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_TOPIC); +#if !THINGSBOARD_ENABLE_STL + static void onStaticMQTTMessage(char * topic, uint8_t * payload, unsigned int length) { + if (m_subscribedInstance == nullptr) { + return; } - // Clean up any not yet answered single event subscriptions - (void)RPC_Request_Unsubscribe(); - (void)Attributes_Request_Unsubscribe(); - (void)Provision_Unsubscribe(); + m_subscribedInstance->onMQTTMessage(topic, payload, length); } - /// @brief Subscribes to the client-side RPC response topic - /// @param callback Callback method that will be called - /// @param registeredCallback Editable pointer to a reference of the local version that was copied from the passed callback - /// @return Whether requesting the given callback was successful or not - bool RPC_Request_Subscribe(RPC_Request_Callback const & callback, RPC_Request_Callback * & registeredCallback) { -#if !THINGSBOARD_ENABLE_DYNAMIC - if (m_rpc_request_callbacks.size() + 1 > m_rpc_request_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, CLIENT_SIDE_RPC_SUBSCRIPTIONS); - return false; - } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(RPC_RESPONSE_SUBSCRIBE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, RPC_RESPONSE_SUBSCRIBE_TOPIC); - return false; + static void staticMQTTConnect() { + if (m_subscribedInstance == nullptr) { + return; } - - // Push back given callback into our local vector - m_rpc_request_callbacks.push_back(callback); - registeredCallback = &m_rpc_request_callbacks.back(); - return true; - } - - /// @brief Unsubscribes all client-side RPC request callbacks - /// @return Whether unsubcribing the previously subscribed callbacks - /// and from the client-side RPC response topic, was successful or not - bool RPC_Request_Unsubscribe() { - m_rpc_request_callbacks.clear(); - return m_client.unsubscribe(RPC_RESPONSE_SUBSCRIBE_TOPIC); + m_subscribedInstance->Resubscribe_Topics(); } - /// @brief Subscribes to attribute response topic - /// @param callback Callback method that will be called - /// @param registeredCallback Editable pointer to a reference of the local version that was copied from the passed callback - /// @return Whether requesting the given callback was successful or not -#if THINGSBOARD_ENABLE_DYNAMIC - bool Attributes_Request_Subscribe(Attribute_Request_Callback const & callback, Attribute_Request_Callback * & registeredCallback) { -#else - bool Attributes_Request_Subscribe(Attribute_Request_Callback const & callback, Attribute_Request_Callback * & registeredCallback) { -#endif // THINGSBOARD_ENABLE_DYNAMIC -#if !THINGSBOARD_ENABLE_DYNAMIC - if (m_attribute_request_callbacks.size() + 1 > m_attribute_request_callbacks.capacity()) { - Logger::printfln(MAX_SUBSCRIPTIONS_EXCEEDED, CLIENT_SHARED_ATTRIBUTE_SUBSCRIPTIONS); - return false; - } -#endif // !THINGSBOARD_ENABLE_DYNAMIC - if (!m_client.subscribe(ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC)) { - Logger::printfln(SUBSCRIBE_TOPIC_FAILED, ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC); - return false; + static void staticSubscribeImplementation(IAPI_Implementation & api) { + if (m_subscribedInstance == nullptr) { + return; } - - // Push back given callback into our local vector - m_attribute_request_callbacks.push_back(callback); - registeredCallback = &m_attribute_request_callbacks.back(); - return true; - } - - /// @brief Unsubscribes all client-side or shared attributes request callbacks - /// @return Whether unsubcribing the previously subscribed callbacks - /// and from the attribute response topic, was successful or not - bool Attributes_Request_Unsubscribe() { - m_attribute_request_callbacks.clear(); - return m_client.unsubscribe(ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC); + m_subscribedInstance->Subscribe_API_Implementation(api); } - /// @brief Attempts to send a single key-value pair with the given key and value of the given type - /// @tparam T Type of the passed value - /// @param key Key of the key value pair we want to send - /// @param value Value of the key value pair we want to send - /// @param telemetry Whether the data we want to send should be sent as an attribute or telemetry data value - /// @return Whether sending the data was successful or not - template - bool sendKeyValue(char const * const key, T const & value, bool telemetry = true) { - const Telemetry t(key, value); - if (t.IsEmpty()) { - return false; - } - - StaticJsonDocument jsonBuffer; - if (!t.SerializeKeyValue(jsonBuffer)) { - Logger::println(UNABLE_TO_SERIALIZE); + static bool staticSendJson(char const * const topic, JsonDocument const & source, size_t const & json_size) { + if (m_subscribedInstance == nullptr) { return false; } - - return telemetry ? sendTelemetryJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)) : sendAttributeJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)); + return m_subscribedInstance->Send_Json(topic, source, json_size); } - /// @brief Process callback that will be called upon client-side RPC response arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callbacks - /// @param topic Previously subscribed topic, we got the response over - /// @param data Payload sent by the server over our given topic, that contains our key value pairs - void process_rpc_request_message(char * const topic, JsonObjectConst const & data) { - size_t const request_id = Helper::parseRequestId(RPC_RESPONSE_TOPIC, topic); - - for (auto it = m_rpc_request_callbacks.cbegin(); it != m_rpc_request_callbacks.cend(); ++it) { - auto const & rpc_request = *it; - - if (rpc_request.Get_Request_ID() != request_id) { - continue; - } - -#if THINGSBOARD_ENABLE_DEBUG - Logger::printfln(CALLING_REQUEST_CB, request_id); -#endif // THINGSBOARD_ENABLE_DEBUG - - rpc_request.template Call_Callback(data); - - // Delete callback because the changes have been requested and the callback is no longer needed - Helper::remove(m_rpc_request_callbacks, it); - break; - } - - // Attempt to unsubscribe from the shared attribute request topic, - // if we are not waiting for any further responses with shared attributes from the server. - // Will be resubscribed if another request is sent anyway - if (m_rpc_request_callbacks.empty()) { - (void)RPC_Request_Unsubscribe(); - } - } - - /// @brief Process callback that will be called upon server-side RPC request arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callbacks - /// @param topic Previously subscribed topic, we got the response over - /// @param data Payload sent by the server over our given topic, that contains our key value pairs - void process_rpc_message(char * const topic, JsonObjectConst const & data) { - char const * const methodName = data[RPC_METHOD_KEY]; - - if (methodName == nullptr) { - Logger::println(RPC_METHOD_NULL); - return; - } - - for (auto const & rpc : m_rpc_callbacks) { - char const * const subscribedMethodName = rpc.Get_Name(); - if (Helper::stringIsNullorEmpty(subscribedMethodName)) { - Logger::println(RPC_METHOD_NULL); - continue; - } - // Strncmp returns the ascii value difference of the ascii characters that are different, - // meaning 0 is the same string and less and more than 0 is the difference in ascci values between the 2 chararacters. - else if (strncmp(subscribedMethodName, methodName, strlen(subscribedMethodName)) != 0) { - continue; - } - - // Do not inform client, if parameter field is missing for some reason - if (!data.containsKey(RPC_PARAMS_KEY)) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(NO_RPC_PARAMS_PASSED); -#endif // THINGSBOARD_ENABLE_DEBUG - } - -#if THINGSBOARD_ENABLE_DEBUG - Logger::printfln(CALLING_RPC_CB, methodName); -#endif // THINGSBOARD_ENABLE_DEBUG - - JsonVariantConst const param = data[RPC_PARAMS_KEY]; -#if THINGSBOARD_ENABLE_DYNAMIC - size_t const & rpc_response_size = rpc.Get_Response_Size(); - // String are char const * and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, - // Data structure size depends on the amount of key value pairs passed. - // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - TBJsonDocument jsonBuffer(rpc_response_size); -#else - size_t constexpr rpc_response_size = MaxRPC; - StaticJsonDocument jsonBuffer; -#endif // THINGSBOARD_ENABLE_DYNAMIC - rpc.template Call_Callback(param, jsonBuffer); - - if (jsonBuffer.isNull()) { - // Message is ignored and not sent at all. - break; - } - else if (jsonBuffer.overflowed()) { - Logger::printfln(RPC_RESPONSE_OVERFLOWED, rpc_response_size); - break; - } - - size_t const request_id = Helper::parseRequestId(RPC_REQUEST_TOPIC, topic); - char responseTopic[Helper::detectSize(RPC_SEND_RESPONSE_TOPIC, request_id)] = {}; - (void)snprintf(responseTopic, sizeof(responseTopic), RPC_SEND_RESPONSE_TOPIC, request_id); - - size_t const jsonSize = Helper::Measure_Json(jsonBuffer); - Send_Json(responseTopic, jsonBuffer, jsonSize); - break; + static bool staticSendJsonString(char const * const topic, char const * const json) { + if (m_subscribedInstance == nullptr) { + return false; } + return m_subscribedInstance->Send_Json_String(topic, json); } -#if THINGSBOARD_ENABLE_OTA - - /// @brief Process callback that will be called upon firmware response arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callback - /// @param topic Previously subscribed topic, we got the response over - /// @param payload Payload that was sent over the cloud and received over the given topic - /// @param length Total length of the received payload - void process_firmware_response(char * const topic, uint8_t * const payload, size_t const & length) { - size_t const request_id = Helper::parseRequestId(FIRMWARE_RESPONSE_TOPIC, topic); - - // Check if the remaining stack size of the current task would overflow the stack, - // if it would allocate the memory on the heap instead to ensure no stack overflow occurs. - if (getMaximumStackSize() < length) { - uint8_t* binary = new uint8_t[length](); - (void)memcpy(binary, payload, length); - m_ota.Process_Firmware_Packet(request_id, binary, length); - // Ensure to actually delete the memory placed onto the heap, to make sure we do not create a memory leak - // and set the pointer to null so we do not have a dangling reference. - delete[] binary; - binary = nullptr; - } - else { - uint8_t binary[length] = {}; - (void)memcpy(binary, payload, length); - m_ota.Process_Firmware_Packet(request_id, binary, length); + static bool staticClientSubscribe(char const * const topic) { + if (m_subscribedInstance == nullptr) { + return false; } + return m_subscribedInstance->clientSubscribe(topic); } -#endif // THINGSBOARD_ENABLE_OTA - - /// @brief Process callback that will be called upon shared attribute update arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callbacks - /// @param topic Previously subscribed topic, we got the response over - /// @param data Payload sent by the server over our given topic, that contains our key value pairs - void process_shared_attribute_update_message(char * const topic, JsonObjectConst & data) { - if (!data) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(NOT_FOUND_ATT_UPDATE); -#endif // THINGSBOARD_ENABLE_DEBUG - return; - } - - if (data.containsKey(SHARED_RESPONSE_KEY)) { - data = data[SHARED_RESPONSE_KEY]; - } - - for (auto const & shared_attribute : m_shared_attribute_update_callbacks) { - if (shared_attribute.Get_Attributes().empty()) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_CB_NO_KEYS); -#endif // THINGSBOARD_ENABLE_DEBUG - // No specifc keys were subscribed so we call the callback anyway, assumed to be subscribed to any update - shared_attribute.template Call_Callback(data); - continue; - } - - char const * requested_att = nullptr; - - for (auto const & att : shared_attribute.Get_Attributes()) { - if (Helper::stringIsNullorEmpty(att)) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_IS_NULL); -#endif // THINGSBOARD_ENABLE_DEBUG - continue; - } - // Check if the request contained any of our requested keys and - // break early if the key was requested from this callback. - if (data.containsKey(att)) { - requested_att = att; - break; - } - } - - // Check if this callback did not request any keys that were in this response, - // if there were not we simply continue with the next subscribed callback. - if (requested_att == nullptr) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_NO_CHANGE); -#endif // THINGSBOARD_ENABLE_DEBUG - continue; - } - -#if THINGSBOARD_ENABLE_DEBUG - Logger::printfln(CALLING_ATT_CB, requested_att); -#endif // THINGSBOARD_ENABLE_DEBUG - - shared_attribute.template Call_Callback(data); + static bool staticClientUnsubscribe(char const * const topic) { + if (m_subscribedInstance == nullptr) { + return false; } + return m_subscribedInstance->clientUnsubscribe(topic); } - /// @brief Process callback that will be called upon client-side or shared attribute request arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callbacks - /// @param topic Previously subscribed topic, we got the response over - /// @param data Payload sent by the server over our given topic, that contains our key value pairs - void process_attribute_request_message(char * const topic, JsonObjectConst & data) { - size_t const request_id = Helper::parseRequestId(ATTRIBUTE_RESPONSE_TOPIC, topic); - - for (auto it = m_attribute_request_callbacks.cbegin(); it != m_attribute_request_callbacks.cend(); ++it) { - auto const & attribute_request = *it; - - if (attribute_request.Get_Request_ID() != request_id) { - continue; - } - char const * const attributeResponseKey = attribute_request.Get_Attribute_Key(); - if (attributeResponseKey == nullptr) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_KEY_NOT_FOUND); -#endif // THINGSBOARD_ENABLE_DEBUG - goto delete_callback; - } - else if (!data) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::println(ATT_KEY_NOT_FOUND); -#endif // THINGSBOARD_ENABLE_DEBUG - goto delete_callback; - } - - if (data.containsKey(attributeResponseKey)) { - data = data[attributeResponseKey]; - } - -#if THINGSBOARD_ENABLE_DEBUG - Logger::printfln(CALLING_REQUEST_CB, request_id); -#endif // THINGSBOARD_ENABLE_DEBUG - - attribute_request.template Call_Callback(data); - - delete_callback: - // Delete callback because the changes have been requested and the callback is no longer needed - Helper::remove(m_attribute_request_callbacks, it); - break; - } - - // Unsubscribe from the shared attribute request topic, - // if we are not waiting for any further responses with shared attributes from the server. - // Will be resubscribed if another request is sent anyway - if (m_attribute_request_callbacks.empty()) { - (void)Attributes_Request_Unsubscribe(); + static size_t * staticGetRequestID() { + if (m_subscribedInstance == nullptr) { + return false; } + return m_subscribedInstance->getRequestID(); } - /// @brief Process callback that will be called upon provision response arrival - /// and is responsible for handling the payload and calling the appropriate previously subscribed callback - /// @param topic Previously subscribed topic, we got the response over - /// @param data Payload sent by the server over our given topic, that contains our key value pairs - void process_provisioning_response(char * const topic, JsonObjectConst const & data) { - m_provision_callback.template Call_Callback(data); - // Unsubscribe from the provision response topic, - // Will be resubscribed if another request is sent anyway - (void)Provision_Unsubscribe(); - } - - /// @brief Attempts to send aggregated attribute or telemetry data - /// @tparam InputIterator Class that points to the begin and end iterator - /// of the given data container, allows for using / passing either std::vector or std::array. - /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator - /// @param first Iterator pointing to the first element in the data container - /// @param last Iterator pointing to the end of the data container (last element + 1) - /// @param telemetry Whether the data we want to send should be sent over the attribute or telemtry topic - /// @return Whether sending the aggregated data was successful or not - template - bool sendDataArray(InputIterator const & first, InputIterator const & last, bool telemetry) { -#if THINGSBOARD_ENABLE_DYNAMIC - // String are char const * and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, - // Data structure size depends on the amount of key value pairs passed. - // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - size_t const size = Helper::distance(first, last); - TBJsonDocument jsonBuffer(JSON_OBJECT_SIZE(size)); -#else - StaticJsonDocument jsonBuffer; -#endif // THINGSBOARD_ENABLE_DYNAMIC - - for (auto it = first; it != last; ++it) { - auto const & data = *it; - if (!data.SerializeKeyValue(jsonBuffer)) { - Logger::println(UNABLE_TO_SERIALIZE); - return false; - } + static uint16_t staticGetClientBufferSize() { + if (m_subscribedInstance == nullptr) { + return 0U; } - - return telemetry ? sendTelemetryJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)) : sendAttributeJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)); + return m_subscribedInstance->getClientBufferSize(); } - /// @brief MQTT callback that will be called if a publish message is received from the server - /// @param topic Previously subscribed topic, we got the response over - /// @param payload Payload that was sent over the cloud and received over the given topic - /// @param length Total length of the received payload - void onMQTTMessage(char * const topic, uint8_t * const payload, unsigned int length) { -#if THINGSBOARD_ENABLE_DEBUG - Logger::printfln(RECEIVE_MESSAGE, topic); -#endif // THINGSBOARD_ENABLE_DEBUG - -#if THINGSBOARD_ENABLE_OTA - // When receiving the ota binary payload we do not want to deserialize it into json, because it only contains - // firmware bytes that should be directly writtin into flash, therefore we can skip that step and directly process those bytes - if (strncmp(FIRMWARE_RESPONSE_TOPIC, topic, strlen(FIRMWARE_RESPONSE_TOPIC)) == 0) { - process_firmware_response(topic, payload, length); - return; - } -#endif // THINGSBOARD_ENABLE_OTA - -#if THINGSBOARD_ENABLE_DYNAMIC - // Buffer that we deserialize is writeable and not read only --> zero copy, meaning the size for the data is 0 bytes, - // Data structure size depends on the amount of key value pairs received. - // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - TBJsonDocument jsonBuffer(JSON_OBJECT_SIZE(Helper::getOccurences(reinterpret_cast(payload), COLON))); -#else - StaticJsonDocument jsonBuffer; -#endif // THINGSBOARD_ENABLE_DYNAMIC - - // The deserializeJson method we use, can use the zero copy mode because a writeable input was passed, - // if that were not the case the needed allocated memory would drastically increase, because the keys would need to be copied as well. - // See https://arduinojson.org/v6/doc/deserialization/ for more info on ArduinoJson deserialization - DeserializationError const error = deserializeJson(jsonBuffer, payload, length); - if (error) { - Logger::printfln(UNABLE_TO_DE_SERIALIZE_JSON, error.c_str()); - return; - } - // .as() is used instead of .to(), because it is meant to cast the JsonDocument to the given type, - // but it does not change the actual content of the JsonDocument, we don't want that because it contains content - // and .to() would result in the data being cleared ()"null"), instead .as() which allows accessing the data over a JsonObjectConst instead - JsonObjectConst data = jsonBuffer.template as(); - - // Checks to ensure we forward the already json serialized data to the correct process function, - // especially important is the order of ATTRIBUTE_RESPONSE_TOPIC and ATTRIBUTE_TOPIC, - // that is the case because the more specific one that is longer but contains the same text has to be checked first, - // because if we do not do that then even if we receive a message from the ATTRIBUTE_RESPONSE_TOPIC - // we would call the process method for the ATTRIBUTE_TOPIC because we only compare until the end of the ATTRIBUTE_TOPIC string, - // therefore the received topic is exactly the same. Therefore the ordering needs to stay the same for thoose two specific checks - if (strncmp(RPC_RESPONSE_TOPIC, topic, strlen(RPC_RESPONSE_TOPIC)) == 0) { - process_rpc_request_message(topic, data); - } - else if (strncmp(RPC_REQUEST_TOPIC, topic, strlen(RPC_REQUEST_TOPIC)) == 0) { - process_rpc_message(topic, data); - } - else if (strncmp(ATTRIBUTE_RESPONSE_TOPIC, topic, strlen(ATTRIBUTE_RESPONSE_TOPIC)) == 0) { - process_attribute_request_message(topic, data); - } - else if (strncmp(ATTRIBUTE_TOPIC, topic, strlen(ATTRIBUTE_TOPIC)) == 0) { - process_shared_attribute_update_message(topic, data); - } - else if (strncmp(PROV_RESPONSE_TOPIC, topic, strlen(PROV_RESPONSE_TOPIC)) == 0) { - process_provisioning_response(topic, data); + static bool staticSetBufferSize(uint16_t buffer_size) { + if (m_subscribedInstance == nullptr) { + return false; } + return m_subscribedInstance->setBufferSize(buffer_size); } -#if !THINGSBOARD_ENABLE_STL - // PubSub client cannot call a method when message arrives on subscribed topic. + // PubSub client cannot call a instanced method when message arrives on subscribed topic. // Only free-standing function is allowed. // To be able to forward event to an instance, rather than to a function, this pointer exists. static ThingsBoardSized *m_subscribedInstance; - - static void onStaticMQTTMessage(char * const topic, uint8_t * const payload, unsigned int length) { - if (m_subscribedInstance == nullptr) { - return; - } - m_subscribedInstance->onMQTTMessage(topic, payload, length); - } - - static void onStaticMQTTConnect() { - if (m_subscribedInstance == nullptr) { - return; - } - m_subscribedInstance->Resubscribe_Topics(); - } #endif // !THINGSBOARD_ENABLE_STL + + IMQTT_Client& m_client = {}; // MQTT client instance. + size_t m_max_stack = {}; // Maximum stack size we allocate at once. + size_t m_request_id = {}; // Internal id used to differentiate which request should receive which response for certain API calls. Can send 4'294'967'296 requests before wrapping back to 0 +#if THINGSBOARD_ENABLE_STREAM_UTILS + size_t m_buffering_size = {}; // Buffering size used to serialize directly into client. +#endif // THINGSBOARD_ENABLE_STREAM_UTILS +#if !THINGSBOARD_ENABLE_DYNAMIC + Array m_api_implementations = {}; // Can hold a pointer to all possible API implementations (Server side RPC, Client side RPC, Shared attribute update, Client-side or shared attribute request, Provision) +#else + size_t m_max_response_size = {}; // Maximum size allocated on the heap to hold the Json data structure for received cloud response payload, prevents possible malicious payload allocaitng a lot of memory + Vector m_api_implementations = {}; // Can hold a pointer to all possible API implementations (Server side RPC, Client side RPC, Shared attribute update, Client-side or shared attribute request, Provision) +#endif // !THINGSBOARD_ENABLE_DYNAMIC }; -#if !THINGSBOARD_ENABLE_STL && !THINGSBOARD_ENABLE_DYNAMIC -template -ThingsBoardSized *ThingsBoardSized::m_subscribedInstance = nullptr; -#elif !THINGSBOARD_ENABLE_STL && THINGSBOARD_ENABLE_DYNAMIC +#if !THINGSBOARD_ENABLE_STL +#if !THINGSBOARD_ENABLE_DYNAMIC +template +ThingsBoardSized *ThingsBoardSized::m_subscribedInstance = nullptr; +#else template ThingsBoardSized *ThingsBoardSized::m_subscribedInstance = nullptr; -#endif +#endif // !THINGSBOARD_ENABLE_DYNAMIC +#endif // !THINGSBOARD_ENABLE_STL using ThingsBoard = ThingsBoardSized<>; diff --git a/src/ThingsBoardHttp.h b/src/ThingsBoardHttp.h index 2450ea67..67baf5cb 100644 --- a/src/ThingsBoardHttp.h +++ b/src/ThingsBoardHttp.h @@ -16,48 +16,24 @@ // HTTP topics. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr HTTP_TELEMETRY_TOPIC[] PROGMEM = "/api/v1/%s/telemetry"; -char constexpr HTTP_ATTRIBUTES_TOPIC[] PROGMEM = "/api/v1/%s/attributes"; -char constexpr HTTP_POST_PATH[] PROGMEM = "application/json"; -int constexpr HTTP_RESPONSE_SUCCESS_RANGE_START PROGMEM = 200; -int constexpr HTTP_RESPONSE_SUCCESS_RANGE_END PROGMEM = 299; -#else char constexpr HTTP_TELEMETRY_TOPIC[] = "/api/v1/%s/telemetry"; char constexpr HTTP_ATTRIBUTES_TOPIC[] = "/api/v1/%s/attributes"; char constexpr HTTP_POST_PATH[] = "application/json"; int constexpr HTTP_RESPONSE_SUCCESS_RANGE_START = 200; int constexpr HTTP_RESPONSE_SUCCESS_RANGE_END = 299; -#endif // THINGSBOARD_ENABLE_PROGMEM // Log messages. -#if THINGSBOARD_ENABLE_PROGMEM -char constexpr POST[] PROGMEM = "POST"; -char constexpr GET[] PROGMEM = "GET"; -char constexpr HTTP_FAILED[] PROGMEM = "(%s) failed HTTP response (%d)"; -#else char constexpr POST[] = "POST"; char constexpr GET[] = "GET"; char constexpr HTTP_FAILED[] = "(%s) failed HTTP response (%d)"; -#endif // THINGSBOARD_ENABLE_PROGMEM -#if THINGSBOARD_ENABLE_DYNAMIC -/// @brief Wrapper around the ArduinoHttpClient or HTTPClient to allow connecting and sending / retrieving data from ThingsBoard over the HTTP orHTTPS protocol. -/// BufferSize of the underlying data buffer as well as the maximum amount of data points that can ever be sent are either dynamic or can be changed during runtime. -/// If this feature is not needed and the values can be sent once as template arguements it is recommended to use the static ThingsBoard instance instead. -/// Simply set THINGSBOARD_ENABLE_DYNAMIC to 0, before including ThingsBoardHttp.h -/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger -template -#else /// @brief Wrapper around the ArduinoHttpClient or HTTPClient to allow connecting and sending / retrieving data from ThingsBoard over the HTTP orHTTPS protocol. /// BufferSize of the underlying data buffer as well as the maximum amount of data points that can ever be sent have to defined as template arguments. /// Changing is only possible if a new instance of this class is created. If theese values should be changeable and dynamic instead. /// Simply set THINGSBOARD_ENABLE_DYNAMIC to 1, before including ThingsBoardHttp.h. -/// @tparam MaxFieldsAmount Maximum amount of key value pair that we will be able to sent to ThingsBoard in one call, default = 8 /// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger -template -#endif // THINGSBOARD_ENABLE_DYNAMIC +template class ThingsBoardHttpSized { public: /// @brief Initalizes the underlying client with the needed information @@ -67,50 +43,48 @@ class ThingsBoardHttpSized { /// @param access_token Token used to verify the devices identity with the ThingsBoard server /// @param host Host server we want to establish a connection to (example: "demo.thingsboard.io") /// @param port Port we want to establish a connection over (80 for HTTP, 443 for HTTPS) - /// @param keepAlive Attempts to keep the establishes TCP connection alive to make sending data faster - /// @param maxStackSize Maximum amount of bytes we want to allocate on the stack, default = Default_Max_Stack_Size - ThingsBoardHttpSized(IHTTP_Client & client, char const * const access_token, char const * const host, uint16_t const & port = 80U, bool keepAlive = true, size_t const & maxStackSize = Default_Max_Stack_Size) + /// @param keep_alive Attempts to keep the establishes TCP connection alive to make sending data faster + /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack, default = Default_Max_Stack_Size + ThingsBoardHttpSized(IHTTP_Client & client, char const * const access_token, char const * const host, uint16_t port = 80U, bool keep_alive = true, size_t const & max_stack_size = Default_Max_Stack_Size) : m_client(client) - , m_max_stack(maxStackSize) + , m_max_stack(max_stack_size) , m_token(access_token) { - m_client.set_keep_alive(keepAlive); + m_client.set_keep_alive(keep_alive); if (m_client.connect(host, port) != 0) { Logger::println(CONNECT_FAILED); } } /// @brief Sets the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead - /// @param maxStackSize Maximum amount of bytes we want to allocate on the stack - void setMaximumStackSize(size_t const & maxStackSize) { - m_max_stack = maxStackSize; + /// @param max_stack_size Maximum amount of bytes we want to allocate on the stack + void setMaximumStackSize(size_t const & max_stack_size) { + m_max_stack = max_stack_size; } /// @brief Attempts to send key value pairs from custom source over the given topic to the server - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server /// @param topic Topic we want to send the data over - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool Send_Json(char const * const topic, TSource const & source, size_t const & jsonSize) { - // Check if allocating needed memory failed when trying to create the JsonObject, + bool Send_Json(char const * const topic, JsonDocument const & source, size_t const & json_size) { + // Check if allocating needed memory failed when trying to create the JsonDocument, // if it did the isNull() method will return true. See https://arduinojson.org/v6/api/jsonvariant/isnull/ for more information if (source.isNull()) { Logger::println(UNABLE_TO_ALLOCATE_JSON); return false; } -#if !THINGSBOARD_ENABLE_DYNAMIC - size_t const amount = source.size(); - if (MaxFieldsAmount < amount) { - Logger::printfln(TOO_MANY_JSON_FIELDS, amount, MaxFieldsAmount); + // Check if inserting any of the internal values failed because the JsonDocument was too small, + // if it did the overflowed() method will return true. See https://arduinojson.org/v6/api/jsondocument/overflowed/ for more information + if (source.overflowed()) { + Logger::println(JSON_SIZE_TO_SMALL); return false; } -#endif // !THINGSBOARD_ENABLE_DYNAMIC bool result = false; - if (getMaximumStackSize() < jsonSize) { - char * json = new char[jsonSize](); - if (serializeJson(source, json, jsonSize) < jsonSize - 1) { + if (getMaximumStackSize() < json_size) { + char * json = new char[json_size](); + if (serializeJson(source, json, json_size) < json_size - 1) { Logger::println(UNABLE_TO_SERIALIZE_JSON); } else { @@ -122,8 +96,8 @@ class ThingsBoardHttpSized { json = nullptr; } else { - char json[jsonSize] = {}; - if (serializeJson(source, json, jsonSize) < jsonSize - 1) { + char json[json_size] = {}; + if (serializeJson(source, json, json_size) < json_size - 1) { Logger::println(UNABLE_TO_SERIALIZE_JSON); return result; } @@ -168,28 +142,37 @@ class ThingsBoardHttpSized { /// @param first Iterator pointing to the first element in the data container /// @param last Iterator pointing to the end of the data container (last element + 1) /// @return Whether sending the aggregated telemetry data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC bool sendTelemetry(InputIterator const & first, InputIterator const & last) { +#if THINGSBOARD_ENABLE_DYNAMIC return sendDataArray(first, last, true); +#else + return sendDataArray(first, last, true); +#endif // THINGSBOARD_ENABLE_DYNAMIC } /// @brief Attempts to send custom json telemetry string. /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information /// @param json String containing our json key value pairs we want to attempt to send /// @return Whetherr sending the data was successful or not - bool sendTelemetryJson(char const * const json) { + bool sendTelemetryString(char const * const json) { return Send_Json_String(HTTP_TELEMETRY_TOPIC, json); } /// @brief Attempts to send telemetry key value pairs from custom source to the server. /// See https://thingsboard.io/docs/user-guide/telemetry/ for more information - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool sendTelemetryJson(TSource const & source, size_t const & jsonSize) { - return Send_Json(HTTP_TELEMETRY_TOPIC, source, jsonSize); + bool sendTelemetryJson(JsonDocument const & source, size_t const & json_size) { + return Send_Json(HTTP_TELEMETRY_TOPIC, source, json_size); } /// @brief Attempts to send a GET request over HTTP or HTTPS @@ -235,35 +218,40 @@ class ThingsBoardHttpSized { /// @param first Iterator pointing to the first element in the data container /// @param last Iterator pointing to the end of the data container (last element + 1) /// @return Whether sending the aggregated attribute data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC bool sendAttributes(InputIterator const & first, InputIterator const & last) { +#if THINGSBOARD_ENABLE_DYNAMIC return sendDataArray(first, last, false); +#else + return sendDataArray(first, last, false); +#endif // THINGSBOARD_ENABLE_DYNAMIC } /// @brief Attempts to send custom json attribute string. /// See https://thingsboard.io/docs/user-guide/attributes/ for more information /// @param json String containing our json key value pairs we want to attempt to send /// @return Whetherr sending the data was successful or not - bool sendAttributeJson(char const * const json) { + bool sendAttributeString(char const * const json) { return Send_Json_String(HTTP_ATTRIBUTES_TOPIC, json); } /// @brief Attempts to send attribute key value pairs from custom source to the server. /// See https://thingsboard.io/docs/user-guide/attributes/ for more information - /// @tparam TSource Source class that should be used to serialize the json that is sent to the server - /// @param source Data source containing our json key value pairs we want to send - /// @param jsonSize Size of the data inside the source + /// @param source JsonDocument containing our json key value pairs we want to send, + /// is checked before usage for any possible occuring internal errors. See https://arduinojson.org/v6/api/jsondocument/ for more information + /// @param json_size Size of the data inside the source /// @return Whether sending the data was successful or not - template - bool sendAttributeJson(TSource const & source, size_t const & jsonSize) { - return Send_Json(HTTP_ATTRIBUTES_TOPIC, source, jsonSize); + bool sendAttributeJson(JsonDocument const & source, size_t const & json_size) { + return Send_Json(HTTP_ATTRIBUTES_TOPIC, source, json_size); } private: - IHTTP_Client& m_client; // HttpClient instance - size_t m_max_stack; // Maximum stack size we allocate at once on the stack. - char const *m_token; // Access token used to connect with - /// @brief Returns the maximum amount of bytes that we want to allocate on the stack, before the memory is allocated on the heap instead /// @return Maximum amount of bytes we want to allocate on the stack size_t const & getMaximumStackSize() const { @@ -311,7 +299,6 @@ class ThingsBoardHttpSized { success = false; goto cleanup; } - response = m_client.get_response_body(); cleanup: @@ -327,27 +314,36 @@ class ThingsBoardHttpSized { /// @param last Iterator pointing to the end of the data container (last element + 1) /// @param telemetry Whether the data we want to send should be sent over the attribute or telemtry topic /// @return Whether sending the aggregated data was successful or not +#if THINGSBOARD_ENABLE_DYNAMIC template +#else + /// @tparam MaxKeyValuePairAmount Maximum amount of json key value pairs, which will ever be sent with this method to the cloud. + /// Should simply be the biggest distance between first and last iterator this method is ever called with + template +#endif // THINGSBOARD_ENABLE_DYNAMIC bool sendDataArray(InputIterator const & first, InputIterator const & last, bool telemetry) { + size_t const size = Helper::distance(first, last); #if THINGSBOARD_ENABLE_DYNAMIC - // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, + // String are char const * and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes, // Data structure size depends on the amount of key value pairs passed. // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument - size_t const size = Helper::distance(first, last); - TBJsonDocument jsonBuffer(JSON_OBJECT_SIZE(size)); + TBJsonDocument json_buffer(JSON_OBJECT_SIZE(size)); #else - StaticJsonDocument jsonBuffer; -#endif // !THINGSBOARD_ENABLE_DYNAMIC + if (size > MaxKeyValuePairAmount) { + Logger::printfln(TOO_MANY_JSON_FIELDS, size, "MaxKeyValuePairAmount", MaxKeyValuePairAmount); + return false; + } + StaticJsonDocument json_buffer; +#endif // THINGSBOARD_ENABLE_DYNAMIC for (auto it = first; it != last; ++it) { auto const & data = *it; - if (!data.SerializeKeyValue(jsonBuffer)) { + if (!data.SerializeKeyValue(json_buffer)) { Logger::println(UNABLE_TO_SERIALIZE); return false; } } - - return telemetry ? sendTelemetryJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)) : sendAttributeJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)); + return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer)); } /// @brief Sends single key-value attribute or telemetry data in a generic way @@ -364,14 +360,17 @@ class ThingsBoardHttpSized { return false; } - StaticJsonDocument jsonBuffer; - if (!t.SerializeKeyValue(jsonBuffer)) { + StaticJsonDocument json_buffer; + if (!t.SerializeKeyValue(json_buffer)) { Logger::printfln(UNABLE_TO_SERIALIZE); return false; } - - return telemetry ? sendTelemetryJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)) : sendAttributeJson(jsonBuffer, Helper::Measure_Json(jsonBuffer)); + return telemetry ? sendTelemetryJson(json_buffer, Helper::Measure_Json(json_buffer)) : sendAttributeJson(json_buffer, Helper::Measure_Json(json_buffer)); } + + IHTTP_Client& m_client = {}; // HttpClient instance + size_t m_max_stack = {}; // Maximum stack size we allocate at once on the stack. + char const *m_token = {}; // Access token used to connect with }; using ThingsBoardHttp = ThingsBoardHttpSized<>; diff --git a/src/Vector.h b/src/Vector.h index 1c581cca..4127921f 100644 --- a/src/Vector.h +++ b/src/Vector.h @@ -2,7 +2,7 @@ #define Vector_h // Local include. -#include "Configuration.h" +#include "Helper.h" // Library includes. #include @@ -14,23 +14,36 @@ template class Vector { public: /// @brief Constructor - Vector(void) + Vector(void) = default; + + /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data between the first and last iterator + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + Vector(InputIterator const & first, InputIterator const & last) : m_elements(nullptr) , m_capacity(0U) , m_size(0U) { - // Nothing to do + insert(nullptr, first, last); } - /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - Vector(T const * const first, T const * const last) + /// @brief Constructor that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data from the given container + /// @tparam Container Class that contains the actual data we want to copy into our internal data container, + /// requires access to a begin() and end() method, that point to the first element and one past the last element we want to copy respectively. + /// Both methods need to return an InputIterator, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param container Data container with begin() and end() method that we want to copy fully into our underlying data container + template + Vector(Container const & container) : m_elements(nullptr) , m_capacity(0U) , m_size(0U) { - insert(nullptr, first, last); + insert(nullptr, container.begin(), container.end()); } /// @brief Destructor @@ -39,13 +52,28 @@ class Vector { m_elements = nullptr; } - /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - void assign(T const * const first, T const * const last) { + /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data between the first and last iterator + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + void assign(InputIterator const & first, InputIterator const & last) { insert(nullptr, first, last); } + /// @brief Method that allows compatibility with std::vector, simply forwards call to internal insert method and copies all data from the given container + /// @tparam Container Class that contains the actual data we want to copy into our internal data container, + /// requires access to a begin() and end() method, that point to the first element and one past the last element we want to copy respectively. + /// Both methods need to return an InputIterator, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param container Data container with begin() and end() method that we want to copy fully into our underlying data container + template + void assign(Container const & container) { + insert(nullptr, container.begin(), container.end()); + } + /// @brief Returns whether there are still any element in the underlying data container /// @return Whether the underlying data container is empty or not bool empty() const { @@ -54,30 +82,30 @@ class Vector { /// @brief Gets the current amount of elements in the underlying data container /// @return The amount of items currently in the underlying data container - size_t const & size() const { + size_t size() const { return m_size; } /// @brief Gets the maximum amount of elements that can currently be stored in the underlying data container /// @return The maximum amount of items that can currently be stored in the underlying data container - size_t const & capacity() const { + size_t capacity() const { return m_capacity; } - /// @brief Returns a pointer to the first element of the vector - /// @return Pointer to the first element of the vector + /// @brief Returns a iterator to the first element of the underlying data container + /// @return Iterator pointing to the first element of the underlying data container T * begin() { return m_elements; } - /// @brief Returns a constant pointer to the first element of the vector - /// @return Constant pointer to the first element of the vector + /// @brief Returns a constant iterator to the first element of the underlying data container + /// @return Constant iterator pointing to the first element of the underlying data container T const * begin() const { return m_elements; } - /// @brief Returns a constant pointer to the first element of the vector - /// @return Constant pointer to the first element of the vector + /// @brief Returns a constant iterator to the first element of the underlying data container + /// @return Constant iterator pointing to the first element of the underlying data container T const * cbegin() const { return m_elements; } @@ -89,58 +117,67 @@ class Vector { return m_elements[m_size - 1U]; } - /// @brief Returns a pointer to one-past-the-end element of the vector - /// @return Pointer to one-past-the-end element of the vector + /// @brief Returns a iterator to one-past-the-end element of the underlying data container + /// @return Iterator pointing to one-past-the-end element of the underlying data container T * end() { return m_elements + m_size; } - /// @brief Returns a constant pointer to one-past-the-end element of the vector - /// @return Constant pointer to one-past-the-end element of the vector + /// @brief Returns a constantiterator to one-past-the-end element of the underlying data container + /// @return Constant iterator pointing to one-past-the-end element of the underlying data container T const * end() const { return m_elements + m_size; } - /// @brief Returns a constant pointer to one-past-the-end element of the vector - /// @return Constant pointer to one-past-the-end element of the vector + /// @brief Returns a constant iterator to one-past-the-end element of the underlying data container + /// @return Constant iterator pointing to one-past-the-end element of the underlying data container T const * cend() const { return m_elements + m_size; } - /// @brief Inserts the given element at the end of the underlying data container. - /// Increases the size of the underlying data container if it is already full and has no remaining space for new elements. - /// The growth is exponential meaning it doubles every time the underlying data container has to be increased. - /// This results in an amortized insertion speed of O(1), but might require 0(n) when the container has to be reallocated to be increased + /// @brief Inserts the given element at the end of the underlying data container, + /// If the interal data structure is full already then this method will assert and stop the application. + /// Because if we do not we could cause an out of bounds write, which could possibly overwrite other memory. + /// Causing hard to debug issues, therefore this behaviour is not allowed in the first place /// @param element Element that should be inserted at the end void push_back(T const & element) { if (m_size == m_capacity) { m_capacity = (m_capacity == 0) ? 1 : 2 * m_capacity; - T* newElements = new T[m_capacity](); + T* new_elements = new T[m_capacity](); if (m_elements != nullptr) { - memcpy(newElements, m_elements, m_size * sizeof(T)); + memcpy(new_elements, m_elements, m_size * sizeof(T)); delete[] m_elements; } - m_elements = newElements; + m_elements = new_elements; } m_elements[m_size] = element; m_size++; } - /// @brief Inserts all element from the given start to the given end iterator into the underlying data container. Simply calls push_back on each element + /// @brief Inserts all element from the given start to the given end iterator into the underlying data container. + /// Simply calls push_back on each element, meaning if the initally allocated size if not big enough to hold all elements, + /// then this method will simply not insert those elements instead + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator /// @param position Attribute is not used and can be left as nullptr, simply there to keep compatibility with std::vector insert method - /// @param first Beginning of the elements we want to copy into our underlying data container - /// @param last One past the end of the elements we want to copy into our underlying data container - void insert(T const * const position, T const * first, T const * const last) { - while (first < last) { - push_back(*first); - first++; + /// @param first Iterator pointing to the first element we want to copy into our underlying data container + /// @param last Iterator pointing to one past the end of the elements we want to copy into our underlying data container + template + void insert(T const * const position, InputIterator const & first, InputIterator const & last) { + for (auto it = first; it != last; ++it) { + push_back(*it); } } - /// @brief Removes the element at the given iterator, has to move all element one to the left if the index is not at the end of the array - /// @param iterator Iterator the element should be removed at from the underlying data container - void erase(T const * const iterator) { - size_t const index = Helper::distance(cbegin(), iterator); + /// @brief Removes the element at the given position, has to move all element one to the left if the index is not at the end of the array + /// @tparam InputIterator Class that points to the begin and end iterator + /// of the given data container, allows for using / passing either std::vector or std::array. + /// See https://en.cppreference.com/w/cpp/iterator/input_iterator for more information on the requirements of the iterator + /// @param position Iterator pointing to the element, that should be removed from the underlying data container + template + void erase(InputIterator const & position) { + size_t const index = Helper::distance(begin(), position); // Check if the given index is bigger or equal than the actual amount of elements if it is we can not erase that element because it does not exist if (index < m_size) { // Move all elements after the index one position to the left @@ -181,9 +218,9 @@ class Vector { } private: - T *m_elements; // Pointer to the start of our elements - size_t m_capacity; // Allocated capacity that shows how many elements we could hold - size_t m_size; // Used size that shows how many elements we entered + T *m_elements = {}; // Pointer to the start of our elements + size_t m_capacity = {}; // Allocated capacity that shows how many elements we could hold + size_t m_size = {}; // Used size that shows how many elements we entered }; #endif // Vector_h