Arduino nano RP2040 and portenta H7 samples (#51)

* Merging nano and portenta samples

* Addressing comments

* Addressing comments Central Arduino Nano

* Addressing comments Central Arduino Portenta

* Adding support for ECCX08 with hmac update

* Renaming and removing mbedtls includes

* Adding install instructions for ArduinoECCX08

* Addressing comments
This commit is contained in:
Raul Leclair 2023-01-12 11:21:39 -08:00 коммит произвёл GitHub
Родитель c59a2b1147
Коммит 739c269478
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
38 изменённых файлов: 9202 добавлений и 0 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,827 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* AzureIoT.cpp contains a state machine that implements the necessary calls to azure-sdk-for-c
* for aiding to connect and work with Azure IoT services, plus abstractions to
* simplify overall use of azure-sdk-for-c.
* Besides the basic configuration needed to access the Azure IoT services,
* all that is needed is to provide the functions required by that layer to:
* - Interact with your MQTT client,
* - Perform data manipulations (HMAC SHA256 encryption, Base64 decoding and encoding),
* - Receive the callbacks for Plug and Play properties and commands.
*/
#ifndef AZURE_IOT_H
#define AZURE_IOT_H
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <az_core.h>
#include <az_iot.h>
/* --- Array and String Helpers --- */
#define lengthof(s) (sizeof(s) - 1)
#define sizeofarray(a) (sizeof(a) / sizeof(a[0]))
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
static const uint8_t null_terminator = '\0';
/* --- Time --- */
#define INDEFINITE_TIME ((time_t)-1)
/* --- Logging --- */
#ifndef DISABLE_LOGGING
typedef enum log_level_t_enum
{
log_level_info,
log_level_error
}
log_level_t;
typedef void (*log_function_t)(log_level_t log_level, char const* const format, ...);
extern log_function_t default_logging_function;
#define set_logging_function(custom_logging_function) \
default_logging_function = custom_logging_function;
#define Log(level, message, ...) default_logging_function(level, message, ##__VA_ARGS__ )
#define LogInfo(message, ...) Log(log_level_info, message, ##__VA_ARGS__ )
#define LogError(message, ...) Log(log_level_error, message, ##__VA_ARGS__ )
#else
#define set_logging_function(custom_logging_function)
#define Log(level, message, ...)
#define LogInfo(message, ...)
#define LogError(message, ...)
#endif // DISABLE_LOGGING
/* --- Azure Definitions --- */
#define DPS_GLOBAL_ENDPOINT_FQDN "global.azure-devices-provisioning.net"
#define DPS_GLOBAL_ENDPOINT_PORT AZ_IOT_DEFAULT_MQTT_CONNECT_PORT
#define IOT_HUB_ENDPOINT_PORT AZ_IOT_DEFAULT_MQTT_CONNECT_PORT
#define DEFAULT_SAS_TOKEN_LIFETIME_IN_MINUTES 60
#define SAS_TOKEN_REFRESH_THRESHOLD_IN_SECS 30
/*
* The structures below define a generic interface to abstract the interaction of this module,
* with any MQTT client used in the user application.
*/
#define MQTT_QOS_AT_MOST_ONCE 0
#define MQTT_QOS_AT_LEAST_ONCE 1
#define MQTT_QOS_EXACTLY_ONCE 2
typedef enum mqtt_qos_t_enum
{
mqtt_qos_at_most_once = MQTT_QOS_AT_MOST_ONCE,
mqtt_qos_at_least_once = MQTT_QOS_AT_LEAST_ONCE,
mqtt_qos_exactly_once = MQTT_QOS_EXACTLY_ONCE
}
mqtt_qos_t;
/*
* @brief Defines a generic MQTT message to be exchanged between the AzureIoT layer
* and the user application.
* @remark It uses azure-sdk-for-c's az_span construct, which is merely a structure that
* has a pointer to a buffer and its size. Messages without a payload will have
* the `payload` member set to AZ_SPAN_EMPTY.
* Please see the az_span.h in azure-sdk-for-c for more details at
* https://azuresdkdocs.blob.core.windows.net/$web/c/az_core/1.2.0/az__span_8h.html
*/
typedef struct mqtt_message_t_struct
{
az_span topic;
az_span payload;
mqtt_qos_t qos;
}
mqtt_message_t;
/*
* @brief Configuration structure passed by `mqtt_client_init_function_t` to the user
* application for initializing the actual MQTT client.
*/
typedef struct mqtt_client_config_t_struct
{
/*
* @brief FQDN address of the broker that the MQTT client shall connect to.
*/
az_span address;
/*
* @brief Port of the broker that the MQTT client shall connect to.
*/
int port;
/*
* @brief Client ID to be provided in the CONNECT sent by the MQTT client.
*/
az_span client_id;
/*
* @brief Username to be provided in the CONNECT sent by the MQTT client.
*/
az_span username;
/*
* @brief Password to be provided in the CONNECT sent by the MQTT client.
*/
az_span password;
}
mqtt_client_config_t;
/*
* @brief Generic pointer to the actual instance of the application MQTT client.
* @remark set by the user application when the `mqtt_client_init_function_t` callback is invoked.
*/
typedef void* mqtt_client_handle_t;
/*
* @brief Function to initialize and connect an MQTT client.
* @remark When this function is called, it provides the information necessary to initialize a
* specific MQTT client (whichever is used by the user application). In this callback
* it is expected that the MQTT client will be created/initialized, started and that it
* start sending a CONNECT to the provided server.
*
* @param[in] mqtt_client_config An instance of mqtt_client_config_t containing all the information
* needed by the MQTT client to connect to the target server.
* Please see `mqtt_client_config_t` documentation for details.
* @param[out] mqtt_client_handle A pointer to the resulting "instance" of the MQTT client shall
* be saved in `mqtt_client_handle` for later use by the calling layer.
*
* @return int 0 on success, non-zero if any failure occurs.
*/
typedef int (*mqtt_client_init_function_t)(mqtt_client_config_t* mqtt_client_config, mqtt_client_handle_t* mqtt_client_handle);
/*
* @brief Function to disconnect and deinitialize an MQTT client.
* @remark When this function is called the MQTT client instance referenced by `mqtt_client_handle` shall disconnect
* from the server and any functions of the MQTT client API that destroy the instance shall be called
* (so any allocated memory resources can be released).
*
* @param[in] mqtt_client_handle A pointer to the instance of the MQTT client previously created with
* `mqtt_client_init_function_t` function.
*
* @return int 0 on success, non-zero if any failure occurs.
* Returning non-zero results in the Azure IoT Client going into error state.
*/
typedef int (*mqtt_client_deinit_function_t)(mqtt_client_handle_t mqtt_client_handle);
/*
* @brief Function to send an MQTT PUBLISH.
* @remark When this function is invoked, the caller expects the actual MQTT client (referenced by `mqtt_client_handle`)
* to invoke the appropriate function in the MQTT client API to publish an MQTT message.
*
* @param[in] mqtt_client_handle A pointer to the instance of the MQTT client previously created with
* `mqtt_client_init_function_t` function.
* @param[in] mqtt_message A structure containing the topic name, payload and QoS to be used to publish
* an actual MQTT message.
*
* @return int The packet ID on success, or NEGATIVE if any failure occurs.
* If the QoS in `mqtt_message` is:
* - AT LEAST ONCE, the Azure IoT client expects `azure_iot_mqtt_client_connected`
* to be called once the MQTT client receives a PUBACK.
* - AT MOST ONCE, there should be no PUBACK, so no further action is
* needed for this PUBLISH.
*/
typedef int (*mqtt_client_publish_function_t)(mqtt_client_handle_t mqtt_client_handle, mqtt_message_t* mqtt_message);
/*
* @brief Function to send an MQTT SUBSCRIBE.
* @remark When this function is invoked, Azure IoT client expects the actual MQTT client (referenced by
* `mqtt_client_handle`) to invoke the appropriate function in the MQTT client API to subscribe to
* an MQTT topic.
*
* @param[in] topic The az_span with the string containing the complete MQTT topic name to subscribed to.
* This string is always NULL-terminated.
* @param[in] qos MQTT QoS to be used for the topic subscription.
*
* @return int The packet ID of the subscription on success, or NEGATIVE if any failure occurs.
* Azure IoT client expects `azure_iot_mqtt_client_subscribe_completed` to be called once the
* MQTT client receives a SUBACK.
*/
typedef int (*mqtt_client_subscribe_function_t)(mqtt_client_handle_t mqtt_client_handle, az_span topic, mqtt_qos_t qos);
/*
* @brief Structure that consolidates all the abstracted MQTT functions.
*/
typedef struct mqtt_client_interface_t_struct
{
mqtt_client_init_function_t mqtt_client_init;
mqtt_client_deinit_function_t mqtt_client_deinit;
mqtt_client_publish_function_t mqtt_client_publish;
mqtt_client_subscribe_function_t mqtt_client_subscribe;
}
mqtt_client_interface_t;
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] data Buffer containing the Base64-encoded content.
* @param[in] data_length Length of `data`.
* @param[in] decoded Buffer where to write the Base64-decoded content of `data`.
* @param[in] decoded_size Size of `decoded`.
* @param[out] decoded_length The final length of the Base64-decoded content written in
* the `decoded` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*base64_decode_function_t)(uint8_t* data, size_t data_length, uint8_t* decoded, size_t decoded_size, size_t* decoded_length);
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] data Buffer containing the Base64-decoded content.
* @param[in] data_length Length of `data`.
* @param[in] encoded Buffer where to write the Base64-encoded content of `data`.
* @param[in] encoded_size Size of `encoded`.
* @param[out] encoded_length The final length of the Base64-encoded content written in
* the `encoded` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*base64_encode_function_t)(uint8_t* data, size_t data_length, uint8_t* encoded, size_t encoded_size, size_t* encoded_length);
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] key Encryption key to be used in the HMAC-SHA256 algorithm.
* @param[in] key_length Length of `key`.
* @param[in] payload Buffer containing the data to be encrypted.
* @param[in] payload_size Size of `payload`.
* @param[in] encrypted_payload Buffer where to write the HMAC-SHA256 encrypted content of `payload`.
* @param[in] encrypted_payload_size The size of the `encrypted_payload` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*hmac_sha256_encryption_function_t)(const uint8_t* key, size_t key_length, const uint8_t* payload, size_t payload_length, uint8_t* encrypted_payload, size_t encrypted_payload_size);
/*
* @brief Structure that consolidates all the data manipulation functions.
*/
typedef struct data_manipulation_functions_t_struct
{
base64_decode_function_t base64_decode;
base64_encode_function_t base64_encode;
hmac_sha256_encryption_function_t hmac_sha256_encrypt;
}
data_manipulation_functions_t;
/*
* @brief Defines the callback for notifying the completion of a reported properties update.
*
* @param[in] request_id Request ID provided by the caller when sending the reported properties update.
* @param[in] status_code Result of the reported properties update (uses HTTP status code semantics).
*
* @return Nothing.
*/
typedef void (*properties_update_completed_t)(uint32_t request_id, az_iot_status status_code);
/*
* @brief Defines the callback for receiving a writable-properties update.
*
* @param[in] request_id Request ID provided by the caller when sending the reported properties update.
* @param[in] status_code Result of the reported properties update (uses HTTP status code semantics).
*
* @return Nothing.
*/
typedef void (*properties_received_t)(az_span properties);
/*
* @brief Structure containing all the details of a IoT Plug and Play Command.
*/
typedef struct command_request_t_struct
{
/*
* @brief ID of the command request, as received from Azure.
*/
az_span request_id;
/*
* @brief Name of the component this command is targeted to.
* This can be empty (set as AZ_SPAN_EMPTY).
*/
az_span component_name;
/*
* @brief Name of the command.
*/
az_span command_name;
/*
* @brief Optional payload sent by the caller for this command.
*/
az_span payload;
}
command_request_t;
/*
* @brief Defines the callback for receiving an IoT Plug and Play Command.
* @remark A response for this command MUST be provided to Azure by calling
* `azure_iot_send_command_response`.
*
* @param[in] command An instance of `command_request_t` containing all the details of
* of the command received.
*
* @return Nothing.
*/
typedef void (*command_request_received_t)(command_request_t command);
/*
* @brief Defines the callback for getting NTP-sync'd seconds since epcoh until now.
*
* @return uint32_t Number of seconds.
*/
typedef uint32_t (*get_time_t)();
/*
* @brief All the possible statuses returned by `azure_iot_get_status`.
*/
typedef enum azure_iot_status_t_struct
{
/*
* @brief The Azure IoT client is completely disconnected.
*/
azure_iot_disconnected,
/*
* @brief The client is in an intermediate state between disconnected and connected.
* @remark When using SAS-based authentication (default for Azure IoT Central), the client
* will automatically perform a reconnection with a new SAS token after the previous
* one expires, going back into `azure_iot_connecting` state briefly.
*/
azure_iot_connecting,
/*
* @brief In this state the Azure IoT client is ready to be used for messaging.
*/
azure_iot_connected,
/*
* @brief The Azure IoT client encountered some internal error and is no longer active.
* @remark This can be caused by:
* - Memory issues (not enough memory provided in `azure_iot_config_t`'s `data_buffer`);
* - Any failures reported by the application's MQTT client
* (through the abstracted interface) return values;
* - Any protocol errors returned by Azure IoT services.
* If not enough memory has been provided in `azure_iot_config_t`'s `data_buffer,
* please expand the size of that memory space.`
* Once the possible mitigations are applied stop the Azure IoT client
* by calling `azure_iot_stop` (which resets the client state) and restart it
* using `azure_iot_start`.
*/
azure_iot_error
}
azure_iot_status_t;
/*
* @brief Internal states of the Azure IoT client.
* @remark These states are not exposed to the user application.
*/
typedef enum azure_iot_client_state_t_struct
{
azure_iot_state_not_initialized = 0,
azure_iot_state_initialized,
azure_iot_state_started,
azure_iot_state_connecting_to_dps,
azure_iot_state_connected_to_dps,
azure_iot_state_subscribing_to_dps,
azure_iot_state_subscribed_to_dps,
azure_iot_state_provisioning_querying,
azure_iot_state_provisioning_waiting,
azure_iot_state_provisioned,
azure_iot_state_connecting_to_hub,
azure_iot_state_connected_to_hub,
azure_iot_state_subscribing_to_pnp_cmds,
azure_iot_state_subscribed_to_pnp_cmds,
azure_iot_state_subscribing_to_pnp_props,
azure_iot_state_subscribed_to_pnp_props,
azure_iot_state_subscribing_to_pnp_writable_props,
azure_iot_state_ready,
azure_iot_state_refreshing_sas,
azure_iot_state_error
}
azure_iot_client_state_t;
/*
* @brief Structure that holds the configuration for the Azure IoT client.
* @remark Once `azure_iot_start` is called, this structure SHALL NOT be modified by the
* user application unless `azure_iot_stop` is called.
* Also make sure that the instance of `azure_iot_config_t` (and its members) do not
* lose scope throughout the lifetime of the Azure IoT client.
* Most of the members of this structure use az_span for encapsulating a buffer and
* its size. For more details on az_span, please explore the code at
* https://github.com/azure/azure-sdk-for-c.
*/
typedef struct azure_iot_config_t_struct
{
/*
* @brief User agent string to be provided to Azure IoT services.
* @remark This string is not exposed back to the user device or service applications,
* currently only being used internally by Azure IoT. However, this
* data can sometimes be useful for customer support, so we recommend
* providing this information in the standard format expected
* (e.g., #define UA "c%2F" AZ_SDK_VERSION_STRING "(<platform>%3B<architecture>)")
*/
az_span user_agent;
/*
* @brief Controls whether Azure IoT client must perform device-provisioning or not.
* @remark If the configuration provided below is for connecting to Azure Device Provisioning
* Service (dps_id_scope, ...), this flag must be set to true. This is the case when
* connecting to Azure IoT Central as well.
*/
bool use_device_provisioning;
/*
* @brief Fully qualified domain name of the Azure IoT Hub to connect to.
* @remark If performing device-provisioning this MUST be set to AZ_SPAN_EMPTY;
* in such case, this member will be set with the target Azure IoT Hub FQDN
* once device-provisioning is completed.
*/
az_span iot_hub_fqdn;
/*
* @brief Device ID to authenticate as when connecting to Azure IoT Hub.
* @remark If performing device-provisioning this MUST be set to AZ_SPAN_EMPTY;
* in such case, this member will be set with the target device ID
* once device-provisioning is completed.
*/
az_span device_id;
/*
* @brief Symmetric key of the device to authenticate as when connecting to Azure IoT Hub.
* @remark This key will be used to generate the MQTT client password, if using SAS-token
* authentication (which is used, for example, when connecting to Azure IoT Central).
*/
az_span device_key;
/*
* @brief X509 certificate to be used for authentication.
*
*/
az_span device_certificate;
/*
* @brief X509 certificate private key to be used for authentication.
*
*/
az_span device_certificate_private_key;
/*
* @brief The "Registration ID" to authenticate with when connecting to
* Azure Device Provisioning service.
* @remark This information is only needed when performing device-provisioning (which is used,
for example, when connecting to Azure IoT Central). If device-provisioning is
not being used (i.e., Azure IoT client is connecting directly to Azure IoT Hub)
this member MUST be set with AZ_SPAN_EMPTY.
Additionaly (!), if connecting to Azure IoT Central, this member MUST be set
with the `device ID` created in the Azure IoT Central application.
*/
az_span dps_registration_id;
/*
* @brief The "ID Scope" to authenticate with when connecting to
* Azure Device Provisioning service.
* @remark This information is only needed when performing device-provisioning (which is used,
for example, when connecting to Azure IoT Central). If device-provisioning is
not being used (i.e., Azure IoT client is connecting directly to Azure IoT Hub)
this member MUST be set with AZ_SPAN_EMPTY.
*/
az_span dps_id_scope;
/*
* @brief Model ID of the IoT Plug and Play template implemented by the user application.
* @remark This is used only when the application uses/implements IoT Plug and Play.
*/
az_span model_id;
/*
* @brief Buffer with memory space to be used internally by Azure IoT client for its
* internal functions.
* @remark This buffer MUST be large enough to perform all the string manipulations done
* internally by the Azure IoT client, like MQTT client client id, username and password
* generation, as well as storing Azure IoT Hub fqdn and device id after device-provisioning
* is completed (if device-provisioning is used).
* The maximum size required depends on which services and authentication mode are used.
* If using device-provisioning with SAS-token authentication (as used with Azure IoT Central),
* this size must be at least:
* sizeof(data_buffer) >= ( lengthof(<iot-hub-fqdn>) + lengthof(<device-id>) +
* lengthof(<MQTT-clientid>) + lengthof(<MQTT-username>) +
* 2 * lengthof(<MQTT-password>) )
*
* Where:
* <MQTT-clientid> = <device-id> + '\0'
* <MQTT-username> = <iot-hub-fqdn> + '/' + lengthof(<device-id>) + '/' + '?' + <api-version> +
* "&DeviceClientType=" + urlenc(<user-agent>) + "&model-id=" + urlenc(<pnp-model-id>) + '\0'
* <api-version> = "api-version=<YYYY-MM-DD>"
* <MQTT-password>) = "SharedAccessSignature sr=" + <iot-hub-fqdn> + "%2Fdevices%2F" + <device-id> +
* "&sig=" + urlenc(<sha256-string>) + "&se=" + <expiration-time> + '\0'
* lengthof(<sha256-string>) <= lengthof(<64-char-string>)
* <expiration-time> = <10-digit-unix-time>
*
* Note: We use two times the length of <MQTT-password> given the internal operations needed to
* generate it.
*
* Example:
* <iot-hub-fqdn> = "iotc-1a430cf3-6f05-4b84-965d-cb1385077966.azure-devices.net"
* <device-id> = "d1"
* <sha256-string> = "vj7jSTPe4CZqqs5c+DkEpdoHMB+m1rzsFF04JyP9Pr4="
* <pnp-model-id> = "dtmi:azureiot:devkit:freertos:Esp32AzureIotKit;1"
* <api-version> = "api-version=2020-09-30"
* <expiration-time> = "1641251566"
* <user-agent> = "c/1.1.0-beta.1(FreeRTOS)"
*
* sizeof(data_buffer) >= 592 bytes (59 bytes + 2 bytes + 3 bytes + 190 bytes + 338 bytes, respectively)
*/
az_span data_buffer;
/*
* @brief Set of functions to serve as interface between Azure IoT client and the user-application MQTT client.
*/
mqtt_client_interface_t mqtt_client_interface;
/*
* @brief Set of functions for Azure IoT client to perform its necessary data manipulations.
*/
data_manipulation_functions_t data_manipulation_functions;
/*
* @brief Amount of minutes for which the MQTT password should be valid.
* @remark If set to zero, Azure IoT client sets it to the default value of 60 minutes.
* Once this amount of minutes has passed and the MQTT password is expired,
* Azure IoT client triggers its logic to generate a new password and reconnect with
* Azure IoT Hub.
*/
uint32_t sas_token_lifetime_in_minutes;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a completion of properties update.
* @remark A properties update is triggered by the user application when
* `azure_iot_send_properties_update` is called.
*/
properties_update_completed_t on_properties_update_completed;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a writable-properties update received from Azure IoT Hub.
* @remark If IoT Plug and Play is used, a response must be sent back to
* Azure IoT Hub.
*/
properties_received_t on_properties_received;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a device command received from Azure IoT Hub.
* @remark A response must be sent back to Azure IoT Hub by calling
* `azure_iot_send_command_response`.
*/
command_request_received_t on_command_request_received;
/*
* @brief Callback handler used by Azure IoT client to retrive current epoch seconds.
*/
get_time_t get_time;
}
azure_iot_config_t;
/*
* @brief Structure that holds the state of the Azure IoT client.
* @remark None of the members within this structure may be accessed
* directly by the user application. Any of the members in azure_iot_t
* may be renamed, re-ordered, added and/or removed without
* notice.
*/
typedef struct azure_iot_t_struct
{
azure_iot_config_t* config;
az_span data_buffer;
mqtt_client_handle_t mqtt_client_handle;
az_iot_hub_client iot_hub_client;
az_iot_hub_client_options iot_hub_client_options;
az_iot_provisioning_client dps_client;
azure_iot_client_state_t state;
uint32_t sas_token_expiration_time;
uint32_t dps_retry_after_seconds;
uint32_t dps_last_query_time;
az_span dps_operation_id;
}
azure_iot_t;
/*
* @brief Initializes the azure_iot_t structure that holds the Azure IoT client state.
* @remark This function must be called only once per `azure_iot_t` instance,
* before any other function can be called using it.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
* @param[in] azure_iot_config A pointer to a `azure_iot_config_t` containing all the
* configuration neeeded for the client to connect and work with
* the Azure IoT services.
* @return Nothing.
*/
void azure_iot_init(azure_iot_t* azure_iot, azure_iot_config_t* azure_iot_config);
/*
* @brief Starts an Azure IoT client.
* @remark This function must be called once the user application is ready to start
* connecting and working with Azure IoT services. By calling this function
* the `azure_iot_t` instance is reset to the default state it needs to work properly.
* Only after a `azure_iot_t` is started `azure_iot_do_work` will be able to perform
* any tasks. If the Azure IoT client gets into an error state, `azure_iot_stop` must
* be called first, followed by a call to `azure_iot_start` so it can reconnect to
* the Azure IoT Hub again.
* Note: if device-provisioning is used, the device is provisioned only the first
* time a given `azure_iot_t` instance is started. Subsequent calls to
* `azure_iot_start` will re-use the Azure IoT Hub FQDN and device ID previously
* provisioned.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
* @param[in] azure_iot_config A pointer to a `azure_iot_config_t` containing all the
* configuration neeeded for the client to connect and work with
* the Azure IoT services.
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_start(azure_iot_t* azure_iot);
/*
* @brief Stops an Azure IoT client.
* @remark This function must be called once the user application wants to stop working and
* disconnect from the Azure IoT services. The same instance of `azure_iot_t` can be
* used again by the user application by calling `azure_iot_start`.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_stop(azure_iot_t* azure_iot);
/*
* @brief Gets the current state of the Azure IoT client.
* @remark The states informed are simplified for ease-of-use of this client, not reflecting
* all the detailed internal states possible within the Azure IoT client.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return azure_iot_status_t One of four possible states as defined by `azure_iot_status_t`.
* Please see the `azure_iot_status_t` documentation above for details.
*/
azure_iot_status_t azure_iot_get_status(azure_iot_t* azure_iot);
/*
* @brief Causes the Azure IoT client to perform its tasks for connecting and working with Azure IoT services.
* @remark This function must be called frequently enough for the Azure IoT client to work properly.
* That frequency should be enough to respect the timeouts implemented by the Azure IoT services for
* its TCP connection and MQTT-protocol traffic with the client application.
* Calling it once within a main loop() function of most embedded implementations should be enough.
* The recommended minimum frequency is no less than once every 100 milliseconds.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return Nothing.
*/
void azure_iot_do_work(azure_iot_t* azure_iot);
/*
* @brief Sends a telemetry payload to the Azure IoT Hub.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] message An az_span instance containing the buffer and size of the actual message to be sent.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_send_telemetry(azure_iot_t* azure_iot, az_span message);
/**
* @brief Sends a property update message to Azure IoT Hub.
*
* @param[in] azure_iot The pointer to the azure_iot_t instance that holds the state of the Azure IoT client.
* @param[in] request_id An unique identification number to correlate the response with when
* @param[in] message An `az_span` with the message with the reported properties update
* (a JSON document formatted according to the DTDL specification).
* `message` gets passed as-is to the MQTT client publish function as the payload, so if
* your MQTT client expects a null-terminated string for payload, make sure `message` is
* a null-terminated string.
* `on_properties_update_completed` (set in azure_iot_config_t) is invoked.
*
* @return int 0 if the function succeeds, or non-zero if any failure occurs.
*/
int azure_iot_send_properties_update(azure_iot_t* azure_iot, uint32_t request_id, az_span message);
/**
* @brief Sends a property update message to Azure IoT Hub.
*
* @param[in] azure_iot The pointer to the azure_iot_t instance that holds the state of the Azure IoT client.
* @param[in] request_id The same `request_id` of the device command received from Azure IoT Hub.
* @param[in] response_status An HTTP-like code informing the success or failure of the command.
* A 202 will inform the command was accepted and succeeded.
* A 404 will inform the command received does not match the commands supported by
* this device application.
* @param[in] payload A custom payload to be sent in the device command response.
* This is expected to be a json content.
* If no payload is to be sent, please set it as AZ_SPAN_EMPTY.
*
* @return int 0 if the function succeeds, or non-zero if any failure occurs.
*/
int azure_iot_send_command_response(azure_iot_t* azure_iot, az_span request_id, uint16_t response_status, az_span payload);
/*
* @brief Informs the Azure IoT client that the MQTT client is connected.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_init` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has received
* a CONNACK from the Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_connected(azure_iot_t* azure_iot);
/*
* @brief Informs the Azure IoT client that the MQTT client is disconnected.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_deinit` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has disconnected
* from the Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_disconnected(azure_iot_t* azure_iot);
/*
* @brief Informs the Azure IoT client that the MQTT client has subscribed to a topic.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_subscribe` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has
* received a SUBACK for a topic subscription.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] packet_id The ID of the subscription performed previous by the Azure IoT client.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet_id);
/*
* @brief Informs the Azure IoT client that the MQTT client has completed a PUBLISH.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_publish` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has
* completed an MQTT PUBLISH. If the QoS is 0 (AT MOST ONCE), this shall be called by
* the user application right after `mqtt_client_publish` is invoked. If the QoS of
* is 1 (AT LEAST ONCE), this shall be called whenever a PUBACK is received.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] packet_id The ID of the subscription performed previous by the Azure IoT client.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_publish_completed(azure_iot_t* azure_iot, int packet_id);
/*
* @brief Informs the Azure IoT client that a new message has been received by the Azure IoT service.
* @remark This must be called whenever an MQTT message is received from an Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] mqtt_message A `mqtt_message_t` instance containing all the details of the MQTT message
* received (topic name, qos and payload).
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_message_received(azure_iot_t* azure_iot, mqtt_message_t* mqtt_message);
/* --- az_core extensions --- */
/*
* These functions are used internally by the Azure IoT client code and its extensions.
*/
/**
* @brief Slices `span` at position `size`, returns the first slice and assigns the second slice to `remainder`.
*
* @param[in] span A span to be sliced.
* @param[in] source The span to copy the contents from.
* @param[out] remainder The pointer where to store the remainder of `span` after it is sliced.
*
* @return az_span A slice of `span` from position zero to `size`.
*/
az_span split_az_span(az_span span, int32_t size, az_span* remainder);
/**
* @brief Slices `destination` to fit `source`, copy `source` into the first slice and returns the second through `remainder`.
*
* @param[in] destination A span large enough to contain the copy of the contents of `source`.
* @param[in] source The span to copy the contents from.
* @param[out] remainder The pointer where to store the remainder of `destination` after `source` is copied.
*
* @return az_span A slice of `destination` with the same size as `source`, with `source`'s content copied over.
*/
static az_span slice_and_copy_az_span(az_span destination, az_span source, az_span* remainder);
#endif // AZURE_IOT_H

Просмотреть файл

@ -0,0 +1,526 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* This is an Arduino-based Azure IoT Central sample specific for Arduino Nano RP2040 Connect.
* It uses our Azure Embedded SDK for C to help interact with Azure IoT.
* For reference, please visit https://github.com/azure/azure-sdk-for-c and https://azureiotcentral.com/.
*
* To connect and work with Azure IoT Hub you need an MQTT client, connecting, subscribing
* and publishing to specific topics to use the messaging features of the hub.
* Our azure-sdk-for-c is an MQTT client support library, helping composing and parsing the
* MQTT topic names and messages exchanged with the Azure IoT Hub.
*
* The additional layers in this sketch provide a structured use of azure-sdk-for-c and
* the MQTT client of your choice to perform all the steps needed to connect and interact with
* Azure IoT Central.
*
* AzureIoT.cpp contains a state machine that implements those steps, plus abstractions to simplify
* its overall use. Besides the basic configuration needed to access the Azure IoT services,
* all that is needed is to provide the functions required by that layer to:
* - Interact with your MQTT client,
* - Perform data manipulations (HMAC SHA256 encryption, Base64 decoding and encoding),
* - Receive the callbacks for Plug and Play properties and commands.
*
* Azure_IoT_PnP_Template.cpp contains the actual implementation of the IoT Plug and Play template
* specific for the Arduino Nano RP2040 Connect board.
*
* To properly connect to your Azure IoT services, please fill the information in the `iot_configs.h` file.
*/
/* --- Dependencies --- */
// C99 libraries
#include <cstdlib>
#include <cstdarg>
#include <string.h>
#include <time.h>
// For hmac SHA256 encryption
#include <ECCX08.h>
// Libraries for SSL client, MQTT client, and WiFi connection
#include <ArduinoBearSSL.h>
#include <ArduinoMqttClient.h>
#include <WiFiNINA.h>
// Azure IoT SDK for C includes
#include <az_core.h>
#include <az_iot.h>
// Additional sample headers
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include "iot_configs.h"
/* --- Sample-specific Settings --- */
#define MQTT_RETAIN_MSG true
#define MQTT_DO_NOT_RETAIN_MSG !MQTT_RETAIN_MSG
#define SERIAL_LOGGER_BAUD_RATE 115200
/* --- Time and NTP Settings --- */
#define SECS_PER_MIN 60
#define SECS_PER_HOUR (SECS_PER_MIN * 60)
#define GMT_OFFSET_SECS (IOT_CONFIG_DAYLIGHT_SAVINGS ? \
((IOT_CONFIG_TIME_ZONE + IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * SECS_PER_HOUR) : \
(IOT_CONFIG_TIME_ZONE * SECS_PER_HOUR))
/* --- Function Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
/* --- Function Declarations --- */
static void sync_device_clock_with_ntp_server();
static void connect_to_wifi();
static void on_message_received(int message_size);
static String get_formatted_date_time(uint32_t epoch_time_in_seconds);
// This is a logging function used by Azure IoT client.
static void logging_function(log_level_t log_level, char const* const format, ...);
/* --- Sample variables --- */
static azure_iot_config_t azure_iot_config;
static azure_iot_t azure_iot;
static WiFiClient wifi_client;
static BearSSLClient bear_ssl_client(wifi_client);
static MqttClient arduino_mqtt_client(bear_ssl_client);
#define AZ_IOT_DATA_BUFFER_SIZE 1500
static uint8_t az_iot_data_buffer[AZ_IOT_DATA_BUFFER_SIZE];
static uint8_t message_buffer[AZ_IOT_DATA_BUFFER_SIZE];
static uint32_t properties_request_id = 0;
static bool send_device_info = true;
/* --- MQTT Interface Functions --- */
/*
* These functions are used by Azure IoT to interact with whatever MQTT client used by the sample
* (in this case, ArduinoMqttClient). Please see the documentation in AzureIoT.h for more details.
*/
/*
* See the documentation of `mqtt_client_init_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, mqtt_client_handle_t *mqtt_client_handle)
{
int result;
const char* client_id = (const char*)az_span_ptr(mqtt_client_config->client_id);
const char* username = (const char*)az_span_ptr(mqtt_client_config->username);
const char* password = (const char*)az_span_ptr(mqtt_client_config->password);
int port = mqtt_client_config->port;
// Address for DPS is az_span from string literal (#define DPS_GLOBAL_ENDPOINT_FQDN). Null terminated.
// Address for Hub is az_span, retrieved from message from DPS. Not null terminated.
// mqtt_client_init_function() is called in both scenarios.
char address[128] = {0}; // Default to null-termination.
memcpy(address, az_span_ptr(mqtt_client_config->address), az_span_size(mqtt_client_config->address));
arduino_mqtt_client.setId(client_id);
arduino_mqtt_client.setUsernamePassword(username, password);
arduino_mqtt_client.setCleanSession(true);
arduino_mqtt_client.onMessage(on_message_received);
LogInfo("MQTT Client ID: %s", client_id);
LogInfo("MQTT Username: %s", username);
LogInfo("MQTT Password: ***");
LogInfo("MQTT client address: %s", address);
LogInfo("MQTT client port: %d", port);
while (!arduino_mqtt_client.connect(address, port))
{
int code = arduino_mqtt_client.connectError();
LogError("Cannot connect. Error Code: %d", code);
delay(5000);
}
LogInfo("MQTT client connected.");
result = azure_iot_mqtt_client_connected(&azure_iot);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT connection.");
}
else
{
*mqtt_client_handle = &arduino_mqtt_client;
}
return result;
}
/*
* See the documentation of `mqtt_client_deinit_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
{
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
LogInfo("MQTT client being disconnected.");
arduino_mqtt_client_handle->stop();
result = azure_iot_mqtt_client_disconnected(&azure_iot);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT disconnection.");
}
return result;
}
/*
* See the documentation of `mqtt_client_subscribe_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_subscribe_function(mqtt_client_handle_t mqtt_client_handle, az_span topic, mqtt_qos_t qos)
{
LogInfo("MQTT client subscribing to '%.*s'", az_span_size(topic), az_span_ptr(topic));
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
int mqtt_result = arduino_mqtt_client_handle->subscribe((const char*)az_span_ptr(topic), (uint8_t)qos);
if (mqtt_result == 1) // ArduinoMqttClient: 1 on success, 0 on failure
{
LogInfo("MQTT topic subscribed");
int packet_id = 0; // packet id is private in ArduinoMqttClient library.
result = azure_iot_mqtt_client_subscribe_completed(&azure_iot, packet_id);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT subscribe.");
}
}
else
{
LogError("ArduinoMqttClient subscribe failed.");
result = RESULT_ERROR;
}
return result;
}
/*
* See the documentation of `mqtt_client_publish_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_publish_function(mqtt_client_handle_t mqtt_client_handle, mqtt_message_t* mqtt_message)
{
LogInfo("MQTT client publishing to '%s'", az_span_ptr(mqtt_message->topic));
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
int mqtt_result = arduino_mqtt_client_handle->beginMessage(
(const char*)az_span_ptr(mqtt_message->topic),
MQTT_DO_NOT_RETAIN_MSG,
(uint8_t)mqtt_message->qos);
if (mqtt_result == 1) // ArduinoMqttClient: 1 on success, 0 on failure
{
arduino_mqtt_client_handle->print((const char*)az_span_ptr(mqtt_message->payload));
mqtt_result = arduino_mqtt_client_handle->endMessage();
if (mqtt_result == 1)
{
int packet_id = 0; // packet id is private in ArduinoMqttClient library.
result = azure_iot_mqtt_client_publish_completed(&azure_iot, packet_id);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT publish.");
}
}
else
{
LogError("ArduinoMqttClient endMessage failed.");
result = RESULT_ERROR;
}
}
else
{
LogError("ArduinoMqttClient beginMessage failed.");
result = RESULT_ERROR;
}
return result;
}
/* --- Other Interface functions required by Azure IoT --- */
/*
* See the documentation of `hmac_sha256_encryption_function_t` in AzureIoT.h for details.
*/
static int eccx08_hmac_sha256(const uint8_t* key, size_t key_length, const uint8_t* payload, size_t payload_length, uint8_t* signed_payload, size_t signed_payload_size)
{
(void)signed_payload_size;
// HMAC-SHA256 sign the signature with the decoded device key.
ECCX08.begin();
ECCX08.nonce(key);
ECCX08.beginHMAC(0xFFFF);
ECCX08.updateHMAC(payload, payload_length);
ECCX08.endHMAC(signed_payload);
return 0;
}
/*
* See the documentation of `base64_decode_function_t` in AzureIoT.h for details.
*/
static int base64_decode(uint8_t* data, size_t data_length, uint8_t* decoded, size_t decoded_size, size_t* decoded_length)
{
az_span dataSpan = az_span_create(data, data_length);
az_span decodedSpan = az_span_create(decoded, decoded_size);
if (az_base64_decode(decodedSpan, dataSpan, (int32_t*)decoded_length) == AZ_OK) {
return 0;
}
return 1;
}
/*
* See the documentation of `base64_encode_function_t` in AzureIoT.h for details.
*/
static int base64_encode(uint8_t* data, size_t data_length, uint8_t* encoded, size_t encoded_size, size_t* encoded_length)
{
az_span dataSpan =az_span_create(data, data_length);
az_span encodedSpan = az_span_create(encoded, encoded_size);
if (az_base64_encode(encodedSpan, dataSpan, (int32_t*)encoded_length) == AZ_OK) {
return 0;
}
return 1;
}
/*
* See the documentation of `properties_update_completed_t` in AzureIoT.h for details.
*/
static void on_properties_update_completed(uint32_t request_id, az_iot_status status_code)
{
LogInfo("Properties update request completed (id=%d, status=%d)", request_id, status_code);
}
/*
* See the documentation of `properties_received_t` in AzureIoT.h for details.
*/
void on_properties_received(az_span properties)
{
LogInfo("Properties update received: %.*s", az_span_size(properties), az_span_ptr(properties));
// It is recommended not to perform work within callbacks.
// The properties are being handled here to simplify the sample.
if (azure_pnp_handle_properties_update(&azure_iot, properties, properties_request_id++) != 0)
{
LogError("Failed handling properties update.");
}
}
/*
* See the documentation of `command_request_received_t` in AzureIoT.h for details.
*/
static void on_command_request_received(command_request_t command)
{
az_span component_name = az_span_size(command.component_name) == 0 ? AZ_SPAN_FROM_STR("") : command.component_name;
LogInfo("Command request received (id=%.*s, component=%.*s, name=%.*s)",
az_span_size(command.request_id), az_span_ptr(command.request_id),
az_span_size(component_name), az_span_ptr(component_name),
az_span_size(command.command_name), az_span_ptr(command.command_name));
// Here the request is being processed within the callback that delivers the command request.
// However, for production application the recommendation is to save `command` and process it outside
// this callback, usually inside the main thread/task/loop.
(void)azure_pnp_handle_command_request(&azure_iot, command);
}
/*
* See the documentation of `get_time_t` in AzureIoT.h for details.
*/
static uint32_t get_time()
{
return (uint32_t)WiFi.getTime();
}
/* --- Arduino setup and loop Functions --- */
void setup()
{
while (!Serial);
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
set_logging_function(logging_function);
connect_to_wifi();
sync_device_clock_with_ntp_server();
ArduinoBearSSL.onGetTime(get_time); // Required for server trusted root validation.
azure_pnp_init();
/*
* The configuration structure used by Azure IoT must remain unchanged (including data buffer)
* throughout the lifetime of the sample. This variable must also not lose context so other
* components do not overwrite any information within this structure.
*/
azure_iot_config.user_agent = AZ_SPAN_FROM_STR(IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT);
azure_iot_config.model_id = azure_pnp_get_model_id();
azure_iot_config.use_device_provisioning = true; // Required for Azure IoT Central.
azure_iot_config.iot_hub_fqdn = AZ_SPAN_EMPTY;
azure_iot_config.device_id = AZ_SPAN_EMPTY;
azure_iot_config.device_certificate = AZ_SPAN_EMPTY;
azure_iot_config.device_certificate_private_key = AZ_SPAN_EMPTY;
azure_iot_config.device_key = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_KEY);
azure_iot_config.dps_id_scope = AZ_SPAN_FROM_STR(IOT_CONFIG_DPS_ID_SCOPE);
azure_iot_config.dps_registration_id = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_ID); // Use Device ID for Azure IoT Central.
azure_iot_config.data_buffer = AZ_SPAN_FROM_BUFFER(az_iot_data_buffer);
azure_iot_config.sas_token_lifetime_in_minutes = IOT_CONFIG_MQTT_PASSWORD_LIFETIME_IN_MINUTES;
azure_iot_config.mqtt_client_interface.mqtt_client_init = mqtt_client_init_function;
azure_iot_config.mqtt_client_interface.mqtt_client_deinit = mqtt_client_deinit_function;
azure_iot_config.mqtt_client_interface.mqtt_client_subscribe = mqtt_client_subscribe_function;
azure_iot_config.mqtt_client_interface.mqtt_client_publish = mqtt_client_publish_function;
azure_iot_config.data_manipulation_functions.hmac_sha256_encrypt = eccx08_hmac_sha256;
azure_iot_config.data_manipulation_functions.base64_decode = base64_decode;
azure_iot_config.data_manipulation_functions.base64_encode = base64_encode;
azure_iot_config.on_properties_update_completed = on_properties_update_completed;
azure_iot_config.on_properties_received = on_properties_received;
azure_iot_config.on_command_request_received = on_command_request_received;
azure_iot_config.get_time = get_time;
azure_iot_init(&azure_iot, &azure_iot_config);
azure_iot_start(&azure_iot);
LogInfo("Azure IoT client initialized (state=%d)", azure_iot.state);
}
void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
connect_to_wifi();
azure_iot_start(&azure_iot);
}
else
{
switch(azure_iot_get_status(&azure_iot))
{
case azure_iot_connected:
if (send_device_info)
{
(void)azure_pnp_send_device_info(&azure_iot, properties_request_id++);
send_device_info = false; // Only need to send once.
}
else if (azure_pnp_send_telemetry(&azure_iot) != 0)
{
LogError("Failed sending telemetry.");
}
break;
case azure_iot_error:
LogError("Azure IoT client is in error state." );
azure_iot_stop(&azure_iot);
WiFi.disconnect();
break;
default:
break;
}
// MQTT loop must be called to process Telemetry and Cloud-to-Device (C2D) messages.
arduino_mqtt_client.poll();
delay(50);
azure_iot_do_work(&azure_iot);
}
}
/* === Function Implementations === */
/*
* These are support functions used by the sample itself to perform its basic tasks
* of connecting to the internet, syncing the board clock, MQTT client callback
* and logging.
*/
/* --- System and Platform Functions --- */
static void sync_device_clock_with_ntp_server()
{
LogInfo("Setting time using SNTP");
while (get_time() == 0)
{
delay(500);
Serial.print(".");
}
Serial.println("");
LogInfo("Time initialized!");
}
static void connect_to_wifi()
{
LogInfo("Connecting to WIFI wifi_ssid %s", IOT_CONFIG_WIFI_SSID);
WiFi.begin(IOT_CONFIG_WIFI_SSID, IOT_CONFIG_WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
LogInfo("WiFi connected, IP address: %u", WiFi.localIP());
}
void on_message_received(int message_size)
{
LogInfo("MQTT message received.");
mqtt_message_t mqtt_message;
// Copy message topic. Avoids any inadvertant ArduinoMqttClient _rxState or _rxMessageTopic changes.
// messageTopic() must be called before read();
String message_topic = arduino_mqtt_client.messageTopic();
arduino_mqtt_client.read(message_buffer, (size_t)message_size);
mqtt_message.topic = az_span_create((uint8_t*)message_topic.c_str(), message_topic.length());
mqtt_message.payload = az_span_create(message_buffer, message_size);
mqtt_message.qos = mqtt_qos_at_most_once; // QoS is unused by azure_iot_mqtt_client_message_received.
if (azure_iot_mqtt_client_message_received(&azure_iot, &mqtt_message) != 0)
{
LogError("azure_iot_mqtt_client_message_received failed (topic=%s).", arduino_mqtt_client.messageTopic().c_str());
}
}
static String get_formatted_date_time(uint32_t epoch_time_in_seconds)
{
char buffer[256];
time_t time = (time_t)epoch_time_in_seconds;
struct tm* timeInfo = localtime(&time);
strftime(buffer, 20, "%F %T", timeInfo);
return String(buffer);
}
static void logging_function(log_level_t log_level, char const* const format, ...)
{
Serial.print(get_formatted_date_time(get_time() + GMT_OFFSET_SECS));
Serial.print(log_level == log_level_info ? " [INFO] " : " [ERROR] ");
char message[256];
va_list ap;
va_start(ap, format);
int message_length = vsnprintf(message, 256, format, ap);
va_end(ap);
if (message_length < 0)
{
Serial.println("Failed encoding log message (!)");
}
else
{
Serial.println(message);
}
}

Просмотреть файл

@ -0,0 +1,544 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <stdlib.h>
#include <stdarg.h>
#include <az_core.h>
#include <az_iot.h>
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include <az_precondition_internal.h>
/* --- Defines --- */
#define AZURE_PNP_MODEL_ID "dtmi:azureiot:devkit:freertos:Esp32AzureIotKit;1"
#define SAMPLE_DEVICE_INFORMATION_NAME "deviceInformation"
#define SAMPLE_MANUFACTURER_PROPERTY_NAME "manufacturer"
#define SAMPLE_MODEL_PROPERTY_NAME "model"
#define SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME "swVersion"
#define SAMPLE_OS_NAME_PROPERTY_NAME "osName"
#define SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME "processorArchitecture"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME "processorManufacturer"
#define SAMPLE_TOTAL_STORAGE_PROPERTY_NAME "totalStorage"
#define SAMPLE_TOTAL_MEMORY_PROPERTY_NAME "totalMemory"
#define SAMPLE_MANUFACTURER_PROPERTY_VALUE "Arduino"
#define SAMPLE_MODEL_PROPERTY_VALUE "Arduino Nano RP2040 Connect"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "Mbed OS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "RP2040"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "Arduino"
// The next couple properties are in KiloBytes.
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 2640
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 16000
#define TELEMETRY_PROP_NAME_TEMPERATURE "temperature"
#define TELEMETRY_PROP_NAME_HUMIDITY "humidity"
#define TELEMETRY_PROP_NAME_LIGHT "light"
#define TELEMETRY_PROP_NAME_PRESSURE "pressure"
#define TELEMETRY_PROP_NAME_ALTITUDE "altitude"
#define TELEMETRY_PROP_NAME_MAGNETOMETERX "magnetometerX"
#define TELEMETRY_PROP_NAME_MAGNETOMETERY "magnetometerY"
#define TELEMETRY_PROP_NAME_MAGNETOMETERZ "magnetometerZ"
#define TELEMETRY_PROP_NAME_PITCH "pitch"
#define TELEMETRY_PROP_NAME_ROLL "roll"
#define TELEMETRY_PROP_NAME_ACCELEROMETERX "accelerometerX"
#define TELEMETRY_PROP_NAME_ACCELEROMETERY "accelerometerY"
#define TELEMETRY_PROP_NAME_ACCELEROMETERZ "accelerometerZ"
static az_span COMMAND_NAME_TOGGLE_LED_1 = AZ_SPAN_FROM_STR("ToggleLed1");
static az_span COMMAND_NAME_TOGGLE_LED_2 = AZ_SPAN_FROM_STR("ToggleLed2");
static az_span COMMAND_NAME_DISPLAY_TEXT = AZ_SPAN_FROM_STR("DisplayText");
#define COMMAND_RESPONSE_CODE_ACCEPTED 202
#define COMMAND_RESPONSE_CODE_REJECTED 404
#define WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS "telemetryFrequencySecs"
#define WRITABLE_PROPERTY_RESPONSE_SUCCESS "success"
#define DOUBLE_DECIMAL_PLACE_DIGITS 2
/* --- Function Checks and Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
#define EXIT_IF_TRUE(condition, retcode, message, ...) \
do \
{ \
if (condition) \
{ \
LogError(message, ##__VA_ARGS__ ); \
return retcode; \
} \
} while (0)
#define EXIT_IF_AZ_FAILED(azresult, retcode, message, ...) \
EXIT_IF_TRUE(az_result_failed(azresult), retcode, message, ##__VA_ARGS__ )
/* --- Data --- */
#define DATA_BUFFER_SIZE 1024
static uint8_t data_buffer[DATA_BUFFER_SIZE];
static uint32_t telemetry_send_count = 0;
static size_t telemetry_frequency_in_seconds = 10; // With default frequency of once in 10 seconds.
static time_t last_telemetry_send_time = INDEFINITE_TIME;
static bool led1_on = false;
static bool led2_on = false;
/* --- Function Prototypes --- */
/* Please find the function implementations at the bottom of this file */
static int generate_telemetry_payload(
uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length);
static int generate_device_info_payload(
az_iot_hub_client const* hub_client, uint8_t* payload_buffer,
size_t payload_buffer_size, size_t* payload_buffer_length);
static int consume_properties_and_generate_response(
azure_iot_t* azure_iot, az_span properties,
uint8_t* buffer, size_t buffer_size, size_t* response_length);
/* --- Public Functions --- */
void azure_pnp_init()
{
}
const az_span azure_pnp_get_model_id()
{
return AZ_SPAN_FROM_STR(AZURE_PNP_MODEL_ID);
}
void azure_pnp_set_telemetry_frequency(size_t frequency_in_seconds)
{
telemetry_frequency_in_seconds = frequency_in_seconds;
LogInfo("Telemetry frequency set to once every %d seconds.", telemetry_frequency_in_seconds);
}
/* Application-specific data section */
int azure_pnp_send_telemetry(azure_iot_t* azure_iot)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
time_t now = time(NULL);
if (now == INDEFINITE_TIME)
{
LogError("Failed getting current time for controlling telemetry.");
return RESULT_ERROR;
}
else if (last_telemetry_send_time == INDEFINITE_TIME ||
difftime(now, last_telemetry_send_time) >= telemetry_frequency_in_seconds)
{
size_t payload_size;
last_telemetry_send_time = now;
if (generate_telemetry_payload(data_buffer, DATA_BUFFER_SIZE, &payload_size) != RESULT_OK)
{
LogError("Failed generating telemetry payload.");
return RESULT_ERROR;
}
if (azure_iot_send_telemetry(azure_iot, az_span_create(data_buffer, payload_size)) != 0)
{
LogError("Failed sending telemetry.");
return RESULT_ERROR;
}
}
return RESULT_OK;
}
int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
int result;
size_t length;
result = generate_device_info_payload(&azure_iot->iot_hub_client, data_buffer, DATA_BUFFER_SIZE, &length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed generating telemetry payload.");
result = azure_iot_send_properties_update(azure_iot, request_id, az_span_create(data_buffer, length));
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed sending reported properties update.");
return RESULT_OK;
}
int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t command)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
uint16_t response_code;
if (az_span_is_content_equal(command.command_name, COMMAND_NAME_TOGGLE_LED_1))
{
led1_on = !led1_on;
LogInfo("LED 1 state: %s", (led1_on ? "ON" : "OFF"));
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else if (az_span_is_content_equal(command.command_name, COMMAND_NAME_TOGGLE_LED_2))
{
led2_on = !led2_on;
LogInfo("LED 2 state: %s", (led2_on ? "ON" : "OFF"));
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else if (az_span_is_content_equal(command.command_name, COMMAND_NAME_DISPLAY_TEXT))
{
// The payload comes surrounded by quotes, so to remove them we offset the payload by 1 and its size by 2.
LogInfo("OLED display: %.*s", az_span_size(command.payload) - 2, az_span_ptr(command.payload) + 1);
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else
{
LogError("Command not recognized (%.*s).", az_span_size(command.command_name), az_span_ptr(command.command_name));
response_code = COMMAND_RESPONSE_CODE_REJECTED;
}
return azure_iot_send_command_response(azure_iot, command.request_id, response_code, AZ_SPAN_EMPTY);
}
int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span properties, uint32_t request_id)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
_az_PRECONDITION_VALID_SPAN(properties, 1, false);
int result;
size_t length;
result = consume_properties_and_generate_response(azure_iot, properties, data_buffer, DATA_BUFFER_SIZE, &length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed generating properties ack payload.");
result = azure_iot_send_properties_update(azure_iot, request_id, az_span_create(data_buffer, length));
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed sending reported properties update.");
return RESULT_OK;
}
/* --- Internal Functions --- */
static float simulated_get_temperature()
{
return 21.0;
}
static float simulated_get_humidity()
{
return 88.0;
}
static float simulated_get_ambientLight()
{
return 700.0;
}
static void simulated_get_pressure_altitude(float* pressure, float* altitude)
{
*pressure = 55.0;
*altitude = 700.0;
}
static void simulated_get_magnetometer(int32_t* magneticFieldX, int32_t* magneticFieldY, int32_t* magneticFieldZ)
{
*magneticFieldX = 2000;
*magneticFieldY = 3000;
*magneticFieldZ = 4000;
}
static void simulated_get_pitch_roll_accel(int32_t* pitch, int32_t* roll, int32_t* accelerationX, int32_t* accelerationY, int32_t* accelerationZ)
{
*pitch = 30;
*roll = 90;
*accelerationX = 33;
*accelerationY = 44;
*accelerationZ = 55;
}
static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
{
az_json_writer jw;
az_result rc;
az_span payload_buffer_span = az_span_create(payload_buffer, payload_buffer_size);
az_span json_span;
float temperature, humidity, light, pressure, altitude;
int32_t magneticFieldX, magneticFieldY, magneticFieldZ;
int32_t pitch, roll, accelerationX, accelerationY, accelerationZ;
// Acquiring the simulated data.
temperature = simulated_get_temperature();
humidity = simulated_get_humidity();
light = simulated_get_ambientLight();
simulated_get_pressure_altitude(&pressure, &altitude);
simulated_get_magnetometer(&magneticFieldX, &magneticFieldY, &magneticFieldZ);
simulated_get_pitch_roll_accel(&pitch, &roll, &accelerationX, &accelerationY, &accelerationZ);
rc = az_json_writer_init(&jw, payload_buffer_span, NULL);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed initializing json writer for telemetry.");
rc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed setting telemetry json root.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_TEMPERATURE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding temperature property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, temperature, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding temperature property value to telemetry payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_HUMIDITY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding humidity property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, humidity, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding humidity property value to telemetry payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_LIGHT));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding light property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, light, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding light property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_PRESSURE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pressure property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, pressure, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pressure property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ALTITUDE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding altitude property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, altitude, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding altitude property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERX));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(X) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldX);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(X) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Y) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldY);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Y) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERZ));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Z) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldZ);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Z) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_PITCH));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pitch property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, pitch);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pitch property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ROLL));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding roll property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, roll);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding roll property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERX));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(X) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationX);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(X) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Y) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationY);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Y) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERZ));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Z) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationZ);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Z) property value to telemetry payload.");
rc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing telemetry json payload.");
payload_buffer_span = az_json_writer_get_bytes_used_in_destination(&jw);
if ((payload_buffer_size - az_span_size(payload_buffer_span)) < 1)
{
LogError("Insufficient space for telemetry payload null terminator.");
return RESULT_ERROR;
}
payload_buffer[az_span_size(payload_buffer_span)] = null_terminator;
*payload_buffer_length = az_span_size(payload_buffer_span);
return RESULT_OK;
}
static int generate_device_info_payload(az_iot_hub_client const* hub_client, uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
{
az_json_writer jw;
az_result rc;
az_span payload_buffer_span = az_span_create(payload_buffer, payload_buffer_size);
az_span json_span;
rc = az_json_writer_init(&jw, payload_buffer_span, NULL);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed initializing json writer for telemetry.");
rc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed setting telemetry json root.");
rc = az_iot_hub_client_properties_writer_begin_component(
hub_client, &jw, AZ_SPAN_FROM_STR(SAMPLE_DEVICE_INFORMATION_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed writting component name.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_MANUFACTURER_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MANUFACTURER_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_MANUFACTURER_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MANUFACTURER_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_MODEL_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MODEL_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_MODEL_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MODEL_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_VERSION_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_VERSION_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_OS_NAME_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_OS_NAME_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_OS_NAME_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_OS_NAME_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_ARCHITECTURE_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_ARCHITECTURE_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_TOTAL_STORAGE_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_STORAGE_PROPERTY_NAME to payload.");
rc = az_json_writer_append_double(&jw, SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_TOTAL_MEMORY_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_MEMORY_PROPERTY_NAME to payload.");
rc = az_json_writer_append_double(&jw, SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE to payload. ");
rc = az_iot_hub_client_properties_writer_end_component(hub_client, &jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing component object.");
rc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing telemetry json payload.");
payload_buffer_span = az_json_writer_get_bytes_used_in_destination(&jw);
if ((payload_buffer_size - az_span_size(payload_buffer_span)) < 1)
{
LogError("Insufficient space for telemetry payload null terminator.");
return RESULT_ERROR;
}
payload_buffer[az_span_size(payload_buffer_span)] = null_terminator;
*payload_buffer_length = az_span_size(payload_buffer_span);
return RESULT_OK;
}
static int generate_properties_update_response(
azure_iot_t* azure_iot,
az_span component_name, int32_t frequency, int32_t version,
uint8_t* buffer, size_t buffer_size, size_t* response_length)
{
az_result azrc;
az_json_writer jw;
az_span response = az_span_create(buffer, buffer_size);
azrc = az_json_writer_init(&jw, response, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed initializing json writer for properties update response.");
azrc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed opening json in properties update response.");
// This Azure PnP Template does not have a named component,
// so az_iot_hub_client_properties_writer_begin_component is not needed.
azrc = az_iot_hub_client_properties_writer_begin_response_status(
&azure_iot->iot_hub_client,
&jw,
AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS),
(int32_t)AZ_IOT_STATUS_OK,
version,
AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_RESPONSE_SUCCESS));
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed appending status to properties update response.");
azrc = az_json_writer_append_int32(&jw, frequency);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed appending frequency value to properties update response.");
azrc = az_iot_hub_client_properties_writer_end_response_status(&azure_iot->iot_hub_client, &jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed closing status section in properties update response.");
// This Azure PnP Template does not have a named component,
// so az_iot_hub_client_properties_writer_end_component is not needed.
azrc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed closing json in properties update response.");
*response_length = az_span_size(az_json_writer_get_bytes_used_in_destination(&jw));
return RESULT_OK;
}
static int consume_properties_and_generate_response(
azure_iot_t* azure_iot, az_span properties,
uint8_t* buffer, size_t buffer_size, size_t* response_length)
{
int result;
az_json_reader jr;
az_span component_name;
int32_t version = 0;
az_result azrc = az_json_reader_init(&jr, properties, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed initializing json reader for properties update.");
const az_iot_hub_client_properties_message_type message_type =
AZ_IOT_HUB_CLIENT_PROPERTIES_MESSAGE_TYPE_WRITABLE_UPDATED;
azrc = az_iot_hub_client_properties_get_properties_version(
&azure_iot->iot_hub_client, &jr, message_type, &version);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed writable properties version.");
azrc = az_json_reader_init(&jr, properties, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed re-initializing json reader for properties update.");
while (az_result_succeeded(
azrc = az_iot_hub_client_properties_get_next_component_property(
&azure_iot->iot_hub_client, &jr, message_type,
AZ_IOT_HUB_CLIENT_PROPERTY_WRITABLE, &component_name)))
{
if (az_json_token_is_text_equal(&jr.token, AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS)))
{
int32_t value;
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed getting writable properties next token.");
azrc = az_json_token_get_int32(&jr.token, &value);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed getting writable properties int32_t value.");
azure_pnp_set_telemetry_frequency((size_t)value);
result = generate_properties_update_response(
azure_iot, component_name, value, version, buffer, buffer_size, response_length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "generate_properties_update_response failed.");
}
else
{
LogError("Unexpected property received (%.*s).",
az_span_size(jr.token.slice), az_span_ptr(jr.token.slice));
}
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed moving to next json token of writable properties.");
azrc = az_json_reader_skip_children(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed skipping children of writable properties.");
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed moving to next json token of writable properties (again).");
}
return RESULT_OK;
}

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* Azure_IoT_PnP_Template.cpp implements the IoT Plug and Play template
* specific for the Arduino Nano RP2040 Connect board.
*/
#ifndef AZURE_IOT_PNP_TEMPLATE_H
#define AZURE_IOT_PNP_TEMPLATE_H
#include "AzureIoT.h"
/*
* @brief Initializes internal components of this module.
* @remark It must be called once by the application, before any other function
* call related to Azure IoT.
*/
void azure_pnp_init();
/*
* @brief Returns the model id of the IoT Plug and Play template implemented by this device.
* @remark Every IoT Plug and Play template has a model id that must be informed by the
* device during Azure IoT provisioning and connection with the Azure IoT Hub.
* @return az_span An `az_span` containing the model id implemented by this module.
*/
const az_span azure_pnp_get_model_id();
/*
* @brief Sends the device description to Azure IoT Central.
* @remark Azure IoT Central expects the application to send a description of device and its capabilities.
* This function generates a description of the Arduino Nano RP2040 Connect and sends it to
* Azure IoT Central.
*
* @param[in] azure_iot A pointer the azure_iot_t instance with the state of the Azure IoT client.
* @param[in] request_id An unique identification number to correlate the response with when
* `on_properties_update_completed` (set in azure_iot_config_t) is invoked.
* @return int 0 if the function succeeds, non-zero if any error occurs.
*/
int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id);
/*
* @brief Sets with which minimum frequency this module should send telemetry to Azure IoT Central.
* @remark `azure_pnp_send_telemetry` is used to send telemetry, but it will not send anything
* unless enough time has passed since the last telemetry has been published.
* This delay is defined internally by `telemetry_frequency_in_seconds`,
* set initially to once every 10 seconds.
*
* @param[in] frequency_in_seconds Period of time, in seconds, to wait between two consecutive
* telemetry payloads are sent to Azure IoT Central.
*/
void azure_pnp_set_telemetry_frequency(size_t frequency_in_seconds);
/*
* @brief Sends telemetry implemented by this IoT Plug and Play application to Azure IoT Central.
* @remark The IoT Plug and Play template implemented by this device is specific to the
* Arduino Nano RP2040 Connect board, which contains several sensors. The template
* defines telemetry data points for temperature, humidity, pressure, altitude,
* luminosity, magnetic field, rolling and pitch angles, as well as acceleration. All of
* these data are read from the board sensors and sent to Azure IoT Central when
* `azure_pnp_send_telemetry` is called. This function must be called frequently enough,
* no slower than the frequency set with `azure_pnp_set_telemetry_frequency` (or the
* default frequency of 10 seconds).
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_send_telemetry(azure_iot_t* azure_iot);
/*
* @brief Handles a command when it is received from Azure IoT Central.
* @remark This function will perform the task requested by the command received
* (if the command matches the expected name) and sends back a response to
* Azure IoT Central.
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
* @param[in] command_request The `command_request_t` instance containing the details of the
* device command.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t command_request);
/*
* @brief Handles a payload with writable properties received from Azure IoT Central.
* @remark This function will consume the writable properties update received
* and send back a response to Azure IoT Central.
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
* @param[in] properties Raw properties writable-properties payload received from Azure.
* @param[in] request_id The request ID of the response that is sent to the Azure IoT Central.
* In Azure IoT Plug and Play, a response to a writable-property update is
* itself a reported-property (device-side property) update, so it gets a
* a response from Azure with the same request ID provided here as argument.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span properties, uint32_t request_id);
#endif // AZURE_IOT_PNP_TEMPLATE_H

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Azure IoT Central
#define IOT_CONFIG_DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
// User-agent (url-encoded) provided by the MQTT client to Azure IoT Services.
// When developing for your own Arduino-based platform, please follow the format '(ard;<platform>)'.
#define IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard;nanorp2040connect)"
// For how long the MQTT password (SAS token) is valid, in minutes.
// After that, the sample automatically generates a new password and re-connects.
#define IOT_CONFIG_MQTT_PASSWORD_LIFETIME_IN_MINUTES 60
// Time Zone Offset
#define IOT_CONFIG_TIME_ZONE -8
#define IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define IOT_CONFIG_DAYLIGHT_SAVINGS true

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 152 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 118 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 27 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 85 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 84 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 69 KiB

Просмотреть файл

@ -0,0 +1,343 @@
---
page_type: sample
description: Connecting Arduino Nano RP2040 Connect to Azure IoT Central using the Azure SDK for C Arduino library
languages:
- c
products:
- azure-iot
- azure-iot-pnp
- azure-iot-central
---
# Getting started with the Arduino Nano RP2040 Connect and Azure IoT Central with Azure SDK for C Arduino library
**Total completion time**: 30 minutes
- [Getting started with the Arduino Nano RP2040 Connect and Azure IoT Central with Azure SDK for C Arduino library](#getting-started-with-the-arduino-nano-rp2040-connect-and-azure-iot-central-with-azure-sdk-for-c-arduino-library)
- [Introduction](#introduction)
- [What is Covered](#what-is-covered)
- [Prerequisites](#prerequisites)
- [IoT Central and Device Setup](#iot-central-and-device-setup)
- [Create the IoT Central Application](#create-the-iot-central-application)
- [Create a new device](#create-a-new-device)
- [Arduino IDE Setup](#arduino-ide-setup)
- [Run the Sample](#run-the-sample)
- [View your device data from IoT Central](#view-your-device-data-from-iot-central)
- [Verify the device status](#verify-the-device-status)
- [View device information](#view-device-information)
- [View telemetry](#view-telemetry)
- [Send a command](#send-a-command)
- [Clean up resources](#clean-up-resources)
- [Certificates - Important to know](#certificates---important-to-know)
- [Additional Information](#additional-information)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## Introduction
In this tutorial you will use the Azure SDK for C to connect the [Arduino Nano RP2040 Connect](https://docs.arduino.cc/hardware/nano-rp2040-connect) to Azure IoT Central. The article is part of the series [IoT Device Development](https://go.microsoft.com/fwlink/p/?linkid=2129824). The series introduces device developers to the Azure SDK for C, and shows how to connect several device evaluation kits to Azure IoT.
### What is Covered
You will complete the following tasks:
* Install the Azure SDK for C library on Arduino
* Build the image and flash it onto the Arduino Nano RP2040 Connect
* Use Azure IoT Central to create cloud components, view properties, view device telemetry, and call direct commands
_The following was run on Windows 10 and WSL1 Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.19 and Arduino Arduino Nano RP2040 Connect with headers._
## Prerequisites
* Have an [Azure account](https://azure.microsoft.com/) created.
* Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
## IoT Central and Device Setup
### Create the IoT Central Application
There are several ways to connect devices to Azure IoT. In this section, you learn how to connect a device by using Azure IoT Central. IoT Central is an IoT application platform that reduces the cost and complexity of creating and managing IoT solutions.
To create a new application:
1. Go to [Azure IoT Central portal](https://apps.azureiotcentral.com/).
1. On the left side menu, select **'My apps'**.
1. Select **'+ New application'**.
1. In the 'Custom app' box, select **'Create app'**.
1. Create a custom Application name and a URL.
1. Under 'Pricing plan', select **'Free'** to activate a 7-day trial.
![IoT Central create an application](media/iotcentralcreate-custom.png)
1. Select **'Create'**.
1. After IoT Central provisions the application, it redirects you automatically to the new application dashboard.
> Note: If you have an existing IoT Central application, you can use it to complete the steps in this article rather than create a new application.
### Create a new device
In this section, you will use the IoT Central application dashboard to create a new logical device.
To create a device:
1. On the left side menu, under 'Connect', select **'Devices'**.
1. Select **'+ New'**. A 'Create a new device' window will appear.
1. Fill in the desired 'Device name' and 'Device ID'.
1. Leave Device template as 'Unassigned'.
![IoT Central create a device](media/iotcentralcreate-device.png)
1. Select **'Create'**. The newly created device will appear in the 'All devices' list.
1. Under 'Device name', select your newly created device name.
1. In the top menu bar, select **'Connect'**. A 'Device connection groups' window will appear.
![IoT Central create a device](media/iotcentraldevice-connection-info.png)
1. We will need the following information from this window:
- ID scope
- Device ID
- Primary key
_NOTE: Device keys are used to automatically generate a SAS token for authentication, which is only valid for one hour._
## Arduino IDE Setup
1. Open the Arduino IDE.
1. Install the Azure SDK for Embedded C library.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'azure-sdk-for-c'** library.
- Install the latest version.
1. Install Arduino Mbed OS Nano Boards support in the Arduino IDE. [Full instructions can be found here.](https://docs.arduino.cc/hardware/nano-rp2040-connect)
- Navigate to **Tools > Board > Boards Manager**.
- Search for **'RP2040'** and install the **Arduino Mbed OS Nano Boards** core.
- Install the latest version.
*Note: This process may take several minutes.*
1. Nagivate to **Tools > Board > Arduino Mbed OS Nano Boards** and select **'Arduino Nano RP2040 Connect'**.
1. Install WiFiNINA library for the Nano RP2040 Embedded C SDK sample.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'WiFiNINA'** library.
- Install the latest version.
*Note: This process may take several minutes.*
1. If this is your first time using the Nano RP2040 Connect, [follow these instructions to update the WiFi firmware on the Nano RP2040 Connect](https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-upgrading-nina-firmware).
1. Install the ArduinoBearSSL, ArduinoMqttClient, and ArduinoECCX08 libraries.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'ArduinoBearSSL'** library. Install the latest version.
- Search for the **'ArduinoMqttClient'** library. Install the latest version.
- Search for the **'ArduinoECCX08'** library. Install the latest version.
1. You may need to restart the Arduino IDE for changes to show up.
## Run the Sample
1. Open the Arduino Nano RP2040 Connect sample.
- In the Arduino IDE, navigate to **File > Examples > Azure SDK For C**
- Select **'Azure_IoT_Central_Arduino_Nano_RP2040_Connect'** to open the sample.
1. Navigate to the '*iot_configs.h*' file
1. In the '*iot_configs.h*' file, fill in your credentials.
- Add in your WiFi SSID and password.
- Paste your ID Scope for the `IOT_CONFIG_DPS_ID_SCOPE` variable.
- Paste your Device ID for the `IOT_CONFIG_DEVICE_ID` variable.
- Finally, paste your Primary key for the `IOT_CONFIG_DEVICE_KEY` variable.
1. Connect the Arduino Nano RP 2040 Connect to your USB port.
1. On the Arduino IDE, select the port.
- Navigate to **Tools > Port**.
- Select the port to which the Nano RP2040 Connect is connected.
1. Upload the sketch.
- Navigate to **Sketch > Upload**.
*Note: This process may take several minutes.*
<details><summary><i>Expected output of the upload:</i></summary>
<p>
```text
Sketch uses 207740 bytes (1%) of program storage space. Maximum is 16777216 bytes.
Global variables use 66812 bytes (24%) of dynamic memory, leaving 203524 bytes for local variables. Maximum is 270336 bytes.
.
```
</p>
</details>
1. While the sketch is uploading, open the Serial Monitor to monitor the MCU (microcontroller) locally via the Serial Port.
- Navigate to **Tools > Serial Monitor**.
If you perform this step right away after uploading the sketch, the serial monitor will show an output similar to the following upon success:
```text
2106-02-06 23:28:16 [INFO] Connecting to WIFI wifi_ssid <ssid>
..
2106-02-06 23:28:16 [INFO] WiFi connected, IP address: 536923696
2106-02-06 23:28:16 [INFO] Setting time using SNTP
....
2022-06-16 16:03:03 [INFO] Time initialized!
2022-06-16 16:03:03 [INFO] Azure IoT client initialized (state=2)
2022-06-16 16:03:04 [INFO] MQTT Client ID: <device id>
2022-06-16 16:03:04 [INFO] MQTT Username: <scope id>/registrations/<device id>/api-version=2019-03-31
2022-06-16 16:03:04 [INFO] MQTT Password: ***
2022-06-16 16:03:04 [INFO] MQTT client address: global.azure-devices-provisioning.net
2022-06-16 16:03:04 [INFO] MQTT client port: 8883
2022-06-16 16:03:08 [INFO] MQTT client connected.
2022-06-16 16:03:09 [INFO] MQTT client subscribing to '$dps/registrations/res/#'
2022-06-16 16:03:09 [INFO] MQTT topic subscribed
2022-06-16 16:03:09 [INFO] MQTT client publishing to '$dps/registrations/PUT/iotdps-register/?$rid=1'
2022-06-16 16:03:10 [INFO] MQTT message received.
2022-06-16 16:03:11 [INFO] MQTT client publishing to '$dps/registrations/GET/iotdps-get-operationstatus/?$rid=1&operationId=4.36e237c8db462f45.7081bb47-3e8f-4e1e-af40-da3477582335'
2022-06-16 16:03:11 [INFO] MQTT message received.
2022-06-16 16:03:14 [INFO] MQTT client publishing to '$dps/registrations/GET/iotdps-get-operationstatus/?$rid=1&operationId=4.36e237c8db462f45.7081bb47-3e8f-4e1e-af40-da3477582335'
2022-06-16 16:03:14 [INFO] MQTT message received.
2022-06-16 16:03:15 [INFO] MQTT client being disconnected.
2022-06-16 16:03:15 [INFO] MQTT Client ID: <device id>
2022-06-16 16:03:15 [INFO] MQTT Username: <provisioned iot hub fqdn>.azure-devices.net/<device id>/?api-version=2020-09-30&DeviceClientType=c%2F1.3.1(ard;portentaH7)&model-id=dtmi%3Aazureiot%3Adevkit%3Afreertos%3AEsp32AzureIotKit%3B1
2022-06-16 16:03:15 [INFO] MQTT Password: ***
2022-06-16 16:03:15 [INFO] MQTT client address: <provisioned iot hub fqdn>.azure-devices.net
2022-06-16 16:03:15 [INFO] MQTT client port: 8883
2022-06-16 16:03:18 [INFO] MQTT client connected.
2022-06-16 16:03:18 [INFO] MQTT client subscribing to '$iothub/methods/POST/#'
2022-06-16 16:03:19 [INFO] MQTT topic subscribed
2022-06-16 16:03:19 [INFO] MQTT client subscribing to '$iothub/twin/res/#'
2022-06-16 16:03:19 [INFO] MQTT topic subscribed
2022-06-16 16:03:20 [INFO] MQTT client subscribing to '$iothub/twin/PATCH/properties/desired/#'
2022-06-16 16:03:20 [INFO] MQTT topic subscribed
2022-06-16 16:03:20 [INFO] MQTT client publishing to '$iothub/twin/PATCH/properties/reported/?$rid=0'
2022-06-16 16:03:20 [INFO] MQTT client publishing to 'devices/<device id>/messages/events/'
2022-06-16 16:03:20 [INFO] MQTT message received.
2022-06-16 16:03:20 [INFO] Properties update request completed (id=0, status=204)
2022-06-16 16:03:29 [INFO] MQTT client publishing to 'devices/<device id>/messages/events/'
```
## View your device data from IoT Central
With IoT Central, you can view the device status and information, observe telemetry, and send commands.
1. Go to your [IoT Central application portal](https://apps.azureiotcentral.com/myapps).
1. Select your application.
1. On the left side menu, under 'Connect', select **'Devices'**.
### Verify the device status
To view the device status in IoT Central portal:
1. Find your device in the devices list.
1. Confirm the 'Device status' of the device is updated to 'Provisioned'.
1. Confirm the 'Device template' of the device has updated to 'Espressif ESP32 Azure IoT Kit'.
> Note: The **'Espressif ESP32 Azure IoT Kit'** device template is used in this **Arduino Nano RP2040 Connect sample for simplicity.** It is a published template available from IoT Central. For more information on creating a custom device template, view these [instructions](https://docs.microsoft.com/en-us/azure/iot-central/core/howto-set-up-template).
![IoT Central device status](media/azure-iot-central-device-view-status.png)
### View device information
To view the device information in IoT Central portal:
1. Click on your device's name in the device list.
1. Select the **'About'** tab.
![IoT Central device info](media/azure-iot-central-device-about.png)
### View telemetry
To view telemetry in IoT Central portal:
1. Click on your device's name in the device list.
1. Select the **'Overview'** tab.
1. View the telemetry as the device sends messages to the cloud.
![IoT Central device telemetry](media/azure-iot-central-device-telemetry.png)
### Send a command
To send a command to the device:
1. Select the **'Commands'** tab.
1. Locate the 'Display Text' box.
1. In the 'Content' textbox, enter the text to be displayed on the screen.
1. Select **'Run'**.
1. Because this is a simulated screen, the text will print to the log.
```
2022-06-16 13:31:50 [INFO] OLED display: <text>
```
To toggle an LED:
1. Select the **'Commands'** tab.
1. Locate the 'Toggle LED 1' or 'Toggle LED 2' box.
1. Select **'Run'**.
1. Because these are simulated LEDs, the following will print to the log.
```
2022-06-16 13:31:46 [INFO] LED <#> state: <ON/OFF>
```
![IoT Central invoke method](media/azure-iot-central-invoke-method.png)
### Clean up resources
If you no longer need the Azure resources created in this tutorial, you can delete them from the IoT Central portal. Optionally, if you continue to another tutorial in this Getting Started guide, you can keep the resources you've already created and reuse them.
To keep the Azure IoT Central sample application but remove only specific devices:
1. On the left side menu, under 'Connect', select **'Devices'**.
1. Hover over your device's name and click on the circle that appears to the left. The circle will turn blue.
1. Select **'Delete'**. A box will appear to confirm deletion.
1. Select **'Delete'** again.
To remove the entire Azure IoT Central sample application and all its devices and resources:
1. On the left side menu, under 'Settings', select **'Application'**.
1. Select the **'Management'** tab.
1. Scroll to the bottom of the page.
1. Select **'Delete'**. A box will appear to confirm deletion.
1. Select **'Delete'** again.
## Certificates - Important to know
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
The Azure SDK for C Arduino library automatically installs the root certificate used in the United States regions, and adds it to the Arduino sketch project when the library is included.
For other regions (and private cloud environments), please use the appropriate root CA certificate.
### Additional Information
For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team.
## Troubleshooting
- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy).
- File an issue via [Github Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose).
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using the `azure` and `c` tags.
## Contributing
This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md).
### License
Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,826 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* AzureIoT.cpp contains a state machine that implements the necessary calls to azure-sdk-for-c
* for aiding to connect and work with Azure IoT services, plus abstractions to
* simplify overall use of azure-sdk-for-c.
* Besides the basic configuration needed to access the Azure IoT services,
* all that is needed is to provide the functions required by that layer to:
* - Interact with your MQTT client,
* - Perform data manipulations (HMAC SHA256 encryption, Base64 decoding and encoding),
* - Receive the callbacks for Plug and Play properties and commands.
*/
#ifndef AZURE_IOT_H
#define AZURE_IOT_H
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <az_core.h>
#include <az_iot.h>
/* --- Array and String Helpers --- */
#define lengthof(s) (sizeof(s) - 1)
#define sizeofarray(a) (sizeof(a) / sizeof(a[0]))
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
static const uint8_t null_terminator = '\0';
/* --- Time --- */
#define INDEFINITE_TIME ((time_t)-1)
/* --- Logging --- */
#ifndef DISABLE_LOGGING
typedef enum log_level_t_enum
{
log_level_info,
log_level_error
}
log_level_t;
typedef void (*log_function_t)(log_level_t log_level, char const* const format, ...);
extern log_function_t default_logging_function;
#define set_logging_function(custom_logging_function) \
default_logging_function = custom_logging_function;
#define Log(level, message, ...) default_logging_function(level, message, ##__VA_ARGS__ )
#define LogInfo(message, ...) Log(log_level_info, message, ##__VA_ARGS__ )
#define LogError(message, ...) Log(log_level_error, message, ##__VA_ARGS__ )
#else
#define set_logging_function(custom_logging_function)
#define Log(level, message, ...)
#define LogInfo(message, ...)
#define LogError(message, ...)
#endif // DISABLE_LOGGING
/* --- Azure Definitions --- */
#define DPS_GLOBAL_ENDPOINT_FQDN "global.azure-devices-provisioning.net"
#define DPS_GLOBAL_ENDPOINT_PORT AZ_IOT_DEFAULT_MQTT_CONNECT_PORT
#define IOT_HUB_ENDPOINT_PORT AZ_IOT_DEFAULT_MQTT_CONNECT_PORT
#define DEFAULT_SAS_TOKEN_LIFETIME_IN_MINUTES 60
#define SAS_TOKEN_REFRESH_THRESHOLD_IN_SECS 30
/*
* The structures below define a generic interface to abstract the interaction of this module,
* with any MQTT client used in the user application.
*/
#define MQTT_QOS_AT_MOST_ONCE 0
#define MQTT_QOS_AT_LEAST_ONCE 1
#define MQTT_QOS_EXACTLY_ONCE 2
typedef enum mqtt_qos_t_enum
{
mqtt_qos_at_most_once = MQTT_QOS_AT_MOST_ONCE,
mqtt_qos_at_least_once = MQTT_QOS_AT_LEAST_ONCE,
mqtt_qos_exactly_once = MQTT_QOS_EXACTLY_ONCE
}
mqtt_qos_t;
/*
* @brief Defines a generic MQTT message to be exchanged between the AzureIoT layer
* and the user application.
* @remark It uses azure-sdk-for-c's az_span construct, which is merely a structure that
* has a pointer to a buffer and its size. Messages without a payload will have
* the `payload` member set to AZ_SPAN_EMPTY.
* Please see the az_span.h in azure-sdk-for-c for more details at
* https://azuresdkdocs.blob.core.windows.net/$web/c/az_core/1.2.0/az__span_8h.html
*/
typedef struct mqtt_message_t_struct
{
az_span topic;
az_span payload;
mqtt_qos_t qos;
}
mqtt_message_t;
/*
* @brief Configuration structure passed by `mqtt_client_init_function_t` to the user
* application for initializing the actual MQTT client.
*/
typedef struct mqtt_client_config_t_struct
{
/*
* @brief FQDN address of the broker that the MQTT client shall connect to.
*/
az_span address;
/*
* @brief Port of the broker that the MQTT client shall connect to.
*/
int port;
/*
* @brief Client ID to be provided in the CONNECT sent by the MQTT client.
*/
az_span client_id;
/*
* @brief Username to be provided in the CONNECT sent by the MQTT client.
*/
az_span username;
/*
* @brief Password to be provided in the CONNECT sent by the MQTT client.
*/
az_span password;
}
mqtt_client_config_t;
/*
* @brief Generic pointer to the actual instance of the application MQTT client.
* @remark set by the user application when the `mqtt_client_init_function_t` callback is invoked.
*/
typedef void* mqtt_client_handle_t;
/*
* @brief Function to initialize and connect an MQTT client.
* @remark When this function is called, it provides the information necessary to initialize a
* specific MQTT client (whichever is used by the user application). In this callback
* it is expected that the MQTT client will be created/initialized, started and that it
* start sending a CONNECT to the provided server.
*
* @param[in] mqtt_client_config An instance of mqtt_client_config_t containing all the information
* needed by the MQTT client to connect to the target server.
* Please see `mqtt_client_config_t` documentation for details.
* @param[out] mqtt_client_handle A pointer to the resulting "instance" of the MQTT client shall
* be saved in `mqtt_client_handle` for later use by the calling layer.
*
* @return int 0 on success, non-zero if any failure occurs.
*/
typedef int (*mqtt_client_init_function_t)(mqtt_client_config_t* mqtt_client_config, mqtt_client_handle_t* mqtt_client_handle);
/*
* @brief Function to disconnect and deinitialize an MQTT client.
* @remark When this function is called the MQTT client instance referenced by `mqtt_client_handle` shall disconnect
* from the server and any functions of the MQTT client API that destroy the instance shall be called
* (so any allocated memory resources can be released).
*
* @param[in] mqtt_client_handle A pointer to the instance of the MQTT client previously created with
* `mqtt_client_init_function_t` function.
*
* @return int 0 on success, non-zero if any failure occurs.
* Returning non-zero results in the Azure IoT Client going into error state.
*/
typedef int (*mqtt_client_deinit_function_t)(mqtt_client_handle_t mqtt_client_handle);
/*
* @brief Function to send an MQTT PUBLISH.
* @remark When this function is invoked, the caller expects the actual MQTT client (referenced by `mqtt_client_handle`)
* to invoke the appropriate function in the MQTT client API to publish an MQTT message.
*
* @param[in] mqtt_client_handle A pointer to the instance of the MQTT client previously created with
* `mqtt_client_init_function_t` function.
* @param[in] mqtt_message A structure containing the topic name, payload and QoS to be used to publish
* an actual MQTT message.
*
* @return int The packet ID on success, or NEGATIVE if any failure occurs.
* If the QoS in `mqtt_message` is:
* - AT LEAST ONCE, the Azure IoT client expects `azure_iot_mqtt_client_connected`
* to be called once the MQTT client receives a PUBACK.
* - AT MOST ONCE, there should be no PUBACK, so no further action is
* needed for this PUBLISH.
*/
typedef int (*mqtt_client_publish_function_t)(mqtt_client_handle_t mqtt_client_handle, mqtt_message_t* mqtt_message);
/*
* @brief Function to send an MQTT SUBSCRIBE.
* @remark When this function is invoked, Azure IoT client expects the actual MQTT client (referenced by
* `mqtt_client_handle`) to invoke the appropriate function in the MQTT client API to subscribe to
* an MQTT topic.
*
* @param[in] topic The az_span with the string containing the complete MQTT topic name to subscribed to.
* This string is always NULL-terminated.
* @param[in] qos MQTT QoS to be used for the topic subscription.
*
* @return int The packet ID of the subscription on success, or NEGATIVE if any failure occurs.
* Azure IoT client expects `azure_iot_mqtt_client_subscribe_completed` to be called once the
* MQTT client receives a SUBACK.
*/
typedef int (*mqtt_client_subscribe_function_t)(mqtt_client_handle_t mqtt_client_handle, az_span topic, mqtt_qos_t qos);
/*
* @brief Structure that consolidates all the abstracted MQTT functions.
*/
typedef struct mqtt_client_interface_t_struct
{
mqtt_client_init_function_t mqtt_client_init;
mqtt_client_deinit_function_t mqtt_client_deinit;
mqtt_client_publish_function_t mqtt_client_publish;
mqtt_client_subscribe_function_t mqtt_client_subscribe;
}
mqtt_client_interface_t;
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] data Buffer containing the Base64-encoded content.
* @param[in] data_length Length of `data`.
* @param[in] decoded Buffer where to write the Base64-decoded content of `data`.
* @param[in] decoded_size Size of `decoded`.
* @param[out] decoded_length The final length of the Base64-decoded content written in
* the `decoded` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*base64_decode_function_t)(uint8_t* data, size_t data_length, uint8_t* decoded, size_t decoded_size, size_t* decoded_length);
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] data Buffer containing the Base64-decoded content.
* @param[in] data_length Length of `data`.
* @param[in] encoded Buffer where to write the Base64-encoded content of `data`.
* @param[in] encoded_size Size of `encoded`.
* @param[out] encoded_length The final length of the Base64-encoded content written in
* the `encoded` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*base64_encode_function_t)(uint8_t* data, size_t data_length, uint8_t* encoded, size_t encoded_size, size_t* encoded_length);
/*
* @brief This function must be provided by the user for the AzureIoT layer
* to perform the generation of the SAS tokens used as MQTT passwords.
*
* @param[in] key Encryption key to be used in the HMAC-SHA256 algorithm.
* @param[in] key_length Length of `key`.
* @param[in] payload Buffer containing the data to be encrypted.
* @param[in] payload_size Size of `payload`.
* @param[in] encrypted_payload Buffer where to write the HMAC-SHA256 encrypted content of `payload`.
* @param[in] encrypted_payload_size The size of the `encrypted_payload` buffer.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
typedef int (*hmac_sha256_encryption_function_t)(const uint8_t* key, size_t key_length, const uint8_t* payload, size_t payload_length, uint8_t* encrypted_payload, size_t encrypted_payload_size);
/*
* @brief Structure that consolidates all the data manipulation functions.
*/
typedef struct data_manipulation_functions_t_struct
{
base64_decode_function_t base64_decode;
base64_encode_function_t base64_encode;
hmac_sha256_encryption_function_t hmac_sha256_encrypt;
}
data_manipulation_functions_t;
/*
* @brief Defines the callback for notifying the completion of a reported properties update.
*
* @param[in] request_id Request ID provided by the caller when sending the reported properties update.
* @param[in] status_code Result of the reported properties update (uses HTTP status code semantics).
*
* @return Nothing.
*/
typedef void (*properties_update_completed_t)(uint32_t request_id, az_iot_status status_code);
/*
* @brief Defines the callback for receiving a writable-properties update.
*
* @param[in] request_id Request ID provided by the caller when sending the reported properties update.
* @param[in] status_code Result of the reported properties update (uses HTTP status code semantics).
*
* @return Nothing.
*/
typedef void (*properties_received_t)(az_span properties);
/*
* @brief Structure containing all the details of a IoT Plug and Play Command.
*/
typedef struct command_request_t_struct
{
/*
* @brief ID of the command request, as received from Azure.
*/
az_span request_id;
/*
* @brief Name of the component this command is targeted to.
* This can be empty (set as AZ_SPAN_EMPTY).
*/
az_span component_name;
/*
* @brief Name of the command.
*/
az_span command_name;
/*
* @brief Optional payload sent by the caller for this command.
*/
az_span payload;
}
command_request_t;
/*
* @brief Defines the callback for receiving an IoT Plug and Play Command.
* @remark A response for this command MUST be provided to Azure by calling
* `azure_iot_send_command_response`.
*
* @param[in] command An instance of `command_request_t` containing all the details of
* of the command received.
*
* @return Nothing.
*/
typedef void (*command_request_received_t)(command_request_t command);
/*
* @brief Defines the callback for getting NTP-sync'd seconds since epcoh until now.
*
* @return uint32_t Number of seconds.
*/
typedef uint32_t (*get_time_t)();
/*
* @brief All the possible statuses returned by `azure_iot_get_status`.
*/
typedef enum azure_iot_status_t_struct
{
/*
* @brief The Azure IoT client is completely disconnected.
*/
azure_iot_disconnected,
/*
* @brief The client is in an intermediate state between disconnected and connected.
* @remark When using SAS-based authentication (default for Azure IoT Central), the client
* will automatically perform a reconnection with a new SAS token after the previous
* one expires, going back into `azure_iot_connecting` state briefly.
*/
azure_iot_connecting,
/*
* @brief In this state the Azure IoT client is ready to be used for messaging.
*/
azure_iot_connected,
/*
* @brief The Azure IoT client encountered some internal error and is no longer active.
* @remark This can be caused by:
* - Memory issues (not enough memory provided in `azure_iot_config_t`'s `data_buffer`);
* - Any failures reported by the application's MQTT client
* (through the abstracted interface) return values;
* - Any protocol errors returned by Azure IoT services.
* If not enough memory has been provided in `azure_iot_config_t`'s `data_buffer,
* please expand the size of that memory space.`
* Once the possible mitigations are applied stop the Azure IoT client
* by calling `azure_iot_stop` (which resets the client state) and restart it
* using `azure_iot_start`.
*/
azure_iot_error
}
azure_iot_status_t;
/*
* @brief Internal states of the Azure IoT client.
* @remark These states are not exposed to the user application.
*/
typedef enum azure_iot_client_state_t_struct
{
azure_iot_state_not_initialized = 0,
azure_iot_state_initialized,
azure_iot_state_started,
azure_iot_state_connecting_to_dps,
azure_iot_state_connected_to_dps,
azure_iot_state_subscribing_to_dps,
azure_iot_state_subscribed_to_dps,
azure_iot_state_provisioning_querying,
azure_iot_state_provisioning_waiting,
azure_iot_state_provisioned,
azure_iot_state_connecting_to_hub,
azure_iot_state_connected_to_hub,
azure_iot_state_subscribing_to_pnp_cmds,
azure_iot_state_subscribed_to_pnp_cmds,
azure_iot_state_subscribing_to_pnp_props,
azure_iot_state_subscribed_to_pnp_props,
azure_iot_state_subscribing_to_pnp_writable_props,
azure_iot_state_ready,
azure_iot_state_refreshing_sas,
azure_iot_state_error
}
azure_iot_client_state_t;
/*
* @brief Structure that holds the configuration for the Azure IoT client.
* @remark Once `azure_iot_start` is called, this structure SHALL NOT be modified by the
* user application unless `azure_iot_stop` is called.
* Also make sure that the instance of `azure_iot_config_t` (and its members) do not
* lose scope throughout the lifetime of the Azure IoT client.
* Most of the members of this structure use az_span for encapsulating a buffer and
* its size. For more details on az_span, please explore the code at
* https://github.com/azure/azure-sdk-for-c.
*/
typedef struct azure_iot_config_t_struct
{
/*
* @brief User agent string to be provided to Azure IoT services.
* @remark This string is not exposed back to the user device or service applications,
* currently only being used internally by Azure IoT. However, this
* data can sometimes be useful for customer support, so we recommend
* providing this information in the standard format expected
* (e.g., #define UA "c%2F" AZ_SDK_VERSION_STRING "(<platform>%3B<architecture>)")
*/
az_span user_agent;
/*
* @brief Controls whether Azure IoT client must perform device-provisioning or not.
* @remark If the configuration provided below is for connecting to Azure Device Provisioning
* Service (dps_id_scope, ...), this flag must be set to true. This is the case when
* connecting to Azure IoT Central as well.
*/
bool use_device_provisioning;
/*
* @brief Fully qualified domain name of the Azure IoT Hub to connect to.
* @remark If performing device-provisioning this MUST be set to AZ_SPAN_EMPTY;
* in such case, this member will be set with the target Azure IoT Hub FQDN
* once device-provisioning is completed.
*/
az_span iot_hub_fqdn;
/*
* @brief Device ID to authenticate as when connecting to Azure IoT Hub.
* @remark If performing device-provisioning this MUST be set to AZ_SPAN_EMPTY;
* in such case, this member will be set with the target device ID
* once device-provisioning is completed.
*/
az_span device_id;
/*
* @brief Symmetric key of the device to authenticate as when connecting to Azure IoT Hub.
* @remark This key will be used to generate the MQTT client password, if using SAS-token
* authentication (which is used, for example, when connecting to Azure IoT Central).
*/
az_span device_key;
/*
* @brief X509 certificate to be used for authentication.
*
*/
az_span device_certificate;
/*
* @brief X509 certificate private key to be used for authentication.
*
*/
az_span device_certificate_private_key;
/*
* @brief The "Registration ID" to authenticate with when connecting to
* Azure Device Provisioning service.
* @remark This information is only needed when performing device-provisioning (which is used,
for example, when connecting to Azure IoT Central). If device-provisioning is
not being used (i.e., Azure IoT client is connecting directly to Azure IoT Hub)
this member MUST be set with AZ_SPAN_EMPTY.
Additionaly (!), if connecting to Azure IoT Central, this member MUST be set
with the `device ID` created in the Azure IoT Central application.
*/
az_span dps_registration_id;
/*
* @brief The "ID Scope" to authenticate with when connecting to
* Azure Device Provisioning service.
* @remark This information is only needed when performing device-provisioning (which is used,
for example, when connecting to Azure IoT Central). If device-provisioning is
not being used (i.e., Azure IoT client is connecting directly to Azure IoT Hub)
this member MUST be set with AZ_SPAN_EMPTY.
*/
az_span dps_id_scope;
/*
* @brief Model ID of the IoT Plug and Play template implemented by the user application.
* @remark This is used only when the application uses/implements IoT Plug and Play.
*/
az_span model_id;
/*
* @brief Buffer with memory space to be used internally by Azure IoT client for its
* internal functions.
* @remark This buffer MUST be large enough to perform all the string manipulations done
* internally by the Azure IoT client, like MQTT client client id, username and password
* generation, as well as storing Azure IoT Hub fqdn and device id after device-provisioning
* is completed (if device-provisioning is used).
* The maximum size required depends on which services and authentication mode are used.
* If using device-provisioning with SAS-token authentication (as used with Azure IoT Central),
* this size must be at least:
* sizeof(data_buffer) >= ( lengthof(<iot-hub-fqdn>) + lengthof(<device-id>) +
* lengthof(<MQTT-clientid>) + lengthof(<MQTT-username>) +
* 2 * lengthof(<MQTT-password>) )
*
* Where:
* <MQTT-clientid> = <device-id> + '\0'
* <MQTT-username> = <iot-hub-fqdn> + '/' + lengthof(<device-id>) + '/' + '?' + <api-version> +
* "&DeviceClientType=" + urlenc(<user-agent>) + "&model-id=" + urlenc(<pnp-model-id>) + '\0'
* <api-version> = "api-version=<YYYY-MM-DD>"
* <MQTT-password>) = "SharedAccessSignature sr=" + <iot-hub-fqdn> + "%2Fdevices%2F" + <device-id> +
* "&sig=" + urlenc(<sha256-string>) + "&se=" + <expiration-time> + '\0'
* lengthof(<sha256-string>) <= lengthof(<64-char-string>)
* <expiration-time> = <10-digit-unix-time>
*
* Note: We use two times the length of <MQTT-password> given the internal operations needed to
* generate it.
*
* Example:
* <iot-hub-fqdn> = "iotc-1a430cf3-6f05-4b84-965d-cb1385077966.azure-devices.net"
* <device-id> = "d1"
* <sha256-string> = "vj7jSTPe4CZqqs5c+DkEpdoHMB+m1rzsFF04JyP9Pr4="
* <pnp-model-id> = "dtmi:azureiot:devkit:freertos:Esp32AzureIotKit;1"
* <api-version> = "api-version=2020-09-30"
* <expiration-time> = "1641251566"
* <user-agent> = "c/1.1.0-beta.1(FreeRTOS)"
*
* sizeof(data_buffer) >= 592 bytes (59 bytes + 2 bytes + 3 bytes + 190 bytes + 338 bytes, respectively)
*/
az_span data_buffer;
/*
* @brief Set of functions to serve as interface between Azure IoT client and the user-application MQTT client.
*/
mqtt_client_interface_t mqtt_client_interface;
/*
* @brief Set of functions for Azure IoT client to perform its necessary data manipulations.
*/
data_manipulation_functions_t data_manipulation_functions;
/*
* @brief Amount of minutes for which the MQTT password should be valid.
* @remark If set to zero, Azure IoT client sets it to the default value of 60 minutes.
* Once this amount of minutes has passed and the MQTT password is expired,
* Azure IoT client triggers its logic to generate a new password and reconnect with
* Azure IoT Hub.
*/
uint32_t sas_token_lifetime_in_minutes;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a completion of properties update.
* @remark A properties update is triggered by the user application when
* `azure_iot_send_properties_update` is called.
*/
properties_update_completed_t on_properties_update_completed;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a writable-properties update received from Azure IoT Hub.
* @remark If IoT Plug and Play is used, a response must be sent back to
* Azure IoT Hub.
*/
properties_received_t on_properties_received;
/*
* @brief Callback handler used by Azure IoT client to inform the user application of
* a device command received from Azure IoT Hub.
* @remark A response must be sent back to Azure IoT Hub by calling
* `azure_iot_send_command_response`.
*/
command_request_received_t on_command_request_received;
/*
* @brief Callback handler used by Azure IoT client to retrive current epoch seconds.
*/
get_time_t get_time;
}
azure_iot_config_t;
/*
* @brief Structure that holds the state of the Azure IoT client.
* @remark None of the members within this structure may be accessed
* directly by the user application. Any of the members in azure_iot_t
* may be renamed, re-ordered, added and/or removed without
* notice.
*/
typedef struct azure_iot_t_struct
{
azure_iot_config_t* config;
az_span data_buffer;
mqtt_client_handle_t mqtt_client_handle;
az_iot_hub_client iot_hub_client;
az_iot_hub_client_options iot_hub_client_options;
az_iot_provisioning_client dps_client;
azure_iot_client_state_t state;
uint32_t sas_token_expiration_time;
uint32_t dps_retry_after_seconds;
uint32_t dps_last_query_time;
az_span dps_operation_id;
}
azure_iot_t;
/*
* @brief Initializes the azure_iot_t structure that holds the Azure IoT client state.
* @remark This function must be called only once per `azure_iot_t` instance,
* before any other function can be called using it.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
* @param[in] azure_iot_config A pointer to a `azure_iot_config_t` containing all the
* configuration neeeded for the client to connect and work with
* the Azure IoT services.
* @return Nothing.
*/
void azure_iot_init(azure_iot_t* azure_iot, azure_iot_config_t* azure_iot_config);
/*
* @brief Starts an Azure IoT client.
* @remark This function must be called once the user application is ready to start
* connecting and working with Azure IoT services. By calling this function
* the `azure_iot_t` instance is reset to the default state it needs to work properly.
* Only after a `azure_iot_t` is started `azure_iot_do_work` will be able to perform
* any tasks. If the Azure IoT client gets into an error state, `azure_iot_stop` must
* be called first, followed by a call to `azure_iot_start` so it can reconnect to
* the Azure IoT Hub again.
* Note: if device-provisioning is used, the device is provisioned only the first
* time a given `azure_iot_t` instance is started. Subsequent calls to
* `azure_iot_start` will re-use the Azure IoT Hub FQDN and device ID previously
* provisioned.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
* @param[in] azure_iot_config A pointer to a `azure_iot_config_t` containing all the
* configuration neeeded for the client to connect and work with
* the Azure IoT services.
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_start(azure_iot_t* azure_iot);
/*
* @brief Stops an Azure IoT client.
* @remark This function must be called once the user application wants to stop working and
* disconnect from the Azure IoT services. The same instance of `azure_iot_t` can be
* used again by the user application by calling `azure_iot_start`.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` defined by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_stop(azure_iot_t* azure_iot);
/*
* @brief Gets the current state of the Azure IoT client.
* @remark The states informed are simplified for ease-of-use of this client, not reflecting
* all the detailed internal states possible within the Azure IoT client.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return azure_iot_status_t One of four possible states as defined by `azure_iot_status_t`.
* Please see the `azure_iot_status_t` documentation above for details.
*/
azure_iot_status_t azure_iot_get_status(azure_iot_t* azure_iot);
/*
* @brief Causes the Azure IoT client to perform its tasks for connecting and working with Azure IoT services.
* @remark This function must be called frequently enough for the Azure IoT client to work properly.
* That frequency should be enough to respect the timeouts implemented by the Azure IoT services for
* its TCP connection and MQTT-protocol traffic with the client application.
* Calling it once within a main loop() function of most embedded implementations should be enough.
* The recommended minimum frequency is no less than once every 100 milliseconds.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return Nothing.
*/
void azure_iot_do_work(azure_iot_t* azure_iot);
/*
* @brief Sends a telemetry payload to the Azure IoT Hub.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] message An az_span instance containing the buffer and size of the actual message to be sent.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_send_telemetry(azure_iot_t* azure_iot, az_span message);
/**
* @brief Sends a property update message to Azure IoT Hub.
*
* @param[in] azure_iot The pointer to the azure_iot_t instance that holds the state of the Azure IoT client.
* @param[in] request_id An unique identification number to correlate the response with when
* @param[in] message An `az_span` with the message with the reported properties update
* (a JSON document formatted according to the DTDL specification).
* `message` gets passed as-is to the MQTT client publish function as the payload, so if
* your MQTT client expects a null-terminated string for payload, make sure `message` is
* a null-terminated string.
* `on_properties_update_completed` (set in azure_iot_config_t) is invoked.
*
* @return int 0 if the function succeeds, or non-zero if any failure occurs.
*/
int azure_iot_send_properties_update(azure_iot_t* azure_iot, uint32_t request_id, az_span message);
/**
* @brief Sends a property update message to Azure IoT Hub.
*
* @param[in] azure_iot The pointer to the azure_iot_t instance that holds the state of the Azure IoT client.
* @param[in] request_id The same `request_id` of the device command received from Azure IoT Hub.
* @param[in] response_status An HTTP-like code informing the success or failure of the command.
* A 202 will inform the command was accepted and succeeded.
* A 404 will inform the command received does not match the commands supported by
* this device application.
* @param[in] payload A custom payload to be sent in the device command response.
* This is expected to be a json content.
* If no payload is to be sent, please set it as AZ_SPAN_EMPTY.
*
* @return int 0 if the function succeeds, or non-zero if any failure occurs.
*/
int azure_iot_send_command_response(azure_iot_t* azure_iot, az_span request_id, uint16_t response_status, az_span payload);
/*
* @brief Informs the Azure IoT client that the MQTT client is connected.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_init` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has received
* a CONNACK from the Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_connected(azure_iot_t* azure_iot);
/*
* @brief Informs the Azure IoT client that the MQTT client is disconnected.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_deinit` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has disconnected
* from the Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_disconnected(azure_iot_t* azure_iot);
/*
* @brief Informs the Azure IoT client that the MQTT client has subscribed to a topic.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_subscribe` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has
* received a SUBACK for a topic subscription.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] packet_id The ID of the subscription performed previous by the Azure IoT client.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet_id);
/*
* @brief Informs the Azure IoT client that the MQTT client has completed a PUBLISH.
* @remark This must be called after Azure IoT client invokes the `mqtt_client_publish` callback
* (provided in the azure_iot_config_t instance) so it knows the MQTT client has
* completed an MQTT PUBLISH. If the QoS is 0 (AT MOST ONCE), this shall be called by
* the user application right after `mqtt_client_publish` is invoked. If the QoS of
* is 1 (AT LEAST ONCE), this shall be called whenever a PUBACK is received.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] packet_id The ID of the subscription performed previous by the Azure IoT client.
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_publish_completed(azure_iot_t* azure_iot, int packet_id);
/*
* @brief Informs the Azure IoT client that a new message has been received by the Azure IoT service.
* @remark This must be called whenever an MQTT message is received from an Azure IoT service.
*
* @param[in] azure_iot A pointer to the instance of `azure_iot_t` previously initialized by the caller.
* @param[in] mqtt_message A `mqtt_message_t` instance containing all the details of the MQTT message
* received (topic name, qos and payload).
*
* @return int 0 on success, or non-zero if any failure occurs.
*/
int azure_iot_mqtt_client_message_received(azure_iot_t* azure_iot, mqtt_message_t* mqtt_message);
/* --- az_core extensions --- */
/*
* These functions are used internally by the Azure IoT client code and its extensions.
*/
/**
* @brief Slices `span` at position `size`, returns the first slice and assigns the second slice to `remainder`.
*
* @param[in] span A span to be sliced.
* @param[in] source The span to copy the contents from.
* @param[out] remainder The pointer where to store the remainder of `span` after it is sliced.
*
* @return az_span A slice of `span` from position zero to `size`.
*/
az_span split_az_span(az_span span, int32_t size, az_span* remainder);
/**
* @brief Slices `destination` to fit `source`, copy `source` into the first slice and returns the second through `remainder`.
*
* @param[in] destination A span large enough to contain the copy of the contents of `source`.
* @param[in] source The span to copy the contents from.
* @param[out] remainder The pointer where to store the remainder of `destination` after `source` is copied.
*
* @return az_span A slice of `destination` with the same size as `source`, with `source`'s content copied over.
*/
static az_span slice_and_copy_az_span(az_span destination, az_span source, az_span* remainder);
#endif // AZURE_IOT_H

Просмотреть файл

@ -0,0 +1,529 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* This is an Arduino-based Azure IoT Central sample specific for Arduino Portenta H7.
* It uses our Azure Embedded SDK for C to help interact with Azure IoT.
* For reference, please visit https://github.com/azure/azure-sdk-for-c and https://azureiotcentral.com/.
*
* To connect and work with Azure IoT Hub you need an MQTT client, connecting, subscribing
* and publishing to specific topics to use the messaging features of the hub.
* Our azure-sdk-for-c is an MQTT client support library, helping composing and parsing the
* MQTT topic names and messages exchanged with the Azure IoT Hub.
*
* The additional layers in this sketch provide a structured use of azure-sdk-for-c and
* the MQTT client of your choice to perform all the steps needed to connect and interact with
* Azure IoT Central.
*
* AzureIoT.cpp contains a state machine that implements those steps, plus abstractions to simplify
* its overall use. Besides the basic configuration needed to access the Azure IoT services,
* all that is needed is to provide the functions required by that layer to:
* - Interact with your MQTT client,
* - Perform data manipulations (HMAC SHA256 encryption, Base64 decoding and encoding),
* - Receive the callbacks for Plug and Play properties and commands.
*
* Azure_IoT_PnP_Template.cpp contains the actual implementation of the IoT Plug and Play template
* specific for the Arduino Portenta H7 board.
*
* To properly connect to your Azure IoT services, please fill the information in the `iot_configs.h` file.
*/
/* --- Dependencies --- */
// C99 libraries
#include <cstdlib>
#include <cstdarg>
#include <string.h>
#include <time.h>
// For hmac SHA256 encryption
#include <ECCX08.h>
// Libraries for SSL client, MQTT client, NTP, and WiFi connection
#include <ArduinoBearSSL.h>
#include <ArduinoMqttClient.h>
#include <NTPClient_Generic.h>
#include <TimeLib.h>
#include <WiFi.h>
// Azure IoT SDK for C includes
#include <az_core.h>
#include <az_iot.h>
// Additional sample headers
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include "iot_configs.h"
/* --- Sample-specific Settings --- */
#define MQTT_RETAIN_MSG true
#define MQTT_DO_NOT_RETAIN_MSG !MQTT_RETAIN_MSG
#define SERIAL_LOGGER_BAUD_RATE 115200
/* --- Time and NTP Settings --- */
#define GMT_OFFSET_SECS (IOT_CONFIG_DAYLIGHT_SAVINGS ? \
((IOT_CONFIG_TIME_ZONE + IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * SECS_PER_HOUR) : \
(IOT_CONFIG_TIME_ZONE * SECS_PER_HOUR))
/* --- Function Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
/* --- Function Declarations --- */
static void sync_device_clock_with_ntp_server();
static void connect_to_wifi();
static void on_message_received(int message_size);
static String get_formatted_date_time(uint32_t epoch_time_in_seconds);
// This is a logging function used by Azure IoT client.
static void logging_function(log_level_t log_level, char const* const format, ...);
/* --- Sample variables --- */
static azure_iot_config_t azure_iot_config;
static azure_iot_t azure_iot;
static WiFiUDP wifi_udp_client;
static NTPClient ntp_client(wifi_udp_client);
static WiFiClient wifi_client;
static BearSSLClient bear_ssl_client(wifi_client);
static MqttClient arduino_mqtt_client(bear_ssl_client);
#define AZ_IOT_DATA_BUFFER_SIZE 1500
static uint8_t az_iot_data_buffer[AZ_IOT_DATA_BUFFER_SIZE];
static uint8_t message_buffer[AZ_IOT_DATA_BUFFER_SIZE];
static uint32_t properties_request_id = 0;
static bool send_device_info = true;
/* --- MQTT Interface Functions --- */
/*
* These functions are used by Azure IoT to interact with whatever MQTT client used by the sample
* (in this case, ArduinoMqttClient). Please see the documentation in AzureIoT.h for more details.
*/
/*
* See the documentation of `mqtt_client_init_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, mqtt_client_handle_t *mqtt_client_handle)
{
int result;
const char* client_id = (const char*)az_span_ptr(mqtt_client_config->client_id);
const char* username = (const char*)az_span_ptr(mqtt_client_config->username);
const char* password = (const char*)az_span_ptr(mqtt_client_config->password);
int port = mqtt_client_config->port;
// Address for DPS is az_span from string literal (#define DPS_GLOBAL_ENDPOINT_FQDN). Null terminated.
// Address for Hub is az_span, retrieved from message from DPS. Not null terminated.
// mqtt_client_init_function() is called in both scenarios.
char address[128] = {0}; // Default to null-termination.
memcpy(address, az_span_ptr(mqtt_client_config->address), az_span_size(mqtt_client_config->address));
arduino_mqtt_client.setId(client_id);
arduino_mqtt_client.setUsernamePassword(username, password);
arduino_mqtt_client.setCleanSession(true);
arduino_mqtt_client.onMessage(on_message_received);
LogInfo("MQTT Client ID: %s", client_id);
LogInfo("MQTT Username: %s", username);
LogInfo("MQTT Password: ***");
LogInfo("MQTT client address: %s", address);
LogInfo("MQTT client port: %d", port);
while (!arduino_mqtt_client.connect(address, port))
{
int code = arduino_mqtt_client.connectError();
LogError("Cannot connect. Error Code: %d", code);
delay(5000);
}
LogInfo("MQTT client connected.");
result = azure_iot_mqtt_client_connected(&azure_iot);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT connection.");
}
else
{
*mqtt_client_handle = &arduino_mqtt_client;
}
return result;
}
/*
* See the documentation of `mqtt_client_deinit_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
{
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
LogInfo("MQTT client being disconnected.");
arduino_mqtt_client_handle->stop();
result = azure_iot_mqtt_client_disconnected(&azure_iot);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT disconnection.");
}
return result;
}
/*
* See the documentation of `mqtt_client_subscribe_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_subscribe_function(mqtt_client_handle_t mqtt_client_handle, az_span topic, mqtt_qos_t qos)
{
LogInfo("MQTT client subscribing to '%.*s'", az_span_size(topic), az_span_ptr(topic));
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
int mqtt_result = arduino_mqtt_client_handle->subscribe((const char*)az_span_ptr(topic), (uint8_t)qos);
if (mqtt_result == 1) // ArduinoMqttClient: 1 on success, 0 on failure
{
LogInfo("MQTT topic subscribed");
int packet_id = 0; // packet id is private in ArduinoMqttClient library.
result = azure_iot_mqtt_client_subscribe_completed(&azure_iot, packet_id);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT subscribe.");
}
}
else
{
LogError("ArduinoMqttClient subscribe failed.");
result = RESULT_ERROR;
}
return result;
}
/*
* See the documentation of `mqtt_client_publish_function_t` in AzureIoT.h for details.
*/
static int mqtt_client_publish_function(mqtt_client_handle_t mqtt_client_handle, mqtt_message_t* mqtt_message)
{
LogInfo("MQTT client publishing to '%s'", az_span_ptr(mqtt_message->topic));
int result;
MqttClient* arduino_mqtt_client_handle = (MqttClient*)mqtt_client_handle;
int mqtt_result = arduino_mqtt_client_handle->beginMessage(
(const char*)az_span_ptr(mqtt_message->topic),
MQTT_DO_NOT_RETAIN_MSG,
(uint8_t)mqtt_message->qos);
if (mqtt_result == 1) // ArduinoMqttClient: 1 on success, 0 on failure
{
arduino_mqtt_client_handle->print((const char*)az_span_ptr(mqtt_message->payload));
mqtt_result = arduino_mqtt_client_handle->endMessage();
if (mqtt_result == 1)
{
int packet_id = 0; // packet id is private in ArduinoMqttClient library.
result = azure_iot_mqtt_client_publish_completed(&azure_iot, packet_id);
if (result != RESULT_OK)
{
LogError("Failed updating azure iot client of MQTT publish.");
}
}
else
{
LogError("ArduinoMqttClient endMessage failed.");
result = RESULT_ERROR;
}
}
else
{
LogError("ArduinoMqttClient beginMessage failed.");
result = RESULT_ERROR;
}
return result;
}
/* --- Other Interface functions required by Azure IoT --- */
/*
* See the documentation of `hmac_sha256_encryption_function_t` in AzureIoT.h for details.
*/
static int eccx08_hmac_sha256(const uint8_t* key, size_t key_length, const uint8_t* payload, size_t payload_length, uint8_t* signed_payload, size_t signed_payload_size)
{
(void)signed_payload_size;
// HMAC-SHA256 sign the signature with the decoded device key.
ECCX08.begin();
ECCX08.nonce(key);
ECCX08.beginHMAC(0xFFFF);
ECCX08.updateHMAC(payload, payload_length);
ECCX08.endHMAC(signed_payload);
return 0;
}
/*
* See the documentation of `base64_decode_function_t` in AzureIoT.h for details.
*/
static int base64_decode(uint8_t* data, size_t data_length, uint8_t* decoded, size_t decoded_size, size_t* decoded_length)
{
az_span dataSpan = az_span_create(data, data_length);
az_span decodedSpan = az_span_create(decoded, decoded_size);
if (az_base64_decode(decodedSpan, dataSpan, (int32_t*)decoded_length) == AZ_OK) {
return 0;
}
return 1;
}
/*
* See the documentation of `base64_encode_function_t` in AzureIoT.h for details.
*/
static int base64_encode(uint8_t* data, size_t data_length, uint8_t* encoded, size_t encoded_size, size_t* encoded_length)
{
az_span dataSpan =az_span_create(data, data_length);
az_span encodedSpan = az_span_create(encoded, encoded_size);
if (az_base64_encode(encodedSpan, dataSpan, (int32_t*)encoded_length) == AZ_OK) {
return 0;
}
return 1;
}
/*
* See the documentation of `properties_update_completed_t` in AzureIoT.h for details.
*/
static void on_properties_update_completed(uint32_t request_id, az_iot_status status_code)
{
LogInfo("Properties update request completed (id=%d, status=%d)", request_id, status_code);
}
/*
* See the documentation of `properties_received_t` in AzureIoT.h for details.
*/
void on_properties_received(az_span properties)
{
LogInfo("Properties update received: %.*s", az_span_size(properties), az_span_ptr(properties));
// It is recommended not to perform work within callbacks.
// The properties are being handled here to simplify the sample.
if (azure_pnp_handle_properties_update(&azure_iot, properties, properties_request_id++) != 0)
{
LogError("Failed handling properties update.");
}
}
/*
* See the documentation of `command_request_received_t` in AzureIoT.h for details.
*/
static void on_command_request_received(command_request_t command)
{
az_span component_name = az_span_size(command.component_name) == 0 ? AZ_SPAN_FROM_STR("") : command.component_name;
LogInfo("Command request received (id=%.*s, component=%.*s, name=%.*s)",
az_span_size(command.request_id), az_span_ptr(command.request_id),
az_span_size(component_name), az_span_ptr(component_name),
az_span_size(command.command_name), az_span_ptr(command.command_name));
// Here the request is being processed within the callback that delivers the command request.
// However, for production application the recommendation is to save `command` and process it outside
// this callback, usually inside the main thread/task/loop.
(void)azure_pnp_handle_command_request(&azure_iot, command);
}
/*
* See the documentation of `get_time_t` in AzureIoT.h for details.
*/
static uint32_t get_time()
{
return (uint32_t)ntp_client.getUTCEpochTime();
}
/* --- Arduino setup and loop Functions --- */
void setup()
{
while (!Serial);
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
set_logging_function(logging_function);
connect_to_wifi();
sync_device_clock_with_ntp_server();
ArduinoBearSSL.onGetTime(get_time); // Required for server trusted root validation.
azure_pnp_init();
/*
* The configuration structure used by Azure IoT must remain unchanged (including data buffer)
* throughout the lifetime of the sample. This variable must also not lose context so other
* components do not overwrite any information within this structure.
*/
azure_iot_config.user_agent = AZ_SPAN_FROM_STR(IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT);
azure_iot_config.model_id = azure_pnp_get_model_id();
azure_iot_config.use_device_provisioning = true; // Required for Azure IoT Central.
azure_iot_config.iot_hub_fqdn = AZ_SPAN_EMPTY;
azure_iot_config.device_id = AZ_SPAN_EMPTY;
azure_iot_config.device_certificate = AZ_SPAN_EMPTY;
azure_iot_config.device_certificate_private_key = AZ_SPAN_EMPTY;
azure_iot_config.device_key = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_KEY);
azure_iot_config.dps_id_scope = AZ_SPAN_FROM_STR(IOT_CONFIG_DPS_ID_SCOPE);
azure_iot_config.dps_registration_id = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_ID); // Use Device ID for Azure IoT Central.
azure_iot_config.data_buffer = AZ_SPAN_FROM_BUFFER(az_iot_data_buffer);
azure_iot_config.sas_token_lifetime_in_minutes = IOT_CONFIG_MQTT_PASSWORD_LIFETIME_IN_MINUTES;
azure_iot_config.mqtt_client_interface.mqtt_client_init = mqtt_client_init_function;
azure_iot_config.mqtt_client_interface.mqtt_client_deinit = mqtt_client_deinit_function;
azure_iot_config.mqtt_client_interface.mqtt_client_subscribe = mqtt_client_subscribe_function;
azure_iot_config.mqtt_client_interface.mqtt_client_publish = mqtt_client_publish_function;
azure_iot_config.data_manipulation_functions.hmac_sha256_encrypt = eccx08_hmac_sha256;
azure_iot_config.data_manipulation_functions.base64_decode = base64_decode;
azure_iot_config.data_manipulation_functions.base64_encode = base64_encode;
azure_iot_config.on_properties_update_completed = on_properties_update_completed;
azure_iot_config.on_properties_received = on_properties_received;
azure_iot_config.on_command_request_received = on_command_request_received;
azure_iot_config.get_time = get_time;
azure_iot_init(&azure_iot, &azure_iot_config);
azure_iot_start(&azure_iot);
LogInfo("Azure IoT client initialized (state=%d)", azure_iot.state);
}
void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
connect_to_wifi();
azure_iot_start(&azure_iot);
}
else
{
switch(azure_iot_get_status(&azure_iot))
{
case azure_iot_connected:
if (send_device_info)
{
(void)azure_pnp_send_device_info(&azure_iot, properties_request_id++);
send_device_info = false; // Only need to send once.
}
else if (azure_pnp_send_telemetry(&azure_iot) != 0)
{
LogError("Failed sending telemetry.");
}
break;
case azure_iot_error:
LogError("Azure IoT client is in error state." );
azure_iot_stop(&azure_iot);
WiFi.disconnect();
break;
default:
break;
}
// MQTT loop must be called to process Telemetry and Cloud-to-Device (C2D) messages.
arduino_mqtt_client.poll();
ntp_client.update();
delay(50);
azure_iot_do_work(&azure_iot);
}
}
/* === Function Implementations === */
/*
* These are support functions used by the sample itself to perform its basic tasks
* of connecting to the internet, syncing the board clock, MQTT client callback
* and logging.
*/
/* --- System and Platform Functions --- */
static void sync_device_clock_with_ntp_server()
{
LogInfo("Setting time using SNTP");
ntp_client.begin();
while (!ntp_client.forceUpdate())
{
delay(500);
Serial.print(".");
}
Serial.println("");
LogInfo("Time initialized!");
}
static void connect_to_wifi()
{
LogInfo("Connecting to WIFI wifi_ssid %s", IOT_CONFIG_WIFI_SSID);
WiFi.begin(IOT_CONFIG_WIFI_SSID, IOT_CONFIG_WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
LogInfo("WiFi connected, IP address: %u", WiFi.localIP());
}
void on_message_received(int message_size)
{
LogInfo("MQTT message received.");
mqtt_message_t mqtt_message;
// Copy message topic. Avoids any inadvertant ArduinoMqttClient _rxState or _rxMessageTopic changes.
// messageTopic() must be called before read();
String message_topic = arduino_mqtt_client.messageTopic();
arduino_mqtt_client.read(message_buffer, (size_t)message_size);
mqtt_message.topic = az_span_create((uint8_t*)message_topic.c_str(), message_topic.length());
mqtt_message.payload = az_span_create(message_buffer, message_size);
mqtt_message.qos = mqtt_qos_at_most_once; // QoS is unused by azure_iot_mqtt_client_message_received.
if (azure_iot_mqtt_client_message_received(&azure_iot, &mqtt_message) != 0)
{
LogError("azure_iot_mqtt_client_message_received failed (topic=%s).", arduino_mqtt_client.messageTopic().c_str());
}
}
static String get_formatted_date_time(uint32_t epoch_time_in_seconds)
{
char buffer[256];
time_t time = (time_t)epoch_time_in_seconds;
struct tm* timeInfo = localtime(&time);
strftime(buffer, 20, "%F %T", timeInfo);
return String(buffer);
}
static void logging_function(log_level_t log_level, char const* const format, ...)
{
Serial.print(get_formatted_date_time(get_time() + GMT_OFFSET_SECS));
Serial.print(log_level == log_level_info ? " [INFO] " : " [ERROR] ");
char message[256];
va_list ap;
va_start(ap, format);
int message_length = vsnprintf(message, 256, format, ap);
va_end(ap);
if (message_length < 0)
{
Serial.println("Failed encoding log message (!)");
}
else
{
Serial.println(message);
}
}

Просмотреть файл

@ -0,0 +1,546 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <stdlib.h>
#include <stdarg.h>
#include <az_core.h>
#include <az_iot.h>
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include <az_precondition_internal.h>
/* --- Defines --- */
#define AZURE_PNP_MODEL_ID "dtmi:azureiot:devkit:freertos:Esp32AzureIotKit;1"
#define SAMPLE_DEVICE_INFORMATION_NAME "deviceInformation"
#define SAMPLE_MANUFACTURER_PROPERTY_NAME "manufacturer"
#define SAMPLE_MODEL_PROPERTY_NAME "model"
#define SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME "swVersion"
#define SAMPLE_OS_NAME_PROPERTY_NAME "osName"
#define SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME "processorArchitecture"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME "processorManufacturer"
#define SAMPLE_TOTAL_STORAGE_PROPERTY_NAME "totalStorage"
#define SAMPLE_TOTAL_MEMORY_PROPERTY_NAME "totalMemory"
#define SAMPLE_MANUFACTURER_PROPERTY_VALUE "Arduino"
#define SAMPLE_MODEL_PROPERTY_VALUE "Arduino Portenta H7"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "Mbed OS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "STM32H747"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "Arduino"
// The next couple properties are in KiloBytes.
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 2000
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 16000
#define TELEMETRY_PROP_NAME_TEMPERATURE "temperature"
#define TELEMETRY_PROP_NAME_HUMIDITY "humidity"
#define TELEMETRY_PROP_NAME_LIGHT "light"
#define TELEMETRY_PROP_NAME_PRESSURE "pressure"
#define TELEMETRY_PROP_NAME_ALTITUDE "altitude"
#define TELEMETRY_PROP_NAME_MAGNETOMETERX "magnetometerX"
#define TELEMETRY_PROP_NAME_MAGNETOMETERY "magnetometerY"
#define TELEMETRY_PROP_NAME_MAGNETOMETERZ "magnetometerZ"
#define TELEMETRY_PROP_NAME_PITCH "pitch"
#define TELEMETRY_PROP_NAME_ROLL "roll"
#define TELEMETRY_PROP_NAME_ACCELEROMETERX "accelerometerX"
#define TELEMETRY_PROP_NAME_ACCELEROMETERY "accelerometerY"
#define TELEMETRY_PROP_NAME_ACCELEROMETERZ "accelerometerZ"
static az_span COMMAND_NAME_TOGGLE_LED_1 = AZ_SPAN_FROM_STR("ToggleLed1");
static az_span COMMAND_NAME_TOGGLE_LED_2 = AZ_SPAN_FROM_STR("ToggleLed2");
static az_span COMMAND_NAME_DISPLAY_TEXT = AZ_SPAN_FROM_STR("DisplayText");
#define COMMAND_RESPONSE_CODE_ACCEPTED 202
#define COMMAND_RESPONSE_CODE_REJECTED 404
#define WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS "telemetryFrequencySecs"
#define WRITABLE_PROPERTY_RESPONSE_SUCCESS "success"
#define DOUBLE_DECIMAL_PLACE_DIGITS 2
/* --- Function Checks and Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
#define EXIT_IF_TRUE(condition, retcode, message, ...) \
do \
{ \
if (condition) \
{ \
LogError(message, ##__VA_ARGS__ ); \
return retcode; \
} \
} while (0)
#define EXIT_IF_AZ_FAILED(azresult, retcode, message, ...) \
EXIT_IF_TRUE(az_result_failed(azresult), retcode, message, ##__VA_ARGS__ )
/* --- Data --- */
#define DATA_BUFFER_SIZE 1024
static uint8_t data_buffer[DATA_BUFFER_SIZE];
static uint32_t telemetry_send_count = 0;
static size_t telemetry_frequency_in_seconds = 10; // With default frequency of once in 10 seconds.
static time_t last_telemetry_send_time = INDEFINITE_TIME;
static bool led1_on = false;
static bool led2_on = false;
/* --- Function Prototypes --- */
/* Please find the function implementations at the bottom of this file */
static int generate_telemetry_payload(
uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length);
static int generate_device_info_payload(
az_iot_hub_client const* hub_client, uint8_t* payload_buffer,
size_t payload_buffer_size, size_t* payload_buffer_length);
static int consume_properties_and_generate_response(
azure_iot_t* azure_iot, az_span properties,
uint8_t* buffer, size_t buffer_size, size_t* response_length);
/* --- Public Functions --- */
void azure_pnp_init()
{
}
const az_span azure_pnp_get_model_id()
{
return AZ_SPAN_FROM_STR(AZURE_PNP_MODEL_ID);
}
void azure_pnp_set_telemetry_frequency(size_t frequency_in_seconds)
{
telemetry_frequency_in_seconds = frequency_in_seconds;
LogInfo("Telemetry frequency set to once every %d seconds.", telemetry_frequency_in_seconds);
}
/* Application-specific data section */
int azure_pnp_send_telemetry(azure_iot_t* azure_iot)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
uint32_t now = azure_iot->config->get_time();
if ((time_t)now == INDEFINITE_TIME)
{
LogError("Failed getting current time for controlling telemetry.");
return RESULT_ERROR;
}
else if (last_telemetry_send_time == INDEFINITE_TIME ||
difftime((time_t)now, last_telemetry_send_time) >= telemetry_frequency_in_seconds)
{
size_t payload_size;
last_telemetry_send_time = (time_t)now;
if (generate_telemetry_payload(data_buffer, DATA_BUFFER_SIZE, &payload_size) != RESULT_OK)
{
LogError("Failed generating telemetry payload.");
return RESULT_ERROR;
}
if (azure_iot_send_telemetry(azure_iot, az_span_create(data_buffer, payload_size)) != 0)
{
LogError("Failed sending telemetry.");
return RESULT_ERROR;
}
}
return RESULT_OK;
}
int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
int result;
size_t length;
// Generates payload with a null terminator. length does NOT include null terminator in count.
result = generate_device_info_payload(&azure_iot->iot_hub_client, data_buffer, DATA_BUFFER_SIZE, &length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed generating telemetry payload.");
result = azure_iot_send_properties_update(azure_iot, request_id, az_span_create(data_buffer, length));
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed sending reported properties update.");
return RESULT_OK;
}
int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t command)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
uint16_t response_code;
if (az_span_is_content_equal(command.command_name, COMMAND_NAME_TOGGLE_LED_1))
{
led1_on = !led1_on;
LogInfo("LED 1 state: %s", (led1_on ? "ON" : "OFF"));
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else if (az_span_is_content_equal(command.command_name, COMMAND_NAME_TOGGLE_LED_2))
{
led2_on = !led2_on;
LogInfo("LED 2 state: %s", (led2_on ? "ON" : "OFF"));
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else if (az_span_is_content_equal(command.command_name, COMMAND_NAME_DISPLAY_TEXT))
{
// The payload comes surrounded by quotes, so to remove them we offset the payload by 1 and its size by 2.
LogInfo("OLED display: %.*s", az_span_size(command.payload) - 2, az_span_ptr(command.payload) + 1);
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else
{
LogError("Command not recognized (%.*s).", az_span_size(command.command_name), az_span_ptr(command.command_name));
response_code = COMMAND_RESPONSE_CODE_REJECTED;
}
return azure_iot_send_command_response(azure_iot, command.request_id, response_code, AZ_SPAN_EMPTY);
}
int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span properties, uint32_t request_id)
{
_az_PRECONDITION_NOT_NULL(azure_iot);
_az_PRECONDITION_VALID_SPAN(properties, 1, false);
int result;
size_t length;
result = consume_properties_and_generate_response(azure_iot, properties, data_buffer, DATA_BUFFER_SIZE, &length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed generating properties ack payload.");
result = azure_iot_send_properties_update(azure_iot, request_id, az_span_create(data_buffer, length));
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "Failed sending reported properties update.");
return RESULT_OK;
}
/* --- Internal Functions --- */
static float simulated_get_temperature()
{
return 21.0;
}
static float simulated_get_humidity()
{
return 88.0;
}
static float simulated_get_ambientLight()
{
return 700.0;
}
static void simulated_get_pressure_altitude(float* pressure, float* altitude)
{
*pressure = 55.0;
*altitude = 700.0;
}
static void simulated_get_magnetometer(int32_t* magneticFieldX, int32_t* magneticFieldY, int32_t* magneticFieldZ)
{
*magneticFieldX = 2000;
*magneticFieldY = 3000;
*magneticFieldZ = 4000;
}
static void simulated_get_pitch_roll_accel(int32_t* pitch, int32_t* roll, int32_t* accelerationX, int32_t* accelerationY, int32_t* accelerationZ)
{
*pitch = 30;
*roll = 90;
*accelerationX = 33;
*accelerationY = 44;
*accelerationZ = 55;
}
static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
{
az_json_writer jw;
az_result rc;
az_span payload_buffer_span = az_span_create(payload_buffer, payload_buffer_size);
az_span json_span;
float temperature, humidity, light, pressure, altitude;
int32_t magneticFieldX, magneticFieldY, magneticFieldZ;
int32_t pitch, roll, accelerationX, accelerationY, accelerationZ;
// Acquiring the simulated data.
temperature = simulated_get_temperature();
humidity = simulated_get_humidity();
light = simulated_get_ambientLight();
simulated_get_pressure_altitude(&pressure, &altitude);
simulated_get_magnetometer(&magneticFieldX, &magneticFieldY, &magneticFieldZ);
simulated_get_pitch_roll_accel(&pitch, &roll, &accelerationX, &accelerationY, &accelerationZ);
rc = az_json_writer_init(&jw, payload_buffer_span, NULL);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed initializing json writer for telemetry.");
rc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed setting telemetry json root.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_TEMPERATURE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding temperature property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, temperature, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding temperature property value to telemetry payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_HUMIDITY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding humidity property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, humidity, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding humidity property value to telemetry payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_LIGHT));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding light property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, light, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding light property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_PRESSURE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pressure property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, pressure, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pressure property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ALTITUDE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding altitude property name to telemetry payload.");
rc = az_json_writer_append_double(&jw, altitude, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding altitude property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERX));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(X) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldX);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(X) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Y) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldY);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Y) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_MAGNETOMETERZ));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Z) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, magneticFieldZ);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding magnetometer(Z) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_PITCH));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pitch property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, pitch);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding pitch property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ROLL));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding roll property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, roll);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding roll property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERX));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(X) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationX);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(X) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERY));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Y) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationY);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Y) property value to telemetry payload.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(TELEMETRY_PROP_NAME_ACCELEROMETERZ));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Z) property name to telemetry payload.");
rc = az_json_writer_append_int32(&jw, accelerationZ);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding acceleration(Z) property value to telemetry payload.");
rc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing telemetry json payload.");
payload_buffer_span = az_json_writer_get_bytes_used_in_destination(&jw);
if ((payload_buffer_size - az_span_size(payload_buffer_span)) < 1)
{
LogError("Insufficient space for telemetry payload null terminator.");
return RESULT_ERROR;
}
payload_buffer[az_span_size(payload_buffer_span)] = null_terminator;
*payload_buffer_length = az_span_size(payload_buffer_span);
return RESULT_OK;
}
static int generate_device_info_payload(az_iot_hub_client const* hub_client, uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
{
az_json_writer jw;
az_result rc;
az_span payload_buffer_span = az_span_create(payload_buffer, payload_buffer_size);
az_span json_span;
rc = az_json_writer_init(&jw, payload_buffer_span, NULL);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed initializing json writer for telemetry.");
rc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed setting telemetry json root.");
rc = az_iot_hub_client_properties_writer_begin_component(
hub_client, &jw, AZ_SPAN_FROM_STR(SAMPLE_DEVICE_INFORMATION_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed writting component name.");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_MANUFACTURER_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MANUFACTURER_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_MANUFACTURER_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MANUFACTURER_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_MODEL_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MODEL_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_MODEL_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_MODEL_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_SOFTWARE_VERSION_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_VERSION_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_VERSION_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_OS_NAME_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_OS_NAME_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_OS_NAME_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_OS_NAME_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_ARCHITECTURE_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_ARCHITECTURE_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_ARCHITECTURE_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_NAME to payload.");
rc = az_json_writer_append_string(&jw, AZ_SPAN_FROM_STR(SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_TOTAL_STORAGE_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_STORAGE_PROPERTY_NAME to payload.");
rc = az_json_writer_append_double(&jw, SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE to payload. ");
rc = az_json_writer_append_property_name(&jw, AZ_SPAN_FROM_STR(SAMPLE_TOTAL_MEMORY_PROPERTY_NAME));
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_MEMORY_PROPERTY_NAME to payload.");
rc = az_json_writer_append_double(&jw, SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE, DOUBLE_DECIMAL_PLACE_DIGITS);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed adding SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE to payload. ");
rc = az_iot_hub_client_properties_writer_end_component(hub_client, &jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing component object.");
rc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(rc, RESULT_ERROR, "Failed closing telemetry json payload.");
payload_buffer_span = az_json_writer_get_bytes_used_in_destination(&jw);
if ((payload_buffer_size - az_span_size(payload_buffer_span)) < 1)
{
LogError("Insufficient space for telemetry payload null terminator.");
return RESULT_ERROR;
}
payload_buffer[az_span_size(payload_buffer_span)] = null_terminator;
*payload_buffer_length = az_span_size(payload_buffer_span);
return RESULT_OK;
}
static int generate_properties_update_response(
azure_iot_t* azure_iot,
az_span component_name, int32_t frequency, int32_t version,
uint8_t* buffer, size_t buffer_size, size_t* response_length)
{
az_result azrc;
az_json_writer jw;
az_span response = az_span_create(buffer, buffer_size);
azrc = az_json_writer_init(&jw, response, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed initializing json writer for properties update response.");
azrc = az_json_writer_append_begin_object(&jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed opening json in properties update response.");
// This Azure PnP Template does not have a named component,
// so az_iot_hub_client_properties_writer_begin_component is not needed.
azrc = az_iot_hub_client_properties_writer_begin_response_status(
&azure_iot->iot_hub_client,
&jw,
AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS),
(int32_t)AZ_IOT_STATUS_OK,
version,
AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_RESPONSE_SUCCESS));
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed appending status to properties update response.");
azrc = az_json_writer_append_int32(&jw, frequency);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed appending frequency value to properties update response.");
azrc = az_iot_hub_client_properties_writer_end_response_status(&azure_iot->iot_hub_client, &jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed closing status section in properties update response.");
// This Azure PnP Template does not have a named component,
// so az_iot_hub_client_properties_writer_end_component is not needed.
azrc = az_json_writer_append_end_object(&jw);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed closing json in properties update response.");
*response_length = az_span_size(az_json_writer_get_bytes_used_in_destination(&jw));
return RESULT_OK;
}
static int consume_properties_and_generate_response(
azure_iot_t* azure_iot, az_span properties,
uint8_t* buffer, size_t buffer_size, size_t* response_length)
{
int result;
az_json_reader jr;
az_span component_name;
int32_t version = 0;
az_result azrc = az_json_reader_init(&jr, properties, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed initializing json reader for properties update.");
const az_iot_hub_client_properties_message_type message_type =
AZ_IOT_HUB_CLIENT_PROPERTIES_MESSAGE_TYPE_WRITABLE_UPDATED;
azrc = az_iot_hub_client_properties_get_properties_version(
&azure_iot->iot_hub_client, &jr, message_type, &version);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed writable properties version.");
azrc = az_json_reader_init(&jr, properties, NULL);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed re-initializing json reader for properties update.");
while (az_result_succeeded(
azrc = az_iot_hub_client_properties_get_next_component_property(
&azure_iot->iot_hub_client, &jr, message_type,
AZ_IOT_HUB_CLIENT_PROPERTY_WRITABLE, &component_name)))
{
if (az_json_token_is_text_equal(&jr.token, AZ_SPAN_FROM_STR(WRITABLE_PROPERTY_TELEMETRY_FREQ_SECS)))
{
int32_t value;
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed getting writable properties next token.");
azrc = az_json_token_get_int32(&jr.token, &value);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed getting writable properties int32_t value.");
azure_pnp_set_telemetry_frequency((size_t)value);
result = generate_properties_update_response(
azure_iot, component_name, value, version, buffer, buffer_size, response_length);
EXIT_IF_TRUE(result != RESULT_OK, RESULT_ERROR, "generate_properties_update_response failed.");
}
else
{
LogError("Unexpected property received (%.*s).",
az_span_size(jr.token.slice), az_span_ptr(jr.token.slice));
}
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed moving to next json token of writable properties.");
azrc = az_json_reader_skip_children(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed skipping children of writable properties.");
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed moving to next json token of writable properties (again).");
}
return RESULT_OK;
}

Просмотреть файл

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*
* Azure_IoT_PnP_Template.cpp implements the IoT Plug and Play template
* specific for the Arduino Portenta H7 board.
*/
#ifndef AZURE_IOT_PNP_TEMPLATE_H
#define AZURE_IOT_PNP_TEMPLATE_H
#include "AzureIoT.h"
/*
* @brief Initializes internal components of this module.
* @remark It must be called once by the application, before any other function
* call related to Azure IoT.
*/
void azure_pnp_init();
/*
* @brief Returns the model id of the IoT Plug and Play template implemented by this device.
* @remark Every IoT Plug and Play template has a model id that must be informed by the
* device during Azure IoT provisioning and connection with the Azure IoT Hub.
* @return az_span An `az_span` containing the model id implemented by this module.
*/
const az_span azure_pnp_get_model_id();
/*
* @brief Sends the device description to Azure IoT Central.
* @remark Azure IoT Central expects the application to send a description of device and its capabilities.
* This function generates a description of the Arduino Portenta H7 and sends it to Azure
* IoT Central.
*
* @param[in] azure_iot A pointer the azure_iot_t instance with the state of the Azure IoT client.
* @param[in] request_id An unique identification number to correlate the response with when
* `on_properties_update_completed` (set in azure_iot_config_t) is invoked.
* @return int 0 if the function succeeds, non-zero if any error occurs.
*/
int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id);
/*
* @brief Sets with which minimum frequency this module should send telemetry to Azure IoT Central.
* @remark `azure_pnp_send_telemetry` is used to send telemetry, but it will not send anything
* unless enough time has passed since the last telemetry has been published.
* This delay is defined internally by `telemetry_frequency_in_seconds`,
* set initially to once every 10 seconds.
*
* @param[in] frequency_in_seconds Period of time, in seconds, to wait between two consecutive
* telemetry payloads are sent to Azure IoT Central.
*/
void azure_pnp_set_telemetry_frequency(size_t frequency_in_seconds);
/*
* @brief Sends telemetry implemented by this IoT Plug and Play application to Azure IoT Central.
* @remark The IoT Plug and Play template implemented by this device is specific to the
* Arduino Portenta H7 board, which contains several sensors. The template defines telemetry
* data points for temperature, humidity, pressure, altitude, luminosity, magnetic field,
* rolling and pitch angles, as well as acceleration. All of these data are read from the
* board sensors and sent to Azure IoT Central when `azure_pnp_send_telemetry` is called.
* This function must be called frequently enough, no slower than the frequency set
* with `azure_pnp_set_telemetry_frequency` (or the default frequency of 10 seconds).
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_send_telemetry(azure_iot_t* azure_iot);
/*
* @brief Handles a command when it is received from Azure IoT Central.
* @remark This function will perform the task requested by the command received
* (if the command matches the expected name) and sends back a response to
* Azure IoT Central.
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
* @param[in] command_request The `command_request_t` instance containing the details of the
* device command.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t command_request);
/*
* @brief Handles a payload with writable properties received from Azure IoT Central.
* @remark This function will consume the writable properties update received
* and send back a response to Azure IoT Central.
*
* @param[in] azure_iot A pointer to a azure_iot_t instance, previously initialized
* with `azure_iot_init`.
* @param[in] properties Raw properties writable-properties payload received from Azure.
* @param[in] request_id The request ID of the response that is sent to the Azure IoT Central.
* In Azure IoT Plug and Play, a response to a writable-property update is
* itself a reported-property (device-side property) update, so it gets a
* a response from Azure with the same request ID provided here as argument.
*
* return int 0 on success, non-zero if any failure occurs.
*/
int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span properties, uint32_t request_id);
#endif // AZURE_IOT_PNP_TEMPLATE_H

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Azure IoT Central
#define IOT_CONFIG_DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
// User-agent (url-encoded) provided by the MQTT client to Azure IoT Services.
// When developing for your own Arduino-based platform, please follow the format '(ard;<platform>)'.
#define IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard;portentaH7)"
// For how long the MQTT password (SAS token) is valid, in minutes.
// After that, the sample automatically generates a new password and re-connects.
#define IOT_CONFIG_MQTT_PASSWORD_LIFETIME_IN_MINUTES 60
// Time Zone Offset
#define IOT_CONFIG_TIME_ZONE -8
#define IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define IOT_CONFIG_DAYLIGHT_SAVINGS true

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 161 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 118 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 27 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 85 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 84 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 69 KiB

Просмотреть файл

@ -0,0 +1,371 @@
---
page_type: sample
description: Connecting Arduino Portenta H7 to Azure IoT Central using the Azure SDK for C Arduino library
languages:
- c
products:
- azure-iot
- azure-iot-pnp
- azure-iot-central
---
# Getting started with the Arduino Portenta H7 and Azure IoT Central with Azure SDK for C Arduino library
**Total completion time**: 30 minutes
- [Getting started with the Arduino Portenta H7 and Azure IoT Central with Azure SDK for C Arduino library](#getting-started-with-the-arduino-portenta-h7-and-azure-iot-central-with-azure-sdk-for-c-arduino-library)
- [Introduction](#introduction)
- [What is Covered](#what-is-covered)
- [Prerequisites](#prerequisites)
- [IoT Central and Device Setup](#iot-central-and-device-setup)
- [Create the IoT Central Application](#create-the-iot-central-application)
- [Create a new device](#create-a-new-device)
- [Arduino IDE Setup](#arduino-ide-setup)
- [Run the Sample](#run-the-sample)
- [View your device data from IoT Central](#view-your-device-data-from-iot-central)
- [Verify the device status](#verify-the-device-status)
- [View device information](#view-device-information)
- [View telemetry](#view-telemetry)
- [Send a command](#send-a-command)
- [Clean up resources](#clean-up-resources)
- [Certificates - Important to know](#certificates---important-to-know)
- [Additional Information](#additional-information)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## Introduction
In this tutorial you will use the Azure SDK for C to connect the [Arduino Portenta H7](https://docs.arduino.cc/hardware/portenta-h7) to Azure IoT Central. The article is part of the series [IoT Device Development](https://go.microsoft.com/fwlink/p/?linkid=2129824). The series introduces device developers to the Azure SDK for C, and shows how to connect several device evaluation kits to Azure IoT.
### What is Covered
You will complete the following tasks:
* Install the Azure SDK for C library on Arduino
* Build the image and flash it onto the Arduino Portenta H7
* Use Azure IoT Central to create cloud components, view properties, view device telemetry, and call direct commands
_The following was run on Windows 10 and WSL1 Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.19 and Arduino Arduino Nano RP2040 Connect with headers._
## Prerequisites
* Have an [Azure account](https://azure.microsoft.com/) created.
* Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
## IoT Central and Device Setup
### Create the IoT Central Application
There are several ways to connect devices to Azure IoT. In this section, you learn how to connect a device by using Azure IoT Central. IoT Central is an IoT application platform that reduces the cost and complexity of creating and managing IoT solutions.
To create a new application:
1. Go to [Azure IoT Central portal](https://apps.azureiotcentral.com/).
1. On the left side menu, select **'My apps'**.
1. Select **'+ New application'**.
1. In the 'Custom app' box, select **'Create app'**.
1. Create a custom Application name and a URL.
1. Under 'Pricing plan', select **'Free'** to activate a 7-day trial.
![IoT Central create an application](media/iotcentralcreate-custom.png)
1. Select **'Create'**.
1. After IoT Central provisions the application, it redirects you automatically to the new application dashboard.
> Note: If you have an existing IoT Central application, you can use it to complete the steps in this article rather than create a new application.
### Create a new device
In this section, you will use the IoT Central application dashboard to create a new logical device.
To create a device:
1. On the left side menu, under 'Connect', select **'Devices'**.
1. Select **'+ New'**. A 'Create a new device' window will appear.
1. Fill in the desired 'Device name' and 'Device ID'.
1. Leave Device template as 'Unassigned'.
![IoT Central create a device](media/iotcentralcreate-device.png)
1. Select **'Create'**. The newly created device will appear in the 'All devices' list.
1. Under 'Device name', select your newly created device name.
1. In the top menu bar, select **'Connect'**. A 'Device connection groups' window will appear.
![IoT Central create a device](media/iotcentraldevice-connection-info.png)
1. We will need the following information from this window:
- ID scope
- Device ID
- Primary key
## Arduino IDE Setup
1. Open the Arduino IDE.
1. Install the Azure SDK for Embedded C library.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'azure-sdk-for-c'** library.
- Install the latest version.
1. Install Arduino Mbed OS Portenta Boards support in the Arduino IDE. [Full instructions can be found here.](https://docs.arduino.cc/software/ide-v1/tutorials/getting-started/cores/arduino-mbed_portenta)
- Navigate to **Tools > Board > Boards Manager**.
- Search for **'Portenta'** and install the **Arduino Mbed OS Portenta Boards** core.
- Install the latest version.
*Note: This process may take several minutes.*
1. Nagivate to **Tools > Board > Arduino Mbed OS Portenta Boards** and select **'Arduino Portenta H7 (M7 core)'**.
1. In **Tools > Flash split**, set the flash split to be **'2MB M7 + M4 in SDRAM'**.
1. If this is your first time using the Portenta, [follow these instructions to update the WiFi firmware on the Portenta](https://support.arduino.cc/hc/en-us/articles/4403365234322-How-to-update-Wi-Fi-firmware-on-Portenta-H7).
1. Install additional libraries for the Portenta Embedded C SDK sample.
- This process is more involved than typical because we are using the NTP Client Generic library which has circular dependencies so we must do a special install to only grab the what we need.
- There are two ways to do this:
1. Download the NTP Client Generic library manually from its repository, or
2. Use the Arduino CLI.
- This tutorial will use the CLI approach because it is faster and easier to describe.
- Using the Arduino CLI, type and run the following command to install the NTP Client :
```
arduino-cli lib install --no-deps NTPClient_Generic
```
- Since we're already in the Arduino CLI, let's install remaining libraries (can also install these from Library Manager):
```
arduino-cli lib install "Azure SDK for C" ArduinoBearSSL Time ArduinoMqttClient ArduinoECCX08
```
1. You may need to restart the Arduino IDE for changes to show up.
## Run the Sample
1. Open the Arduino Portenta H7 sample.
- In the Arduino IDE, navigate to **File > Examples > Azure SDK For C**
- Select **Azure_IoT_Central_Arduino_Portenta_H7** to open the sample.
1. Navigate to the '*iot_configs.h*' file
1. In the '*iot_configs.h*' file, fill in your credentials.
- Add in your WiFi SSID and password.
- Paste your ID Scope for the `IOT_CONFIG_DPS_ID_SCOPE` variable.
- Paste your Device ID for the `IOT_CONFIG_DEVICE_ID` variable.
- Finally, paste your Primary Key for the `IOT_CONFIG_DEVICE_KEY` variable.
1. Connect the Arduino Portenta H7 to your USB port.
1. On the Arduino IDE, select the port.
- Navigate to **Tools > Port**.
- Select the port to which the Portenta H7 is connected.
1. Upload the sketch.
- Navigate to **Sketch > Upload**.
*Note: This process may take several minutes.*
<details><summary><i>Expected output of the upload:</i></summary>
<p>
```text
Sketch uses 431236 bytes (21%) of program storage space. Maximum is 1966080 bytes.
Global variables use 95208 bytes (18%) of dynamic memory, leaving 428416 bytes for local variables. Maximum is 523624 bytes.
dfu-util 0.10-dev
Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/
Opening DFU capable USB device...
Device ID 2341:035b
Device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Interface #0 ...
Determining device status...
DFU state(2) = dfuIDLE, status(0) = No error condition is present
DFU mode device DFU version 011a
Device returned transfer size 4096
DfuSe interface name: "Internal Flash "
Downloading element to address = 0x08040000, size = 435916
Erase [=========================] 100% 435916 bytes
Erase done.
Download [=========================] 100% 435916 bytes
Download done.
File downloaded successfully
Transitioning to dfuMANIFEST state
```
</p>
</details>
1. While the sketch is uploading, open the Serial Monitor to monitor the MCU (microcontroller) locally via the Serial Port.
- Navigate to **Tools > Serial Monitor**.
If you perform this step right away after uploading the sketch, the serial monitor will show an output similar to the following upon success:
```text
2106-02-06 23:32:04 [INFO] Connecting to WIFI wifi_ssid <ssid>
...
2106-02-06 23:32:44 [INFO] WiFi connected, IP address: 604051168
2106-02-06 23:32:44 [INFO] Setting time using SNTP
2022-06-16 13:04:50 [INFO] Time initialized!
2022-06-16 13:04:50 [INFO] Azure IoT client initialized (state=2)
2022-06-16 13:04:50 [INFO] MQTT Client ID: <device id>
2022-06-16 13:04:50 [INFO] MQTT Username: <id scope>/registrations/<device id>/api-version=2019-03-31
2022-06-16 13:04:50 [INFO] MQTT Password: ***
2022-06-16 13:04:50 [INFO] MQTT client address: global.azure-devices-provisioning.net
2022-06-16 13:04:50 [INFO] MQTT client port: 8883
2022-06-16 13:04:52 [INFO] MQTT client connected.
2022-06-16 13:04:53 [INFO] MQTT client subscribing to '$dps/registrations/res/#'
2022-06-16 13:04:53 [INFO] MQTT topic subscribed
2022-06-16 13:04:54 [INFO] MQTT client publishing to '$dps/registrations/PUT/iotdps-register/?$rid=1'
2022-06-16 13:04:55 [INFO] MQTT message received.
2022-06-16 13:04:55 [INFO] MQTT client publishing to '$dps/registrations/GET/iotdps-get-operationstatus/?$rid=1&operationId=4.36e237c8db462f45.1209aff3-9c58-4c56-b070-0fd4359286d1'
2022-06-16 13:04:56 [INFO] MQTT message received.
2022-06-16 13:04:58 [INFO] MQTT client publishing to '$dps/registrations/GET/iotdps-get-operationstatus/?$rid=1&operationId=4.36e237c8db462f45.1209aff3-9c58-4c56-b070-0fd4359286d1'
2022-06-16 13:04:59 [INFO] MQTT message received.
2022-06-16 13:05:00 [INFO] MQTT client being disconnected.
2022-06-16 13:05:00 [INFO] MQTT Client ID: <device id>
2022-06-16 13:05:00 [INFO] MQTT Username: <provisioned iot hub fqdn>.azure-devices.net/<device id>/?api-version=2020-09-30&DeviceClientType=c%2F1.3.1(ard;portentaH7)&model-id=dtmi%3Aazureiot%3Adevkit%3Afreertos%3AEsp32AzureIotKit%3B1
2022-06-16 13:05:00 [INFO] MQTT Password: ***
2022-06-16 13:05:00 [INFO] MQTT client address: <provisioned iot hub fqdn>.azure-devices.net
2022-06-16 13:05:00 [INFO] MQTT client port: 8883
2022-06-16 13:05:02 [INFO] MQTT client connected.
2022-06-16 13:05:02 [INFO] MQTT client subscribing to '$iothub/methods/POST/#'
2022-06-16 13:05:03 [INFO] MQTT topic subscribed
2022-06-16 13:05:03 [INFO] MQTT client subscribing to '$iothub/twin/res/#'
2022-06-16 13:05:03 [INFO] MQTT topic subscribed
2022-06-16 13:05:04 [INFO] MQTT client subscribing to '$iothub/twin/PATCH/properties/desired/#'
2022-06-16 13:05:04 [INFO] MQTT topic subscribed
2022-06-16 13:05:04 [INFO] MQTT client publishing to '$iothub/twin/PATCH/properties/reported/?$rid=0'
2022-06-16 13:05:04 [INFO] MQTT client publishing to 'devices/<device id>/messages/events/'
2022-06-16 13:05:04 [INFO] MQTT message received.
2022-06-16 13:05:04 [INFO] Properties update request completed (id=0, status=204)
2022-06-16 13:05:14 [INFO] MQTT client publishing to 'devices/<device id>/messages/events/'
```
## View your device data from IoT Central
With IoT Central, you can view the device status and information, observe telemetry, and send commands.
1. Go to your [IoT Central application portal](https://apps.azureiotcentral.com/myapps).
1. Select your application.
1. On the left side menu, under 'Connect', select **'Devices'**.
### Verify the device status
To view the device status in IoT Central portal:
1. Find your device in the devices list.
1. Confirm the 'Device status' of the device is updated to 'Provisioned'.
1. Confirm the 'Device template' of the device has updated to 'Espressif ESP32 Azure IoT Kit'.
> Note: The **'Espressif ESP32 Azure IoT Kit'** device template is used in this **Arduino Portenta H7 sample for simplicity.** It is a published template available from IoT Central. For more information on creating a custom device template, view these [instructions](https://docs.microsoft.com/en-us/azure/iot-central/core/howto-set-up-template).
![IoT Central device status](media/azure-iot-central-device-view-status.png)
### View device information
To view the device information in IoT Central portal:
1. Click on your device's name in the device list.
1. Select the **'About'** tab.
![IoT Central device info](media/azure-iot-central-device-about.png)
### View telemetry
To view telemetry in IoT Central portal:
1. Click on your device's name in the device list.
1. Select the **'Overview'** tab.
1. View the telemetry as the device sends messages to the cloud.
![IoT Central device telemetry](media/azure-iot-central-device-telemetry.png)
### Send a command
To send a command to the device:
1. Select the **'Commands'** tab.
1. Locate the 'Display Text' box.
1. In the 'Content' textbox, enter the text to be displayed on the screen.
1. Select **'Run'**.
1. Because this is a simulated screen, the text will print to the log.
```
2022-06-16 13:31:50 [INFO] OLED display: <text>
```
To toggle an LED:
1. Select the **'Commands'** tab.
1. Locate the 'Toggle LED 1' or 'Toggle LED 2' box.
1. Select **'Run'**.
1. Because these are simulated LEDs, the following will print to the log.
```
2022-06-16 13:31:46 [INFO] LED <#> state: <ON/OFF>
```
![IoT Central invoke method](media/azure-iot-central-invoke-method.png)
### Clean up resources
If you no longer need the Azure resources created in this tutorial, you can delete them from the IoT Central portal. Optionally, if you continue to another tutorial in this Getting Started guide, you can keep the resources you've already created and reuse them.
To keep the Azure IoT Central sample application but remove only specific devices:
1. On the left side menu, under 'Connect', select **'Devices'**.
1. Hover over your device's name and click on the circle that appears to the left. The circle will turn blue.
1. Select **'Delete'**. A box will appear to confirm deletion.
1. Select **'Delete'** again.
To remove the entire Azure IoT Central sample application and all its devices and resources:
1. On the left side menu, under 'Settings', select **'Application'**.
1. Select the **'Management'** tab.
1. Scroll to the bottom of the page.
1. Select **'Delete'**. A box will appear to confirm deletion.
1. Select **'Delete'** again.
## Certificates - Important to know
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
The Azure SDK for C Arduino library automatically installs the root certificate used in the United States regions, and adds it to the Arduino sketch project when the library is included.
For other regions (and private cloud environments), please use the appropriate root CA certificate.
### Additional Information
For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team.
## Troubleshooting
- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy).
- File an issue via [Github Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose).
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using the `azure` and `c` tags.
## Contributing
This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md).
### License
Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license.

Просмотреть файл

@ -0,0 +1,514 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*--- Libraries ---*/
// C99 libraries.
#include <cstdbool>
#include <cstdlib>
#include <cstring>
#include <time.h>
// Libraries for SSL client, MQTT client, and WiFi connection.
#include <ArduinoBearSSL.h>
#include <ArduinoMqttClient.h>
#include <WiFiNINA.h>
// Libraries for SAS token generation.
#include <ECCX08.h>
// Azure IoT SDK for C includes.
#include <az_core.h>
#include <az_iot.h>
// Sample header.
#include "iot_configs.h"
// Logging
#include "SerialLogger.h"
/*--- Macros ---*/
#define BUFFER_LENGTH_MQTT_CLIENT_ID 256
#define BUFFER_LENGTH_MQTT_PASSWORD 256
#define BUFFER_LENGTH_MQTT_TOPIC 128
#define BUFFER_LENGTH_MQTT_USERNAME 512
#define BUFFER_LENGTH_SAS 32
#define BUFFER_LENGTH_SAS_ENCODED_SIGNED_SIGNATURE 64
#define BUFFER_LENGTH_SAS_SIGNATURE 512
#define BUFFER_LENGTH_DATETIME_STRING 256
#define LED_PIN 2 // High on error. Briefly high for each successful send.
// Time and Time Zone.
#define SECS_PER_MIN 60
#define SECS_PER_HOUR (SECS_PER_MIN * 60)
#define GMT_OFFSET_SECS (IOT_CONFIG_DAYLIGHT_SAVINGS ? \
((IOT_CONFIG_TIME_ZONE + IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * SECS_PER_HOUR) : \
(IOT_CONFIG_TIME_ZONE * SECS_PER_HOUR))
// Exit into infinite loop
#define EXIT_LOOP(condition, errorMessage) \
do \
{ \
if (condition) { \
Logger.Error(errorMessage); \
while (1); \
} \
} while (0)
/*--- Sample static variables --*/
// Clients for WiFi connection, SSL, MQTT, and Azure IoT SDK for C.
static WiFiClient wiFiClient;
static BearSSLClient bearSSLClient(wiFiClient);
static MqttClient mqttClient(bearSSLClient);
static az_iot_hub_client azIoTHubClient;
// MQTT variables.
static char mqttClientId[BUFFER_LENGTH_MQTT_CLIENT_ID];
static char mqttUsername[BUFFER_LENGTH_MQTT_USERNAME];
static char mqttPassword[BUFFER_LENGTH_MQTT_PASSWORD];
// Telemetry variables.
static char telemetryTopic[BUFFER_LENGTH_MQTT_TOPIC];
static unsigned long telemetryNextSendTimeMs;
static String telemetryPayload;
static uint32_t telemetrySendCount;
/*--- Functions ---*/
// Initialization and connection functions.
void connectToWiFi();
void initializeAzureIoTHubClient();
void initializeMQTTClient();
void connectMQTTClientToAzureIoTHub();
// Telemetry and message-callback functions.
void onMessageReceived(int messageSize);
static void sendTelemetry();
static char* generateTelemetry();
// SAS Token related functions.
static void generateMQTTPassword();
static void generateSASBase64EncodedSignedSignature(
uint8_t const* sasSignature, size_t const sasSignatureSize,
uint8_t* encodedSignedSignature, size_t encodedSignedSignatureSize,
size_t* encodedSignedSignatureLength);
static uint64_t getSASTokenExpirationTime(uint32_t minutes);
// Time and Error functions.
static unsigned long getTime();
static String getFormattedDateTime(unsigned long epochTimeInSeconds);
static String mqttErrorCodeName(int errorCode);
/*---------------------------*/
/* Main code execution */
/*---------------------------*/
/*
* setup:
* Initialization and connection of serial communication, WiFi client, Azure IoT SDK for C client,
* and MQTT client.
*/
void setup()
{
while (!Serial);
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
connectToWiFi();
initializeAzureIoTHubClient();
initializeMQTTClient();
connectMQTTClientToAzureIoTHub();
digitalWrite(LED_PIN, LOW);
telemetryNextSendTimeMs = 0;
}
/*
* loop:
* Check for connection and reconnect if necessary.
* Send Telemetry and receive messages.
*/
void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
connectToWiFi();
}
// Telemetry
if (millis() > telemetryNextSendTimeMs)
{
// Check for MQTT Client connection to Azure IoT hub. Reconnect if needed.
if (!mqttClient.connected())
{
connectMQTTClientToAzureIoTHub();
}
sendTelemetry();
telemetryNextSendTimeMs = millis() + IOT_CONFIG_TELEMETRY_FREQUENCY_MS;
}
// MQTT loop must be called to process Telemetry and Cloud-to-Device (C2D) messages.
mqttClient.poll();
delay(50);
}
/*-----------------------------------------------*/
/* Initialization and connection functions */
/*-----------------------------------------------*/
/*
* connectToWifi:
* The WiFi client connects, using the provided SSID and password.
* The WiFi client synchronizes the time on the device.
*/
void connectToWiFi()
{
Logger.Info("Attempting to connect to WIFI SSID: " + String(IOT_CONFIG_WIFI_SSID));
WiFi.begin(IOT_CONFIG_WIFI_SSID, IOT_CONFIG_WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(IOT_CONFIG_WIFI_CONNECT_RETRY_MS);
}
Serial.println();
Logger.Info("WiFi connected, IP address: " + String(WiFi.localIP()) + ", Strength (dBm): " + WiFi.RSSI());
Logger.Info("Syncing time.");
while (getTime() == 0)
{
Serial.print(".");
delay(500);
}
Serial.println();
Logger.Info("Time synced!");
}
/*
* initializeAzureIoTHubClient:
* The Azure IoT SDK for C client uses the provided hostname, device id, and user agent.
*/
void initializeAzureIoTHubClient()
{
Logger.Info("Initializing Azure IoT Hub client.");
az_span hostname = AZ_SPAN_FROM_STR(IOT_CONFIG_IOTHUB_FQDN);
az_span deviceId = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_ID);
az_iot_hub_client_options options = az_iot_hub_client_options_default();
options.user_agent = AZ_SPAN_FROM_STR(IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT);
int result = az_iot_hub_client_init(&azIoTHubClient, hostname, deviceId, &options);
EXIT_LOOP(az_result_failed(result), "Failed to initialize Azure IoT Hub client. Return code: " + result);
Logger.Info("Azure IoT Hub hostname: " + String(IOT_CONFIG_IOTHUB_FQDN));
Logger.Info("Azure IoT Hub client initialized.");
}
/*
* initializeMQTTClient:
* The MQTT client uses the client id and username from the Azure IoT SDK for C client.
* The MQTT client uses the generated password (the SAS token).
*/
void initializeMQTTClient()
{
Logger.Info("Initializing MQTT client.");
int result;
result = az_iot_hub_client_get_client_id(
&azIoTHubClient, mqttClientId, sizeof(mqttClientId), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get MQTT client ID. Return code: " + result);
result = az_iot_hub_client_get_user_name(
&azIoTHubClient, mqttUsername, sizeof(mqttUsername), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get MQTT username. Return code: " + result);
generateMQTTPassword(); // SAS Token
mqttClient.setId(mqttClientId);
mqttClient.setUsernamePassword(mqttUsername, mqttPassword);
mqttClient.onMessage(onMessageReceived); // Set callback for C2D messages
Logger.Info("Client ID: " + String(mqttClientId));
Logger.Info("Username: " + String(mqttUsername));
Logger.Info("MQTT client initialized.");
}
/*
* connectMQTTClientToAzureIoTHub:
* The SSL library sets a callback to validate the server certificate.
* The MQTT client connects to the provided hostname. The port is pre-set.
* The MQTT client subscribes to the Cloud to Device (C2D) topic to receive messages.
*/
void connectMQTTClientToAzureIoTHub()
{
Logger.Info("Connecting to Azure IoT Hub.");
// Set a callback to get the current time used to validate the server certificate.
ArduinoBearSSL.onGetTime(getTime);
while (!mqttClient.connect(IOT_CONFIG_IOTHUB_FQDN, AZ_IOT_DEFAULT_MQTT_CONNECT_PORT))
{
int code = mqttClient.connectError();
Logger.Error("Cannot connect to Azure IoT Hub. Reason: " + mqttErrorCodeName(code) + ", Code: " + code);
delay(5000);
}
Logger.Info("Connected to your Azure IoT Hub!");
mqttClient.subscribe(AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC);
Logger.Info("Subscribed to MQTT topic: " + String(AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC));
}
/*------------------------------------------------*/
/* Telemetry and message-callback functions */
/*------------------------------------------------*/
/*
* onMessageReceived:
* The function called when device receives a message on the subscribed C2D topic.
* Callback function signature is defined by the ArduinoMQTTClient library.
* Message received is printed to the terminal.
*/
void onMessageReceived(int messageSize)
{
Logger.Info("Message received: Topic: " + mqttClient.messageTopic() + ", Length: " + messageSize);
Logger.Info("Message: ");
while (mqttClient.available())
{
Serial.print((char)mqttClient.read());
}
Serial.println();
}
/*
* sendTelemetry:
* The Azure IoT SDK for C client creates the MQTT topic to publish a telemetry message.
* The MQTT client creates and sends the telemetry mesage on the topic.
*/
static void sendTelemetry()
{
digitalWrite(LED_PIN, HIGH);
Logger.Info("Arduino Nano RP2040 Connect sending telemetry . . . ");
int result = az_iot_hub_client_telemetry_get_publish_topic(
&azIoTHubClient, NULL, telemetryTopic, sizeof(telemetryTopic), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get telemetry publish topic. Return code: " + result);
mqttClient.beginMessage(telemetryTopic);
mqttClient.print(generateTelemetry());
mqttClient.endMessage();
Logger.Info("Telemetry sent.");
delay(100);
digitalWrite(LED_PIN, LOW);
}
/*
* generateTelemetry:
* Simulated telemetry.
* In your application, this function should retrieve real telemetry data from the device and format
* it as needed.
*/
static char* generateTelemetry()
{
telemetryPayload = String("{ \"msgCount\": ") + telemetrySendCount + " }";
telemetrySendCount++;
return (char*)telemetryPayload.c_str();
}
/*************************************/
/* SAS Token related functions */
/*************************************/
/*
* generateMQTTPassword:
* The MQTT password is the generated SAS token. The process is:
* 1. Get the SAS token expiration time from the provided value. (Default 60 minutes).
* 2. Azure IoT SDK for C creates the SAS signature from this expiration time.
* 3. Sign and encode the SAS signature.
* 4. Azure IoT SDK for C creates the MQTT Password from the expiration time and the encoded,
* signed SAS signature.
*/
static void generateMQTTPassword()
{
int result;
uint64_t sasTokenDuration = 0;
uint8_t signature[BUFFER_LENGTH_SAS_SIGNATURE] = {0};
az_span signatureAzSpan = AZ_SPAN_FROM_BUFFER(signature);
uint8_t encodedSignedSignature[BUFFER_LENGTH_SAS_ENCODED_SIGNED_SIGNATURE] = {0};
size_t encodedSignedSignatureLength = 0;
// Get the signature. It will be signed later with the decoded device key.
// To change the sas token duration, see IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES in iot_configs.h
sasTokenDuration = getSASTokenExpirationTime(IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES);
result = az_iot_hub_client_sas_get_signature(
&azIoTHubClient, sasTokenDuration, signatureAzSpan, &signatureAzSpan);
EXIT_LOOP(az_result_failed(result), "Could not get the signature for SAS Token. Return code: " + result);
// Sign and encode the signature (b64 encoded, HMAC-SHA256 signing).
// Uses the decoded device key.
generateSASBase64EncodedSignedSignature(
az_span_ptr(signatureAzSpan), az_span_size(signatureAzSpan),
encodedSignedSignature, sizeof(encodedSignedSignature), &encodedSignedSignatureLength);
// Get the MQTT password (SAS Token) from the base64 encoded, HMAC signed bytes.
az_span encodedSignedSignatureAzSpan = az_span_create(encodedSignedSignature,
encodedSignedSignatureLength);
result = az_iot_hub_client_sas_get_password(
&azIoTHubClient, sasTokenDuration, encodedSignedSignatureAzSpan, AZ_SPAN_EMPTY,
mqttPassword, sizeof(mqttPassword), NULL);
EXIT_LOOP(az_result_failed(result), "Could not get the MQTT password. Return code: " + result);
}
/*
* generateSASBase64EncodedSignedSignature:
* Sign and encode a signature. It is signed using the provided device key.
* The process is:
* 1. Decode the encoded device key.
* 2. Sign the signature with the decoded device key.
* 3. Encode the signed signature.
*/
static void generateSASBase64EncodedSignedSignature(
uint8_t const* sasSignature, size_t const sasSignatureSize,
uint8_t* encodedSignedSignature, size_t encodedSignedSignatureSize,
size_t* encodedSignedSignatureLength)
{
int result;
unsigned char sasDecodedKey[BUFFER_LENGTH_SAS] = {0};
az_span sasDecodedKeySpan = AZ_SPAN_FROM_BUFFER(sasDecodedKey);
int32_t sasDecodedKeyLength = 0;
uint8_t sasHMAC256SignedSignature[BUFFER_LENGTH_SAS] = {0};
// Decode the SAS base64 encoded device key to use for HMAC signing.
az_span configDeviceKeySpan = az_span_create((uint8_t*)IOT_CONFIG_DEVICE_KEY, sizeof(IOT_CONFIG_DEVICE_KEY) - 1);
result = az_base64_decode(sasDecodedKeySpan, configDeviceKeySpan, &sasDecodedKeyLength);
EXIT_LOOP(result != AZ_OK, "az_base64_decode failed. Return code: " + result);
// HMAC-SHA256 sign the signature with the decoded device key.
result = ECCX08.begin();
EXIT_LOOP(!result, "Failed to communicate with ATECC608.");
result = ECCX08.nonce(sasDecodedKey);
EXIT_LOOP(!result, "Failed to do nonce.");
result = ECCX08.beginHMAC(0xFFFF);
EXIT_LOOP(!result, "Failed to start HMAC operation.");
result = ECCX08.updateHMAC(sasSignature, sasSignatureSize);
EXIT_LOOP(!result, "Failed to update HMAC with signature.");
result = ECCX08.endHMAC(sasHMAC256SignedSignature);
EXIT_LOOP(!result, "Failed to end HMAC operation.");
// Base64 encode the result of the HMAC signing.
az_span signedSignatureSpan = az_span_create(sasHMAC256SignedSignature, sizeof(sasHMAC256SignedSignature));
az_span encodedSignedSignatureSpan = az_span_create(encodedSignedSignature, encodedSignedSignatureSize);
result = az_base64_encode(encodedSignedSignatureSpan, signedSignatureSpan, (int32_t*) encodedSignedSignatureLength);
EXIT_LOOP(result != AZ_OK, "az_base64_encode failed. Return code: " + result);
}
/*
* getSASTokenExpirationTime:
* Calculate expiration time from current time and duration value.
*/
static uint64_t getSASTokenExpirationTime(uint32_t minutes)
{
unsigned long now = getTime(); // GMT
unsigned long expiryTime = now + (SECS_PER_MIN * minutes); // For SAS Token
unsigned long localNow = now + GMT_OFFSET_SECS;
unsigned long localExpiryTime = expiryTime + GMT_OFFSET_SECS;
Logger.Info("UTC Current time: " + getFormattedDateTime(now) + " (epoch: " + now + " secs)");
Logger.Info("UTC Expiry time: " + getFormattedDateTime(expiryTime) + " (epoch: " + expiryTime + " secs)");
Logger.Info("Local Current time: " + getFormattedDateTime(localNow));
Logger.Info("Local Expiry time: " + getFormattedDateTime(localExpiryTime));
return (uint64_t)expiryTime;
}
/**********************************/
/* Time and Error functions */
/**********************************/
/*
* getTime:
* WiFi client returns the seconds corresponding to GMT epoch time.
* This function used as a callback by the SSL library to validate the server certificate
* and in SAS token generation.
*/
static unsigned long getTime()
{
return WiFi.getTime();
}
/*
* getFormattedDateTime:
* Custom formatting for epoch seconds. Used in logging.
*/
static String getFormattedDateTime(unsigned long epochTimeInSeconds)
{
char dateTimeString[BUFFER_LENGTH_DATETIME_STRING];
time_t epochTimeInSecondsAsTimeT = (time_t)epochTimeInSeconds;
struct tm* timeInfo = localtime(&epochTimeInSecondsAsTimeT);
strftime(dateTimeString, 20, "%F %T", timeInfo);
return String(dateTimeString);
}
/*
* mqttErrorCodeName:
* Legibly prints AruinoMqttClient library error enum values.
*/
static String mqttErrorCodeName(int errorCode)
{
String errorMessage;
switch (errorCode)
{
case MQTT_CONNECTION_REFUSED:
errorMessage = "MQTT_CONNECTION_REFUSED";
break;
case MQTT_CONNECTION_TIMEOUT:
errorMessage = "MQTT_CONNECTION_TIMEOUT";
break;
case MQTT_SUCCESS:
errorMessage = "MQTT_SUCCESS";
break;
case MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
errorMessage = "MQTT_UNACCEPTABLE_PROTOCOL_VERSION";
break;
case MQTT_IDENTIFIER_REJECTED:
errorMessage = "MQTT_IDENTIFIER_REJECTED";
break;
case MQTT_SERVER_UNAVAILABLE:
errorMessage = "MQTT_SERVER_UNAVAILABLE";
break;
case MQTT_BAD_USER_NAME_OR_PASSWORD:
errorMessage = "MQTT_BAD_USER_NAME_OR_PASSWORD";
break;
case MQTT_NOT_AUTHORIZED:
errorMessage = "MQTT_NOT_AUTHORIZED";
break;
default:
errorMessage = "Unknown";
break;
}
return errorMessage;
}

Просмотреть файл

@ -0,0 +1,302 @@
---
page_type: sample
description: Connecting Arduino Nano RP2040 Connect to Azure IoT Hub using the Azure SDK for C Arduino library
languages:
- c
products:
- azure-iot
- azure-iot-pnp
- azure-iot-dps
- azure-iot-hub
---
# Getting started with the Arduino Nano RP2040 Connect and Embedded C IoT Hub Client with Azure SDK for C Arduino library
**Total completion time**: 30 minutes
- [Getting started with the Arduino Nano RP2040 Connect and Embedded C IoT Hub Client with Azure SDK for C Arduino library](#getting-started-with-the-arduino-nano-rp2040-connect-and-embedded-c-iot-hub-client-with-azure-sdk-for-c-arduino-library)
- [Introduction](#introduction)
- [What is Covered](#what-is-covered)
- [Prerequisites](#prerequisites)
- [IoT Hub Device Setup](#iot-hub-device-setup)
- [Arduino IDE Setup](#arduino-ide-setup)
- [Run the Sample](#run-the-sample)
- [Troubleshooting](#troubleshooting)
- [Additional Help](#additional-help)
- [Certificates - Important to know](#certificates---important-to-know)
- [Additional Information](#additional-information)
- [Contributing](#contributing)
- [License](#license)
## Introduction
In this tutorial you will use the Azure SDK for C to connect the Arduino Nano RP2040 Connect to Azure IoT Hub. The article is part of the series [IoT Device Development](https://go.microsoft.com/fwlink/p/?linkid=2129824). The series introduces device developers to the Azure SDK for C, and shows how to connect several device evaluation kits to Azure IoT.
### What is Covered
You will complete the following tasks:
* Install the Azure SDK for C library on Arduino
* Build the image and flash it onto the Nano RP2040 Connect
* Use Azure IoT Hub to create and view device telemetry
_The following was run on Windows 11 and WSL Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.15 and Arduino Arduino Nano RP2040 Connect with headers._
## Prerequisites
- Have an [Azure account](https://azure.microsoft.com/) created.
- Have an [Azure IoT Hub](https://docs.microsoft.com/azure/iot-hub/iot-hub-create-through-portal) created.
- Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
- Have one of the following interfaces to your Azure IoT Hub set up:
- [Azure Command Line Interface](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) (Azure CLI) utility installed, along with the [Azure IoT CLI extension](https://github.com/Azure/azure-iot-cli-extension).
On Windows:
Download and install: https://aka.ms/installazurecliwindows
```powershell
PS C:\>az extension add --name azure-iot
```
On Linux:
```bash
$ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
$ az extension add --name azure-iot
```
A list of all the Azure IoT CLI extension commands can be found [here](https://docs.microsoft.com/cli/azure/iot?view=azure-cli-latest).
- **Optional**: The most recent version of [Azure IoT Explorer](https://github.com/Azure/azure-iot-explorer/releases) installed. More instruction on its usage can be found [here](https://docs.microsoft.com/azure/iot-pnp/howto-use-iot-explorer).
*NOTE: This guide demonstrates use of the Azure CLI and does NOT demonstrate use of Azure IoT Explorer.*
## IoT Hub Device Setup
1. In the Azure portal, navigate to your IoT Hub resource.
1. On the left side menu, click on **'Overview'**. We will need the following information from this page:
- Hostname
1. On the left side menu under **Device management**, click on **'Devices'**.
1. Select **'+ Add Device'**.
1. Give your device a unique name.
1. Select **'Symmetric key'**, **'Auto-generate keys'**, and **'Enable'** for Connecting the device to an IoT Hub, then click **'Save'**.
1. When the device has been created, select its name under 'Device ID'.
1. We will need the following information from the device page:
- Device ID
- Primary Key
_NOTE: Device keys are used to automatically generate a SAS token for authentication, which is only valid for one hour._
## Arduino IDE Setup
1. Open the Arduino IDE.
1. Install the Azure SDK for Embedded C library.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'azure-sdk-for-c'** library.
- Install the latest version.
1. Install Arduino Mbed OS Nano Boards support in the Arduino IDE. [Full instructions can be found here.](https://docs.arduino.cc/hardware/nano-rp2040-connect)
- Navigate to **Tools > Board > Board Manager**.
- Search for **'RP2040'** and install the **Arduino Mbed OS Nano Boards** core.
- Install the latest version.
*Note: This process may take several minutes.*
1. Nagivate to **Tools > Board > Arduino Mbed OS Nano Boards** and select **'Arduino Nano RP2040 Connect'**.
1. Install WiFiNINA library for the Nano RP2040 Embedded C SDK sample.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'WiFiNINA'** library.
- Install the latest version.
*Note: This process may take several minutes.*
1. If this is your first time using the Nano RP2040 Connect, [follow these instructions to update the WiFi firmware on the Nano RP2040 Connect](https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-upgrading-nina-firmware).
1. Install the ArduinoBearSSL, ArduinoMqttClient, and ArduinoECCX08 libraries.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'ArduinoBearSSL'** library. Install the latest version.
- Search for the **'Arduino MQTT Client'** library. Install the latest version.
- Search for the **'ArduinoECCX08'** library. Install the latest version.
1. You may need to restart the Arduino IDE for changes to show up.
## Run the Sample
1. Open the Arduino Nano RP2040 Connect sample.
- In the Arduino IDE, navigate to **File > Examples > Azure SDK For C**
- Select **'Azure_IoT_Hub_Arduino_Nano_RP2040_Connect'** to open the sample.
1. Navigate to the '*iot_configs.h*' file
1. In the '*iot_configs.h*' file, fill in your credentials.
- Add in your WiFi SSID and password.
- Paste your IoT Hub device Hostname for the `IOT_CONFIG_IOTHUB_FQDN` variable. It should look something like:
```#define IOT_CONFIG_IOTHUB_FQDN "my-resource-group.azure-devices.net"```
- Paste your IoT Hub device ID for the `IOT_CONFIG_DEVICE_ID` variable.
- Finally, paste your IoT Hub Primary Key for the `IOT_CONFIG_DEVICE_KEY` variable.
1. Connect the Arduino Nano RP 2040 to your USB port.
1. On the Arduino IDE, select the port.
- Navigate to **Tools > Port**.
- Select the port to which the Nano RP2040 Connect is connected.
1. Upload the sketch.
- Navigate to **Sketch > Upload**.
*Note: This process may take several minutes.*
<details><summary><i>Expected output of the upload:</i></summary>
<p>
```text
Sketch uses 185534 bytes (1%) of program storage space. Maximum is 16777216 bytes.
Global variables use 63708 bytes (23%) of dynamic memory, leaving 206628 bytes for local variables. Maximum is 270336 bytes.
.
```
</p>
</details>
1. While the sketch is uploading, open the Serial Monitor to monitor the MCU (microcontroller) locally via the Serial Port.
- Navigate to **Tools > Serial Monitor**.
If you perform this step right away after uploading the sketch, the serial monitor will show an output similar to the following upon success:
```text
2106-02-06 23:28:16 [INFO] Attempting to connect to WIFI SSID: <ssid>
.
2106-02-06 23:28:16 [INFO] WiFi connected, IP address: 67769516, Strength (dBm): -67
2106-02-06 23:28:16 [INFO] Syncing time.
....
2022-06-07 14:28:16 [INFO] Time synced!
2022-06-07 14:28:16 [INFO] Initializing Azure IoT Hub client.
2022-06-07 14:28:16 [INFO] Azure IoT Hub hostname: <hostname>
2022-06-07 14:28:16 [INFO] Azure IoT Hub client initialized.
2022-06-07 14:28:16 [INFO] Initializing MQTT client.
2022-06-07 14:28:16 [INFO] UTC Current time: 2022-06-07 21:28:16 (epoch: 1654637296 secs)
2022-06-07 14:28:16 [INFO] UTC Expiry time: 2022-06-07 22:28:16 (epoch: 1654640896 secs)
2022-06-07 14:28:16 [INFO] Local Current time: 2022-06-07 14:28:16
2022-06-07 14:28:16 [INFO] Local Expiry time: 2022-06-07 15:28:16
2022-06-07 14:28:16 [INFO] MQTT Client ID: <device id>
2022-06-07 14:28:16 [INFO] MQTT Username: <hostname>/<device id>/?api-version=2020-09-30&DeviceClientType=c/1.3.1(ard;nanorp2040connect)
2022-06-07 14:28:16 [INFO] MQTT Password (SAS Token): ***
2022-06-07 14:28:16 [INFO] MQTT client initialized.
2022-06-07 14:28:16 [INFO] Connecting to Azure IoT Hub.
2022-06-07 14:28:18 [INFO] Connected to your Azure IoT Hub!
2022-06-07 14:28:18 [INFO] Subscribed to MQTT topic: devices/+/messages/devicebound/#
2022-06-07 14:28:18 [INFO] Arduino Nano RP2040 Connect sending telemetry . . .
2022-06-07 14:28:18 [INFO] Telemetry sent.
2022-06-07 14:28:20 [INFO] Arduino Nano RP2040 Connect sending telemetry . . .
2022-06-07 14:28:20 [INFO] Telemetry sent.
2022-06-07 14:28:23 [INFO] Arduino Nano RP2040 Connect sending telemetry . . .
2022-06-07 14:28:23 [INFO] Telemetry sent.
```
1. Monitor the telemetry messages sent to the Azure IoT Hub.
- On the left side menu under **Security settings**, click on **'Shared access policies'**.
- Under **Manage shared acess policies** and **Policy Name**, Select **'iothubowner'**
- Copy the **'Primary connection string'**.
- Using the Azure CLI, type and run the following command, inserting your Primary connection string and Device ID.
```
az iot hub monitor-events --login "<Primary connection string in quotes>" --device-id <device id>
```
<details><summary><i>Expected telemetry output:</i></summary>
<p>
```text
Starting event monitor, filtering on device: <device id>, use ctrl-c to stop...
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
^CStopping event monitor...
```
</p>
</details>
## Troubleshooting
1. Both the WiFi SSID and password are case sensitive.
1. In the Serial Monitor, select 'Show Timestamp'. Check that the device clock and the Serial Monitor clock are accurate within a few seconds.
1. In the Serial Monitor, check the Broker, Client ID, and Username are accurate.
1. Check the SAS Token output 'se' variable - this is the expiration time in seconds from 1/1/1970. It should be set to 1 hour from your local time. If this time is ealier than the local time, your device certificate will be invalid and IoT Hub will timeout.
### Additional Help
- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy).
- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose).
- Check the error logs in the Arduino IDE Serial Monitor and search [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using the `azure` and `c` tags.
## Certificates - Important to know
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
The Azure SDK for C Arduino library automatically installs the root certificate used in the United States regions, and adds it to the Arduino sketch project when the library is included.
For other regions (and private cloud environments), please use the appropriate root CA certificate.
### Additional Information
For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team.
## Contributing
This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md).
### License
This Azure SDK for C Arduino library is licensed under [MIT](https://github.com/Azure/azure-sdk-for-c-arduino/blob/main/LICENSE) license.
Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license.

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "SerialLogger.h"
SerialLogger::SerialLogger() { }
void SerialLogger::Info(String message)
{
Serial.print("[INFO] ");
Serial.println(message);
}
void SerialLogger::Error(String message)
{
Serial.print("[ERROR] ");
Serial.println(message);
}
SerialLogger Logger;

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#ifndef SERIALLOGGER_H
#define SERIALLOGGER_H
#include <Arduino.h>
#ifndef SERIAL_LOGGER_BAUD_RATE
#define SERIAL_LOGGER_BAUD_RATE 115200
#endif
class SerialLogger
{
public:
SerialLogger();
void Info(String message);
void Error(String message);
};
extern SerialLogger Logger;
#endif // SERIALLOGGER_H

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "Password"
#define IOT_CONFIG_WIFI_CONNECT_RETRY_MS 10000
// Azure IoT
#define IOT_CONFIG_IOTHUB_FQDN "[your Azure IoT host name].azure-devices.net"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
// When developing for your own Arduino-based platform,
// please follow the format '(ard;<platform>)'.
#define IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT "c/" AZ_SDK_VERSION_STRING "(ard;nanorp2040connect)"
// Publish 1 message every 2 seconds
#define IOT_CONFIG_TELEMETRY_FREQUENCY_MS 2000
// How long a SAS token generated by the sample will be valid, in minutes.
// The sample will stop working after the SAS token is expired, requiring the device to be reset.
#define IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES 60
// Time Zone Offset
#define IOT_CONFIG_TIME_ZONE -8
#define IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define IOT_CONFIG_DAYLIGHT_SAVINGS true

Просмотреть файл

@ -0,0 +1,516 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/*--- Libraries ---*/
// C99 libraries.
#include <cstdbool>
#include <cstdlib>
#include <cstring>
#include <time.h>
// Libraries for SSL client, MQTT client, NTP, and WiFi connection.
#include <ArduinoBearSSL.h>
#include <ArduinoMqttClient.h>
#include <NTPClient_Generic.h>
#include <TimeLib.h>
#include <WiFi.h>
// Libraries for SAS token generation.
#include <ECCX08.h>
// Azure IoT SDK for C includes.
#include <az_core.h>
#include <az_iot.h>
// Sample header.
#include "iot_configs.h"
// Logging
#include "SerialLogger.h"
/*--- Macros ---*/
#define BUFFER_LENGTH_MQTT_CLIENT_ID 256
#define BUFFER_LENGTH_MQTT_PASSWORD 256
#define BUFFER_LENGTH_MQTT_TOPIC 128
#define BUFFER_LENGTH_MQTT_USERNAME 512
#define BUFFER_LENGTH_SAS 32
#define BUFFER_LENGTH_SAS_ENCODED_SIGNED_SIGNATURE 64
#define BUFFER_LENGTH_SAS_SIGNATURE 512
#define BUFFER_LENGTH_DATETIME_STRING 256
#define LED_PIN 2 // High on error. Briefly high for each successful send.
// Time and Time Zone for NTP.
#define GMT_OFFSET_SECS (IOT_CONFIG_DAYLIGHT_SAVINGS ? \
((IOT_CONFIG_TIME_ZONE + IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * SECS_PER_HOUR) : \
(IOT_CONFIG_TIME_ZONE * SECS_PER_HOUR))
// Exit into infinite loop
#define EXIT_LOOP(condition, errorMessage) \
do \
{ \
if (condition) { \
Logger.Error(errorMessage); \
while (1); \
} \
} while (0)
/*--- Sample static variables --*/
// Clients for NTP, WiFi connection, SSL, MQTT, and Azure IoT SDK for C.
static WiFiUDP wiFiUDPClient;
static NTPClient ntpClient(wiFiUDPClient);
static WiFiClient wiFiClient;
static BearSSLClient bearSSLClient(wiFiClient);
static MqttClient mqttClient(bearSSLClient);
static az_iot_hub_client azIoTHubClient;
// MQTT variables.
static char mqttClientId[BUFFER_LENGTH_MQTT_CLIENT_ID];
static char mqttUsername[BUFFER_LENGTH_MQTT_USERNAME];
static char mqttPassword[BUFFER_LENGTH_MQTT_PASSWORD];
// Telemetry variables.
static char telemetryTopic[BUFFER_LENGTH_MQTT_TOPIC];
static unsigned long telemetryNextSendTimeMs;
static String telemetryPayload;
static uint32_t telemetrySendCount;
/*--- Functions ---*/
// Initialization and connection functions.
void connectToWiFi();
void initializeAzureIoTHubClient();
void initializeMQTTClient();
void connectMQTTClientToAzureIoTHub();
// Telemetry and message-callback functions.
void onMessageReceived(int messageSize);
static void sendTelemetry();
static char* generateTelemetry();
// SAS Token related functions.
static void generateMQTTPassword();
static void generateSASBase64EncodedSignedSignature(
uint8_t const* sasSignature, size_t const sasSignatureSize,
uint8_t* encodedSignedSignature, size_t encodedSignedSignatureSize,
size_t* encodedSignedSignatureLength);
static uint64_t getSASTokenExpirationTime(uint32_t minutes);
// Time and Error functions.
static unsigned long getTime();
static String getFormattedDateTime(unsigned long epochTimeInSeconds);
static String mqttErrorCodeName(int errorCode);
/*---------------------------*/
/* Main code execution */
/*---------------------------*/
/*
* setup:
* Initialization and connection of serial communication, WiFi client, Azure IoT SDK for C client,
* and MQTT client.
*/
void setup()
{
while (!Serial);
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
connectToWiFi();
initializeAzureIoTHubClient();
initializeMQTTClient();
connectMQTTClientToAzureIoTHub();
digitalWrite(LED_PIN, LOW);
telemetryNextSendTimeMs = 0;
}
/*
* loop:
* Check for connection and reconnect if necessary.
* Send Telemetry and receive messages.
*/
void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
connectToWiFi();
}
// Telemetry
if (millis() > telemetryNextSendTimeMs)
{
// Check for MQTT Client connection to Azure IoT Hub. Reconnect if needed.
if (!mqttClient.connected())
{
connectMQTTClientToAzureIoTHub();
}
sendTelemetry();
telemetryNextSendTimeMs = millis() + IOT_CONFIG_TELEMETRY_FREQUENCY_MS;
}
// MQTT loop must be called to process Telemetry and Cloud-to-Device (C2D) messages.
mqttClient.poll();
ntpClient.update();
delay(50);
}
/*-----------------------------------------------*/
/* Initialization and connection functions */
/*-----------------------------------------------*/
/*
* connectToWifi:
* The WiFi client connects, using the provided SSID and password.
* The NTP client synchronizes the time on the device.
*/
void connectToWiFi()
{
Logger.Info("Attempting to connect to WIFI SSID: " + String(IOT_CONFIG_WIFI_SSID));
WiFi.begin(IOT_CONFIG_WIFI_SSID, IOT_CONFIG_WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
Serial.println(".");
delay(IOT_CONFIG_WIFI_CONNECT_RETRY_MS);
}
Serial.println();
Logger.Info("WiFi connected, IP address: " + String(WiFi.localIP()) + ", Strength (dBm): " + WiFi.RSSI());
Logger.Info("Syncing time.");
ntpClient.begin();
while (!ntpClient.forceUpdate())
{
Serial.print(".");
delay(500);
}
Serial.println();
Logger.Info("Time synced!");
}
/*
* initializeAzureIoTHubClient:
* The Azure IoT SDK for C client uses the provided hostname, device id, and user agent.
*/
void initializeAzureIoTHubClient()
{
Logger.Info("Initializing Azure IoT Hub client.");
az_span hostname = AZ_SPAN_FROM_STR(IOT_CONFIG_IOTHUB_FQDN);
az_span deviceId = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_ID);
az_iot_hub_client_options options = az_iot_hub_client_options_default();
options.user_agent = AZ_SPAN_FROM_STR(IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT);
int result = az_iot_hub_client_init(&azIoTHubClient, hostname, deviceId, &options);
EXIT_LOOP(az_result_failed(result), "Failed to initialize Azure IoT Hub client. Return code: " + result);
Logger.Info("Azure IoT Hub hostname: " + String(IOT_CONFIG_IOTHUB_FQDN));
Logger.Info("Azure IoT Hub client initialized.");
}
/*
* initializeMQTTClient:
* The MQTT client uses the client id and username from the Azure IoT SDK for C client.
* The MQTT client uses the generated password (the SAS token).
*/
void initializeMQTTClient()
{
Logger.Info("Initializing MQTT client.");
int result;
result = az_iot_hub_client_get_client_id(
&azIoTHubClient, mqttClientId, sizeof(mqttClientId), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get MQTT client ID. Return code: " + result);
result = az_iot_hub_client_get_user_name(
&azIoTHubClient, mqttUsername, sizeof(mqttUsername), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get MQTT username. Return code: " + result);
generateMQTTPassword(); // SAS Token
mqttClient.setId(mqttClientId);
mqttClient.setUsernamePassword(mqttUsername, mqttPassword);
mqttClient.onMessage(onMessageReceived); // Set callback for C2D messages
Logger.Info("Client ID: " + String(mqttClientId));
Logger.Info("Username: " + String(mqttUsername));
Logger.Info("MQTT client initialized.");
}
/*
* connectMQTTClientToAzureIoTHub:
* The SSL library sets a callback to validate the server certificate.
* The MQTT client connects to the provided hostname. The port is pre-set.
* The MQTT client subscribes to the Cloud to Device (C2D) topic to receive messages.
*/
void connectMQTTClientToAzureIoTHub()
{
Logger.Info("Connecting to Azure IoT Hub.");
// Set a callback to get the current time used to validate the server certificate.
ArduinoBearSSL.onGetTime(getTime);
while (!mqttClient.connect(IOT_CONFIG_IOTHUB_FQDN, AZ_IOT_DEFAULT_MQTT_CONNECT_PORT))
{
int code = mqttClient.connectError();
Logger.Error("Cannot connect to Azure IoT Hub. Reason: " + mqttErrorCodeName(code) + ", Code: " + code);
delay(5000);
}
Logger.Info("Connected to your Azure IoT Hub!");
mqttClient.subscribe(AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC);
Logger.Info("Subscribed to MQTT topic: " + String(AZ_IOT_HUB_CLIENT_C2D_SUBSCRIBE_TOPIC));
}
/*------------------------------------------------*/
/* Telemetry and message-callback functions */
/*------------------------------------------------*/
/*
* onMessageReceived:
* The function called when device receives a message on the subscribed C2D topic.
* Callback function signature is defined by the ArduinoMQTTClient library.
* Message received is printed to the terminal.
*/
void onMessageReceived(int messageSize)
{
Logger.Info("Message received: Topic: " + mqttClient.messageTopic() + ", Length: " + messageSize);
Logger.Info("Message: ");
while (mqttClient.available())
{
Serial.print((char)mqttClient.read());
}
Serial.println();
}
/*
* sendTelemetry:
* The Azure IoT SDK for C client creates the MQTT topic to publish a telemetry message.
* The MQTT client creates and sends the telemetry mesage on the topic.
*/
static void sendTelemetry()
{
digitalWrite(LED_PIN, HIGH);
Logger.Info("Arduino Nano RP2040 Connect sending telemetry . . . ");
int result = az_iot_hub_client_telemetry_get_publish_topic(
&azIoTHubClient, NULL, telemetryTopic, sizeof(telemetryTopic), NULL);
EXIT_LOOP(az_result_failed(result), "Failed to get telemetry publish topic. Return code: " + result);
mqttClient.beginMessage(telemetryTopic);
mqttClient.print(generateTelemetry());
mqttClient.endMessage();
Logger.Info("Telemetry sent.");
delay(100);
digitalWrite(LED_PIN, LOW);
}
/*
* generateTelemetry:
* Simulated telemetry.
* In your application, this function should retrieve real telemetry data from the device and format
* it as needed.
*/
static char* generateTelemetry()
{
telemetryPayload = String("{ \"msgCount\": ") + telemetrySendCount + " }";
telemetrySendCount++;
return (char*)telemetryPayload.c_str();
}
/*************************************/
/* SAS Token related functions */
/*************************************/
/*
* generateMQTTPassword:
* The MQTT password is the generated SAS token. The process is:
* 1. Get the SAS token expiration time from the provided value. (Default 60 minutes).
* 2. Azure IoT SDK for C creates the SAS signature from this expiration time.
* 3. Sign and encode the SAS signature.
* 4. Azure IoT SDK for C creates the MQTT Password from the expiration time and the encoded,
* signed SAS signature.
*/
static void generateMQTTPassword()
{
int result;
uint64_t sasTokenDuration = 0;
uint8_t signature[BUFFER_LENGTH_SAS_SIGNATURE] = {0};
az_span signatureAzSpan = AZ_SPAN_FROM_BUFFER(signature);
uint8_t encodedSignedSignature[BUFFER_LENGTH_SAS_ENCODED_SIGNED_SIGNATURE] = {0};
size_t encodedSignedSignatureLength = 0;
// Get the signature. It will be signed later with the decoded device key.
// To change the sas token duration, see IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES in iot_configs.h
sasTokenDuration = getSASTokenExpirationTime(IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES);
result = az_iot_hub_client_sas_get_signature(
&azIoTHubClient, sasTokenDuration, signatureAzSpan, &signatureAzSpan);
EXIT_LOOP(az_result_failed(result), "Could not get the signature for SAS Token. Return code: " + result);
// Sign and encode the signature (b64 encoded, HMAC-SHA256 signing).
// Uses the decoded device key.
generateSASBase64EncodedSignedSignature(
az_span_ptr(signatureAzSpan), az_span_size(signatureAzSpan),
encodedSignedSignature, sizeof(encodedSignedSignature), &encodedSignedSignatureLength);
// Get the resulting MQTT password (SAS Token) from the base64 encoded, HMAC signed bytes.
az_span encodedSignedSignatureAzSpan = az_span_create(encodedSignedSignature,
encodedSignedSignatureLength);
result = az_iot_hub_client_sas_get_password(
&azIoTHubClient, sasTokenDuration, encodedSignedSignatureAzSpan, AZ_SPAN_EMPTY,
mqttPassword, sizeof(mqttPassword), NULL);
EXIT_LOOP(az_result_failed(result), "Could not get the MQTT password. Return code: " + result);
}
/*
* generateSASBase64EncodedSignedSignature:
* Sign and encode a signature. It is signed using the provided device key.
* The process is:
* 1. Decode the encoded device key.
* 2. Sign the signature with the decoded device key.
* 3. Encode the signed signature.
*/
static void generateSASBase64EncodedSignedSignature(
uint8_t const* sasSignature, size_t const sasSignatureSize,
uint8_t* encodedSignedSignature, size_t encodedSignedSignatureSize,
size_t* encodedSignedSignatureLength)
{
int result;
unsigned char sasDecodedKey[BUFFER_LENGTH_SAS] = {0};
az_span sasDecodedKeySpan = AZ_SPAN_FROM_BUFFER(sasDecodedKey);
int32_t sasDecodedKeyLength = 0;
uint8_t sasHMAC256SignedSignature[BUFFER_LENGTH_SAS] = {0};
// Decode the SAS base64 encoded device key to use for HMAC signing.
az_span configDeviceKeySpan = az_span_create((uint8_t*)IOT_CONFIG_DEVICE_KEY, sizeof(IOT_CONFIG_DEVICE_KEY) - 1);
result = az_base64_decode(sasDecodedKeySpan, configDeviceKeySpan, &sasDecodedKeyLength);
EXIT_LOOP(result != AZ_OK, "az_base64_decode failed. Return code: " + result);
// HMAC-SHA256 sign the signature with the decoded device key.
result = ECCX08.begin();
EXIT_LOOP(!result, "Failed to communicate with ATECC608.");
result = ECCX08.nonce(sasDecodedKey);
EXIT_LOOP(!result, "Failed to do nonce.");
result = ECCX08.beginHMAC(0xFFFF);
EXIT_LOOP(!result, "Failed to start HMAC operation.");
result = ECCX08.updateHMAC(sasSignature, sasSignatureSize);
EXIT_LOOP(!result, "Failed to update HMAC with signature.");
result = ECCX08.endHMAC(sasHMAC256SignedSignature);
EXIT_LOOP(!result, "Failed to end HMAC operation.");
// Base64 encode the result of the HMAC signing.
az_span signedSignatureSpan = az_span_create(sasHMAC256SignedSignature, sizeof(sasHMAC256SignedSignature));
az_span encodedSignedSignatureSpan = az_span_create(encodedSignedSignature, encodedSignedSignatureSize);
result = az_base64_encode(encodedSignedSignatureSpan, signedSignatureSpan, (int32_t*) encodedSignedSignatureLength);
EXIT_LOOP(result != AZ_OK, "az_base64_encode failed. Return code: " + result);
}
/*
* getSASTokenExpirationTime:
* Calculate expiration time from current time and duration value.
*/
static uint64_t getSASTokenExpirationTime(uint32_t minutes)
{
unsigned long now = getTime(); // GMT
unsigned long expiryTime = now + (SECS_PER_MIN * minutes); // For SAS Token
unsigned long localNow = now + GMT_OFFSET_SECS;
unsigned long localExpiryTime = expiryTime + GMT_OFFSET_SECS;
Logger.Info("UTC Current time: " + getFormattedDateTime(now) + " (epoch: " + now + " secs)");
Logger.Info("UTC Expiry time: " + getFormattedDateTime(expiryTime) + " (epoch: " + expiryTime + " secs)");
Logger.Info("Local Current time: " + getFormattedDateTime(localNow));
Logger.Info("Local Expiry time: " + getFormattedDateTime(localExpiryTime));
return (uint64_t)expiryTime;
}
/**********************************/
/* Time and Error functions */
/**********************************/
/*
* getTime:
* NTP client returns the seconds corresponding to GMT epoch time.
* This function used as a callback by the SSL library to validate the server certificate
* and in SAS token generation.
*/
static unsigned long getTime()
{
return ntpClient.getUTCEpochTime();
}
/*
* getFormattedDateTime:
* Custom formatting for epoch seconds. Used in logging.
*/
static String getFormattedDateTime(unsigned long epochTimeInSeconds)
{
char dateTimeString[BUFFER_LENGTH_DATETIME_STRING];
time_t epochTimeInSecondsAsTimeT = (time_t)epochTimeInSeconds;
struct tm* timeInfo = localtime(&epochTimeInSecondsAsTimeT);
strftime(dateTimeString, 20, "%F %T", timeInfo);
return String(dateTimeString);
}
/*
* mqttErrorCodeName:
* Legibly prints AruinoMqttClient library error enum values.
*/
static String mqttErrorCodeName(int errorCode)
{
String errorMessage;
switch (errorCode)
{
case MQTT_CONNECTION_REFUSED:
errorMessage = "MQTT_CONNECTION_REFUSED";
break;
case MQTT_CONNECTION_TIMEOUT:
errorMessage = "MQTT_CONNECTION_TIMEOUT";
break;
case MQTT_SUCCESS:
errorMessage = "MQTT_SUCCESS";
break;
case MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
errorMessage = "MQTT_UNACCEPTABLE_PROTOCOL_VERSION";
break;
case MQTT_IDENTIFIER_REJECTED:
errorMessage = "MQTT_IDENTIFIER_REJECTED";
break;
case MQTT_SERVER_UNAVAILABLE:
errorMessage = "MQTT_SERVER_UNAVAILABLE";
break;
case MQTT_BAD_USER_NAME_OR_PASSWORD:
errorMessage = "MQTT_BAD_USER_NAME_OR_PASSWORD";
break;
case MQTT_NOT_AUTHORIZED:
errorMessage = "MQTT_NOT_AUTHORIZED";
break;
default:
errorMessage = "Unknown";
break;
}
return errorMessage;
}

Просмотреть файл

@ -0,0 +1,343 @@
---
page_type: sample
description: Connecting Arduino Portenta H7 to Azure IoT Hub using the Azure SDK for C Arduino library
languages:
- c
products:
- azure-iot
- azure-iot-pnp
- azure-iot-dps
- azure-iot-hub
---
# Getting started with the Arduino Portenta H7 and Embedded C IoT Hub Client with Azure SDK for C Arduino library
**Total completion time**: 30 minutes
- [Getting started with the Arduino Portenta H7 and Embedded C IoT Hub Client with Azure SDK for C Arduino library](#getting-started-with-the-arduino-portenta-h7-and-embedded-c-iot-hub-client-with-azure-sdk-for-c-arduino-library)
- [Introduction](#introduction)
- [What is Covered](#what-is-covered)
- [Prerequisites](#prerequisites)
- [IoT Hub Device Setup](#iot-hub-device-setup)
- [Arduino IDE Setup](#arduino-ide-setup)
- [Run the Sample](#run-the-sample)
- [Troubleshooting](#troubleshooting)
- [Additional Help](#additional-help)
- [Certificates - Important to know](#certificates---important-to-know)
- [Additional Information](#additional-information)
- [Contributing](#contributing)
- [License](#license)
## Introduction
In this tutorial you use the Azure SDK for C to connect the Arduino Portenta H7 to Azure IoT Hub. The article is part of the series [IoT Device Development](https://go.microsoft.com/fwlink/p/?linkid=2129824). The series introduces device developers to the Azure SDK for C, and shows how to connect several device evaluation kits to Azure IoT.
### What is Covered
You will complete the following tasks:
* Install the Azure SDK for C library on Arduino
* Build the image and flash it onto the Portenta H7
* Use Azure IoT Hub to create view device telemetry
_The following was run on Windows 11 and WSL Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.15 and Arduino Portenta H7 Rev2._
## Prerequisites
- Have an [Azure account](https://azure.microsoft.com/) created.
- Have an [Azure IoT Hub](https://docs.microsoft.com/azure/iot-hub/iot-hub-create-through-portal) created.
- Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
- Have the [Arduino CLI installed](https://arduino.github.io/arduino-cli/0.21/installation/).
- Have one of the following interfaces to your Azure IoT Hub set up:
- [Azure Command Line Interface](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) (Azure CLI) utility installed, along with the [Azure IoT CLI extension](https://github.com/Azure/azure-iot-cli-extension).
On Windows:
Download and install: https://aka.ms/installazurecliwindows
```powershell
PS C:\>az extension add --name azure-iot
```
On Linux:
```bash
$ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
$ az extension add --name azure-iot
```
A list of all the Azure IoT CLI extension commands can be found [here](https://docs.microsoft.com/cli/azure/iot?view=azure-cli-latest).
- **Optional**: The most recent version of [Azure IoT Explorer](https://github.com/Azure/azure-iot-explorer/releases) installed. More instruction on its usage can be found [here](https://docs.microsoft.com/azure/iot-pnp/howto-use-iot-explorer).
*NOTE: This guide demonstrates use of the Azure CLI and does NOT demonstrate use of Azure IoT Explorer.*
## IoT Hub Device Setup
1. In the Azure portal, navigate to your IoT Hub resource.
1. On the left side menu, click on **'Overview'**. We will need the following information from this page:
- Hostname
1. On the left side menu under **Device management**, click on **'Devices'**.
1. Select **'+ Add Device'**.
1. Give your device a unique name.
1. Select **'Symmetric key'**, **'Auto-generate keys'**, and **'Enable'** for Connecting the device to an IoT Hub, then click **'Save'**.
1. When the device has been created, select its name under 'Device ID'.
1. We'll need the following information from the device page:
- Device ID
- Primary Key
_NOTE: Device keys are used to automatically generate a SAS token for authentication, which is only valid for one hour._
## Arduino IDE Setup
1. Open the Arduino IDE.
1. Install the Azure SDK for Embedded C library.
- Navigate to **Tools > Manage Libraries**.
- Search for the **'azure-sdk-for-c'** library.
- Install the latest version.
1. Install Arduino Mbed OS Portenta board support in the Arduino IDE. [Full instructions can be found here.](https://docs.arduino.cc/software/ide-v1/tutorials/getting-started/cores/arduino-mbed_portenta)
- Navigate to **Tools > Board > Boards Manager**
- Search for **'Portenta'** and install the **Arduino Mbed OS Portenta Boards** core.
- Install the latest version.
*Note: This process may take several minutes.*
1. Nagivate to **Tools > Board > Arduino Mbed OS Portenta Boards** and select **'Arduino Portenta H7 (M7 core)'**.
1. In **Tools > Flash split**, set the flash split to be **'2MB M7 + M4 in SDRAM'**.
1. If this is your first time using the Portenta, [follow these instructions to update the WiFi firmware on the Portenta](https://support.arduino.cc/hc/en-us/articles/4403365234322-How-to-update-Wi-Fi-firmware-on-Portenta-H7).
1. Install additional libraries for the Portenta Embedded C SDK sample.
- This process is more involved than typical because we are using the NTP Client Generic library which has circular dependencies so we must do a special install to only grab the what we need.
- There are two ways to do this:
1. Download the NTP Client Generic library manually from its repository, or
2. Use the Arduino CLI.
- This tutorial will use the CLI approach because it is faster and easier to describe.
- Using the Arduino CLI, type and run the following command to install the NTP Client :
```
arduino-cli lib install --no-deps NTPClient_Generic
```
- Since we're already in the Arduino CLI, let's install remaining libraries (can also install these from Library Manager):
```
arduino-cli lib install "Azure SDK for C" ArduinoBearSSL Time ArduinoMqttClient ArduinoECCX08
```
1. You may need to restart the Arduino IDE for changes to show up.
## Run the Sample
1. Open the Arduino Portenta sample.
- In the Arduino IDE, navigate to **File > Examples > Azure SDK For C**
- Select **Azure_IoT_Hub_PortentaH7** to open the sample.
1. Navigate to the '*iot_configs.h*' file
1. In the '*iot_configs.h*' file, fill in your credentials.
- Add in your WiFi SSID and password.
- Paste your IoT Hub device HostName for the `IOT_CONFIG_IOTHUB_FQDN` variable. It should look something like:
```#define IOT_CONFIG_IOTHUB_FQDN "my-resource-group.azure-devices.net"```
- Paste your IoT Hub device ID for the `IOT_CONFIG_DEVICE_ID` variable.
- Finally, paste your IoT Hub Primary Key for the `IOT_CONFIG_DEVICE_KEY` variable.
1. This sample was configured for a PST timezone (GMT -8hrs) with a Daylight Savings offset. If you live in a different timezone, update the values in '*Time Zone Offset*' at the bottom of the *iot_configs.h* file.
- Change the `IOT_CONFIG_TIME_ZONE` value to reflect the number of hours to add or subtract from the GMT timezone for your timezone.
- Why is this necessary?
- Our sample generates a temporary SAS token that is valid for 1 hour. If your device clock is off from your local timezone, the SAS token may appear to be expired and IoT Hub will refuse the device connection (it will timeout).
1. Connect the Arduino Portenta to your USB port.
1. On the Arduino IDE, select the port.
- Navigate to **Tools > Port**.
- Select the port to which the Portenta is connected.
1. Upload the sketch.
- Navigate to **Sketch > Upload**.
*Note: This process may take several minutes.*
<details><summary><i>Expected output of the upload:</i></summary>
<p>
```text
Sketch uses 398116 bytes (20%) of program storage space. Maximum is 1966080 bytes.
Global variables use 92104 bytes (17%) of dynamic memory, leaving 431520 bytes for local variables. Maximum is 523624 bytes.
dfu-util 0.10-dev
Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/
Opening DFU capable USB device...
Device ID 2341:035b
Device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Interface #0 ...
Determining device status...
DFU state(2) = dfuIDLE, status(0) = No error condition is present
DFU mode device DFU version 011a
Device returned transfer size 4096
DfuSe interface name: "Internal Flash "
Downloading element to address = 0x08040000, size = 403188
Erase [=========================] 100% 403188 bytes
Erase done.
Download [=========================] 100% 403188 bytes
Download done.
File downloaded successfully
Transitioning to dfuMANIFEST state
```
</p>
</details>
1. While the sketch is uploading, open the Serial Monitor to monitor the MCU (microcontroller) locally via the Serial Port.
- Navigate to **Tools > Serial Monitor**.
If you perform this step right away after uploading the sketch, the serial monitor will show an output similar to the following upon success:
```text
2106-02-06 23:29:28 [INFO] Attempting to connect to WIFI SSID: <ssid>
.
2106-02-06 23:29:48 [INFO] WiFi connected, IP address: 84546732, Strength (dBm): -42
2106-02-06 23:29:48 [INFO] Syncing time.
2022-06-07 16:38:17 [INFO] Time synced!
2022-06-07 16:38:17 [INFO] Initializing Azure IoT Hub client.
2022-06-07 16:38:17 [INFO] Azure IoT Hub hostname: <hostname>
2022-06-07 16:38:17 [INFO] Azure IoT Hub client initialized.
2022-06-07 16:38:17 [INFO] Initializing MQTT client.
2022-06-07 16:38:17 [INFO] UTC Current time: 2022-06-07 23:38:17 (epoch: 1654645097 secs)
2022-06-07 16:38:17 [INFO] UTC Expiry time: 2022-06-08 00:38:17 (epoch: 1654648697 secs)
2022-06-07 16:38:17 [INFO] Local Current time: 2022-06-07 16:38:17
2022-06-07 16:38:17 [INFO] Local Expiry time: 2022-06-07 17:38:17
2022-06-07 16:38:17 [INFO] MQTT Client ID: <device id>
2022-06-07 16:38:17 [INFO] MQTT Username: <hostname>/<device id>/?api-version=2020-09-30&DeviceClientType=c/1.3.1(ard;portentaH7)
2022-06-07 16:38:17 [INFO] MQTT Password (SAS Token): ***
2022-06-07 16:38:17 [INFO] MQTT client initialized.
2022-06-07 16:38:17 [INFO] Connecting to Azure IoT Hub.
2022-06-07 16:38:19 [INFO] Connected to your Azure IoT Hub!
2022-06-07 16:38:20 [INFO] Subscribed to MQTT topic: devices/+/messages/devicebound/#
2022-06-07 16:38:20 [INFO] Arduino Portenta H7 sending telemetry . . .
2022-06-07 16:38:20 [INFO] Telemetry sent.
2022-06-07 16:38:22 [INFO] Arduino Portenta H7 sending telemetry . . .
2022-06-07 16:38:22 [INFO] Telemetry sent.
2022-06-07 16:38:25 [INFO] Arduino Portenta H7 sending telemetry . . .
2022-06-07 16:38:25 [INFO] Telemetry sent.
```
1. Monitor the telemetry messages sent to the Azure IoT Hub.
- On the left side menu under **Security settings**, click on **'Shared access policies'**.
- Under **Manage shared acess policies** and **Policy Name**, Select **'iothubowner'**
- Copy the **'Primary connection string'**.
- Using the Azure CLI, type and run the following command, inserting your Primary connection string and Device ID.
```
az iot hub monitor-events --login "<Primary connection string in quotes>" --device-id <device id>
```
<details><summary><i>Expected telemetry output:</i></summary>
<p>
```text
Starting event monitor, filtering on device: <device id>, use ctrl-c to stop...
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
{
"event": {
"origin": "<device id>",
"module": "",
"interface": "",
"component": "",
"payload": "<payload>"
}
}
^CStopping event monitor...
```
</p>
</details>
## Troubleshooting
1. Both the WiFi SSID and password are case sensitive.
1. In the Serial Monitor, select 'Show Timestamp'. Check that the device clock and the Serial Monitor clock are accurate within a few seconds.
1. In the Serial Monitor, check the Broker, Client ID, and Username are accurate.
1. Check the SAS Token output 'se' variable - this is the expiration time in seconds from 1/1/1970. It should be set to 1 hour from your local time. If this time is ealier than the local time, your device certificate will be invalid and IoT Hub will timeout.
### Additional Help
- The error policy for the Embedded C SDK client library is documented [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/docs/iot/mqtt_state_machine.md#error-policy).
- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-c/issues/new/choose).
- Check the error logs in the Arduino IDE Serial Monitor and search [previous questions](https://stackoverflow.com/questions/tagged/azure+c) or ask new ones on StackOverflow using the `azure` and `c` tags.
## Certificates - Important to know
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
The Azure SDK for C Arduino library automatically installs the root certificate used in the United States regions, and adds it to the Arduino sketch project when the library is included.
For other regions (and private cloud environments), please use the appropriate root CA certificate.
### Additional Information
For important information and additional guidance about certificates, please refer to [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-changes-are-coming-and-why-you-should-care/ba-p/1658456) from the security team.
## Contributing
This project welcomes contributions and suggestions. Find more contributing details [here](https://github.com/Azure/azure-sdk-for-c/blob/main/CONTRIBUTING.md).
### License
This Azure SDK for C Arduino library is licensed under [MIT](https://github.com/Azure/azure-sdk-for-c-arduino/blob/main/LICENSE) license.
Azure SDK for Embedded C is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-c/blob/main/LICENSE) license.

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "SerialLogger.h"
SerialLogger::SerialLogger() { }
void SerialLogger::Info(String message)
{
Serial.print("[INFO] ");
Serial.println(message);
}
void SerialLogger::Error(String message)
{
Serial.print("[ERROR] ");
Serial.println(message);
}
SerialLogger Logger;

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#ifndef SERIALLOGGER_H
#define SERIALLOGGER_H
#include <Arduino.h>
#ifndef SERIAL_LOGGER_BAUD_RATE
#define SERIAL_LOGGER_BAUD_RATE 115200
#endif
class SerialLogger
{
public:
SerialLogger();
void Info(String message);
void Error(String message);
};
extern SerialLogger Logger;
#endif // SERIALLOGGER_H

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
#define IOT_CONFIG_WIFI_CONNECT_RETRY_MS 10000
// Azure IoT
#define IOT_CONFIG_IOTHUB_FQDN "[your Azure IoT host name].azure-devices.net"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
// When developing for your own Arduino-based platform,
// please follow the format '(ard;<platform>)'.
#define IOT_CONFIG_AZURE_SDK_CLIENT_USER_AGENT "c/" AZ_SDK_VERSION_STRING "(ard;portentaH7)"
// Publish 1 message every 2 seconds
#define IOT_CONFIG_TELEMETRY_FREQUENCY_MS 2000
// How long a SAS token generated by the sample will be valid, in minutes.
// The sample will stop working after the SAS token is expired, requiring the device to be reset.
#define IOT_CONFIG_SAS_TOKEN_EXPIRY_MINUTES 60
// Time Zone Offset
#define IOT_CONFIG_TIME_ZONE -8
#define IOT_CONFIG_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define IOT_CONFIG_DAYLIGHT_SAVINGS true