Merge branch 'main' into personal/preer/configuretelemetry

This commit is contained in:
Ewerton Scaboro da Silva 2023-06-14 14:08:21 -07:00 коммит произвёл GitHub
Родитель 2c35c45d27 0ab09769ca
Коммит 76a40c76c8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
114 изменённых файлов: 17255 добавлений и 2738 удалений

19
.clang-format Normal file
Просмотреть файл

@ -0,0 +1,19 @@
---
# We'll use defaults from the LLVM style, but with the following differences.
# This mirrors the Azure SDK for C
BasedOnStyle: LLVM
AlignAfterOpenBracket: AlwaysBreak
AlignEscapedNewlines: Left
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Allman
ColumnLimit: 100
Cpp11BracedListStyle: false
IndentCaseLabels: true
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Left
...

30
.github/workflows/main.yml поставляемый
Просмотреть файл

@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
branches: [ main, feature/* ]
workflow_dispatch:
@ -17,6 +17,31 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Fetch Needed Libs
run: |
apt install -y libncurses5
wget https://releases.llvm.org/9.0.0/clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar -xvf clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
mv clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang-format /usr/local/bin/
- name: Style Check
run: |
# Run clang-format recursively on each source and header file within the repo.
clang-format --version
find ./examples \( -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' -o -iname '*.ino' \) -exec clang-format -i {} \;
git status --untracked-files=no --porcelain
if [[ `git status --untracked-files=no --porcelain` ]]; then
echo Some files were not formatted correctly according to the .clang-format file.
echo Please run clang-format version 9.0.0 to fix the issue by using this bash command at the root of the repo:
echo "find ./examples \( -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' -o -iname '*.ino' \) -exec clang-format -i {} \;"
exit 1
fi
echo Success, all files are formatted correctly according to the .clang-format file.
exit 0
- name: Install dependent Arduino libraries
run: |
arduino --install-library "PubSubClient",
@ -31,3 +56,6 @@ jobs:
- name: Build Azure_IoT_Central_ESP32
run: arduino --verify --board esp32:esp32:esp32 -v --preserve-temp-files $GITHUB_WORKSPACE/examples/Azure_IoT_Central_ESP32/Azure_IoT_Central_ESP32.ino
- name: Build Azure_IoT_Adu_ESP32
run: arduino --verify --board esp32:esp32:esp32 -v --preserve-temp-files $GITHUB_WORKSPACE/examples/Azure_IoT_Adu_ESP32/Azure_IoT_Adu_ESP32.ino

1
.gitignore поставляемый
Просмотреть файл

@ -50,3 +50,4 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
/.vs

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

@ -1,19 +1,37 @@
# Project
For more details about the Azure Embedded SDK for C please refer to the [official library website](https://github.com/azure/azure-sdk-for-c).
The Arduino library is built from the Azure Embedded SDK for C. For more details about it, please refer to the [official library website](https://github.com/azure/azure-sdk-for-c).
This library package contains the following samples.
Please refer to their documentation for setup and execution instructions.
There are several other alternatives to get MCU-based devices connected to Azure. See [Other Azure IoT SDKs](https://learn.microsoft.com/azure/iot-develop/concepts-using-c-sdk-and-embedded-c-sdk) to learn more.
[Azure IoT Central ESPRESSIF ESP32 Azure IoT Kit](examples/Azure_IoT_Central_ESP32_AzureIoTKit/readme.md)
This library package contains the following samples. Please refer to their documentation for setup and execution instructions:
[Azure IoT Central ESPRESSIF ESP32](examples/Azure_IoT_Central_ESP32/readme.md)
- [Azure IoT Central ESPRESSIF ESP32 Azure IoT Kit](examples/Azure_IoT_Central_ESP32_AzureIoTKit/readme.md)
[Azure IoT Hub ESPRESSIF ESP-8266](examples/Azure_IoT_Hub_ESP8266/readme.md)
- [Azure IoT Central ESPRESSIF ESP32](examples/Azure_IoT_Central_ESP32/readme.md)
[Azure IoT Hub ESPRESSIF ESP-32](examples/Azure_IoT_Hub_ESP32/readme.md)
- [Azure IoT Central Arduino Nano RP2040](examples/Azure_IoT_Central_Arduino_Nano_RP2040_Connect/readme.md)
[Azure IoT Hub Realtek AmebaD](examples/Azure_IoT_Hub_RealtekAmebaD/readme.md)
- [Azure IoT Central Arduino Portenta H7](examples/Azure_IoT_Central_Arduino_Portenta_H7/readme.md)
- [Azure IoT Hub ESPRESSIF ESP8266](examples/Azure_IoT_Hub_ESP8266/readme.md)
- [Azure IoT Hub ESPRESSIF ESP32](examples/Azure_IoT_Hub_ESP32/readme.md)
- [Azure IoT Hub Realtek AmebaD](examples/Azure_IoT_Hub_RealtekAmebaD/readme.md)
- [Azure IoT Hub Arduino Nano RP2040](examples/Azure_IoT_Hub_Arduino_Nano_RP2040_Connect/README.md)
- [Azure IoT Hub Arduino Portenta H7](examples/Azure_IoT_Hub_PortentaH7/README.md)
- [Azure IoT Device Update ESP32](examples/Azure_IoT_Adu_ESP32/readme.md)
What is the difference between **IoT Hub** and **IoT Central** samples?
1. IoT Hub samples will get devices connected directly to [Azure IoT Hub](https://docs.microsoft.com/azure/iot-hub/iot-concepts-and-iot-hub)
1. IoT Central samples will leverage DPS ([Device Provisioning Service](https://docs.microsoft.com/azure/iot-dps/about-iot-dps)) to provision the device and then connect it to [Azure IoT Central](https://docs.microsoft.com/azure/iot-central/core/overview-iot-central).
Please note that provisioning through DPS is mandatory for IoT Central scenarios, but DPS can also be used for IoT Hub devices as well.
## Contributing
@ -23,6 +41,16 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
### Fix Code Formatting
Run the following command from the root of the sdk with `clang-format` version 9.0.0.
```bash
find ./examples \( -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' -o -iname '*.ino' \) -exec clang-format -i {} \;
```
Commit the resulting code formatting changes if there are any.
### Reporting Security Issues and Security Bugs
Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) <secure@microsoft.com>. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue).

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

@ -0,0 +1,288 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "AzIoTSasToken.h"
#include "SerialLogger.h"
#include <az_result.h>
#include <mbedtls/base64.h>
#include <mbedtls/md.h>
#include <mbedtls/sha256.h>
#include <stdlib.h>
#include <time.h>
#define INDEFINITE_TIME ((time_t)-1)
#define az_span_is_content_equal(x, AZ_SPAN_EMPTY) \
(az_span_size(x) == az_span_size(AZ_SPAN_EMPTY) && az_span_ptr(x) == az_span_ptr(AZ_SPAN_EMPTY))
static uint32_t getSasTokenExpiration(const char* sasToken)
{
const char SE[] = { '&', 's', 'e', '=' };
uint32_t se_as_unix_time = 0;
int i, j;
for (i = 0, j = 0; sasToken[i] != '\0'; i++)
{
if (sasToken[i] == SE[j])
{
j++;
if (j == sizeof(SE))
{
// i is still at the '=' position. We must advance it by 1.
i++;
break;
}
}
else
{
j = 0;
}
}
if (j != sizeof(SE))
{
Logger.Error("Failed finding `se` field in SAS token");
}
else
{
int k = i;
while (sasToken[k] != '\0' && sasToken[k] != '&')
{
k++;
}
if (az_result_failed(
az_span_atou32(az_span_create((uint8_t*)sasToken + i, k - i), &se_as_unix_time)))
{
Logger.Error("Failed parsing SAS token expiration timestamp");
}
}
return se_as_unix_time;
}
static void mbedtls_hmac_sha256(az_span key, az_span payload, az_span signed_payload)
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, (const unsigned char*)az_span_ptr(key), az_span_size(key));
mbedtls_md_hmac_update(&ctx, (const unsigned char*)az_span_ptr(payload), az_span_size(payload));
mbedtls_md_hmac_finish(&ctx, (byte*)az_span_ptr(signed_payload));
mbedtls_md_free(&ctx);
}
static void hmac_sha256_sign_signature(
az_span decoded_key,
az_span signature,
az_span signed_signature,
az_span* out_signed_signature)
{
mbedtls_hmac_sha256(decoded_key, signature, signed_signature);
*out_signed_signature = az_span_slice(signed_signature, 0, 32);
}
static void base64_encode_bytes(
az_span decoded_bytes,
az_span base64_encoded_bytes,
az_span* out_base64_encoded_bytes)
{
size_t len;
if (mbedtls_base64_encode(
az_span_ptr(base64_encoded_bytes),
(size_t)az_span_size(base64_encoded_bytes),
&len,
az_span_ptr(decoded_bytes),
(size_t)az_span_size(decoded_bytes))
!= 0)
{
Logger.Error("mbedtls_base64_encode fail");
}
*out_base64_encoded_bytes = az_span_create(az_span_ptr(base64_encoded_bytes), (int32_t)len);
}
static int decode_base64_bytes(
az_span base64_encoded_bytes,
az_span decoded_bytes,
az_span* out_decoded_bytes)
{
memset(az_span_ptr(decoded_bytes), 0, (size_t)az_span_size(decoded_bytes));
size_t len;
if (mbedtls_base64_decode(
az_span_ptr(decoded_bytes),
(size_t)az_span_size(decoded_bytes),
&len,
az_span_ptr(base64_encoded_bytes),
(size_t)az_span_size(base64_encoded_bytes))
!= 0)
{
Logger.Error("mbedtls_base64_decode fail");
return 1;
}
else
{
*out_decoded_bytes = az_span_create(az_span_ptr(decoded_bytes), (int32_t)len);
return 0;
}
}
static int iot_sample_generate_sas_base64_encoded_signed_signature(
az_span sas_base64_encoded_key,
az_span sas_signature,
az_span sas_base64_encoded_signed_signature,
az_span* out_sas_base64_encoded_signed_signature)
{
// Decode the sas base64 encoded key to use for HMAC signing.
char sas_decoded_key_buffer[32];
az_span sas_decoded_key = AZ_SPAN_FROM_BUFFER(sas_decoded_key_buffer);
if (decode_base64_bytes(sas_base64_encoded_key, sas_decoded_key, &sas_decoded_key) != 0)
{
Logger.Error("Failed generating encoded signed signature");
return 1;
}
// HMAC-SHA256 sign the signature with the decoded key.
char sas_hmac256_signed_signature_buffer[32];
az_span sas_hmac256_signed_signature = AZ_SPAN_FROM_BUFFER(sas_hmac256_signed_signature_buffer);
hmac_sha256_sign_signature(
sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature);
// Base64 encode the result of the HMAC signing.
base64_encode_bytes(
sas_hmac256_signed_signature,
sas_base64_encoded_signed_signature,
out_sas_base64_encoded_signed_signature);
return 0;
}
int64_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes)
{
time_t now = time(NULL);
return (int64_t)(now + minutes * 60);
}
az_span generate_sas_token(
az_iot_hub_client* hub_client,
az_span device_key,
az_span sas_signature,
unsigned int expiryTimeInMinutes,
az_span sas_token)
{
az_result rc;
// Create the POSIX expiration time from input minutes.
uint64_t sas_duration = iot_sample_get_epoch_expiration_time_from_minutes(expiryTimeInMinutes);
// Get the signature that will later be signed with the decoded key.
// az_span sas_signature = AZ_SPAN_FROM_BUFFER(signature);
rc = az_iot_hub_client_sas_get_signature(hub_client, sas_duration, sas_signature, &sas_signature);
if (az_result_failed(rc))
{
Logger.Error("Could not get the signature for SAS key: az_result return code " + rc);
return AZ_SPAN_EMPTY;
}
// Generate the encoded, signed signature (b64 encoded, HMAC-SHA256 signing).
char b64enc_hmacsha256_signature[64];
az_span sas_base64_encoded_signed_signature = AZ_SPAN_FROM_BUFFER(b64enc_hmacsha256_signature);
if (iot_sample_generate_sas_base64_encoded_signed_signature(
device_key,
sas_signature,
sas_base64_encoded_signed_signature,
&sas_base64_encoded_signed_signature)
!= 0)
{
Logger.Error("Failed generating SAS token signed signature");
return AZ_SPAN_EMPTY;
}
// Get the resulting MQTT password, passing the base64 encoded, HMAC signed
// bytes.
size_t mqtt_password_length;
rc = az_iot_hub_client_sas_get_password(
hub_client,
sas_duration,
sas_base64_encoded_signed_signature,
AZ_SPAN_EMPTY,
(char*)az_span_ptr(sas_token),
az_span_size(sas_token),
&mqtt_password_length);
if (az_result_failed(rc))
{
Logger.Error("Could not get the password: az_result return code " + rc);
return AZ_SPAN_EMPTY;
}
else
{
return az_span_slice(sas_token, 0, mqtt_password_length);
}
}
AzIoTSasToken::AzIoTSasToken(
az_iot_hub_client* client,
az_span deviceKey,
az_span signatureBuffer,
az_span sasTokenBuffer)
{
this->client = client;
this->deviceKey = deviceKey;
this->signatureBuffer = signatureBuffer;
this->sasTokenBuffer = sasTokenBuffer;
this->expirationUnixTime = 0;
this->sasToken = AZ_SPAN_EMPTY;
}
int AzIoTSasToken::Generate(unsigned int expiryTimeInMinutes)
{
this->sasToken = generate_sas_token(
this->client,
this->deviceKey,
this->signatureBuffer,
expiryTimeInMinutes,
this->sasTokenBuffer);
if (az_span_is_content_equal(this->sasToken, AZ_SPAN_EMPTY))
{
Logger.Error("Failed generating SAS token");
return 1;
}
else
{
this->expirationUnixTime = getSasTokenExpiration((const char*)az_span_ptr(this->sasToken));
if (this->expirationUnixTime == 0)
{
Logger.Error("Failed getting the SAS token expiration time");
this->sasToken = AZ_SPAN_EMPTY;
return 1;
}
else
{
return 0;
}
}
}
bool AzIoTSasToken::IsExpired()
{
time_t now = time(NULL);
if (now == INDEFINITE_TIME)
{
Logger.Error("Failed getting current time");
return true;
}
else
{
return (now >= this->expirationUnixTime);
}
}
az_span AzIoTSasToken::Get() { return this->sasToken; }

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

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#ifndef AZIOTSASTOKEN_H
#define AZIOTSASTOKEN_H
#include <Arduino.h>
#include <az_iot_hub_client.h>
#include <az_span.h>
class AzIoTSasToken
{
public:
AzIoTSasToken(
az_iot_hub_client* client,
az_span deviceKey,
az_span signatureBuffer,
az_span sasTokenBuffer);
int Generate(unsigned int expiryTimeInMinutes);
bool IsExpired();
az_span Get();
private:
az_iot_hub_client* client;
az_span deviceKey;
az_span signatureBuffer;
az_span sasTokenBuffer;
az_span sasToken;
uint32_t expirationUnixTime;
};
#endif // AZIOTSASTOKEN_H

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

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

@ -0,0 +1,982 @@
/* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License. */
#include "SampleAduJWS.h"
#include <az_core.h>
#include <az_iot.h>
#include "mbedtls/base64.h"
#include "mbedtls/cipher.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/pk.h"
#include "mbedtls/rsa.h"
/* For logging */
#include "SerialLogger.h"
/**
* @brief Convenience macro to return if an operation failed.
*/
#define _az_adu_jws_return_if_failed(exp) \
do \
{ \
az_result const _azResult = (exp); \
if (_azResult != AZ_OK) \
{ \
return _azResult; \
} \
} while (0)
const az_span jws_sha256_json_value = AZ_SPAN_FROM_STR("sha256");
const az_span jws_sjwk_json_value = AZ_SPAN_FROM_STR("sjwk");
const az_span jws_kid_json_value = AZ_SPAN_FROM_STR("kid");
const az_span jws_n_json_value = AZ_SPAN_FROM_STR("n");
const az_span jws_e_json_value = AZ_SPAN_FROM_STR("e");
const az_span jws_alg_json_value = AZ_SPAN_FROM_STR("alg");
const az_span jws_alg_rs256 = AZ_SPAN_FROM_STR("RS256");
typedef struct prvJWSValidationContext
{
az_span jwk_header;
az_span jwk_payload;
az_span jwk_signature;
az_span scratch_calculation_buffer;
az_span jws_header;
az_span jws_payload;
az_span jws_signature;
az_span signing_key_n;
az_span signing_key_e;
az_span manifest_sha_calculation;
az_span parsed_manifest_sha;
az_span base64_encoded_header;
az_span base64_encoded_payload;
az_span base64_encoded_signature;
az_span jwk_base64_encoded_header;
az_span jwk_base64_encoded_payload;
az_span jwk_base64_encoded_signature;
int32_t base64_signature_length;
int32_t out_parsed_manifest_sha_size;
int32_t out_signing_key_e_length;
int32_t out_signing_key_n_length;
int32_t out_jws_header_length;
int32_t out_jws_payload_length;
int32_t out_jws_signature_length;
int32_t out_jwk_header_length;
int32_t out_jwk_payload_length;
int32_t out_jwk_signature_length;
az_span kid_span;
az_span sha256_span;
az_span base64_encoded_n_span;
az_span base64_encoded_e_span;
az_span alg_span;
az_span jwk_manifest_span;
} jws_validation_context;
/* split_jws takes a JWS payload and returns pointers to its constituent header,
* payload, and signature parts. */
static az_result split_jws(
az_span jws_span,
az_span* header_span,
az_span* payload_span,
az_span* signature_span)
{
uint8_t* first_dot;
uint8_t* second_dot;
int32_t dot_count = 0;
int32_t index = 0;
int32_t jws_length = az_span_size(jws_span);
uint8_t* jws_ptr = az_span_ptr(jws_span);
while (index < jws_length)
{
if (*jws_ptr == '.')
{
dot_count++;
if (dot_count == 1)
{
first_dot = jws_ptr;
}
else if (dot_count == 2)
{
second_dot = jws_ptr;
}
else if (dot_count > 2)
{
Logger.Error("JWS had more '.' than required (2)");
return AZ_ERROR_UNEXPECTED_CHAR;
}
}
jws_ptr++;
index++;
}
if ((dot_count != 2) || (second_dot >= (az_span_ptr(jws_span) + jws_length - 1)))
{
return AZ_ERROR_UNEXPECTED_CHAR;
}
*header_span = az_span_create(az_span_ptr(jws_span), first_dot - az_span_ptr(jws_span));
*payload_span = az_span_create(first_dot + 1, second_dot - first_dot - 1);
*signature_span
= az_span_create(second_dot + 1, az_span_ptr(jws_span) + jws_length - second_dot - 1);
return AZ_OK;
}
/* Usual base64 encoded characters use `+` and `/` for the two extra characters
*/
/* In URL encoded schemes, those aren't allowed, so the characters are swapped
*/
/* for `-` and `_`. We have to swap them back to the usual characters. */
static void swap_to_url_encoding_chars(az_span signature_span)
{
int32_t index = 0;
uint8_t* signature_ptr = az_span_ptr(signature_span);
int32_t signature_length = az_span_size(signature_span);
while (index < signature_length)
{
if (*signature_ptr == '-')
{
*signature_ptr = '+';
}
else if (*signature_ptr == '_')
{
*signature_ptr = '/';
}
signature_ptr++;
index++;
}
}
/**
* @brief Calculate the SHA256 over a buffer of bytes
*
* @param input_span The input span over which to calculate the SHA256.
* @param output_span The output span into which the SHA256. It must be 32 bytes
* in length.
* @return az_result The result of the operation.
* @retval AZ_OK if successful.
*/
static az_result jws_sha256_calculate(az_span input_span, az_span output_span)
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, az_span_ptr(input_span), az_span_size(input_span));
mbedtls_md_finish(&ctx, az_span_ptr(output_span));
mbedtls_md_free(&ctx);
return AZ_OK;
}
/**
* @brief Verify the manifest via RS256 for the JWS.
*
* @param input_span The input span over which the RS256 will be verified.
* @param signature_span The encrypted signature span which will be decrypted by \p
* n_span and \p e_span.
* @param n_span The key's modulus which is used to decrypt \p signature.
* @param e_span The exponent used for the key.
* @param buffer_span The buffer used as scratch space to make the calculations.
* It should be at least `jwsRSA3072_SIZE` + `jwsSHA256_SIZE` in size.
* @return az_result The result of the operation.
* @retval AZ_OK if successful.
*/
static az_result jws_rs256_verify(
az_span input_span,
az_span signature_span,
az_span n_span,
az_span e_span,
az_span buffer_span)
{
az_result result;
int32_t mbed_tls_result;
uint8_t* sha_buffer_ptr;
size_t decrypted_length;
mbedtls_rsa_context ctx;
int sha_match_result;
if (az_span_size(buffer_span) < jwsSHA_CALCULATION_SCRATCH_SIZE)
{
Logger.Error("[JWS] Buffer Not Large Enough");
return AZ_ERROR_NOT_ENOUGH_SPACE;
}
sha_buffer_ptr = az_span_ptr(buffer_span) + jwsRSA3072_SIZE;
/* The signature is encrypted using the input key. We need to decrypt the */
/* signature which gives us the SHA256 inside a PKCS7 structure. We then
* compare */
/* that to the SHA256 of the input. */
mbedtls_rsa_init(&ctx, MBEDTLS_RSA_PKCS_V15, 0);
mbed_tls_result = mbedtls_rsa_import_raw(
&ctx,
az_span_ptr(n_span),
az_span_size(n_span),
NULL,
0,
NULL,
0,
NULL,
0,
az_span_ptr(e_span),
az_span_size(e_span));
if (mbed_tls_result != 0)
{
Logger.Error("[JWS] mbedtls_rsa_import_raw res: " + String(mbed_tls_result));
mbedtls_rsa_free(&ctx);
return AZ_ERROR_NOT_SUPPORTED;
}
mbed_tls_result = mbedtls_rsa_complete(&ctx);
if (mbed_tls_result != 0)
{
Logger.Error("[JWS] mbedtls_rsa_complete res: " + String(mbed_tls_result));
mbedtls_rsa_free(&ctx);
return AZ_ERROR_NOT_SUPPORTED;
}
mbed_tls_result = mbedtls_rsa_check_pubkey(&ctx);
if (mbed_tls_result != 0)
{
Logger.Error("[JWS] mbedtls_rsa_check_pubkey res: " + String(mbed_tls_result));
mbedtls_rsa_free(&ctx);
return AZ_ERROR_NOT_SUPPORTED;
}
/* RSA */
mbed_tls_result = mbedtls_rsa_pkcs1_decrypt(
&ctx,
NULL,
NULL,
MBEDTLS_RSA_PUBLIC,
&decrypted_length,
az_span_ptr(signature_span),
az_span_ptr(buffer_span),
jwsRSA3072_SIZE);
if (mbed_tls_result != 0)
{
Logger.Error("[JWS] mbedtls_rsa_pkcs1_decrypt res: " + String(mbed_tls_result));
mbedtls_rsa_free(&ctx);
return AZ_ERROR_NOT_SUPPORTED;
}
mbedtls_rsa_free(&ctx);
result = jws_sha256_calculate(input_span, az_span_create(sha_buffer_ptr, jwsSHA256_SIZE));
if (result != AZ_OK)
{
Logger.Error("[JWS] jws_sha256_calculate failed");
return result;
}
/* TODO: remove this once we have a valid PKCS7 parser. */
sha_match_result
= memcmp(az_span_ptr(buffer_span) + jwsPKCS7_PAYLOAD_OFFSET, sha_buffer_ptr, jwsSHA256_SIZE);
if (sha_match_result)
{
Logger.Error("[JWS] SHA of JWK does NOT match");
result = AZ_ERROR_NOT_SUPPORTED;
}
return AZ_OK;
}
static az_result find_sjwk_value(az_json_reader* payload_json_reader, az_span* jwk_value_ptr)
{
az_result result = AZ_OK;
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
while (result == AZ_OK)
{
if (az_json_token_is_text_equal(&payload_json_reader->token, jws_sjwk_json_value))
{
/* Found name, move to value */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
break;
}
else
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
_az_adu_jws_return_if_failed(az_json_reader_skip_children(payload_json_reader));
result = az_json_reader_next_token(payload_json_reader);
}
}
if (result != AZ_OK)
{
Logger.Error("[JWS] Parse JSK JSON Payload Error: " + String(result, HEX));
return result;
}
if (payload_json_reader->token.kind != AZ_JSON_TOKEN_STRING)
{
Logger.Error("[JWS] JSON token type wrong | type: " + String(payload_json_reader->token.kind));
return AZ_ERROR_JSON_INVALID_STATE;
}
*jwk_value_ptr = payload_json_reader->token.slice;
return AZ_OK;
}
static az_result find_root_key_value(az_json_reader* payload_json_reader, az_span* kid_span_ptr)
{
az_result result = AZ_OK;
/*Begin object */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
/*Property Name */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
while (result == AZ_OK)
{
if (az_json_token_is_text_equal(&payload_json_reader->token, jws_kid_json_value))
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
*kid_span_ptr = payload_json_reader->token.slice;
break;
}
else
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
_az_adu_jws_return_if_failed(az_json_reader_skip_children(payload_json_reader));
result = az_json_reader_next_token(payload_json_reader);
}
}
if (result != AZ_OK)
{
Logger.Error("[JWS] Parse Root Key Error: " + String(result, HEX));
return result;
}
if (payload_json_reader->token.kind != AZ_JSON_TOKEN_STRING)
{
Logger.Error("[JWS] JSON token type wrong | type: " + String(payload_json_reader->token.kind));
return AZ_ERROR_JSON_INVALID_STATE;
}
return AZ_OK;
}
static az_result find_key_parts(
az_json_reader* payload_json_reader,
az_span* base64_encoded_n_span_ptr,
az_span* base64_encoded_e_span_ptr,
az_span* alg_span_ptr)
{
az_result result = AZ_OK;
/*Begin object */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
/*Property Name */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
while (result == AZ_OK
&& (az_span_size(*base64_encoded_n_span_ptr) == 0
|| az_span_size(*base64_encoded_e_span_ptr) == 0 || az_span_size(*alg_span_ptr) == 0))
{
if (az_json_token_is_text_equal(&payload_json_reader->token, jws_n_json_value))
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
*base64_encoded_n_span_ptr = payload_json_reader->token.slice;
result = az_json_reader_next_token(payload_json_reader);
}
else if (az_json_token_is_text_equal(&payload_json_reader->token, jws_e_json_value))
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
*base64_encoded_e_span_ptr = payload_json_reader->token.slice;
result = az_json_reader_next_token(payload_json_reader);
}
else if (az_json_token_is_text_equal(&payload_json_reader->token, jws_alg_json_value))
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
*alg_span_ptr = payload_json_reader->token.slice;
result = az_json_reader_next_token(payload_json_reader);
}
else
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
_az_adu_jws_return_if_failed(az_json_reader_skip_children(payload_json_reader));
result = az_json_reader_next_token(payload_json_reader);
}
}
if ((result != AZ_OK) || (az_span_size(*base64_encoded_n_span_ptr) == 0)
|| (az_span_size(*base64_encoded_e_span_ptr) == 0) || (az_span_size(*alg_span_ptr) == 0))
{
Logger.Error("[JWS] Parse Signing Key Payload Error: " + String(result, HEX));
return result;
}
return AZ_OK;
}
static az_result find_manifest_sha(az_json_reader* payload_json_reader, az_span* sha_span_ptr)
{
az_result result = AZ_OK;
/*Begin object */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
/*Property Name */
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
while (result == AZ_OK)
{
if (az_json_token_is_text_equal(&payload_json_reader->token, jws_sha256_json_value))
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
break;
}
else
{
_az_adu_jws_return_if_failed(az_json_reader_next_token(payload_json_reader));
_az_adu_jws_return_if_failed(az_json_reader_skip_children(payload_json_reader));
result = az_json_reader_next_token(payload_json_reader);
}
}
if (result != AZ_OK)
{
Logger.Error("[JWS] Parse manifest SHA error: " + String(result, HEX));
return result;
}
if (payload_json_reader->token.kind != AZ_JSON_TOKEN_STRING)
{
Logger.Error("[JWS] JSON token type wrong | type: " + String(payload_json_reader->token.kind));
return AZ_ERROR_JSON_INVALID_STATE;
}
*sha_span_ptr = payload_json_reader->token.slice;
return AZ_OK;
}
static az_result base64_decode_jwk(jws_validation_context* manifest_context)
{
az_result result;
result = az_base64_url_decode(
manifest_context->jwk_header,
manifest_context->jwk_base64_encoded_header,
&manifest_context->out_jwk_header_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWK header az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsJWK_HEADER_SIZE) + " bytes");
}
return result;
}
manifest_context->jwk_header
= az_span_slice(manifest_context->jwk_header, 0, manifest_context->out_jwk_header_length);
result = az_base64_url_decode(
manifest_context->jwk_payload,
manifest_context->jwk_base64_encoded_payload,
&manifest_context->out_jwk_payload_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWK payload az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsJWK_PAYLOAD_SIZE) + " bytes");
}
return result;
}
manifest_context->jwk_payload
= az_span_slice(manifest_context->jwk_payload, 0, manifest_context->out_jwk_payload_length);
result = az_base64_url_decode(
manifest_context->jwk_signature,
manifest_context->jwk_base64_encoded_signature,
&manifest_context->out_jwk_signature_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWK signature az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsSIGNATURE_SIZE) + " bytes");
}
return result;
}
manifest_context->jwk_signature = az_span_slice(
manifest_context->jwk_signature, 0, manifest_context->out_jwk_signature_length);
return AZ_OK;
}
static az_result base64_decode_signing_key(jws_validation_context* manifest_context)
{
az_result result;
result = az_base64_decode(
manifest_context->signing_key_n,
manifest_context->base64_encoded_n_span,
&manifest_context->out_signing_key_n_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] Signing key n az_base64_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsRSA3072_SIZE) + " bytes");
}
return result;
}
manifest_context->signing_key_n = az_span_slice(
manifest_context->signing_key_n, 0, manifest_context->out_signing_key_n_length);
result = az_base64_decode(
manifest_context->signing_key_e,
manifest_context->base64_encoded_e_span,
&manifest_context->out_signing_key_e_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] Signing key e az_base64_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error(
"[JWS] Decode buffer was too small: " + String(jwsSIGNING_KEY_E_SIZE) + " bytes");
}
return result;
}
manifest_context->signing_key_e = az_span_slice(
manifest_context->signing_key_e, 0, manifest_context->out_signing_key_e_length);
return AZ_OK;
}
static az_result base64_decode_jws_header_and_payload(jws_validation_context* manifest_context)
{
az_result result;
result = az_base64_url_decode(
manifest_context->jws_payload,
manifest_context->base64_encoded_payload,
&manifest_context->out_jws_payload_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWS payload az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsJWS_PAYLOAD_SIZE) + " bytes");
}
return result;
}
manifest_context->jws_payload
= az_span_slice(manifest_context->jws_payload, 0, manifest_context->out_jws_payload_length);
result = az_base64_url_decode(
manifest_context->jws_signature,
manifest_context->base64_encoded_signature,
&manifest_context->out_jws_signature_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWS signature az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsSIGNATURE_SIZE) + " bytes");
}
return result;
}
manifest_context->jws_signature = az_span_slice(
manifest_context->jws_signature, 0, manifest_context->out_jws_signature_length);
return AZ_OK;
}
static az_result validate_root_key(
jws_validation_context* manifest_context,
SampleJWS::RootKey* root_keys,
uint32_t root_keys_length,
int32_t* adu_root_key_index)
{
az_result result;
az_json_reader json_reader;
result = az_json_reader_init(&json_reader, manifest_context->jwk_header, NULL);
if (az_result_failed(result))
{
Logger.Error("[JWS] az_json_reader_init failed: result " + String(result, HEX));
return result;
}
if (find_root_key_value(&json_reader, &manifest_context->kid_span) != AZ_OK)
{
Logger.Error("Could not find kid in JSON");
return AZ_ERROR_ITEM_NOT_FOUND;
}
for (int i = 0; i < root_keys_length; i++)
{
if (az_span_is_content_equal(root_keys[i].root_key_id, manifest_context->kid_span))
{
*adu_root_key_index = i;
return AZ_OK;
}
}
return AZ_ERROR_NOT_SUPPORTED;
}
static az_result verify_sha_match(jws_validation_context* manifest_context, az_span manifest_span)
{
az_json_reader json_reader;
az_result verification_result;
az_result result;
verification_result
= jws_sha256_calculate(manifest_span, manifest_context->manifest_sha_calculation);
if (verification_result != AZ_OK)
{
Logger.Error("[JWS] SHA256 Calculation failed");
return verification_result;
}
result = az_json_reader_init(&json_reader, manifest_context->jws_payload, NULL);
if (az_result_failed(result))
{
Logger.Error("[JWS] az_json_reader_init failed: result " + String(result, HEX));
return result;
}
if (find_manifest_sha(&json_reader, &manifest_context->sha256_span) != AZ_OK)
{
Logger.Error("Error finding manifest signature SHA");
return AZ_ERROR_ITEM_NOT_FOUND;
}
result = az_base64_decode(
manifest_context->parsed_manifest_sha,
manifest_context->sha256_span,
&manifest_context->out_parsed_manifest_sha_size);
if (az_result_failed(result))
{
Logger.Error(
"[JWS] Parsed manifest SHA az_base64_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsSHA256_SIZE) + " bytes");
}
return result;
}
manifest_context->parsed_manifest_sha = az_span_slice(
manifest_context->parsed_manifest_sha, 0, manifest_context->out_parsed_manifest_sha_size);
if (manifest_context->out_parsed_manifest_sha_size != jwsSHA256_SIZE)
{
Logger.Error(
"[JWS] Base64 decoded SHA256 is not the correct length | expected: "
+ String(jwsSHA256_SIZE)
+ " | actual: " + String(manifest_context->out_parsed_manifest_sha_size));
return AZ_ERROR_ITEM_NOT_FOUND;
}
int32_t comparison_result = memcmp(
az_span_ptr(manifest_context->manifest_sha_calculation),
az_span_ptr(manifest_context->parsed_manifest_sha),
jwsSHA256_SIZE);
if (comparison_result != 0)
{
Logger.Error("[JWS] Calculated manifest SHA does not match SHA in payload");
return AZ_ERROR_NOT_SUPPORTED;
}
else
{
Logger.Info(("[JWS] Calculated manifest SHA matches parsed SHA"));
}
return AZ_OK;
}
az_result SampleJWS::ManifestAuthenticate(
az_span manifest_span,
az_span jws_span,
SampleJWS::RootKey* root_keys,
uint32_t root_keys_length,
az_span scratch_buffer_span)
{
az_result result;
az_json_reader json_reader;
jws_validation_context manifest_context = { 0 };
int32_t root_key_index;
/* Break up scratch buffer for reusable and persistent sections */
uint8_t* persistent_scratch_space_head = az_span_ptr(scratch_buffer_span);
uint8_t* reusable_scratch_space_root
= persistent_scratch_space_head + jwsJWS_HEADER_SIZE + jwsJWK_PAYLOAD_SIZE;
uint8_t* reusable_scratch_space_head = reusable_scratch_space_root;
/*------------------- Parse and Decode the JWS Header
* ------------------------*/
result = split_jws(
jws_span,
&manifest_context.base64_encoded_header,
&manifest_context.base64_encoded_payload,
&manifest_context.base64_encoded_signature);
if (az_result_failed(result))
{
Logger.Error("[JWS] split_jws failed");
return result;
}
/* Note that we do not use mbedTLS to base64 decode values since we need the
* ability to assume padding characters. */
/* mbedTLS will stop the decoding short and we would then need to add in the
* remaining characters. */
manifest_context.jws_header = az_span_create(persistent_scratch_space_head, jwsJWS_HEADER_SIZE);
persistent_scratch_space_head += jwsJWS_HEADER_SIZE;
result = az_base64_url_decode(
manifest_context.jws_header,
manifest_context.base64_encoded_header,
&manifest_context.out_jws_header_length);
if (az_result_failed(result))
{
Logger.Error("[JWS] JWS header az_base64_url_decode failed: result " + String(result, HEX));
if (result == AZ_ERROR_NOT_ENOUGH_SPACE)
{
Logger.Error("[JWS] Decode buffer was too small: " + String(jwsJWS_HEADER_SIZE) + " bytes");
}
return result;
}
manifest_context.jws_header
= az_span_slice(manifest_context.jws_header, 0, manifest_context.out_jws_header_length);
/*------------------- Parse SJWK JSON Payload ------------------------*/
/* The "sjwk" is the signed signing public key */
result = az_json_reader_init(&json_reader, manifest_context.jws_header, NULL);
if (az_result_failed(result))
{
Logger.Error("[JWS] az_json_reader_init failed: result " + String(result, HEX));
return result;
}
result = find_sjwk_value(&json_reader, &manifest_context.jwk_manifest_span);
if (az_result_failed(result))
{
Logger.Error("Error finding sjwk value in payload");
return AZ_ERROR_ITEM_NOT_FOUND;
}
/*------------------- Split JWK and Base64 Decode the JWK Payload
* ------------------------*/
result = split_jws(
manifest_context.jwk_manifest_span,
&manifest_context.jwk_base64_encoded_header,
&manifest_context.jwk_base64_encoded_payload,
&manifest_context.jwk_base64_encoded_signature);
if (az_result_failed(result))
{
Logger.Error("[JWS] split_jws failed");
return result;
}
manifest_context.jwk_header = az_span_create(reusable_scratch_space_head, jwsJWK_HEADER_SIZE);
reusable_scratch_space_head += jwsJWK_HEADER_SIZE;
/* Needs to be persisted so we can parse the signing key N and E later */
manifest_context.jwk_payload = az_span_create(persistent_scratch_space_head, jwsJWK_PAYLOAD_SIZE);
persistent_scratch_space_head += jwsJWK_PAYLOAD_SIZE;
manifest_context.jwk_signature = az_span_create(reusable_scratch_space_head, jwsSIGNATURE_SIZE);
reusable_scratch_space_head += jwsSIGNATURE_SIZE;
result = base64_decode_jwk(&manifest_context);
if (az_result_failed(result))
{
Logger.Error("[JWS] base64_decode_jwk failed: result " + String(result, HEX));
return result;
}
/*------------------- Parse root key id ------------------------*/
result = validate_root_key(&manifest_context, root_keys, root_keys_length, &root_key_index);
if (az_result_failed(result))
{
Logger.Error("[JWS] validate_root_key failed: result " + String(result, HEX));
return result;
}
/*------------------- Parse necessary pieces for signing key
* ------------------------*/
result = az_json_reader_init(&json_reader, manifest_context.jwk_payload, NULL);
if (az_result_failed(result))
{
Logger.Error("[JWS] az_json_reader_init failed: result " + String(result, HEX));
return result;
}
if (find_key_parts(
&json_reader,
&manifest_context.base64_encoded_n_span,
&manifest_context.base64_encoded_e_span,
&manifest_context.alg_span)
!= AZ_OK)
{
Logger.Error("Could not find parts for the signing key");
return AZ_ERROR_ITEM_NOT_FOUND;
}
/*------------------- Verify the signature ------------------------*/
manifest_context.scratch_calculation_buffer
= az_span_create(reusable_scratch_space_head, jwsSHA_CALCULATION_SCRATCH_SIZE);
reusable_scratch_space_head += jwsSHA_CALCULATION_SCRATCH_SIZE;
result = jws_rs256_verify(
az_span_create(
az_span_ptr(manifest_context.jwk_base64_encoded_header),
az_span_size(manifest_context.jwk_base64_encoded_header)
+ az_span_size(manifest_context.jwk_base64_encoded_payload) + 1),
manifest_context.jwk_signature,
root_keys[root_key_index].root_key_n,
root_keys[root_key_index].root_key_exponent,
manifest_context.scratch_calculation_buffer);
if (result != AZ_OK)
{
Logger.Error("[JWS] jws_rs256_verify failed");
return result;
}
/*------------------- Reuse Buffer Space ------------------------*/
/* The JWK verification is now done, so we can reuse the buffers which it
* used. */
reusable_scratch_space_head = reusable_scratch_space_root;
/*------------------- Decode remaining values from JWS
* ------------------------*/
manifest_context.jws_payload = az_span_create(reusable_scratch_space_head, jwsJWS_PAYLOAD_SIZE);
reusable_scratch_space_head += jwsJWS_PAYLOAD_SIZE;
manifest_context.jws_signature = az_span_create(reusable_scratch_space_head, jwsSIGNATURE_SIZE);
reusable_scratch_space_head += jwsSIGNATURE_SIZE;
result = base64_decode_jws_header_and_payload(&manifest_context);
if (result != AZ_OK)
{
Logger.Error("[JWS] base64_decode_jws_header_and_payload failed");
return result;
}
/*------------------- Base64 decode the signing key ------------------------*/
manifest_context.signing_key_n = az_span_create(reusable_scratch_space_head, jwsRSA3072_SIZE);
reusable_scratch_space_head += jwsRSA3072_SIZE;
manifest_context.signing_key_e
= az_span_create(reusable_scratch_space_head, jwsSIGNING_KEY_E_SIZE);
reusable_scratch_space_head += jwsSIGNING_KEY_E_SIZE;
result = base64_decode_signing_key(&manifest_context);
if (result != AZ_OK)
{
Logger.Error("[JWS] base64_decode_signing_key failed");
return result;
}
/*------------------- Verify that the signature was signed by signing key
* ------------------------*/
if (!az_span_is_content_equal(manifest_context.alg_span, jws_alg_rs256))
{
Logger.Error("[JWS] Algorithm not supported");
return AZ_ERROR_NOT_SUPPORTED;
}
result = jws_rs256_verify(
az_span_create(
az_span_ptr(manifest_context.base64_encoded_header),
az_span_size(manifest_context.base64_encoded_header)
+ az_span_size(manifest_context.base64_encoded_payload) + 1),
manifest_context.jws_signature,
manifest_context.signing_key_n,
manifest_context.signing_key_e,
manifest_context.scratch_calculation_buffer);
if (result != AZ_OK)
{
Logger.Error("[JWS] Verification of signed manifest SHA failed");
return result;
}
/*------------------- Verify that the SHAs match ------------------------*/
manifest_context.manifest_sha_calculation
= az_span_create(reusable_scratch_space_head, jwsSHA256_SIZE);
reusable_scratch_space_head += jwsSHA256_SIZE;
manifest_context.parsed_manifest_sha
= az_span_create(reusable_scratch_space_head, jwsSHA256_SIZE);
reusable_scratch_space_head += jwsSHA256_SIZE;
return verify_sha_match(&manifest_context, manifest_span);
}

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

@ -0,0 +1,72 @@
/* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License. */
/**
* @file
*
* @brief APIs to authenticate an ADU manifest.
*
*/
#ifndef SAMPLEADUJWS_H
#define SAMPLEADUJWS_H
#include <stdint.h>
#include <az_core.h>
#define jwsPKCS7_PAYLOAD_OFFSET 19
#define jwsRSA3072_SIZE 384
#define jwsSHA256_SIZE 32
#define jwsJWS_HEADER_SIZE 1400
#define jwsJWS_PAYLOAD_SIZE 60
#define jwsJWK_HEADER_SIZE 48
#define jwsJWK_PAYLOAD_SIZE 700
#define jwsSIGNATURE_SIZE 400
#define jwsSIGNING_KEY_E_SIZE 10
#define jwsSIGNING_KEY_N_SIZE jwsRSA3072_SIZE
#define jwsSHA_CALCULATION_SCRATCH_SIZE jwsRSA3072_SIZE + jwsSHA256_SIZE
/* This is the minimum amount of space needed to store values which are held at
* the same time. jwsJWS_PAYLOAD_SIZE, one jwsSIGNATURE_SIZE, and one
* jwsSHA256_SIZE are excluded since they will reuse buffer space. */
#define jwsSCRATCH_BUFFER_SIZE \
(jwsJWS_HEADER_SIZE + jwsJWK_HEADER_SIZE + jwsJWK_PAYLOAD_SIZE + jwsSIGNATURE_SIZE \
+ jwsSIGNING_KEY_N_SIZE + jwsSIGNING_KEY_E_SIZE + jwsSHA_CALCULATION_SCRATCH_SIZE)
namespace SampleJWS
{
/**
* @brief Holds the values of the root key used to verify the JWS signature.
*/
typedef struct RootKey
{
az_span root_key_id;
az_span root_key_n;
az_span root_key_exponent;
} RootKey;
/**
* @brief Authenticate the manifest from ADU.
*
* @param[in] manifest_span The escaped manifest from the ADU twin property.
* @param[in] jws_span The JWS used to authenticate \p manifest_span.
* @param[in] root_keys An array of root keys that may be used to verify the payload.
* @param[in] root_keys_length The number of root keys in \p root_keys.
* @param[in] scratch_buffer_span Scratch buffer space for calculations. It
* should be `jwsSCRATCH_BUFFER_SIZE` in length.
* @return az_result The return value of this function.
* @retval AZ_OK if successful.
* @retval Otherwise if failed.
*/
az_result ManifestAuthenticate(
az_span manifest_span,
az_span jws_span,
RootKey* root_keys,
uint32_t root_keys_length,
az_span scratch_buffer_span);
}; // namespace SampleJWS
#endif /* SAMPLEADUJWS_H */

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

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "SerialLogger.h"
#include <time.h>
#define UNIX_EPOCH_START_YEAR 1900
static void writeTime()
{
struct tm* ptm;
time_t now = time(NULL);
ptm = gmtime(&now);
Serial.print(ptm->tm_year + UNIX_EPOCH_START_YEAR);
Serial.print("/");
Serial.print(ptm->tm_mon + 1);
Serial.print("/");
Serial.print(ptm->tm_mday);
Serial.print(" ");
if (ptm->tm_hour < 10)
{
Serial.print(0);
}
Serial.print(ptm->tm_hour);
Serial.print(":");
if (ptm->tm_min < 10)
{
Serial.print(0);
}
Serial.print(ptm->tm_min);
Serial.print(":");
if (ptm->tm_sec < 10)
{
Serial.print(0);
}
Serial.print(ptm->tm_sec);
}
SerialLogger::SerialLogger() { Serial.begin(SERIAL_LOGGER_BAUD_RATE); }
void SerialLogger::Info(String message)
{
writeTime();
Serial.print(" [INFO] ");
Serial.println(message);
}
void SerialLogger::Error(String message)
{
writeTime();
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

Двоичные данные
examples/Azure_IoT_Adu_ESP32/docs/tagged-twin.png Normal file

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

После

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

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

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Enable macro IOT_CONFIG_USE_X509_CERT to use an x509 certificate to
// authenticate the IoT device. The two main modes of authentication are through
// SAS tokens (automatically generated by the sample using the provided device
// symmetric key) or through x509 certificates. Please choose the appropriate
// option according to your device authentication mode.
// #define IOT_CONFIG_USE_X509_CERT
#ifdef IOT_CONFIG_USE_X509_CERT
/*
* Please set the define IOT_CONFIG_DEVICE_CERT below with
* the content of your device x509 certificate.
*
* Example:
* #define IOT_CONFIG_DEVICE_CERT "-----BEGIN CERTIFICATE-----\r\n" \
* "MIIBJDCBywIUfeHrebBVa2eZAbouBgACp9R3BncwCgYIKoZIzj0EAwIwETEPMA0G\r\n" \
* "A1UEAwwGRFBTIENBMB4XDTIyMDMyMjazMTAzN1oXDTIzMDMyMjIzMTAzN1owGTEX\r\n" \
* "MBUGA1UEAwwOY29udG9zby1kZXZpY2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\r\n" \
* .......
* "YmGzdaHTb6P1W+p+jmc+jJn1MAoGCXqGSM49BAMCA0gAMEUCIEnbEMsAdGFroMwl\r\n" \
* "vTfQahwsxN3xink9z1gtirrjQlqDAiEAyU+6TUJcG6d9JF+uJqsLFpsbbF3IzGAw\r\n" \
* "yC+koNRC0MU=\r\n" \
* "-----END CERTIFICATE-----"
*
*/
#define IOT_CONFIG_DEVICE_CERT "Device Certificate"
/*
* Please set the define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY below with
* the content of your device x509 private key.
*
* Example:
*
* #define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "-----BEGIN EC PRIVATE
* KEY-----\r\n" \
* "MHcCAQEEIKGXkMMiO9D7jYpUjUGTBn7gGzeKPeNzCP83wbfQfLd9obAoGCCqGSM49\r\n" \
* "AwEHoUQDQgAEU6nQoYbjgJvBwaeD6MyAYmOSDg0QhEdyyV337qrlIbDEKvFsn1El\r\n" \
* "yRabc4dNp2Jhs3Xh02+j9Vvqfo5nPoyZ9Q==\r\n" \
* "-----END EC PRIVATE KEY-----"
*
* Note the type of key may different in your case. Such as BEGIN PRIVATE KEY
* or BEGIN RSA PRIVATE KEY.
*
*/
#define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "Device Certificate Private Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Azure IoT
#define IOT_CONFIG_IOTHUB_FQDN "[your Azure IoT host name].azure-devices.net"
#define IOT_CONFIG_DEVICE_ID "Device ID"
// Use device key if not using certificates
#ifndef IOT_CONFIG_USE_X509_CERT
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Azure IoT ADU parameters
#define ADU_DEVICE_MANUFACTURER "ESPRESSIF"
#define ADU_DEVICE_MODEL "ESP32-Embedded"
#define ADU_UPDATE_PROVIDER "Contoso"
#define ADU_UPDATE_NAME "ESP32-Embedded"
#define ADU_DEVICE_VERSION "1.0"
// Publish 1 message every 2 seconds
#define TELEMETRY_FREQUENCY_MILLISECS 2000

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

@ -0,0 +1,304 @@
---
page_type: sample
description: Connecting an ESP32 device to Azure IoT using the Azure SDK for Embedded C
languages:
- c
products:
- azure-iot
- azure-iot-pnp
- azure-iot-dps
- azure-iot-hub
---
# How to Setup and Run Azure SDK for Embedded C ADU on Espressif ESP32
> ## Before you proceed
>
>_This sample is based on the Azure IoT SDK for C, uses a bare metal (no RTOS) approach and has support for Arduino IDE._
>
>_There is a different sample for the ESP32 with the Azure IoT middleware for FreeRTOS (requires FreeRTOS to work)._
>
> _If this is what youre looking for please visit [this GitHub repo](https://github.com/Azure-Samples/iot-middleware-freertos-samples/blob/main/README.md)_.
- [Introduction](#introduction)
- [What is Covered](#what-is-covered)
- [Prerequisites](#prerequisites)
- [Add Azure IoT Hub Device to an ADU Deployment Group](#add-azure-iot-hub-device-to-an-adu-deployment-group)
- [Device Setup Instructions](#device-setup-instructions)
- [New Image Instructions](#new-image-instructions)
- [Generate the ADU Update Manifest](#generate-the-adu-update-manifest)
- [Import the Update Manifest](#import-the-update-manifest)
- [Deploy Update](#deploy-update)
- [Monitor the Update Process](#monitor-the-update-process)
- [Certificates - Important to know](#certificates---important-to-know)
- [Additional Information](#additional-information)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## Introduction
This is a "to-the-point" guide outlining how to run an Azure SDK for Embedded C ADU sample on an ESP32 microcontroller. The command line examples are tailored to Debian/Ubuntu environments.
### What is Covered
- Configuration instructions for the Arduino IDE to compile a sample using the [Azure SDK for Embedded C](https://github.com/Azure/azure-sdk-for-c).
- Setting up and configuring the necessary services for the scenario.
- Configuration, build, and run instructions for the IoT ADU sample.
_The following was run on Windows 11, with Arduino IDE 2.1.0 and ESP32 board library version 2.0.9._
## 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.
> Important: To use Azure Device Update you must have an Azure IoT Hub with [scale unit](https://learn.microsoft.com/azure/iot-hub/iot-hub-scaling) S1, S2, S3. This sample will not work with a Azure IoT Hub 'Basic' scale unit or free tier.
- Have an [Azure Device Update](https://docs.microsoft.com/azure/iot-hub-device-update/create-device-update-account?tabs=portal) instance created and linked to your Azure IoT Hub.
- Have a logical device created in your Azure IoT Hub: using authentication type "Symmetric Key" or "X.509 self-signed".
- **Symmetric Key**: follow [this guidance](https://docs.microsoft.com/azure/iot-hub/iot-hub-create-through-portal#register-a-new-device-in-the-iot-hub) to create a device.In this case, the device keys are used to automatically generate a SAS token for authentication.
- **X.509 self-signed cert**: Instructions on how to create an X.509 cert for tests can be found [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#configure-and-run-the-samples) (Step 1). Please note that you might need to install some of the [prerequisites](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#prerequisites) like OpenSSL.
- Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
- Have the [ESP32 board support](https://github.com/espressif/arduino-esp32) installed on Arduino IDE.
- ESP32 boards are not natively supported by Arduino IDE, so you need to add them manually.
- Follow the [instructions](https://github.com/espressif/arduino-esp32) in the official ESP32 repository.
- If your ESP32 board is not recognized and a COM port is not mapped by your computer, try installing the [serial port drivers](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html#connect-esp32-to-pc) recommended by ESPRESSIF.
- Install Azure CLI and Azure IoT Module
See steps to install both [here](https://learn.microsoft.com/azure/iot-hub-device-update/create-update?source=recommendations#prerequisites).
## Add Azure IoT Hub Device to an ADU Deployment Group
Add the `"ADUGroup"` tag to the device's top-level twin document. This is used to group devices together, and you may choose whichever tag you prefer (e.g., "embeddedSDK").
```json
"tags": {
"ADUGroup": "<your-tag-here>"
},
```
Viewing the device twin on the portal, the "tag" section should look similar to the following. Don't worry if you do or do not have a `"deviceUpdate"` section in the `"ADUGroup"` tag. ADU adds that as a default group.
![img](./docs/tagged-twin.png)
## Device Setup Instructions
1. Run the Arduino IDE.
1. Install the Azure SDK for Embedded C library.
- On the Arduino IDE, go to menu `Sketch`, `Include Library`, `Manage Libraries...`.
- Search for and install `azure-sdk-for-c`.
- **Make sure to install version `1.1.0` or later to use the new Azure Device Update client of the Embedded C SDK.**
1. Open the ESPRESSIF ESP32 sample.
- On the Arduino IDE, go to menu `File`, `Examples`, `azure-sdk-for-c`.
- Click on `Azure_IoT_Adu_ESP32` to open the sample.
1. Configure the ESPRESSIF ESP32 sample.
Enter your Azure IoT Hub and device information into the sample's `iot_configs.h`:
- Add your Wi-Fi SSID to `IOT_CONFIG_WIFI_SSID`
- Add your Wi-Fi password to `IOT_CONFIG_WIFI_PASSWORD`
- Add you IoTHub Name to `IOT_CONFIG_IOTHUB_FQDN`
- Add your Device ID to `IOT_CONFIG_DEVICE_ID`
- If using **X.509 Cert**:
- Uncomment the `#define IOT_CONFIG_USE_X509_CERT`
- Add your cert to `IOT_CONFIG_USE_X509_CERT`
- Add your cert PK to `IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY`
- If using **Symmetric Key**:
- Add your device key to `IOT_CONFIG_DEVICE_KEY`
1. Connect the ESP32 microcontroller to your USB port.
After connecting the device,
- Go to menu `Tools`, `Board` and select `ESP32`.
- Go to menu `Tools`, `Port` and select the port to which the microcontroller is connected.
1. Select the appropriate partition scheme for your device. Go to `Tools` -> `Partition Scheme` -> `Minimal SPIFFS`.
1. Upload the sketch.
- Go to menu `Sketch` and click on `Upload`.
<details><summary><i>Expected output of the upload:</i></summary>
<p>
```text
Executable segment sizes:
IROM : 361788 - code in flash (default or ICACHE_FLASH_ATTR)
IRAM : 26972 / 32768 - code in IRAM (ICACHE_RAM_ATTR, ISRs...)
DATA : 1360 ) - initialized variables (global, static) in RAM/HEAP
RODATA : 2152 ) / 81920 - constants (global, static) in RAM/HEAP
BSS : 26528 ) - zeroed variables (global, static) in RAM/HEAP
Sketch uses 392272 bytes (37%) of program storage space. Maximum is 1044464 bytes.
Global variables use 30040 bytes (36%) of dynamic memory, leaving 51880 bytes for local variables. Maximum is 81920 bytes.
/home/user/.arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3 /home/user/.arduino15/packages/esp8266/hardware/esp8266/2.7.1/tools/upload.py --chip esp8266 --port /dev/ttyUSB0 --baud 230400 --before default_reset --after hard_reset write_flash 0x0 /tmp/arduino_build_826987/azure_iot_hub_telemetry.ino.bin
esptool.py v2.8
Serial port /dev/ttyUSB0
Connecting....
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: dc:4f:22:5e:a7:09
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 230400
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 396432 bytes to 292339...
Writing at 0x00000000... (5 %)
Writing at 0x00004000... (11 %)
Writing at 0x00008000... (16 %)
Writing at 0x0000c000... (22 %)
Writing at 0x00010000... (27 %)
Writing at 0x00014000... (33 %)
Writing at 0x00018000... (38 %)
Writing at 0x0001c000... (44 %)
Writing at 0x00020000... (50 %)
Writing at 0x00024000... (55 %)
Writing at 0x00028000... (61 %)
Writing at 0x0002c000... (66 %)
Writing at 0x00030000... (72 %)
Writing at 0x00034000... (77 %)
Writing at 0x00038000... (83 %)
Writing at 0x0003c000... (88 %)
Writing at 0x00040000... (94 %)
Writing at 0x00044000... (100 %)
Wrote 396432 bytes (292339 compressed) at 0x00000000 in 13.0 seconds (effective 243.4 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
```
</p>
</details>
1. Monitor the MCU (microcontroller) locally via the Serial Port.
- Go to menu `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
1970/1/1 00:00:41 [INFO] WiFi connected, IP address: 192.168.1.123
1970/1/1 00:00:41 [INFO] Setting time using SNTP
....
2022/11/2 20:56:20 [INFO] Time initialized!
2022/11/2 20:56:20 [INFO] ------------------------------------------------------------------------------
2022/11/2 20:56:20 [INFO] ADU SAMPLE
2022/11/2 20:56:20 [INFO] Version: 1.0
2022/11/2 20:56:20 [INFO] ------------------------------------------------------------------------------
2022/11/2 20:56:20 [INFO] Client ID: mydeviceid
2022/11/2 20:56:20 [INFO] Username: myiothub.azure-devices.net/mydeviceid/?api-version=2020-09-30&DeviceClientType=c%2f1.4.0-beta.1(ard;esp32)&model-id=dtmi%3Aazure%3Aiot%3AdeviceUpdateModel%3B1
2022/11/2 20:202256/11/2: 20:56:2020 [INFO] [INFO] MQTT event MQTT_EVENT_BEFORE_CONNECT
MQTT client started
2022/11/2 20:56:20 [INFO] Requesting all device properties
2022/11/2 20:56:20 [INFO] Client requesting device property document from service.
2022/11/2 20:56:27 [INFO] MQTT event MQTT_EVENT_CONNECTED
```
## New Image Instructions
In order to update our device, we have to build the image which our device will update to. We will have to direct the Arduino IDE to specify an output directory so that we can easily find the binary. Open the `preferences.txt` (find its path going to menu `File`, `Preferences`) and add `build.path=C:\Arduino-output` (or whichever directory you prefer), then restart the Arduino IDE.
Once you are done with the ADU sample, you may remove the added configuration to restore the build output to its original location.
1. Update the image version in the sketch configuration
- In `iot_configs.h`, change the `ADU_DEVICE_VERSION` to version 1.1.
1. Click on "Verify" to build the update image.
Once the build is complete, you should then have a file called `Azure_IoT_Adu_ESP32.ino.bin` in your output directory. Copy that file to a new directory `C:\ADU-update`, and rename it `Azure_IoT_Adu_ESP32_1.1.bin`
### Generate the ADU Update Manifest
Open PowerShell.
Navigate to the `C:\ADU-update` directory.
Run the following command:
```powershell
az iot du update init v5 --update-provider Contoso --update-name ESP32-Embedded --update-version 1.1 --compat deviceModel=ESP32-Embedded deviceManufacturer=ESPRESSIF --step handler=microsoft/swupdate:1 properties='{\"installedCriteria\":\"1.1\"}' --file path=./Azure_IoT_Adu_ESP32_1.1.bin > ./Contoso.ESP32-Embedded.1.1.importmanifest.json
```
Verify you have the following files in your ADU-update directory:
- `Azure_IoT_Adu_ESP32_1.1.bin`
- `Contoso.ESP32-Embedded.1.1.importmanifest.json`
### Import the Update Manifest
To import the update (`Azure_IoT_Adu_ESP32_1.1.bin`) and manifest (`Contoso.ESP32-Embedded.1.1.importmanifest.json`), follow the instructions at the link below:
- [Import Update and Manifest](https://docs.microsoft.com/azure/iot-hub-device-update/import-update)
### Deploy Update
To deploy the update to your ESP32, follow the link below:
- [Deploy Update](https://docs.microsoft.com/azure/iot-hub-device-update/deploy-update)
### Monitor the Update Process
In Arduino IDE, go to menu `Tools`, `Serial Monitor`.
Once the update request is received by the device, a log similar to this should be displayed:
```text
2022/11/3 05:25:32 [INFO] Client received a properties topic.
2022/11/3 05:25:32 [INFO] Status: 200
2022/11/3 05:25:32 [INFO] Message Type: Desired Properties
2022/11/3 05:25:32 [INFO] Parsed Azure device update manifest.
2022/11/3 05:25:32 [INFO] [JWS] Calculated manifest SHA matches parsed SHA
2022/11/3 05:25:32 [INFO] Manifest authenticated successfully
2022/11/3 05:25:32 [INFO] Sending manifest property accept
...
```
And once the update is complete (including the device automatic reboot), the new version should be printed:
```text
2022/11/3 05:26:22 [INFO] ------------------------------------------------------------------------------
2022/11/3 05:26:22 [INFO] ADU SAMPLE
2022/11/3 05:26:22 [INFO] Version: 1.1
2022/11/3 05:26:22 [INFO] ------------------------------------------------------------------------------
```
## 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,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

После

Ширина:  |  Высота:  |  Размер: 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

После

Ширина:  |  Высота:  |  Размер: 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.

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

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

@ -15,8 +15,8 @@
#ifndef AZURE_IOT_H
#define AZURE_IOT_H
#include <stdlib.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <az_core.h>
@ -38,8 +38,7 @@ typedef enum log_level_t_enum
{
log_level_info,
log_level_error
}
log_level_t;
} log_level_t;
typedef void (*log_function_t)(log_level_t log_level, char const* const format, ...);
@ -48,45 +47,43 @@ 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__ )
#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 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 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
#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.
* 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_MOST_ONCE 0
#define MQTT_QOS_AT_LEAST_ONCE 1
#define MQTT_QOS_EXACTLY_ONCE 2
#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;
} mqtt_qos_t;
/*
* @brief Defines a generic MQTT message to be exchanged between the AzureIoT layer
* and the user application.
* 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.
@ -98,8 +95,7 @@ typedef struct mqtt_message_t_struct
az_span topic;
az_span payload;
mqtt_qos_t qos;
}
mqtt_message_t;
} mqtt_message_t;
/*
* @brief Configuration structure passed by `mqtt_client_init_function_t` to the user
@ -131,8 +127,7 @@ typedef struct mqtt_client_config_t_struct
* @brief Password to be provided in the CONNECT sent by the MQTT client.
*/
az_span password;
}
mqtt_client_config_t;
} mqtt_client_config_t;
/*
* @brief Generic pointer to the actual instance of the application MQTT client.
@ -142,70 +137,79 @@ 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.
* @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.
*
* @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);
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).
* @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.
*
* @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.
* 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.
* @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.
*
* @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.
* - 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);
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.
* @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] 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.
*
* @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);
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.
@ -216,53 +220,69 @@ typedef struct mqtt_client_interface_t_struct
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;
} 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
* @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);
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
* @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);
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 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);
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.
@ -272,14 +292,15 @@ 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;
} 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).
*
* @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.
*/
@ -287,9 +308,11 @@ typedef void (*properties_update_completed_t)(uint32_t request_id, az_iot_status
/*
* @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).
*
* @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.
*/
@ -320,14 +343,13 @@ typedef struct command_request_t_struct
* @brief Optional payload sent by the caller for this command.
*/
az_span payload;
}
command_request_t;
} 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
* @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.
*
@ -347,7 +369,7 @@ typedef enum azure_iot_status_t_struct
/*
* @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
* 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,
@ -358,19 +380,19 @@ typedef enum azure_iot_status_t_struct
/*
* @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`);
* - 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
* 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;
} azure_iot_status_t;
/*
* @brief Internal states of the Azure IoT client.
@ -398,8 +420,7 @@ typedef enum azure_iot_client_state_t_struct
azure_iot_state_ready,
azure_iot_state_refreshing_sas,
azure_iot_state_error
}
azure_iot_client_state_t;
} azure_iot_client_state_t;
/*
* @brief Structure that holds the configuration for the Azure IoT client.
@ -408,7 +429,7 @@ azure_iot_client_state_t;
* 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
* 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
@ -454,6 +475,18 @@ typedef struct azure_iot_config_t_struct
*/
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.
@ -486,29 +519,29 @@ typedef struct azure_iot_config_t_struct
* @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:
* 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.
*
* <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"
@ -516,14 +549,16 @@ typedef struct azure_iot_config_t_struct
* <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)
* <user-agent> = "c%2F1.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.
* @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;
@ -564,8 +599,7 @@ typedef struct azure_iot_config_t_struct
* `azure_iot_send_command_response`.
*/
command_request_received_t on_command_request_received;
}
azure_iot_config_t;
} azure_iot_config_t;
/*
* @brief Structure that holds the state of the Azure IoT client.
@ -587,15 +621,15 @@ typedef struct azure_iot_t_struct
uint32_t dps_retry_after_seconds;
uint32_t dps_last_query_time;
az_span dps_operation_id;
}
azure_iot_t;
} 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 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.
@ -613,11 +647,12 @@ void azure_iot_init(azure_iot_t* azure_iot, azure_iot_config_t* azure_iot_config
* 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
* 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 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.
@ -630,8 +665,9 @@ int azure_iot_start(azure_iot_t* azure_iot);
* @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.
*
* @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.
*/
@ -641,23 +677,26 @@ 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.
* @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.
* @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.
*/
@ -665,9 +704,11 @@ 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.
*
* @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.
*/
@ -676,14 +717,15 @@ 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] 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.
* `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.
*/
@ -692,27 +734,33 @@ int azure_iot_send_properties_update(azure_iot_t* azure_iot, uint32_t request_id
/**
* @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] 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);
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.
* (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.
*/
@ -720,11 +768,12 @@ 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.
* @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.
*/
@ -732,11 +781,12 @@ 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.
* @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.
@ -745,13 +795,14 @@ int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet
/*
* @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.
* @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.
@ -759,51 +810,48 @@ int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet
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.
* @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).
*
* @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 Checks whether `span` is equal to AZ_SPAN_EMPTY.
*
* @param[in] span A span to be verified.
*
* @return boolean True if `span`'s pointer and size are equal to AZ_SPAN_EMPTY, or false otherwise.
*/
#define is_az_span_empty(span) az_span_is_content_equal(span, AZ_SPAN_EMPTY)
/**
* @brief Slices `span` at position `size`, returns the first slice and assigns the second slice to `remainder`.
* @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.
* @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`.
* @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.
* @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.
* @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);

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

@ -4,34 +4,36 @@
/*
* This is an Arduino-based Azure IoT Central sample specific for Espressif ESP32.
* 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/.
*
* 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
* 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 Espressif ESP32 board.
*
* To properly connect to your Azure IoT services, please fill the information in the `iot_configs.h` file.
*
* 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 <cstdlib>
#include <string.h>
#include <time.h>
@ -49,20 +51,20 @@
#include <az_iot.h>
#include <azure_ca.h>
// Additional sample headers
// Additional sample headers
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include "iot_configs.h"
/* --- Sample-specific Settings --- */
#define SERIAL_LOGGER_BAUD_RATE 115200
#define MQTT_DO_NOT_RETAIN_MSG 0
#define MQTT_DO_NOT_RETAIN_MSG 0
/* --- Time and NTP Settings --- */
#define NTP_SERVERS "pool.ntp.org", "time.nist.gov"
#define PST_TIME_ZONE -8
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define GMT_OFFSET_SECS (PST_TIME_ZONE * 3600)
#define GMT_OFFSET_SECS_DST ((PST_TIME_ZONE + PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * 3600)
@ -71,16 +73,13 @@
#define UNIX_EPOCH_START_YEAR 1900
/* --- Function Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
/* --- Handling iot_config.h Settings --- */
static const char* wifi_ssid = IOT_CONFIG_WIFI_SSID;
static const char* wifi_password = IOT_CONFIG_WIFI_PASSWORD;
// TODO: remove this after updating library with Azure SDK for C version 1.3.0-beta.2 or later.
#define AZURE_SDK_CLIENT_USER_AGENT_WORKAROUND "DeviceClientType=" AZURE_SDK_CLIENT_USER_AGENT
/* --- Function Declarations --- */
static void sync_device_clock_with_ntp_server();
static void connect_to_wifi();
@ -103,22 +102,25 @@ static uint8_t az_iot_data_buffer[AZ_IOT_DATA_BUFFER_SIZE];
static uint32_t properties_request_id = 0;
static bool send_device_info = true;
static bool azure_initial_connect = false; //Turns true when ESP32 successfully connects to Azure IoT Central for the first time
/* --- MQTT Interface Functions --- */
/*
* These functions are used by Azure IoT to interact with whatever MQTT client used by the sample
* (in this case, Espressif's ESP MQTT). Please see the documentation in AzureIoT.h for more details.
* (in this case, Espressif's ESP MQTT). 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)
static int mqtt_client_init_function(
mqtt_client_config_t* mqtt_client_config,
mqtt_client_handle_t* mqtt_client_handle)
{
int result;
esp_mqtt_client_config_t mqtt_config;
memset(&mqtt_config, 0, sizeof(mqtt_config));
memset(&mqtt_config, 0, sizeof(mqtt_config));
az_span mqtt_broker_uri_span = AZ_SPAN_FROM_BUFFER(mqtt_broker_uri);
mqtt_broker_uri_span = az_span_copy(mqtt_broker_uri_span, AZ_SPAN_FROM_STR(MQTT_PROTOCOL_PREFIX));
@ -129,7 +131,15 @@ static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, m
mqtt_config.port = mqtt_client_config->port;
mqtt_config.client_id = (const char*)az_span_ptr(mqtt_client_config->client_id);
mqtt_config.username = (const char*)az_span_ptr(mqtt_client_config->username);
#ifdef IOT_CONFIG_USE_X509_CERT
LogInfo("MQTT client using X509 Certificate authentication");
mqtt_config.client_cert_pem = IOT_CONFIG_DEVICE_CERT;
mqtt_config.client_key_pem = IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY;
#else // Using SAS key
mqtt_config.password = (const char*)az_span_ptr(mqtt_client_config->password);
#endif
mqtt_config.keepalive = 30;
mqtt_config.disable_clean_session = 0;
mqtt_config.disable_auto_reconnect = false;
@ -149,7 +159,7 @@ static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, m
else
{
esp_err_t start_result = esp_mqtt_client_start(mqtt_client);
if (start_result != ESP_OK)
{
LogError("esp_mqtt_client_start failed (error code: 0x%08x).", start_result);
@ -174,7 +184,7 @@ static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
esp_mqtt_client_handle_t esp_mqtt_client_handle = (esp_mqtt_client_handle_t)mqtt_client_handle;
LogInfo("MQTT client being disconnected.");
if (esp_mqtt_client_stop(esp_mqtt_client_handle) != ESP_OK)
{
LogError("Failed stopping MQTT client.");
@ -187,7 +197,7 @@ static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
if (azure_iot_mqtt_client_disconnected(&azure_iot) != 0)
{
LogError("Failed updating azure iot client of MQTT disconnection.");
LogError("Failed updating azure iot client of MQTT disconnection.");
}
return 0;
@ -196,13 +206,18 @@ static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
/*
* 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)
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));
// As per documentation, `topic` always ends with a null-terminator.
// esp_mqtt_client_subscribe returns the packet id or negative on error already, so no conversion is needed.
int packet_id = esp_mqtt_client_subscribe((esp_mqtt_client_handle_t)mqtt_client_handle, (const char*)az_span_ptr(topic), (int)qos);
// esp_mqtt_client_subscribe returns the packet id or negative on error already, so no conversion
// is needed.
int packet_id = esp_mqtt_client_subscribe(
(esp_mqtt_client_handle_t)mqtt_client_handle, (const char*)az_span_ptr(topic), (int)qos);
return packet_id;
}
@ -210,18 +225,20 @@ static int mqtt_client_subscribe_function(mqtt_client_handle_t mqtt_client_handl
/*
* 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)
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 mqtt_result = esp_mqtt_client_publish(
(esp_mqtt_client_handle_t)mqtt_client_handle,
(const char*)az_span_ptr(mqtt_message->topic), // topic is always null-terminated.
(const char*)az_span_ptr(mqtt_message->payload),
az_span_size(mqtt_message->payload),
(int)mqtt_message->qos,
MQTT_DO_NOT_RETAIN_MSG);
(esp_mqtt_client_handle_t)mqtt_client_handle,
(const char*)az_span_ptr(mqtt_message->topic), // topic is always null-terminated.
(const char*)az_span_ptr(mqtt_message->payload),
az_span_size(mqtt_message->payload),
(int)mqtt_message->qos,
MQTT_DO_NOT_RETAIN_MSG);
if (mqtt_result == -1)
{
return RESULT_ERROR;
@ -237,7 +254,13 @@ static int mqtt_client_publish_function(mqtt_client_handle_t mqtt_client_handle,
/*
* See the documentation of `hmac_sha256_encryption_function_t` in AzureIoT.h for details.
*/
static int mbedtls_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)
static int mbedtls_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;
mbedtls_md_context_t ctx;
@ -256,7 +279,12 @@ static int mbedtls_hmac_sha256(const uint8_t* key, size_t key_length, const uint
/*
* 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)
static int base64_decode(
uint8_t* data,
size_t data_length,
uint8_t* decoded,
size_t decoded_size,
size_t* decoded_length)
{
return mbedtls_base64_decode(decoded, decoded_size, decoded_length, data, data_length);
}
@ -264,7 +292,12 @@ static int base64_decode(uint8_t* data, size_t data_length, uint8_t* decoded, si
/*
* 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)
static int base64_encode(
uint8_t* data,
size_t data_length,
uint8_t* encoded,
size_t encoded_size,
size_t* encoded_length)
{
return mbedtls_base64_encode(encoded, encoded_size, encoded_length, data, data_length);
}
@ -296,44 +329,51 @@ void on_properties_received(az_span properties)
* 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));
{
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.
// 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);
}
/* --- Arduino setup and loop Functions --- */
void setup()
{
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
set_logging_function(logging_function);
connect_to_wifi();
sync_device_clock_with_ntp_server();
azure_pnp_init();
/*
* The configuration structure used by Azure IoT must remain unchanged (including data buffer)
static void configure_azure_iot() {
/*
* 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(AZURE_SDK_CLIENT_USER_AGENT_WORKAROUND);
azure_iot_config.user_agent = AZ_SPAN_FROM_STR(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;
#ifdef IOT_CONFIG_USE_X509_CERT
azure_iot_config.device_certificate = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_CERT);
azure_iot_config.device_certificate_private_key
= AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY);
azure_iot_config.device_key = AZ_SPAN_EMPTY;
#else
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);
#endif // IOT_CONFIG_USE_X509_CERT
azure_iot_config.dps_id_scope = AZ_SPAN_FROM_STR(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.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 = MQTT_PASSWORD_LIFETIME_IN_MINUTES;
azure_iot_config.mqtt_client_interface.mqtt_client_init = mqtt_client_init_function;
@ -348,6 +388,20 @@ void setup()
azure_iot_config.on_command_request_received = on_command_request_received;
azure_iot_init(&azure_iot, &azure_iot_config);
}
/* --- Arduino setup and loop Functions --- */
void setup()
{
Serial.begin(SERIAL_LOGGER_BAUD_RATE);
set_logging_function(logging_function);
connect_to_wifi();
sync_device_clock_with_ntp_server();
azure_pnp_init();
configure_azure_iot();
azure_iot_start(&azure_iot);
LogInfo("Azure IoT client initialized (state=%d)", azure_iot.state);
@ -357,30 +411,44 @@ void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
azure_iot_stop(&azure_iot);
connect_to_wifi();
if (!azure_initial_connect)
{
configure_azure_iot();
}
azure_iot_start(&azure_iot);
}
else
{
switch(azure_iot_get_status(&azure_iot))
switch (azure_iot_get_status(&azure_iot))
{
case azure_iot_connected:
azure_initial_connect = true;
if (send_device_info)
{
(void)azure_pnp_send_device_info(&azure_iot, properties_request_id++);
send_device_info = false; // Only need to send once.
send_device_info = false; // Only need to send once.
}
else if (azure_pnp_send_telemetry(&azure_iot) != 0)
{
LogError("Failed sending telemetry.");
LogError("Failed sending telemetry.");
}
break;
case azure_iot_error:
LogError("Azure IoT client is in error state." );
LogError("Azure IoT client is in error state.");
azure_iot_stop(&azure_iot);
break;
case azure_iot_disconnected:
WiFi.disconnect();
break;
default:
break;
}
@ -389,12 +457,11 @@ void loop()
}
}
/* === 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, ESP MQTT client event handler
* of connecting to the internet, syncing the board clock, ESP MQTT client event handler
* and logging.
*/
@ -420,6 +487,8 @@ static void connect_to_wifi()
LogInfo("Connecting to WIFI wifi_ssid %s", wifi_ssid);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED)
{
@ -440,37 +509,39 @@ static esp_err_t esp_mqtt_event_handler(esp_mqtt_event_handle_t event)
case MQTT_EVENT_ERROR:
LogError("MQTT client in ERROR state.");
LogError(
"esp_tls_stack_err=%d; esp_tls_cert_verify_flags=%d;esp_transport_sock_errno=%d;error_type=%d;connect_return_code=%d",
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_tls_cert_verify_flags,
event->error_handle->esp_transport_sock_errno,
event->error_handle->error_type,
event->error_handle->connect_return_code);
LogError(
"esp_tls_stack_err=%d; "
"esp_tls_cert_verify_flags=%d;esp_transport_sock_errno=%d;error_type=%d;connect_return_"
"code=%d",
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_tls_cert_verify_flags,
event->error_handle->esp_transport_sock_errno,
event->error_handle->error_type,
event->error_handle->connect_return_code);
switch (event->error_handle->connect_return_code)
switch (event->error_handle->connect_return_code)
{
case MQTT_CONNECTION_ACCEPTED:
LogError("connect_return_code=MQTT_CONNECTION_ACCEPTED");
break;
case MQTT_CONNECTION_REFUSE_PROTOCOL:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_PROTOCOL");
break;
case MQTT_CONNECTION_REFUSE_ID_REJECTED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_ID_REJECTED");
break;
case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE");
break;
case MQTT_CONNECTION_REFUSE_BAD_USERNAME:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_BAD_USERNAME");
break;
case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED");
break;
default:
LogError("connect_return_code=unknown (%d)", event->error_handle->connect_return_code);
break;
case MQTT_CONNECTION_ACCEPTED:
LogError("connect_return_code=MQTT_CONNECTION_ACCEPTED");
break;
case MQTT_CONNECTION_REFUSE_PROTOCOL:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_PROTOCOL");
break;
case MQTT_CONNECTION_REFUSE_ID_REJECTED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_ID_REJECTED");
break;
case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE");
break;
case MQTT_CONNECTION_REFUSE_BAD_USERNAME:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_BAD_USERNAME");
break;
case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED");
break;
default:
LogError("connect_return_code=unknown (%d)", event->error_handle->connect_return_code);
break;
};
break;
@ -519,11 +590,15 @@ static esp_err_t esp_mqtt_event_handler(esp_mqtt_event_handle_t event)
mqtt_message_t mqtt_message;
mqtt_message.topic = az_span_create((uint8_t*)event->topic, event->topic_len);
mqtt_message.payload = az_span_create((uint8_t*)event->data, event->data_len);
mqtt_message.qos = mqtt_qos_at_most_once; // QoS is unused by azure_iot_mqtt_client_message_received.
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).", event->topic_len, event->topic);
LogError(
"azure_iot_mqtt_client_message_received failed (topic=%.*s).",
event->topic_len,
event->topic);
}
break;

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

@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <stdlib.h>
#include <stdarg.h>
#include <stdlib.h>
#include <az_core.h>
#include <az_iot.h>
@ -15,67 +15,67 @@
/* --- 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_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 "ESPRESSIF"
#define SAMPLE_MODEL_PROPERTY_VALUE "ESP32 Azure IoT Kit"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "FreeRTOS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "ESP32 WROVER-B"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
#define SAMPLE_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
#define SAMPLE_MODEL_PROPERTY_VALUE "ESP32 Azure IoT Kit"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "FreeRTOS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "ESP32 WROVER-B"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
// The next couple properties are in KiloBytes.
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 4096
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 8192
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 4096
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 8192
#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"
#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 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 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 RESULT_OK 0
#define RESULT_ERROR __LINE__
#define EXIT_IF_TRUE(condition, retcode, message, ...) \
do \
{ \
if (condition) \
{ \
LogError(message, ##__VA_ARGS__ ); \
return retcode; \
} \
#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__ )
#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
@ -91,23 +91,25 @@ 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);
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);
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);
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()
{
}
void azure_pnp_init() {}
const az_span azure_pnp_get_model_id()
{
return AZ_SPAN_FROM_STR(AZURE_PNP_MODEL_ID);
}
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)
{
@ -128,8 +130,9 @@ int azure_pnp_send_telemetry(azure_iot_t* azure_iot)
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)
else if (
last_telemetry_send_time == INDEFINITE_TIME
|| difftime(now, last_telemetry_send_time) >= telemetry_frequency_in_seconds)
{
size_t payload_size;
@ -156,12 +159,14 @@ 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);
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));
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;
@ -187,20 +192,29 @@ int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t c
}
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);
// 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));
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);
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)
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);
@ -208,30 +222,23 @@ int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span propertie
int result;
size_t length;
result = consume_properties_and_generate_response(azure_iot, properties, data_buffer, DATA_BUFFER_SIZE, &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));
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_temperature() { return 21.0; }
static float simulated_get_humidity()
{
return 88.0;
}
static float simulated_get_humidity() { return 88.0; }
static float simulated_get_ambientLight()
{
return 700.0;
}
static float simulated_get_ambientLight() { return 700.0; }
static void simulated_get_pressure_altitude(float* pressure, float* altitude)
{
@ -239,14 +246,22 @@ static void simulated_get_pressure_altitude(float* pressure, float* altitude)
*altitude = 700.0;
}
static void simulated_get_magnetometer(int32_t* magneticFieldX, int32_t* magneticFieldY, int32_t* magneticFieldZ)
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)
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;
@ -255,7 +270,10 @@ static void simulated_get_pitch_roll_accel(int32_t* pitch, int32_t* roll, int32_
*accelerationZ = 55;
}
static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
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;
@ -280,14 +298,17 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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.");
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. ");
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. ");
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.");
@ -297,27 +318,38 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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.");
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.");
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_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.");
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_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.");
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_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.");
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.");
@ -329,20 +361,29 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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_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.");
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_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.");
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_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.");
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.");
@ -357,11 +398,15 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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)
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;
@ -373,23 +418,28 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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));
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_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. ");
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_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. ");
@ -398,25 +448,40 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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_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. ");
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_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_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_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.");
@ -434,21 +499,26 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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)
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.");
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.");
@ -457,19 +527,21 @@ static int generate_properties_update_response(
// 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));
&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.");
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.");
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.
@ -483,8 +555,11 @@ static int generate_properties_update_response(
}
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)
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;
@ -494,22 +569,27 @@ static int consume_properties_and_generate_response(
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;
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);
&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.");
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)))
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)))
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);
@ -521,23 +601,28 @@ static int consume_properties_and_generate_response(
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.");
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));
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.");
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).");
EXIT_IF_AZ_FAILED(
azrc, RESULT_ERROR, "Failed moving to next json token of writable properties (again).");
}
return RESULT_OK;

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

@ -5,7 +5,7 @@
* Azure_IoT_PnP_Template.cpp implements the IoT Plug and Play template
* specific for the Espressif ESP32 Azure IoT Kit board.
*/
#ifndef AZURE_IOT_PNP_TEMPLATE_H
#define AZURE_IOT_PNP_TEMPLATE_H
@ -20,7 +20,7 @@ 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
* @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.
*/
@ -28,19 +28,22 @@ 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 Espressif ESP32 Azure IoT Kit and sends it 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 Espressif ESP32 Azure IoT Kit 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] 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.
* `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.
* @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`,
@ -52,32 +55,33 @@ int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id);
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.
* @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
* Espressif ESP32 Azure IoT Kit 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
* 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
*
* @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
* @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
* @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
* @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.
@ -86,19 +90,22 @@ int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t c
/*
* @brief Handles a payload with writable properties received from Azure IoT Central.
* @remark This function will consume the writable properties update received
* @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
*
* @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.
* 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);
int azure_pnp_handle_properties_update(
azure_iot_t* azure_iot,
az_span properties,
uint32_t request_id);
#endif // AZURE_IOT_PNP_TEMPLATE_H

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

@ -2,21 +2,71 @@
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Enable macro IOT_CONFIG_USE_X509_CERT to use an x509 certificate to authenticate the IoT device.
// The two main modes of authentication are through SAS tokens (automatically generated by the
// sample using the provided device symmetric key) or through x509 certificates. Please choose the
// appropriate option according to your device authentication mode.
// #define IOT_CONFIG_USE_X509_CERT
#ifdef IOT_CONFIG_USE_X509_CERT
/*
* Please set the define IOT_CONFIG_DEVICE_CERT below with
* the content of your device x509 certificate.
*
* Example:
* #define IOT_CONFIG_DEVICE_CERT "-----BEGIN CERTIFICATE-----\r\n" \
* "MIIBJDCBywIUfeHrebBVa2eZAbouBgACp9R3BncwCgYIKoZIzj0EAwIwETEPMA0G\r\n" \
* "A1UEAwwGRFBTIENBMB4XDTIyMDMyMjazMTAzN1oXDTIzMDMyMjIzMTAzN1owGTEX\r\n" \
* "MBUGA1UEAwwOY29udG9zby1kZXZpY2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\r\n" \
* .......
* "YmGzdaHTb6P1W+p+jmc+jJn1MAoGCXqGSM49BAMCA0gAMEUCIEnbEMsAdGFroMwl\r\n" \
* "vTfQahwsxN3xink9z1gtirrjQlqDAiEAyU+6TUJcG6d9JF+uJqsLFpsbbF3IzGAw\r\n" \
* "yC+koNRC0MU=\r\n" \
* "-----END CERTIFICATE-----"
*
*/
#define IOT_CONFIG_DEVICE_CERT "Device Certificate"
/*
* Please set the define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY below with
* the content of your device x509 private key.
*
* Example:
*
* #define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "-----BEGIN EC PRIVATE KEY-----\r\n" \
* "MHcCAQEEIKGXkMMiO9D7jYpUjUGTBn7gGzeKPeNzCP83wbfQfLd9obAoGCCqGSM49\r\n" \
* "AwEHoUQDQgAEU6nQoYbjgJvBwaeD6MyAYmOSDg0QhEdyyV337qrlIbDEKvFsn1El\r\n" \
* "yRabc4dNp2Jhs3Xh02+j9Vvqfo5nPoyZ9Q==\r\n" \
* "-----END EC PRIVATE KEY-----"
*
* Note the type of key may different in your case. Such as BEGIN PRIVATE KEY
* or BEGIN RSA PRIVATE KEY.
*
*/
#define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "Device Certificate Private Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Azure IoT Central
#define DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#define DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
// Use device key if not using certificates
#ifndef IOT_CONFIG_USE_X509_CERT
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#endif // IOT_CONFIG_USE_X509_CERT
// User-agent (url-encoded) provided by the MQTT client to Azure IoT Services.
// When developing for your own Arduino-based platform,
// please update the suffix with the format '(ard;<platform>)' as an url-encoded string.
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard%3Besp32)"
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard%3Besp32)"
// Publish 1 message every 2 seconds.
#define TELEMETRY_FREQUENCY_IN_SECONDS 2
#define TELEMETRY_FREQUENCY_IN_SECONDS 2
// For how long the MQTT password (SAS token) is valid, in minutes.
// After that, the sample automatically generates a new password and re-connects.

Двоичные данные
examples/Azure_IoT_Central_ESP32/media/iot-central.png Normal file

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

После

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

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

@ -21,6 +21,8 @@ You will complete the following tasks:
* Build the image and flash it onto the ESP32
* Use Azure IoT Central to create cloud components, view properties, view device telemetry, and call direct commands
_The following was run on Windows 11, with Arduino IDE 2.1.0 and ESP32 board library version 2.0.9._
## Prerequisites
* Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
@ -29,6 +31,7 @@ You will complete the following tasks:
- ESP32 boards are not natively supported by Arduino IDE, so you need to add them manually.
- Follow the [instructions](https://github.com/espressif/arduino-esp32) in the official ESP32 repository.
- If your ESP32 board is not recognized and a COM port is not mapped by your computer, try installing the [serial port drivers](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html#connect-esp32-to-pc) recommended by ESPRESSIF.
* Hardware
@ -48,9 +51,9 @@ To create a new application:
1. Select **+ New application**.
1. Select **Custom apps**.
1. Add Application Name and a URL.
1. Choose the **Free** Pricing plan to activate a 7-day trial.
1. Choose the pricing plan of your preference.
![IoT Central create an application](media/iotcentralcreate-custom.png)
![IoT Central create an application](media/iot-central.png)
1. Select **Create**.
1. After IoT Central provisions the application, it redirects you automatically to the new application dashboard.
@ -76,11 +79,18 @@ To create a device:
![IoT Central create a device](media/iotcentraldevice-connection-info.png)
The information provided above will create a device with **Symmetric Key** authentication. If you prefer, you can also use **X.509 certificates**. In order to do that:
- In the `Authentication type` box, select `individual enrollment`.
- In the `Authentication method` box, select `Certificates (X.509)`.
- Click on the blue folder icon for both primary and secondary and select your device Certificate.
> If needed, instructions on how to create an X.509 cert for testing can be found [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#configure-and-run-the-samples) (Step 1). Please note that you might need to install some of the [prerequisites](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#prerequisites) like OpenSSL.
1. Note the connection values for the following connection string parameters displayed in **Connect** dialog. You'll use these values during the following configuration step:
> * `ID scope`
> * `Device ID`
> * `Primary key`
> * `Primary key` (only if you choose Symmetric Key authentication)
## Setup and Run Instructions
@ -99,7 +109,17 @@ To create a device:
4. Configure the sample.
Enter your Azure IoT Central and device information into the sample's `iot_configs.h`.
Enter your Azure IoT Hub and device information into the sample's `iot_configs.h`:
- Add your Wi-Fi SSID to `IOT_CONFIG_WIFI_SSID`
- Add your Wi-Fi password to `IOT_CONFIG_WIFI_PASSWORD`
- Add you Device Provisioning Scope ID to `DPS_ID_SCOPE`
- Add your Device ID to `IOT_CONFIG_DEVICE_ID`
- If using **X.509 Cert**:
- Uncomment the `#define IOT_CONFIG_USE_X509_CERT`
- Add your cert to `IOT_CONFIG_USE_X509_CERT`
- Add your cert private key to `IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY`
- If using **Symmetric Key**:
- Add your device key to `IOT_CONFIG_DEVICE_KEY`
5. Connect the ESP32 Azure IoT Kit microcontroller to your USB port.

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

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

@ -15,8 +15,8 @@
#ifndef AZURE_IOT_H
#define AZURE_IOT_H
#include <stdlib.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <az_core.h>
@ -38,8 +38,7 @@ typedef enum log_level_t_enum
{
log_level_info,
log_level_error
}
log_level_t;
} log_level_t;
typedef void (*log_function_t)(log_level_t log_level, char const* const format, ...);
@ -48,45 +47,43 @@ 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__ )
#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 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 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
#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.
* 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_MOST_ONCE 0
#define MQTT_QOS_AT_LEAST_ONCE 1
#define MQTT_QOS_EXACTLY_ONCE 2
#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;
} mqtt_qos_t;
/*
* @brief Defines a generic MQTT message to be exchanged between the AzureIoT layer
* and the user application.
* 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.
@ -98,8 +95,7 @@ typedef struct mqtt_message_t_struct
az_span topic;
az_span payload;
mqtt_qos_t qos;
}
mqtt_message_t;
} mqtt_message_t;
/*
* @brief Configuration structure passed by `mqtt_client_init_function_t` to the user
@ -131,8 +127,7 @@ typedef struct mqtt_client_config_t_struct
* @brief Password to be provided in the CONNECT sent by the MQTT client.
*/
az_span password;
}
mqtt_client_config_t;
} mqtt_client_config_t;
/*
* @brief Generic pointer to the actual instance of the application MQTT client.
@ -142,70 +137,79 @@ 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.
* @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.
*
* @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);
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).
* @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.
*
* @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.
* 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.
* @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.
*
* @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.
* - 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);
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.
* @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] 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.
*
* @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);
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.
@ -216,53 +220,69 @@ typedef struct mqtt_client_interface_t_struct
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;
} 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
* @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);
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
* @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);
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 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);
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.
@ -272,14 +292,15 @@ 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;
} 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).
*
* @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.
*/
@ -287,9 +308,11 @@ typedef void (*properties_update_completed_t)(uint32_t request_id, az_iot_status
/*
* @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).
*
* @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.
*/
@ -320,14 +343,13 @@ typedef struct command_request_t_struct
* @brief Optional payload sent by the caller for this command.
*/
az_span payload;
}
command_request_t;
} 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
* @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.
*
@ -347,7 +369,7 @@ typedef enum azure_iot_status_t_struct
/*
* @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
* 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,
@ -358,19 +380,19 @@ typedef enum azure_iot_status_t_struct
/*
* @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`);
* - 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
* 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;
} azure_iot_status_t;
/*
* @brief Internal states of the Azure IoT client.
@ -398,8 +420,7 @@ typedef enum azure_iot_client_state_t_struct
azure_iot_state_ready,
azure_iot_state_refreshing_sas,
azure_iot_state_error
}
azure_iot_client_state_t;
} azure_iot_client_state_t;
/*
* @brief Structure that holds the configuration for the Azure IoT client.
@ -408,7 +429,7 @@ azure_iot_client_state_t;
* 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
* 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
@ -454,6 +475,18 @@ typedef struct azure_iot_config_t_struct
*/
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.
@ -486,29 +519,29 @@ typedef struct azure_iot_config_t_struct
* @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:
* 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.
*
* <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"
@ -516,14 +549,16 @@ typedef struct azure_iot_config_t_struct
* <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)
* <user-agent> = "c%2F1.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.
* @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;
@ -564,8 +599,7 @@ typedef struct azure_iot_config_t_struct
* `azure_iot_send_command_response`.
*/
command_request_received_t on_command_request_received;
}
azure_iot_config_t;
} azure_iot_config_t;
/*
* @brief Structure that holds the state of the Azure IoT client.
@ -587,15 +621,15 @@ typedef struct azure_iot_t_struct
uint32_t dps_retry_after_seconds;
uint32_t dps_last_query_time;
az_span dps_operation_id;
}
azure_iot_t;
} 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 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.
@ -613,11 +647,12 @@ void azure_iot_init(azure_iot_t* azure_iot, azure_iot_config_t* azure_iot_config
* 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
* 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 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.
@ -630,8 +665,9 @@ int azure_iot_start(azure_iot_t* azure_iot);
* @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.
*
* @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.
*/
@ -641,23 +677,26 @@ 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.
* @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.
* @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.
*/
@ -665,9 +704,11 @@ 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.
*
* @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.
*/
@ -676,14 +717,15 @@ 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] 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.
* `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.
*/
@ -692,27 +734,33 @@ int azure_iot_send_properties_update(azure_iot_t* azure_iot, uint32_t request_id
/**
* @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] 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);
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.
* (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.
*/
@ -720,11 +768,12 @@ 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.
* @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.
*/
@ -732,11 +781,12 @@ 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.
* @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.
@ -745,13 +795,14 @@ int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet
/*
* @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.
* @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.
@ -759,51 +810,48 @@ int azure_iot_mqtt_client_subscribe_completed(azure_iot_t* azure_iot, int packet
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.
* @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).
*
* @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 Checks whether `span` is equal to AZ_SPAN_EMPTY.
*
* @param[in] span A span to be verified.
*
* @return boolean True if `span`'s pointer and size are equal to AZ_SPAN_EMPTY, or false otherwise.
*/
#define is_az_span_empty(span) az_span_is_content_equal(span, AZ_SPAN_EMPTY)
/**
* @brief Slices `span` at position `size`, returns the first slice and assigns the second slice to `remainder`.
* @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.
* @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`.
* @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.
* @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.
* @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);

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

@ -6,38 +6,39 @@
* This sample depends on the following Arduino library:
* "Espressif ESP32 Azure IoT Kit Sensors" (by Ewerton Scaboro da Silva).
* Please install it before proceeding.
*
*
* This is an Arduino-based Azure IoT Central sample specific for the Espressif ESP32 Azure IoT Kit board.
* 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/.
*
*
*
* This is an Arduino-based Azure IoT Central sample specific for the Espressif ESP32 Azure IoT Kit
* board. 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
* 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 Espressif ESP32 Azure IoT Kit board.
*
* To properly connect to your Azure IoT services, please fill the information in the `iot_configs.h` file.
*
* 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 <cstdlib>
#include <string.h>
#include <time.h>
@ -55,20 +56,20 @@
#include <az_iot.h>
#include <azure_ca.h>
// Additional sample headers
// Additional sample headers
#include "AzureIoT.h"
#include "Azure_IoT_PnP_Template.h"
#include "iot_configs.h"
/* --- Sample-specific Settings --- */
#define SERIAL_LOGGER_BAUD_RATE 115200
#define MQTT_DO_NOT_RETAIN_MSG 0
#define MQTT_DO_NOT_RETAIN_MSG 0
/* --- Time and NTP Settings --- */
#define NTP_SERVERS "pool.ntp.org", "time.nist.gov"
#define PST_TIME_ZONE -8
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define GMT_OFFSET_SECS (PST_TIME_ZONE * 3600)
#define GMT_OFFSET_SECS_DST ((PST_TIME_ZONE + PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * 3600)
@ -77,16 +78,13 @@
#define UNIX_EPOCH_START_YEAR 1900
/* --- Function Returns --- */
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
#define RESULT_OK 0
#define RESULT_ERROR __LINE__
/* --- Handling iot_config.h Settings --- */
static const char* wifi_ssid = IOT_CONFIG_WIFI_SSID;
static const char* wifi_password = IOT_CONFIG_WIFI_PASSWORD;
// TODO: remove this after updating library with Azure SDK for C version 1.3.0-beta.2 or later.
#define AZURE_SDK_CLIENT_USER_AGENT_WORKAROUND "DeviceClientType=" AZURE_SDK_CLIENT_USER_AGENT
/* --- Function Declarations --- */
static void sync_device_clock_with_ntp_server();
static void connect_to_wifi();
@ -110,21 +108,23 @@ static uint8_t az_iot_data_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, Espressif's ESP MQTT). Please see the documentation in AzureIoT.h for more details.
* (in this case, Espressif's ESP MQTT). 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)
static int mqtt_client_init_function(
mqtt_client_config_t* mqtt_client_config,
mqtt_client_handle_t* mqtt_client_handle)
{
int result;
esp_mqtt_client_config_t mqtt_config;
memset(&mqtt_config, 0, sizeof(mqtt_config));
memset(&mqtt_config, 0, sizeof(mqtt_config));
az_span mqtt_broker_uri_span = AZ_SPAN_FROM_BUFFER(mqtt_broker_uri);
mqtt_broker_uri_span = az_span_copy(mqtt_broker_uri_span, AZ_SPAN_FROM_STR(MQTT_PROTOCOL_PREFIX));
@ -135,7 +135,15 @@ static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, m
mqtt_config.port = mqtt_client_config->port;
mqtt_config.client_id = (const char*)az_span_ptr(mqtt_client_config->client_id);
mqtt_config.username = (const char*)az_span_ptr(mqtt_client_config->username);
#ifdef IOT_CONFIG_USE_X509_CERT
LogInfo("MQTT client using X509 Certificate authentication");
mqtt_config.client_cert_pem = IOT_CONFIG_DEVICE_CERT;
mqtt_config.client_key_pem = IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY;
#else // Using SAS key
mqtt_config.password = (const char*)az_span_ptr(mqtt_client_config->password);
#endif
mqtt_config.keepalive = 30;
mqtt_config.disable_clean_session = 0;
mqtt_config.disable_auto_reconnect = false;
@ -154,7 +162,7 @@ static int mqtt_client_init_function(mqtt_client_config_t* mqtt_client_config, m
else
{
esp_err_t start_result = esp_mqtt_client_start(mqtt_client);
if (start_result != ESP_OK)
{
LogError("esp_mqtt_client_start failed (error code: 0x%08x).", start_result);
@ -192,7 +200,7 @@ static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
if (azure_iot_mqtt_client_disconnected(&azure_iot) != 0)
{
LogError("Failed updating azure iot client of MQTT disconnection.");
LogError("Failed updating azure iot client of MQTT disconnection.");
}
return 0;
@ -201,13 +209,18 @@ static int mqtt_client_deinit_function(mqtt_client_handle_t mqtt_client_handle)
/*
* 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)
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));
// As per documentation, `topic` always ends with a null-terminator.
// esp_mqtt_client_subscribe returns the packet id or negative on error already, so no conversion is needed.
int packet_id = esp_mqtt_client_subscribe((esp_mqtt_client_handle_t)mqtt_client_handle, (const char*)az_span_ptr(topic), (int)qos);
// esp_mqtt_client_subscribe returns the packet id or negative on error already, so no conversion
// is needed.
int packet_id = esp_mqtt_client_subscribe(
(esp_mqtt_client_handle_t)mqtt_client_handle, (const char*)az_span_ptr(topic), (int)qos);
return packet_id;
}
@ -215,18 +228,20 @@ static int mqtt_client_subscribe_function(mqtt_client_handle_t mqtt_client_handl
/*
* 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)
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 mqtt_result = esp_mqtt_client_publish(
(esp_mqtt_client_handle_t)mqtt_client_handle,
(const char*)az_span_ptr(mqtt_message->topic), // topic is always null-terminated.
(const char*)az_span_ptr(mqtt_message->payload),
az_span_size(mqtt_message->payload),
(int)mqtt_message->qos,
MQTT_DO_NOT_RETAIN_MSG);
(esp_mqtt_client_handle_t)mqtt_client_handle,
(const char*)az_span_ptr(mqtt_message->topic), // topic is always null-terminated.
(const char*)az_span_ptr(mqtt_message->payload),
az_span_size(mqtt_message->payload),
(int)mqtt_message->qos,
MQTT_DO_NOT_RETAIN_MSG);
if (mqtt_result == -1)
{
return RESULT_ERROR;
@ -242,7 +257,13 @@ static int mqtt_client_publish_function(mqtt_client_handle_t mqtt_client_handle,
/*
* See the documentation of `hmac_sha256_encryption_function_t` in AzureIoT.h for details.
*/
static int mbedtls_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)
static int mbedtls_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;
mbedtls_md_context_t ctx;
@ -261,7 +282,12 @@ static int mbedtls_hmac_sha256(const uint8_t* key, size_t key_length, const uint
/*
* 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)
static int base64_decode(
uint8_t* data,
size_t data_length,
uint8_t* decoded,
size_t decoded_size,
size_t* decoded_length)
{
return mbedtls_base64_decode(decoded, decoded_size, decoded_length, data, data_length);
}
@ -269,7 +295,12 @@ static int base64_decode(uint8_t* data, size_t data_length, uint8_t* decoded, si
/*
* 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)
static int base64_encode(
uint8_t* data,
size_t data_length,
uint8_t* encoded,
size_t encoded_size,
size_t* encoded_length)
{
return mbedtls_base64_encode(encoded, encoded_size, encoded_length, data, data_length);
}
@ -301,17 +332,22 @@ void on_properties_received(az_span properties)
* 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));
{
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.
// 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);
}
@ -326,19 +362,31 @@ void setup()
azure_pnp_init();
/*
* The configuration structure used by Azure IoT must remain unchanged (including data buffer)
/*
* 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(AZURE_SDK_CLIENT_USER_AGENT_WORKAROUND);
azure_iot_config.user_agent = AZ_SPAN_FROM_STR(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;
#ifdef IOT_CONFIG_USE_X509_CERT
azure_iot_config.device_certificate = AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_CERT);
azure_iot_config.device_certificate_private_key
= AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY);
azure_iot_config.device_key = AZ_SPAN_EMPTY;
#else
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);
#endif // IOT_CONFIG_USE_X509_CERT
azure_iot_config.dps_id_scope = AZ_SPAN_FROM_STR(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.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 = MQTT_PASSWORD_LIFETIME_IN_MINUTES;
azure_iot_config.mqtt_client_interface.mqtt_client_init = mqtt_client_init_function;
@ -362,29 +410,31 @@ void loop()
{
if (WiFi.status() != WL_CONNECTED)
{
azure_iot_stop(&azure_iot);
connect_to_wifi();
azure_iot_start(&azure_iot);
}
else
{
switch(azure_iot_get_status(&azure_iot))
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.
send_device_info = false; // Only need to send once.
}
else if (azure_pnp_send_telemetry(&azure_iot) != 0)
{
LogError("Failed sending telemetry.");
LogError("Failed sending telemetry.");
}
break;
case azure_iot_error:
LogError("Azure IoT client is in error state." );
LogError("Azure IoT client is in error state.");
azure_iot_stop(&azure_iot);
WiFi.disconnect();
break;
case azure_iot_disconnected:
azure_iot_start(&azure_iot);
break;
default:
break;
@ -394,12 +444,11 @@ void loop()
}
}
/* === 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, ESP MQTT client event handler
* of connecting to the internet, syncing the board clock, ESP MQTT client event handler
* and logging.
*/
@ -425,6 +474,8 @@ static void connect_to_wifi()
LogInfo("Connecting to WIFI wifi_ssid %s", wifi_ssid);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED)
{
@ -445,37 +496,39 @@ static esp_err_t esp_mqtt_event_handler(esp_mqtt_event_handle_t event)
case MQTT_EVENT_ERROR:
LogError("MQTT client in ERROR state.");
LogError(
"esp_tls_stack_err=%d; esp_tls_cert_verify_flags=%d;esp_transport_sock_errno=%d;error_type=%d;connect_return_code=%d",
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_tls_cert_verify_flags,
event->error_handle->esp_transport_sock_errno,
event->error_handle->error_type,
event->error_handle->connect_return_code);
LogError(
"esp_tls_stack_err=%d; "
"esp_tls_cert_verify_flags=%d;esp_transport_sock_errno=%d;error_type=%d;connect_return_"
"code=%d",
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_tls_cert_verify_flags,
event->error_handle->esp_transport_sock_errno,
event->error_handle->error_type,
event->error_handle->connect_return_code);
switch (event->error_handle->connect_return_code)
switch (event->error_handle->connect_return_code)
{
case MQTT_CONNECTION_ACCEPTED:
LogError("connect_return_code=MQTT_CONNECTION_ACCEPTED");
break;
case MQTT_CONNECTION_REFUSE_PROTOCOL:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_PROTOCOL");
break;
case MQTT_CONNECTION_REFUSE_ID_REJECTED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_ID_REJECTED");
break;
case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE");
break;
case MQTT_CONNECTION_REFUSE_BAD_USERNAME:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_BAD_USERNAME");
break;
case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED");
break;
default:
LogError("connect_return_code=unknown (%d)", event->error_handle->connect_return_code);
break;
case MQTT_CONNECTION_ACCEPTED:
LogError("connect_return_code=MQTT_CONNECTION_ACCEPTED");
break;
case MQTT_CONNECTION_REFUSE_PROTOCOL:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_PROTOCOL");
break;
case MQTT_CONNECTION_REFUSE_ID_REJECTED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_ID_REJECTED");
break;
case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE");
break;
case MQTT_CONNECTION_REFUSE_BAD_USERNAME:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_BAD_USERNAME");
break;
case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED:
LogError("connect_return_code=MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED");
break;
default:
LogError("connect_return_code=unknown (%d)", event->error_handle->connect_return_code);
break;
};
break;
@ -524,11 +577,15 @@ static esp_err_t esp_mqtt_event_handler(esp_mqtt_event_handle_t event)
mqtt_message_t mqtt_message;
mqtt_message.topic = az_span_create((uint8_t*)event->topic, event->topic_len);
mqtt_message.payload = az_span_create((uint8_t*)event->data, event->data_len);
mqtt_message.qos = mqtt_qos_at_most_once; // QoS is unused by azure_iot_mqtt_client_message_received.
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).", event->topic_len, event->topic);
LogError(
"azure_iot_mqtt_client_message_received failed (topic=%.*s).",
event->topic_len,
event->topic);
}
break;

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

@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <stdlib.h>
#include <stdarg.h>
#include <stdlib.h>
#include <az_core.h>
#include <az_iot.h>
@ -16,67 +16,67 @@
/* --- 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_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 "ESPRESSIF"
#define SAMPLE_MODEL_PROPERTY_VALUE "ESP32 Azure IoT Kit"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "FreeRTOS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "ESP32 WROVER-B"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
#define SAMPLE_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
#define SAMPLE_MODEL_PROPERTY_VALUE "ESP32 Azure IoT Kit"
#define SAMPLE_VERSION_PROPERTY_VALUE "1.0.0"
#define SAMPLE_OS_NAME_PROPERTY_VALUE "FreeRTOS"
#define SAMPLE_ARCHITECTURE_PROPERTY_VALUE "ESP32 WROVER-B"
#define SAMPLE_PROCESSOR_MANUFACTURER_PROPERTY_VALUE "ESPRESSIF"
// The next couple properties are in KiloBytes.
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 4096
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 8192
#define SAMPLE_TOTAL_STORAGE_PROPERTY_VALUE 4096
#define SAMPLE_TOTAL_MEMORY_PROPERTY_VALUE 8192
#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"
#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 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 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 RESULT_OK 0
#define RESULT_ERROR __LINE__
#define EXIT_IF_TRUE(condition, retcode, message, ...) \
do \
{ \
if (condition) \
{ \
LogError(message, ##__VA_ARGS__ ); \
return retcode; \
} \
#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__ )
#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
@ -86,7 +86,7 @@ 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;
#define OLED_SPLASH_MESSAGE "Espressif ESP32 Azure IoT Kit + Central"
#define OLED_SPLASH_MESSAGE "Espressif ESP32 Azure IoT Kit + Central"
static bool led1_on = false;
static bool led2_on = false;
@ -94,13 +94,20 @@ 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);
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);
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);
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()
@ -114,10 +121,7 @@ void azure_pnp_init()
esp32_azureiotkit_led2_set_state(LED_STATE_OFF);
}
const az_span azure_pnp_get_model_id()
{
return AZ_SPAN_FROM_STR(AZURE_PNP_MODEL_ID);
}
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)
{
@ -138,8 +142,9 @@ int azure_pnp_send_telemetry(azure_iot_t* azure_iot)
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)
else if (
last_telemetry_send_time == INDEFINITE_TIME
|| difftime(now, last_telemetry_send_time) >= telemetry_frequency_in_seconds)
{
size_t payload_size;
@ -166,12 +171,14 @@ 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);
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));
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;
@ -197,20 +204,29 @@ int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t c
}
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.
esp32_azureiotkit_oled_show_message(az_span_ptr(command.payload) + 1, az_span_size(command.payload) - 2);
// The payload comes surrounded by quotes, so to remove them we offset the payload by 1 and its
// size by 2.
esp32_azureiotkit_oled_show_message(
az_span_ptr(command.payload) + 1, az_span_size(command.payload) - 2);
response_code = COMMAND_RESPONSE_CODE_ACCEPTED;
}
else
{
LogError("Command not recognized (%.*s).", az_span_size(command.command_name), az_span_ptr(command.command_name));
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);
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)
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);
@ -218,10 +234,13 @@ int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span propertie
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 consuming/generating properties ack payload.");
result = consume_properties_and_generate_response(
azure_iot, properties, data_buffer, DATA_BUFFER_SIZE, &length);
EXIT_IF_TRUE(
result != RESULT_OK, RESULT_ERROR, "Failed consuming/generating properties ack payload.");
result = azure_iot_send_properties_update(azure_iot, request_id, az_span_create(data_buffer, length));
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;
@ -229,7 +248,10 @@ int azure_pnp_handle_properties_update(azure_iot_t* azure_iot, az_span propertie
/* --- Internal Functions --- */
static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_buffer_size, size_t* payload_buffer_length)
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;
@ -245,7 +267,8 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
light = esp32_azureiotkit_get_ambientLight();
esp32_azureiotkit_get_pressure_altitude(&pressure, &altitude);
esp32_azureiotkit_get_magnetometer(&magneticFieldX, &magneticFieldY, &magneticFieldZ);
esp32_azureiotkit_get_pitch_roll_accel(&pitch, &roll, &accelerationX, &accelerationY, &accelerationZ);
esp32_azureiotkit_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.");
@ -254,14 +277,17 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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.");
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. ");
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. ");
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.");
@ -271,27 +297,38 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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.");
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.");
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_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.");
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_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.");
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_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.");
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.");
@ -303,20 +340,29 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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_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.");
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_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.");
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_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.");
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.");
@ -331,11 +377,15 @@ static int generate_telemetry_payload(uint8_t* payload_buffer, size_t payload_bu
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)
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;
@ -347,23 +397,28 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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));
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_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. ");
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_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. ");
@ -372,25 +427,40 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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_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. ");
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_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_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_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.");
@ -408,21 +478,26 @@ static int generate_device_info_payload(az_iot_hub_client const* hub_client, uin
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)
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.");
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.");
@ -431,19 +506,21 @@ static int generate_properties_update_response(
// 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));
&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.");
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.");
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.
@ -457,8 +534,11 @@ static int generate_properties_update_response(
}
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)
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;
@ -468,22 +548,27 @@ static int consume_properties_and_generate_response(
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;
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);
&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.");
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)))
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)))
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);
@ -495,24 +580,29 @@ static int consume_properties_and_generate_response(
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.");
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));
return RESULT_ERROR;
LogError(
"Unexpected property received (%.*s).",
az_span_size(jr.token.slice),
az_span_ptr(jr.token.slice));
return RESULT_ERROR;
}
azrc = az_json_reader_next_token(&jr);
EXIT_IF_AZ_FAILED(azrc, RESULT_ERROR, "Failed moving to next json token of writable properties.");
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).");
EXIT_IF_AZ_FAILED(
azrc, RESULT_ERROR, "Failed moving to next json token of writable properties (again).");
}
return RESULT_OK;

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

@ -5,7 +5,7 @@
* Azure_IoT_PnP_Template.cpp implements the IoT Plug and Play template
* specific for the Espressif ESP32 Azure IoT Kit board.
*/
#ifndef AZURE_IOT_PNP_TEMPLATE_H
#define AZURE_IOT_PNP_TEMPLATE_H
@ -20,7 +20,7 @@ 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
* @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.
*/
@ -28,19 +28,22 @@ 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 Espressif ESP32 Azure IoT Kit and sends it 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 Espressif ESP32 Azure IoT Kit 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] 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.
* `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.
* @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`,
@ -52,32 +55,33 @@ int azure_pnp_send_device_info(azure_iot_t* azure_iot, uint32_t request_id);
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.
* @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
* Espressif ESP32 Azure IoT Kit 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
* 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
*
* @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
* @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
* @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
* @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.
@ -86,19 +90,22 @@ int azure_pnp_handle_command_request(azure_iot_t* azure_iot, command_request_t c
/*
* @brief Handles a payload with writable properties received from Azure IoT Central.
* @remark This function will consume the writable properties update received
* @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
*
* @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.
* 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);
int azure_pnp_handle_properties_update(
azure_iot_t* azure_iot,
az_span properties,
uint32_t request_id);
#endif // AZURE_IOT_PNP_TEMPLATE_H

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

@ -2,21 +2,71 @@
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Enable macro IOT_CONFIG_USE_X509_CERT to use an x509 certificate to authenticate the IoT device.
// The two main modes of authentication are through SAS tokens (automatically generated by the
// sample using the provided device symmetric key) or through x509 certificates. Please choose the
// appropriate option according to your device authentication mode.
// #define IOT_CONFIG_USE_X509_CERT
#ifdef IOT_CONFIG_USE_X509_CERT
/*
* Please set the define IOT_CONFIG_DEVICE_CERT below with
* the content of your device x509 certificate.
*
* Example:
* #define IOT_CONFIG_DEVICE_CERT "-----BEGIN CERTIFICATE-----\r\n" \
* "MIIBJDCBywIUfeHrebBVa2eZAbouBgACp9R3BncwCgYIKoZIzj0EAwIwETEPMA0G\r\n" \
* "A1UEAwwGRFBTIENBMB4XDTIyMDMyMjazMTAzN1oXDTIzMDMyMjIzMTAzN1owGTEX\r\n" \
* "MBUGA1UEAwwOY29udG9zby1kZXZpY2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\r\n" \
* .......
* "YmGzdaHTb6P1W+p+jmc+jJn1MAoGCXqGSM49BAMCA0gAMEUCIEnbEMsAdGFroMwl\r\n" \
* "vTfQahwsxN3xink9z1gtirrjQlqDAiEAyU+6TUJcG6d9JF+uJqsLFpsbbF3IzGAw\r\n" \
* "yC+koNRC0MU=\r\n" \
* "-----END CERTIFICATE-----"
*
*/
#define IOT_CONFIG_DEVICE_CERT "Device Certificate"
/*
* Please set the define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY below with
* the content of your device x509 private key.
*
* Example:
*
* #define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "-----BEGIN EC PRIVATE KEY-----\r\n" \
* "MHcCAQEEIKGXkMMiO9D7jYpUjUGTBn7gGzeKPeNzCP83wbfQfLd9obAoGCCqGSM49\r\n" \
* "AwEHoUQDQgAEU6nQoYbjgJvBwaeD6MyAYmOSDg0QhEdyyV337qrlIbDEKvFsn1El\r\n" \
* "yRabc4dNp2Jhs3Xh02+j9Vvqfo5nPoyZ9Q==\r\n" \
* "-----END EC PRIVATE KEY-----"
*
* Note the type of key may different in your case. Such as BEGIN PRIVATE KEY
* or BEGIN RSA PRIVATE KEY.
*
*/
#define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "Device Certificate Private Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Azure IoT Central
#define DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#define DPS_ID_SCOPE "ID Scope"
#define IOT_CONFIG_DEVICE_ID "Device ID"
// Use device key if not using certificates
#ifndef IOT_CONFIG_USE_X509_CERT
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#endif // IOT_CONFIG_USE_X509_CERT
// User-agent (url-encoded) provided by the MQTT client to Azure IoT Services.
// When developing for your own Arduino-based platform,
// please update the suffix with the format '(ard;<platform>)' as an url-encoded string.
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard%3Besp32)"
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard%3Besp32)"
// Publish 1 message every 2 seconds.
#define TELEMETRY_FREQUENCY_IN_SECONDS 2
#define TELEMETRY_FREQUENCY_IN_SECONDS 2
// For how long the MQTT password (SAS token) is valid, in minutes.
// After that, the sample automatically generates a new password and re-connects.

Двоичные данные
examples/Azure_IoT_Central_ESP32_AzureIoTKit/media/iot-central.png Normal file

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

После

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

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

@ -21,6 +21,8 @@ You will complete the following tasks:
* Build the image and flash it onto the ESP32 DevKit
* Use Azure IoT Central to create cloud components, view properties, view device telemetry, and call direct commands
_The following was run on Windows 11, with Arduino IDE 2.1.0 and ESP32 board library version 2.0.9._
## Prerequisites
* Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
@ -29,6 +31,7 @@ You will complete the following tasks:
- ESP32 boards are not natively supported by Arduino IDE, so you need to add them manually.
- Follow the [instructions](https://github.com/espressif/arduino-esp32) in the official ESP32 repository.
- If your ESP32 board is not recognized and a COM port is not mapped by your computer, try installing the [serial port drivers](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html#connect-esp32-to-pc) recommended by ESPRESSIF.
* Hardware
@ -48,9 +51,9 @@ To create a new application:
1. Select **+ New application**.
1. Select **Custom apps**.
1. Add Application Name and a URL.
1. Choose the **Free** Pricing plan to activate a 7-day trial.
1. Choose the pricing plan of your preference.
![IoT Central create an application](media/iotcentralcreate-custom.png)
![IoT Central create an application](media/iot-central.png)
1. Select **Create**.
1. After IoT Central provisions the application, it redirects you automatically to the new application dashboard.
@ -76,12 +79,18 @@ To create a device:
![IoT Central create a device](media/iotcentraldevice-connection-info.png)
The information provided above will create a device with **Symmetric Key** authentication. If you prefer, you can also use **X.509 certificates**. In order to do that:
- In the `Authentication type` box, select `individual enrollment`
- In the `Authentication method` box, select `Certificates (X.509)`
- Click on the blue folder icon for both primary and secondary and select your device Certificate.
> If needed, instructions on how to create an X.509 cert for tests can be found [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#configure-and-run-the-samples) (Step 1). Please note that you might need to install some of the [prerequisites](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#prerequisites) like OpenSSL.
1. Note the connection values for the following connection string parameters displayed in **Connect** dialog. You'll use these values during the following configuration step:
> * `ID scope`
> * `Device ID`
> * `Primary key`
> * `Primary key` (only if you choose Symmetric Key authentication)
## Setup and Run Instructions
@ -104,7 +113,17 @@ To create a device:
4. Configure the sample.
Enter your Azure IoT Central and device information into the sample's `iot_configs.h`.
Enter your Azure IoT Hub and device information into the sample's `iot_configs.h`:
- Add your Wi-Fi SSID to `IOT_CONFIG_WIFI_SSID`
- Add your Wi-Fi password to `IOT_CONFIG_WIFI_PASSWORD`
- Add you Device Provisioning Scope ID to `DPS_ID_SCOPE`
- Add your Device ID to `IOT_CONFIG_DEVICE_ID`
- If using **X.509 Cert**:
- Uncomment the `#define IOT_CONFIG_USE_X509_CERT`
- Add your cert to `IOT_CONFIG_USE_X509_CERT`
- Add your cert private key to `IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY`
- If using **Symmetric Key**:
- Add your device key to `IOT_CONFIG_DEVICE_KEY`
5. Connect the ESP32 Azure IoT Kit microcontroller to your USB port.

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

@ -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

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

@ -12,7 +12,8 @@
#define INDEFINITE_TIME ((time_t)-1)
#define is_az_span_empty(x) (az_span_size(x) == az_span_size(AZ_SPAN_EMPTY) && az_span_ptr(x) == az_span_ptr(AZ_SPAN_EMPTY))
#define az_span_is_content_equal(x, AZ_SPAN_EMPTY) \
(az_span_size(x) == az_span_size(AZ_SPAN_EMPTY) && az_span_ptr(x) == az_span_ptr(AZ_SPAN_EMPTY))
static uint32_t getSasTokenExpiration(const char* sasToken)
{
@ -44,8 +45,11 @@ static uint32_t getSasTokenExpiration(const char* sasToken)
}
else
{
int k = i;
while (sasToken[k] != '\0' && sasToken[k] != '&') { k++; }
int k = i;
while (sasToken[k] != '\0' && sasToken[k] != '&')
{
k++;
}
if (az_result_failed(
az_span_atou32(az_span_create((uint8_t*)sasToken + i, k - i), &se_as_unix_time)))
@ -135,7 +139,7 @@ static int iot_sample_generate_sas_base64_encoded_signed_signature(
// Decode the sas base64 encoded key to use for HMAC signing.
char sas_decoded_key_buffer[32];
az_span sas_decoded_key = AZ_SPAN_FROM_BUFFER(sas_decoded_key_buffer);
if (decode_base64_bytes(sas_base64_encoded_key, sas_decoded_key, &sas_decoded_key) != 0)
{
Logger.Error("Failed generating encoded signed signature");
@ -186,12 +190,13 @@ az_span generate_sas_token(
// Generate the encoded, signed signature (b64 encoded, HMAC-SHA256 signing).
char b64enc_hmacsha256_signature[64];
az_span sas_base64_encoded_signed_signature = AZ_SPAN_FROM_BUFFER(b64enc_hmacsha256_signature);
if (iot_sample_generate_sas_base64_encoded_signed_signature(
device_key,
sas_signature,
sas_base64_encoded_signed_signature,
&sas_base64_encoded_signed_signature) != 0)
device_key,
sas_signature,
sas_base64_encoded_signed_signature,
&sas_base64_encoded_signed_signature)
!= 0)
{
Logger.Error("Failed generating SAS token signed signature");
return AZ_SPAN_EMPTY;
@ -215,7 +220,7 @@ az_span generate_sas_token(
}
else
{
return az_span_slice(sas_token, 0, mqtt_password_length);
return az_span_slice(sas_token, 0, mqtt_password_length);
}
}
@ -241,8 +246,8 @@ int AzIoTSasToken::Generate(unsigned int expiryTimeInMinutes)
this->signatureBuffer,
expiryTimeInMinutes,
this->sasTokenBuffer);
if (is_az_span_empty(this->sasToken))
if (az_span_is_content_equal(this->sasToken, AZ_SPAN_EMPTY))
{
Logger.Error("Failed generating SAS token");
return 1;
@ -259,7 +264,7 @@ int AzIoTSasToken::Generate(unsigned int expiryTimeInMinutes)
}
else
{
return 0;
return 0;
}
}
}

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

@ -5,7 +5,7 @@
* This is an Arduino-based Azure IoT Hub sample for ESPRESSIF ESP32 boards.
* 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.
*
*
* 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
@ -14,11 +14,14 @@
* This sample performs the following tasks:
* - Synchronize the device clock with a NTP server;
* - Initialize our "az_iot_hub_client" (struct for data, part of our azure-sdk-for-c);
* - Initialize the MQTT client (here we use ESPRESSIF's esp_mqtt_client, which also handle the tcp connection and TLS);
* - Connect the MQTT client (using server-certificate validation, SAS-tokens for client authentication);
* - Initialize the MQTT client (here we use ESPRESSIF's esp_mqtt_client, which also handle the tcp
* connection and TLS);
* - Connect the MQTT client (using server-certificate validation, SAS-tokens for client
* authentication);
* - Periodically send telemetry data to the Azure IoT Hub.
*
* To properly connect to your Azure IoT Hub, please fill the information in the `iot_configs.h` file.
*
* To properly connect to your Azure IoT Hub, please fill the information in the `iot_configs.h`
* file.
*/
// C99 libraries
@ -35,14 +38,14 @@
#include <az_iot.h>
#include <azure_ca.h>
// Additional sample headers
// Additional sample headers
#include "AzIoTSasToken.h"
#include "SerialLogger.h"
#include "iot_configs.h"
// When developing for your own Arduino-based platform,
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c/" AZ_SDK_VERSION_STRING "(ard;esp32)"
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard;esp32)"
// Utility macros and defines
#define sizeofarray(a) (sizeof(a) / sizeof(a[0]))
@ -53,7 +56,7 @@
#define UNIX_TIME_NOV_13_2017 1510592825
#define PST_TIME_ZONE -8
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF 1
#define GMT_OFFSET_SECS (PST_TIME_ZONE * 3600)
#define GMT_OFFSET_SECS_DST ((PST_TIME_ZONE + PST_TIME_ZONE_DAYLIGHT_SAVINGS_DIFF) * 3600)
@ -84,18 +87,21 @@ static String myTelemetry = "{}";
static char incoming_data[INCOMING_DATA_BUFFER_SIZE];
// Auxiliary functions
#ifndef IOT_CONFIG_USE_X509_CERT
static AzIoTSasToken sasToken(
&client,
AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_KEY),
AZ_SPAN_FROM_BUFFER(sas_signature_buffer),
AZ_SPAN_FROM_BUFFER(mqtt_password));
#endif // IOT_CONFIG_USE_X509_CERT
static void connectToWiFi()
{
Logger.Info("Connecting to WIFI SSID " + String(ssid));
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
@ -155,7 +161,7 @@ static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
}
else
{
Logger.Info("Subscribed for cloud-to-device messages; message id:" + String(r));
Logger.Info("Subscribed for cloud-to-device messages; message id:" + String(r));
}
break;
@ -176,14 +182,14 @@ static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
for (i = 0; i < (INCOMING_DATA_BUFFER_SIZE - 1) && i < event->topic_len; i++)
{
incoming_data[i] = event->topic[i];
incoming_data[i] = event->topic[i];
}
incoming_data[i] = '\0';
Logger.Info("Topic: " + String(incoming_data));
for (i = 0; i < (INCOMING_DATA_BUFFER_SIZE - 1) && i < event->data_len; i++)
{
incoming_data[i] = event->data[i];
incoming_data[i] = event->data[i];
}
incoming_data[i] = '\0';
Logger.Info("Data: " + String(incoming_data));
@ -236,11 +242,13 @@ static void initializeIoTHubClient()
static int initializeMqttClient()
{
#ifndef IOT_CONFIG_USE_X509_CERT
if (sasToken.Generate(SAS_TOKEN_DURATION_IN_MINUTES) != 0)
{
Logger.Error("Failed generating SAS token");
return 1;
}
#endif
esp_mqtt_client_config_t mqtt_config;
memset(&mqtt_config, 0, sizeof(mqtt_config));
@ -248,7 +256,15 @@ static int initializeMqttClient()
mqtt_config.port = mqtt_port;
mqtt_config.client_id = mqtt_client_id;
mqtt_config.username = mqtt_username;
#ifdef IOT_CONFIG_USE_X509_CERT
Logger.Info("MQTT client using X509 Certificate authentication");
mqtt_config.client_cert_pem = IOT_CONFIG_DEVICE_CERT;
mqtt_config.client_key_pem = IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY;
#else // Using SAS key
mqtt_config.password = (const char*)az_span_ptr(sasToken.Get());
#endif
mqtt_config.keepalive = 30;
mqtt_config.disable_clean_session = 0;
mqtt_config.disable_auto_reconnect = false;
@ -282,10 +298,7 @@ static int initializeMqttClient()
* @brief Gets the number of seconds since UNIX epoch until now.
* @return uint32_t Number of seconds.
*/
static uint32_t getEpochTimeInSecs()
{
return (uint32_t)time(NULL);
}
static uint32_t getEpochTimeInSecs() { return (uint32_t)time(NULL); }
static void establishConnection()
{
@ -298,7 +311,7 @@ static void establishConnection()
static void getTelemetryPayload(az_span payload, az_span* out_payload)
{
// You can generate the JSON using any lib you want. Here we're showing how to do it manually, for simplicity
myTelemetry = "{ \"msgCount\": "+ String(telemetry_send_count++) + " }";
myTelemetry = "{ \"msgCount\": " + String(telemetry_send_count++) + " }";
*out_payload = az_span_create((uint8_t*)myTelemetry.c_str(), myTelemetry.length());
}
@ -340,10 +353,7 @@ static void sendTelemetry()
// Arduino setup and loop main functions.
void setup()
{
establishConnection();
}
void setup() { establishConnection(); }
void loop()
{
@ -351,12 +361,14 @@ void loop()
{
connectToWiFi();
}
#ifndef IOT_CONFIG_USE_X509_CERT
else if (sasToken.IsExpired())
{
Logger.Info("SAS token expired; reconnecting with a new one.");
(void)esp_mqtt_client_destroy(mqtt_client);
initializeMqttClient();
}
#endif
else if (millis() > next_telemetry_send_time_ms)
{
sendTelemetry();

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

@ -5,10 +5,60 @@
#define IOT_CONFIG_WIFI_SSID "SSID"
#define IOT_CONFIG_WIFI_PASSWORD "PWD"
// Enable macro IOT_CONFIG_USE_X509_CERT to use an x509 certificate to authenticate the IoT device.
// The two main modes of authentication are through SAS tokens (automatically generated by the
// sample using the provided device symmetric key) or through x509 certificates. Please choose the
// appropriate option according to your device authentication mode.
// #define IOT_CONFIG_USE_X509_CERT
#ifdef IOT_CONFIG_USE_X509_CERT
/*
* Please set the define IOT_CONFIG_DEVICE_CERT below with
* the content of your device x509 certificate.
*
* Example:
* #define IOT_CONFIG_DEVICE_CERT "-----BEGIN CERTIFICATE-----\r\n" \
* "MIIBJDCBywIUfeHrebBVa2eZAbouBgACp9R3BncwCgYIKoZIzj0EAwIwETEPMA0G\r\n" \
* "A1UEAwwGRFBTIENBMB4XDTIyMDMyMjazMTAzN1oXDTIzMDMyMjIzMTAzN1owGTEX\r\n" \
* "MBUGA1UEAwwOY29udG9zby1kZXZpY2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\r\n" \
* .......
* "YmGzdaHTb6P1W+p+jmc+jJn1MAoGCXqGSM49BAMCA0gAMEUCIEnbEMsAdGFroMwl\r\n" \
* "vTfQahwsxN3xink9z1gtirrjQlqDAiEAyU+6TUJcG6d9JF+uJqsLFpsbbF3IzGAw\r\n" \
* "yC+koNRC0MU=\r\n" \
* "-----END CERTIFICATE-----"
*
*/
#define IOT_CONFIG_DEVICE_CERT "Device Certificate"
/*
* Please set the define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY below with
* the content of your device x509 private key.
*
* Example:
*
* #define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "-----BEGIN EC PRIVATE KEY-----\r\n" \
* "MHcCAQEEIKGXkMMiO9D7jYpUjUGTBn7gGzeKPeNzCP83wbfQfLd9obAoGCCqGSM49\r\n" \
* "AwEHoUQDQgAEU6nQoYbjgJvBwaeD6MyAYmOSDg0QhEdyyV337qrlIbDEKvFsn1El\r\n" \
* "yRabc4dNp2Jhs3Xh02+j9Vvqfo5nPoyZ9Q==\r\n" \
* "-----END EC PRIVATE KEY-----"
*
* Note the type of key may different in your case. Such as BEGIN PRIVATE KEY
* or BEGIN RSA PRIVATE KEY.
*
*/
#define IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY "Device Certificate Private Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Azure IoT
#define IOT_CONFIG_IOTHUB_FQDN "[your Azure IoT host name].azure-devices.net"
#define IOT_CONFIG_DEVICE_ID "Device ID"
// Use device key if not using certificates
#ifndef IOT_CONFIG_USE_X509_CERT
#define IOT_CONFIG_DEVICE_KEY "Device Key"
#endif // IOT_CONFIG_USE_X509_CERT
// Publish 1 message every 2 seconds
#define TELEMETRY_FREQUENCY_MILLISECS 2000

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

@ -38,22 +38,22 @@ This is a "to-the-point" guide outlining how to run an Azure SDK for Embedded C
- Configuration instructions for the Arduino IDE to compile a sample using the [Azure SDK for Embedded C](https://github.com/Azure/azure-sdk-for-c).
- Configuration, build, and run instructions for the IoT Hub telemetry sample.
_The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.15 and ESP32 board library version 1.0.6._
_The following was run on Windows 11, with Arduino IDE 2.1.0 and ESP32 board library version 2.0.9._
## 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 a [logical device](https://docs.microsoft.com/azure/iot-hub/iot-hub-create-through-portal#register-a-new-device-in-the-iot-hub) created in your Azure IoT Hub using the authentication type "Symmetric Key".
NOTE: Device keys are used to automatically generate a SAS token for authentication.
- Have a logical device created in your Azure IoT Hub: using authentication type "Symmetric Key" or "X.509 self-signed".
- **Symmetric Key**: follow [this guidance](https://docs.microsoft.com/azure/iot-hub/iot-hub-create-through-portal#register-a-new-device-in-the-iot-hub) to create a device.In this case, the device keys are used to automatically generate a SAS token for authentication.
- **X.509 self-signed cert**: Instructions on how to create an X.509 cert for tests can be found [here](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#configure-and-run-the-samples) (Step 1). Please note that you might need to install some of the [prerequisites](https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/docs/how_to_iot_hub_samples_linux.md#prerequisites) like OpenSSL.
- Have the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) installed.
- Have the [ESP32 board support](https://github.com/espressif/arduino-esp32) installed on Arduino IDE.
- ESP32 boards are not natively supported by Arduino IDE, so you need to add them manually.
- Follow the [instructions](https://github.com/espressif/arduino-esp32) in the official ESP32 repository.
- If your ESP32 board is not recognized and a COM port is not mapped by your computer, try installing the [serial port drivers](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html#connect-esp32-to-pc) recommended by ESPRESSIF.
- 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).
@ -95,7 +95,17 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with
4. Configure the ESPRESSIF ESP32 sample.
Enter your Azure IoT Hub and device information into the sample's `iot_configs.h`.
Enter your Azure IoT Hub and device information into the sample's `iot_configs.h`:
- Add your Wi-Fi SSID to `IOT_CONFIG_WIFI_SSID`
- Add your Wi-Fi password to `IOT_CONFIG_WIFI_PASSWORD`
- Add you IoTHub Name to `IOT_CONFIG_IOTHUB_FQDN`
- Add your Device ID to `IOT_CONFIG_DEVICE_ID`
- If using **X.509 Cert**:
- Uncomment the `#define IOT_CONFIG_USE_X509_CERT`
- Add your cert to `IOT_CONFIG_USE_X509_CERT`
- Add your cert PK to `IOT_CONFIG_DEVICE_CERT_PRIVATE_KEY`
- If using **Symmetric Key**:
- Add your device key to `IOT_CONFIG_DEVICE_KEY`
5. Connect the ESP32 microcontroller to your USB port.
@ -240,7 +250,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
For the ESP32 sample, our script `generate_arduino_zip_library.sh` automatically downloads the root certificate used in the United States regions (Baltimore CA certificate) and adds it to the Arduino sketch project.
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.

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

@ -5,7 +5,7 @@
* This is an Arduino-based Azure IoT Hub sample for ESPRESSIF ESP8266 board.
* 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.
*
*
* 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 to compose and parse the
@ -14,18 +14,21 @@
* This sample performs the following tasks:
* - Synchronize the device clock with a NTP server;
* - Initialize our "az_iot_hub_client" (struct for data, part of our azure-sdk-for-c);
* - Initialize the MQTT client (here we use Nick Oleary's PubSubClient, which also handle the tcp connection and TLS);
* - Connect the MQTT client (using server-certificate validation, SAS-tokens for client authentication);
* - Initialize the MQTT client (here we use Nick Oleary's PubSubClient, which also handle the tcp
* connection and TLS);
* - Connect the MQTT client (using server-certificate validation, SAS-tokens for client
* authentication);
* - Periodically send telemetry data to the Azure IoT Hub.
*
* To properly connect to your Azure IoT Hub, please fill the information in the `iot_configs.h` file.
*
* To properly connect to your Azure IoT Hub, please fill the information in the `iot_configs.h`
* file.
*/
// C99 libraries
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <cstdlib>
#include <stdbool.h>
#include <string.h>
#include <time.h>
// Libraries for MQTT client, WiFi connection and SAS-token generation.
#include <ESP8266WiFi.h>
@ -41,12 +44,12 @@
#include <az_iot.h>
#include <azure_ca.h>
// Additional sample headers
// Additional sample headers
#include "iot_configs.h"
// When developing for your own Arduino-based platform,
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c/" AZ_SDK_VERSION_STRING "(ard;esp8266)"
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard;esp8266)"
// Utility macros and defines
#define LED_PIN 2
@ -77,7 +80,6 @@ static char telemetry_topic[128];
static uint8_t telemetry_payload[100];
static uint32_t telemetry_send_count = 0;
// Auxiliary functions
static void connectToWiFi()
@ -88,6 +90,8 @@ static void connectToWiFi()
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
@ -162,10 +166,7 @@ static void initializeClients()
* @brief Gets the number of seconds since UNIX epoch until now.
* @return uint32_t Number of seconds.
*/
static uint32_t getSecondsSinceEpoch()
{
return (uint32_t)time(NULL);
}
static uint32_t getSecondsSinceEpoch() { return (uint32_t)time(NULL); }
static int generateSasToken(char* sas_token, size_t size)
{
@ -282,7 +283,7 @@ static int connectToAzureIoTHub()
return 0;
}
static void establishConnection()
static void establishConnection()
{
connectToWiFi();
initializeTime();
@ -308,7 +309,7 @@ static char* getTelemetryPayload()
{
az_span temp_span = az_span_create(telemetry_payload, sizeof(telemetry_payload));
temp_span = az_span_copy(temp_span, AZ_SPAN_FROM_STR("{ \"msgCount\": "));
(void)az_span_u32toa(temp_span, telemetry_send_count++, &temp_span);
(void)az_span_u32toa(temp_span, telemetry_send_count++, &temp_span);
temp_span = az_span_copy(temp_span, AZ_SPAN_FROM_STR(" }"));
temp_span = az_span_copy_u8(temp_span, '\0');
@ -333,7 +334,6 @@ static void sendTelemetry()
digitalWrite(LED_PIN, LOW);
}
// Arduino setup and loop main functions.
void setup()
@ -348,7 +348,7 @@ void loop()
if (millis() > next_telemetry_send_time_ms)
{
// Check if connected, reconnect if needed.
if(!mqtt_client.connected())
if (!mqtt_client.connected())
{
establishConnection();
}

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

@ -32,7 +32,7 @@ This is a "to-the-point" guide outlining how to run an Azure SDK for Embedded C
- Configuration instructions for the Arduino IDE to compile a sample using the Azure SDK for Embedded C.
- Configuration, build, and run instructions for the IoT Hub telemetry sample.
_The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with Arduino IDE 1.8.15 and Esp8266 module 3.0.1._
_The following was run on Windows 11, with Arduino IDE 2.1.0 and Esp8266 module 3.1.2._
## Prerequisites
@ -240,7 +240,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with
The Azure IoT service certificates presented during TLS negotiation shall be always validated, on the device, using the appropriate trusted root CA certificate(s).
For the Node MCU ESP8266 sample, our script `generate_arduino_zip_library.sh` automatically downloads the root certificate used in the United States regions (Baltimore CA certificate) and adds it to the Arduino sketch project.
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.

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

@ -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

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

@ -2,29 +2,29 @@
// SPDX-License-Identifier: MIT
// C99 libraries
#include <string.h>
#include <stdbool.h>
#include <cstdlib>
#include <stdbool.h>
#include <string.h>
// Libraries for NTP, MQTT client, WiFi connection and SAS-token generation.
#include <NTPClient.h>
#include <sntp/sntp.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFi.h>
#include <mbedtls/base64.h>
#include <mbedtls/sha256.h>
#include <sntp/sntp.h>
// Azure IoT SDK for C includes
#include <az_core.h>
#include <az_iot.h>
#include <azure_ca.h>
// Additional sample headers
// Additional sample headers
#include "iot_configs.h"
// When developing for your own Arduino-based platform,
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c/" AZ_SDK_VERSION_STRING "(ard;amebaD)"
// please follow the format '(ard;<platform>)'.
#define AZURE_SDK_CLIENT_USER_AGENT "c%2F" AZ_SDK_VERSION_STRING "(ard;amebaD)"
// Utility macros and defines
// Status LED: will remain high on error and pulled high for a short time for each successful send.
@ -54,10 +54,26 @@ static uint32_t telemetry_send_count = 0;
static unsigned char* ca_pem_nullterm;
// Auxiliary functions
extern "C"{
extern int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen);
extern int rom_hmac_sha256(const u8 *key, size_t key_len, const u8 *data, size_t data_len, u8 *mac);
extern int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen);
extern "C"
{
extern int mbedtls_base64_decode(
unsigned char* dst,
size_t dlen,
size_t* olen,
const unsigned char* src,
size_t slen);
extern int rom_hmac_sha256(
const u8* key,
size_t key_len,
const u8* data,
size_t data_len,
u8* mac);
extern int mbedtls_base64_encode(
unsigned char* dst,
size_t dlen,
size_t* olen,
const unsigned char* src,
size_t slen);
}
static void createNullTerminatedRootCert()
{
@ -74,7 +90,6 @@ static void createNullTerminatedRootCert()
}
}
static void connectToWiFi()
{
Serial.begin(115200);
@ -139,15 +154,16 @@ int64_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes)
long tus = 0;
unsigned int ttk = 0;
//it should be ok to do init more than one time. It'll handle inside sntp_init().
// it should be ok to do init more than one time. It'll handle inside sntp_init().
sntp_init();
sntp_get_lasttime(&ts, &tus, &ttk);
while(ts == 0){
while (ts == 0)
{
vTaskDelay(1000 / portTICK_RATE_MS);
sntp_get_lasttime(&ts, &tus, &ttk);
}
}
return (int64_t)ts + minutes * 60;
}
@ -157,13 +173,14 @@ static void hmac_sha256_sign_signature(
az_span signature,
az_span signed_signature,
az_span* out_signed_signature)
{
if(rom_hmac_sha256(
az_span_ptr(decoded_key),
(size_t)az_span_size(decoded_key),
az_span_ptr(signature),
(size_t)az_span_size(signature),
az_span_ptr(signed_signature)) != 0)
{
if (rom_hmac_sha256(
az_span_ptr(decoded_key),
(size_t)az_span_size(decoded_key),
az_span_ptr(signature),
(size_t)az_span_size(signature),
az_span_ptr(signed_signature))
!= 0)
{
Serial.println("[ERROR] rom_hmac_sha256 failed");
@ -178,12 +195,17 @@ static void base64_encode_bytes(
az_span* out_base64_encoded_bytes)
{
size_t len;
if(mbedtls_base64_encode(az_span_ptr(base64_encoded_bytes), (size_t)az_span_size(base64_encoded_bytes),
&len, az_span_ptr(decoded_bytes), (size_t)az_span_size(decoded_bytes)) != 0)
if (mbedtls_base64_encode(
az_span_ptr(base64_encoded_bytes),
(size_t)az_span_size(base64_encoded_bytes),
&len,
az_span_ptr(decoded_bytes),
(size_t)az_span_size(decoded_bytes))
!= 0)
{
Serial.println("[ERROR] mbedtls_base64_encode fail");
}
*out_base64_encoded_bytes = az_span_create(az_span_ptr(base64_encoded_bytes), (int32_t)len);
}
@ -192,16 +214,21 @@ static void decode_base64_bytes(
az_span decoded_bytes,
az_span* out_decoded_bytes)
{
memset(az_span_ptr(decoded_bytes), 0, (size_t)az_span_size(decoded_bytes));
size_t len;
if( mbedtls_base64_decode( az_span_ptr(decoded_bytes), (size_t)az_span_size(decoded_bytes),
&len, az_span_ptr(base64_encoded_bytes), (size_t)az_span_size(base64_encoded_bytes)) != 0)
if (mbedtls_base64_decode(
az_span_ptr(decoded_bytes),
(size_t)az_span_size(decoded_bytes),
&len,
az_span_ptr(base64_encoded_bytes),
(size_t)az_span_size(base64_encoded_bytes))
!= 0)
{
Serial.println("[ERROR] mbedtls_base64_decode fail");
}
*out_decoded_bytes = az_span_create(az_span_ptr(decoded_bytes), (int32_t)len);
}
@ -219,26 +246,27 @@ static void iot_sample_generate_sas_base64_encoded_signed_signature(
// HMAC-SHA256 sign the signature with the decoded key.
char sas_hmac256_signed_signature_buffer[32];
az_span sas_hmac256_signed_signature = AZ_SPAN_FROM_BUFFER(sas_hmac256_signed_signature_buffer);
hmac_sha256_sign_signature(sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature);
hmac_sha256_sign_signature(
sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature);
// Base64 encode the result of the HMAC signing.
base64_encode_bytes(
sas_hmac256_signed_signature,
sas_base64_encoded_signed_signature,
out_sas_base64_encoded_signed_signature);
sas_hmac256_signed_signature,
sas_base64_encoded_signed_signature,
out_sas_base64_encoded_signed_signature);
}
static void generate_sas_key(void)
{
az_result rc;
// Create the POSIX expiration time from input minutes.
uint64_t sas_duration = iot_sample_get_epoch_expiration_time_from_minutes(SAS_TOKEN_EXPIRY_IN_MINUTES);
uint64_t sas_duration
= iot_sample_get_epoch_expiration_time_from_minutes(SAS_TOKEN_EXPIRY_IN_MINUTES);
// Get the signature that will later be signed with the decoded key.
az_span sas_signature = AZ_SPAN_FROM_BUFFER(signature);
rc = az_iot_hub_client_sas_get_signature(
&hub_client, sas_duration, sas_signature, &sas_signature);
&hub_client, sas_duration, sas_signature, &sas_signature);
if (az_result_failed(rc))
{
Serial.print("Could not get the signature for SAS key: az_result return code ");
@ -249,21 +277,21 @@ static void generate_sas_key(void)
char b64enc_hmacsha256_signature[64];
az_span sas_base64_encoded_signed_signature = AZ_SPAN_FROM_BUFFER(b64enc_hmacsha256_signature);
iot_sample_generate_sas_base64_encoded_signed_signature(
AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_KEY),
sas_signature,
sas_base64_encoded_signed_signature,
&sas_base64_encoded_signed_signature);
AZ_SPAN_FROM_STR(IOT_CONFIG_DEVICE_KEY),
sas_signature,
sas_base64_encoded_signed_signature,
&sas_base64_encoded_signed_signature);
// Get the resulting MQTT password, passing the base64 encoded, HMAC signed bytes.
size_t mqtt_password_length;
rc = az_iot_hub_client_sas_get_password(
&hub_client,
sas_duration,
sas_base64_encoded_signed_signature,
AZ_SPAN_EMPTY,
sas_token,
sizeof(sas_token),
&sas_token_length);
&hub_client,
sas_duration,
sas_base64_encoded_signed_signature,
AZ_SPAN_EMPTY,
sas_token,
sizeof(sas_token),
&sas_token_length);
if (az_result_failed(rc))
{
Serial.print("Could not get the password: az_result return code ");
@ -320,7 +348,7 @@ static int connect_to_azure_iot_hub()
return 0;
}
void establishConnection()
void establishConnection()
{
connectToWiFi();
@ -337,7 +365,7 @@ static char* get_telemetry_payload()
{
az_span temp_span = az_span_create(telemetry_payload, sizeof(telemetry_payload));
temp_span = az_span_copy(temp_span, AZ_SPAN_FROM_STR("{ \"msgCount\": "));
(void)az_span_u32toa(temp_span, telemetry_send_count++, &temp_span);
(void)az_span_u32toa(temp_span, telemetry_send_count++, &temp_span);
temp_span = az_span_copy(temp_span, AZ_SPAN_FROM_STR(" }"));
temp_span = az_span_copy_u8(temp_span, '\0');
@ -362,7 +390,6 @@ static void send_telemetry()
digitalWrite(LED_PIN, LOW);
}
// Arduino setup and loop main functions.
void setup()
@ -378,7 +405,7 @@ void loop()
if (millis() > next_telemetry_send_time_ms)
{
// Check if connected, reconnect if needed.
if(!mqtt_client.connected())
if (!mqtt_client.connected())
{
establishConnection();
}

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

@ -181,7 +181,7 @@ _The following was run on Windows 10 and Ubuntu Desktop 20.04 environments, with
The Azure IoT service certificates presented during TLS negotiation shall always be validated, on the device, using the appropriate trusted root CA certificate(s).
For the Realtek AmebaD sample, our script `generate_arduino_zip_library.sh` automatically downloads the root certificate used in the United States regions (Baltimore CA certificate) and adds it to the Arduino sketch project.
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.

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

@ -1,10 +1,10 @@
name=Azure SDK for C
version=1.0.0-beta.3
author=Microsoft Corporation
maintainer=Microsoft Corporation <aziotarduino@microsoft.com>
sentence=Azure SDK for C library for Arduino.
paragraph=This is an Arduino port of the Azure SDK for C (1.3.0-beta.1). It allows you to use your Arduino device with Azure services like Azure IoT Hub and Azure Device Provisioning Service. See README.md for more details. Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
category=Communication
url=https://github.com/Azure/azure-sdk-for-c/tree/1.3.0-beta.1
architectures=*
includes=az_core.h,az_iot.h,azure_ca.h
name=Azure SDK for C
version=1.1.5
author=Microsoft Corporation
maintainer=Microsoft Corporation <aziotarduino@microsoft.com>
sentence=Azure SDK for C library for Arduino.
paragraph=This is an Arduino port of the Azure SDK for C (1.5.0-beta.1). It allows you to use your Arduino device with Azure services like Azure IoT Hub and Azure Device Provisioning Service. See README.md for more details. Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
category=Communication
url=https://github.com/Azure/azure-sdk-for-c-arduino/releases
architectures=*
includes=az_core.h,az_iot.h,azure_ca.h

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

@ -12,269 +12,15 @@
#define _az_ENCODING_PAD '='
typedef enum
{
_az_base64_mode_standard,
_az_base64_mode_url
} _az_base64_mode;
static char const _az_base64_encode_array[65]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int8_t const _az_base64_decode_array[256] = {
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
62,
-1,
-1,
-1,
63, // 62 is placed at index 43 (for +), 63 at index 47 (for /)
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
-1,
-1,
-1,
-1,
-1,
-1, // 52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =)
-1,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
-1,
-1,
-1,
-1,
-1, // 0-25 are placed at index 65-90 (for A-Z)
-1,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
-1,
-1,
-1,
-1,
-1, // 26-51 are placed at index 97-122 (for a-z)
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1, // Bytes over 122 ('z') are invalid and cannot be decoded. Hence, padding the map with 255,
// which indicates invalid input
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
};
static AZ_NODISCARD int32_t _az_base64_encode(uint8_t* three_bytes)
{
int32_t i = (*three_bytes << 16) | (*(three_bytes + 1) << 8) | *(three_bytes + 2);
@ -370,17 +116,70 @@ AZ_NODISCARD int32_t az_base64_get_max_encoded_size(int32_t source_bytes_size)
return (((source_bytes_size + 2) / 3) * 4);
}
static AZ_NODISCARD int32_t _az_base64_decode(uint8_t* encoded_bytes)
static int32_t _get_base64_decoded_char(int32_t c, _az_base64_mode mode)
{
if (mode == _az_base64_mode_url)
{
if (c == '+' || c == '/')
{
return -1; // can't use + or / with URL encoding
}
if (c == '-')
{
c = '+'; // - becomes a +
}
else if (c == '_')
{
c = '/'; // _ becomes a /
}
}
if (c >= 'A' && c <= 'Z')
{
return (c - 'A');
}
if (c >= 'a' && c <= 'z')
{
return 26 + (c - 'a');
}
if (c >= '0' && c <= '9')
{
return 52 + (c - '0');
}
if (c == '+')
{
return 62;
}
if (c == '/')
{
return 63;
}
return -1;
}
static AZ_NODISCARD int32_t
_az_base64_decode_four_bytes(uint8_t* encoded_bytes, _az_base64_mode mode)
{
int32_t i0 = *encoded_bytes;
int32_t i1 = *(encoded_bytes + 1);
int32_t i2 = *(encoded_bytes + 2);
int32_t i3 = *(encoded_bytes + 3);
i0 = _az_base64_decode_array[i0];
i1 = _az_base64_decode_array[i1];
i2 = _az_base64_decode_array[i2];
i3 = _az_base64_decode_array[i3];
i0 = _get_base64_decoded_char(i0, mode);
i1 = _get_base64_decoded_char(i1, mode);
i2 = _get_base64_decoded_char(i2, mode);
i3 = _get_base64_decoded_char(i3, mode);
if (i0 == -1 || i1 == -1 || i2 == -1 || i3 == -1)
{
return -1;
}
i0 <<= 18;
i1 <<= 12;
@ -400,25 +199,18 @@ static void _az_base64_write_three_low_order_bytes(uint8_t* destination, int32_t
*(destination + 2) = (uint8_t)(value);
}
AZ_NODISCARD az_result
az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written)
static az_result _az_base64_decode(
az_span destination_bytes,
az_span source_base64_url_text,
int32_t* out_written,
_az_base64_mode mode)
{
_az_PRECONDITION_VALID_SPAN(destination_bytes, 1, false);
_az_PRECONDITION_VALID_SPAN(source_base64_text, 4, false);
_az_PRECONDITION_NOT_NULL(out_written);
int32_t source_length = az_span_size(source_base64_text);
uint8_t* source_ptr = az_span_ptr(source_base64_text);
int32_t source_length = az_span_size(source_base64_url_text);
uint8_t* source_ptr = az_span_ptr(source_base64_url_text);
int32_t destination_length = az_span_size(destination_bytes);
uint8_t* destination_ptr = az_span_ptr(destination_bytes);
// The input must be non-empty and a multiple of 4 to be valid.
if (source_length == 0 || source_length % 4 != 0)
{
return AZ_ERROR_UNEXPECTED_END;
}
if (destination_length < az_base64_get_max_decoded_size(source_length) - 2)
{
return AZ_ERROR_NOT_ENOUGH_SPACE;
@ -429,7 +221,7 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
while (source_index < source_length - 4)
{
int32_t result = _az_base64_decode(source_ptr + source_index);
int32_t result = _az_base64_decode_four_bytes(source_ptr + source_index, mode);
if (result < 0)
{
return AZ_ERROR_UNEXPECTED_CHAR;
@ -440,15 +232,19 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
source_index += 4;
}
// We are guaranteed to have an input with at least 4 bytes at this point, with a size that is a
// multiple of 4.
int32_t i0 = *(source_ptr + source_length - 4);
int32_t i1 = *(source_ptr + source_length - 3);
int32_t i2 = *(source_ptr + source_length - 2);
int32_t i3 = *(source_ptr + source_length - 1);
// If using standard base64 decoding, there is a precondition guaranteeing size is divisible by 4.
// Otherwise with url encoding, we can assume padding characters.
// If length is divisible by four, do nothing. Else, we assume up to two padding characters.
int32_t source_length_mod_four = source_length % 4;
int32_t i0 = *(source_ptr + source_index);
int32_t i1 = *(source_ptr + source_index + 1);
int32_t i2 = source_length_mod_four == 2 ? _az_ENCODING_PAD : *(source_ptr + source_index + 2);
int32_t i3 = source_length_mod_four == 2 || source_length_mod_four == 3
? _az_ENCODING_PAD
: *(source_ptr + source_index + 3);
i0 = _az_base64_decode_array[i0];
i1 = _az_base64_decode_array[i1];
i0 = _get_base64_decoded_char(i0, mode);
i1 = _get_base64_decoded_char(i1, mode);
i0 <<= 18;
i1 <<= 12;
@ -457,8 +253,8 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
if (i3 != _az_ENCODING_PAD)
{
i2 = _az_base64_decode_array[i2];
i3 = _az_base64_decode_array[i3];
i2 = _get_base64_decoded_char(i2, mode);
i3 = _get_base64_decoded_char(i3, mode);
i2 <<= 6;
@ -478,7 +274,7 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
}
else if (i2 != _az_ENCODING_PAD)
{
i2 = _az_base64_decode_array[i2];
i2 = _get_base64_decoded_char(i2, mode);
i2 <<= 6;
@ -514,8 +310,55 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
return AZ_OK;
}
AZ_NODISCARD az_result
az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written)
{
_az_PRECONDITION_VALID_SPAN(destination_bytes, 1, false);
_az_PRECONDITION_VALID_SPAN(source_base64_text, 4, false);
_az_PRECONDITION_NOT_NULL(out_written);
int32_t source_length = az_span_size(source_base64_text);
// The input must be non-empty and a multiple of 4 to be valid.
if (source_length == 0 || source_length % 4 != 0)
{
return AZ_ERROR_UNEXPECTED_END;
}
return _az_base64_decode(
destination_bytes, source_base64_text, out_written, _az_base64_mode_standard);
}
AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size)
{
_az_PRECONDITION(source_base64_text_size >= 0);
return (source_base64_text_size / 4) * 3;
}
AZ_NODISCARD az_result az_base64_url_decode(
az_span destination_bytes,
az_span source_base64_url_text,
int32_t* out_written)
{
_az_PRECONDITION_VALID_SPAN(destination_bytes, 1, false);
_az_PRECONDITION_VALID_SPAN(source_base64_url_text, 2, false);
_az_PRECONDITION_NOT_NULL(out_written);
int32_t source_length = az_span_size(source_base64_url_text);
// The input must be non-empty and a minimum of two characters long.
// There can only be two assumed padding characters.
if (source_length == 0 || source_length % 4 == 1)
{
return AZ_ERROR_UNEXPECTED_END;
}
return _az_base64_decode(
destination_bytes, source_base64_url_text, out_written, _az_base64_mode_url);
}
AZ_NODISCARD int32_t az_base64_url_get_max_decoded_size(int32_t source_base64_url_text_size)
{
_az_PRECONDITION(source_base64_url_text_size >= 0);
return (source_base64_url_text_size / 4) * 3;
}

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

@ -84,6 +84,41 @@ az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t*
*/
AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size);
/**
* @brief Decodes the span of UTF-8 encoded text represented as base 64 url into binary data.
*
* @param destination_bytes The output #az_span where the decoded binary data should be copied to as
* a result of the operation.
* @param[in] source_base64_url_text The input #az_span that contains the base 64 text to be
* decoded.
* @param[out] out_written A pointer to an `int32_t` that receives the number of bytes written into
* the destination #az_span. This can be used to slice the output for subsequent calls, if
* necessary.
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK Success.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p destination_bytes is not large enough to contain
* the decoded text.
* @retval #AZ_ERROR_UNEXPECTED_CHAR The input \p source_base64_url_text contains characters outside
* of the expected base 64 range or is incomplete (that is, has length % 4 == 1 characters).
* @retval #AZ_ERROR_UNEXPECTED_END The input \p source_base64_url_text is incomplete (that is, it
* is of a size which is length % 4 == 1 characters).
*/
AZ_NODISCARD az_result az_base64_url_decode(
az_span destination_bytes,
az_span source_base64_url_text,
int32_t* out_written);
/**
* @brief Returns the maximum length of the result if you were to decode an #az_span of the
* specified length which contained base 64 url encoded text.
*
* @param source_base64_url_text_size The size of the span containing base 64 encoded text.
*
* @return The maximum length of the result.
*/
AZ_NODISCARD int32_t az_base64_url_get_max_decoded_size(int32_t source_base64_url_text_size);
#include <_az_cfg_suffix.h>
#endif // _az_BASE64_H

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines internal constants.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_CONFIG_INTERNAL_H
#define _az_CONFIG_INTERNAL_H

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines internals used by credentials.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_CREDENTIALS_INTERNAL_H
#define _az_CREDENTIALS_INTERNAL_H

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines private implementation used by hex.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_HEX_PRIVATE_H
#define _az_HEX_PRIVATE_H

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

@ -7,6 +7,10 @@
* @brief This header defines a bit array that is used to validate whenever or not an ASCII char is
* valid within an http header name.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_HTTP_HEADER_VALIDATION_PRIVATE_H

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines internals used by http.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_HTTP_INTERNAL_H
#define _az_HTTP_INTERNAL_H
@ -83,9 +94,9 @@ AZ_NODISCARD AZ_INLINE _az_http_policy_apiversion_options
_az_http_policy_apiversion_options_default()
{
return (_az_http_policy_apiversion_options){
._internal = { .option_location = _az_http_policy_apiversion_option_location_header,
.name = AZ_SPAN_EMPTY,
.version = AZ_SPAN_EMPTY }
._internal = { .name = AZ_SPAN_EMPTY,
.version = AZ_SPAN_EMPTY,
.option_location = _az_http_policy_apiversion_option_location_header }
};
}

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

@ -70,7 +70,7 @@ static az_result _az_http_policy_logging_append_http_request_msg(
}
else
{
required_length = az_span_size(request->_internal.method) + request->_internal.url_length + 1;
required_length += az_span_size(request->_internal.method) + request->_internal.url_length + 1;
}
_az_RETURN_IF_NOT_ENOUGH_SIZE(*ref_log_msg, required_length);

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines private implementation used by http logging.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_HTTP_POLICY_LOGGING_PRIVATE_H
#define _az_HTTP_POLICY_LOGGING_PRIVATE_H

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

@ -220,6 +220,4 @@ AZ_NODISCARD az_result az_http_pipeline_policy_retry(
}
}
}
return result;
}

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines private implementation used by http.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_HTTP_PRIVATE_H
#define _az_HTTP_PRIVATE_H

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

@ -15,6 +15,7 @@
#ifndef _az_IOT_H
#define _az_IOT_H
#include <az_iot_adu_client.h>
#include <az_iot_common.h>
#include <az_iot_hub_client.h>
#include <az_iot_hub_client_properties.h>

871
src/az_iot_adu_client.c Normal file
Просмотреть файл

@ -0,0 +1,871 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <az_iot_adu_client.h>
#include <az_iot_hub_client_properties.h>
#include <az_log_internal.h>
#include <az_precondition_internal.h>
#include <az_result_internal.h>
#include <stdio.h>
/* Define the ADU agent component name. */
#define AZ_IOT_ADU_CLIENT_AGENT_COMPONENT_NAME "deviceUpdate"
#define AZ_IOT_ADU_CLIENT_AGENT_CONTRACT_MODEL_ID "dtmi:azure:iot:deviceUpdateContractModel;2"
/* Define the ADU agent property name "agent" and sub property names. */
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_AGENT "agent"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DEVICEPROPERTIES "deviceProperties"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MANUFACTURER "manufacturer"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MODEL "model"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_CONTRACT_MODEL_ID "contractModelId"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ADU_VERSION "aduVer"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DO_VERSION "doVer"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_COMPAT_PROPERTY_NAMES "compatPropertyNames"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTALLED_UPDATE_ID "installedUpdateId"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_PROVIDER "provider"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_NAME "name"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_VERSION "version"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_LAST_INSTALL_RESULT "lastInstallResult"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_CODE "resultCode"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_EXTENDED_RESULT_CODE "extendedResultCode"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_DETAILS "resultDetails"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_STEP_RESULTS "stepResults"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_STATE "state"
/* Define the ADU agent property name "service" and sub property names. */
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SERVICE "service"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_WORKFLOW "workflow"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ACTION "action"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ID "id"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RETRY_TIMESTAMP "retryTimestamp"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_MANIFEST "updateManifest"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_MANIFEST_SIGNATURE "updateManifestSignature"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILEURLS "fileUrls"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MANIFEST_VERSION "manifestVersion"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_ID "updateId"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_COMPATIBILITY "compatibility"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DEVICE_MANUFACTURER "deviceManufacturer"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DEVICE_MODEL "deviceModel"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_GROUP "group"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTRUCTIONS "instructions"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_STEPS "steps"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_TYPE "type"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HANDLER "handler"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HANDLER_PROPERTIES "handlerProperties"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILES "files"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DETACHED_MANIFEST_FILED "detachedManifestFileId"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTALLED_CRITERIA "installedCriteria"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILE_NAME "fileName"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SIZE_IN_BYTES "sizeInBytes"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HASHES "hashes"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SHA256 "sha256"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_CREATED_DATE_TIME "createdDateTime"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DOWNLOAD_HANDLER "downloadHandler"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RELATED_FILES "relatedFiles"
#define AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MIME_TYPE "mimeType"
#define NULL_TERM_CHAR_SIZE 1
#define RESULT_STEP_ID_PREFIX "step_"
#define MAX_UINT32_NUMBER_OF_DIGITS 10
#define RESULT_STEP_ID_MAX_SIZE (sizeof(RESULT_STEP_ID_PREFIX) - 1 + MAX_UINT32_NUMBER_OF_DIGITS)
#define RETURN_IF_JSON_TOKEN_NOT_TYPE(jr_ptr, json_token_type) \
if (jr_ptr->token.kind != json_token_type) \
{ \
return AZ_ERROR_JSON_INVALID_STATE; \
}
#define RETURN_IF_JSON_TOKEN_NOT_TEXT(jr_ptr, literal_text) \
if (!az_json_token_is_text_equal(&jr_ptr->token, AZ_SPAN_FROM_STR(literal_text))) \
{ \
return AZ_ERROR_JSON_INVALID_STATE; \
}
const az_span default_compatibility_properties
= AZ_SPAN_LITERAL_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_DEFAULT_COMPATIBILITY_PROPERTIES);
AZ_NODISCARD az_iot_adu_client_options az_iot_adu_client_options_default()
{
return (az_iot_adu_client_options){ .device_compatibility_properties
= default_compatibility_properties };
}
AZ_NODISCARD az_iot_adu_client_device_properties az_iot_adu_client_device_properties_default()
{
return (az_iot_adu_client_device_properties){ .manufacturer = AZ_SPAN_LITERAL_EMPTY,
.model = AZ_SPAN_LITERAL_EMPTY,
.custom_properties = NULL,
.adu_version = AZ_SPAN_LITERAL_EMPTY,
.delivery_optimization_agent_version
= AZ_SPAN_LITERAL_EMPTY,
.update_id = AZ_SPAN_LITERAL_EMPTY };
}
AZ_NODISCARD az_result
az_iot_adu_client_init(az_iot_adu_client* client, az_iot_adu_client_options* options)
{
_az_PRECONDITION_NOT_NULL(client);
client->_internal.options = options == NULL ? az_iot_adu_client_options_default() : *options;
return AZ_OK;
}
AZ_NODISCARD bool az_iot_adu_client_is_component_device_update(
az_iot_adu_client* client,
az_span component_name)
{
_az_PRECONDITION_NOT_NULL(client);
(void)client;
return az_span_is_content_equal(
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_COMPONENT_NAME), component_name);
}
static az_result _generate_step_id(az_span buffer, uint32_t step_index, az_span* step_id)
{
az_result result;
*step_id = buffer;
buffer = az_span_copy(buffer, AZ_SPAN_FROM_STR(RESULT_STEP_ID_PREFIX));
result = az_span_u32toa(buffer, step_index, &buffer);
_az_RETURN_IF_FAILED(result);
*step_id = az_span_slice(*step_id, 0, az_span_size(*step_id) - az_span_size(buffer));
return AZ_OK;
}
AZ_NODISCARD az_result az_iot_adu_client_get_agent_state_payload(
az_iot_adu_client* client,
az_iot_adu_client_device_properties* device_properties,
az_iot_adu_client_agent_state agent_state,
az_iot_adu_client_workflow* workflow,
az_iot_adu_client_install_result* last_install_result,
az_json_writer* ref_json_writer)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(device_properties);
_az_PRECONDITION_VALID_SPAN(device_properties->manufacturer, 1, false);
_az_PRECONDITION_VALID_SPAN(device_properties->model, 1, false);
_az_PRECONDITION_VALID_SPAN(device_properties->update_id, 1, false);
_az_PRECONDITION_VALID_SPAN(device_properties->adu_version, 1, false);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
uint8_t step_id_scratch_buffer[7];
/* Update reported property */
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
/* Fill the ADU agent component name. */
_az_RETURN_IF_FAILED(az_iot_hub_client_properties_writer_begin_component(
NULL, ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_COMPONENT_NAME)));
/* Fill the agent property name. */
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_AGENT)));
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
/* Fill the deviceProperties. */
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DEVICEPROPERTIES)));
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MANUFACTURER)));
_az_RETURN_IF_FAILED(
az_json_writer_append_string(ref_json_writer, device_properties->manufacturer));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MODEL)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(ref_json_writer, device_properties->model));
if (device_properties->custom_properties != NULL)
{
for (int32_t custom_property_index = 0;
custom_property_index < device_properties->custom_properties->count;
custom_property_index++)
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, device_properties->custom_properties->names[custom_property_index]));
_az_RETURN_IF_FAILED(az_json_writer_append_string(
ref_json_writer, device_properties->custom_properties->values[custom_property_index]));
}
}
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_CONTRACT_MODEL_ID)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_CONTRACT_MODEL_ID)));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ADU_VERSION)));
_az_RETURN_IF_FAILED(
az_json_writer_append_string(ref_json_writer, device_properties->adu_version));
if (!az_span_is_content_equal(
device_properties->delivery_optimization_agent_version, AZ_SPAN_EMPTY))
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DO_VERSION)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(
ref_json_writer, device_properties->delivery_optimization_agent_version));
}
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
/* Fill the compatibility property names. */
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_COMPAT_PROPERTY_NAMES)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(
ref_json_writer, client->_internal.options.device_compatibility_properties));
/* Add last installed update information */
if (last_install_result != NULL)
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_LAST_INSTALL_RESULT)));
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_CODE)));
_az_RETURN_IF_FAILED(
az_json_writer_append_int32(ref_json_writer, last_install_result->result_code));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_EXTENDED_RESULT_CODE)));
_az_RETURN_IF_FAILED(
az_json_writer_append_int32(ref_json_writer, last_install_result->extended_result_code));
if (!az_span_is_content_equal(last_install_result->result_details, AZ_SPAN_EMPTY))
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_DETAILS)));
_az_RETURN_IF_FAILED(
az_json_writer_append_string(ref_json_writer, last_install_result->result_details));
}
for (int32_t i = 0; i < last_install_result->step_results_count; i++)
{
az_span step_id = AZ_SPAN_FROM_BUFFER(step_id_scratch_buffer);
_az_RETURN_IF_FAILED(_generate_step_id(step_id, (uint32_t)i, &step_id));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(ref_json_writer, step_id));
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_CODE)));
_az_RETURN_IF_FAILED(az_json_writer_append_int32(
ref_json_writer, last_install_result->step_results[i].result_code));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_EXTENDED_RESULT_CODE)));
_az_RETURN_IF_FAILED(az_json_writer_append_int32(
ref_json_writer, last_install_result->step_results[i].extended_result_code));
if (!az_span_is_content_equal(
last_install_result->step_results[i].result_details, AZ_SPAN_EMPTY))
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RESULT_DETAILS)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(
ref_json_writer, last_install_result->step_results[i].result_details));
}
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
}
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
}
/* Fill the agent state. */
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_STATE)));
_az_RETURN_IF_FAILED(az_json_writer_append_int32(ref_json_writer, (int32_t)agent_state));
/* Fill the workflow. */
if (workflow != NULL && (az_span_ptr(workflow->id) != NULL && az_span_size(workflow->id) > 0))
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_WORKFLOW)));
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ACTION)));
_az_RETURN_IF_FAILED(az_json_writer_append_int32(ref_json_writer, (int32_t)workflow->action));
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ID)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(ref_json_writer, workflow->id));
/* Append retry timestamp in workflow if existed. */
if (!az_span_is_content_equal(workflow->retry_timestamp, AZ_SPAN_EMPTY))
{
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RETRY_TIMESTAMP)));
_az_RETURN_IF_FAILED(
az_json_writer_append_string(ref_json_writer, workflow->retry_timestamp));
}
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
}
/* Fill installed update id. */
_az_RETURN_IF_FAILED(az_json_writer_append_property_name(
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTALLED_UPDATE_ID)));
_az_RETURN_IF_FAILED(az_json_writer_append_string(ref_json_writer, device_properties->update_id));
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_iot_hub_client_properties_writer_end_component(NULL, ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
return AZ_OK;
}
AZ_NODISCARD az_result az_iot_adu_client_parse_service_properties(
az_iot_adu_client* client,
az_json_reader* ref_json_reader,
az_iot_adu_client_update_request* update_request)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_reader);
_az_PRECONDITION_NOT_NULL(update_request);
(void)client;
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_PROPERTY_NAME);
RETURN_IF_JSON_TOKEN_NOT_TEXT(ref_json_reader, AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SERVICE);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_request->workflow.action = 0;
update_request->workflow.id = AZ_SPAN_EMPTY;
update_request->workflow.retry_timestamp = AZ_SPAN_EMPTY;
update_request->update_manifest = AZ_SPAN_EMPTY;
update_request->update_manifest_signature = AZ_SPAN_EMPTY;
update_request->file_urls_count = 0;
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_WORKFLOW)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ACTION)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
_az_RETURN_IF_FAILED(az_json_token_get_int32(
&ref_json_reader->token, (int32_t*)&update_request->workflow.action));
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_ID)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_request->workflow.id = ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RETRY_TIMESTAMP)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_request->workflow.retry_timestamp = ref_json_reader->token.slice;
}
else
{
_az_LOG_WRITE(
AZ_LOG_IOT_ADU,
AZ_SPAN_FROM_STR("Unexpected property found in ADU manifest workflow:"));
_az_LOG_WRITE(AZ_LOG_IOT_ADU, ref_json_reader->token.slice);
return AZ_ERROR_JSON_INVALID_STATE;
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_MANIFEST)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
if (ref_json_reader->token.kind != AZ_JSON_TOKEN_NULL)
{
update_request->update_manifest = ref_json_reader->token.slice;
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_MANIFEST_SIGNATURE)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
if (ref_json_reader->token.kind != AZ_JSON_TOKEN_NULL)
{
update_request->update_manifest_signature = ref_json_reader->token.slice;
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILEURLS)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
if (ref_json_reader->token.kind != AZ_JSON_TOKEN_NULL)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE(ref_json_reader, AZ_JSON_TOKEN_PROPERTY_NAME);
// If object isn't ended and we have reached max files allowed, next would overflow.
if (update_request->file_urls_count == _az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT)
{
return AZ_ERROR_NOT_ENOUGH_SPACE;
}
update_request->file_urls[update_request->file_urls_count].id
= ref_json_reader->token.slice;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
if (ref_json_reader->token.kind != AZ_JSON_TOKEN_NULL)
{
update_request->file_urls[update_request->file_urls_count].url
= ref_json_reader->token.slice;
update_request->file_urls_count++;
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
return AZ_OK;
}
AZ_NODISCARD az_result az_iot_adu_client_get_service_properties_response(
az_iot_adu_client* client,
int32_t version,
az_iot_adu_client_request_decision status,
az_json_writer* ref_json_writer)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
(void)client;
// Component and response status
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_iot_hub_client_properties_writer_begin_component(
NULL, ref_json_writer, AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_COMPONENT_NAME)));
_az_RETURN_IF_FAILED(az_iot_hub_client_properties_writer_begin_response_status(
NULL,
ref_json_writer,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SERVICE),
(int32_t)status,
version,
AZ_SPAN_EMPTY));
// It is not necessary to send the properties back in the acknowledgement.
// We opt not to send them to reduce the size of the payload.
_az_RETURN_IF_FAILED(az_json_writer_append_begin_object(ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
_az_RETURN_IF_FAILED(
az_iot_hub_client_properties_writer_end_response_status(NULL, ref_json_writer));
_az_RETURN_IF_FAILED(az_iot_hub_client_properties_writer_end_component(NULL, ref_json_writer));
_az_RETURN_IF_FAILED(az_json_writer_append_end_object(ref_json_writer));
return AZ_OK;
}
AZ_NODISCARD az_result az_iot_adu_client_parse_update_manifest(
az_iot_adu_client* client,
az_json_reader* ref_json_reader,
az_iot_adu_client_update_manifest* update_manifest)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_reader);
_az_PRECONDITION_NOT_NULL(update_manifest);
(void)client;
// Initialize the update_manifest with empty values.
update_manifest->manifest_version = AZ_SPAN_EMPTY;
update_manifest->update_id.name = AZ_SPAN_EMPTY;
update_manifest->update_id.provider = AZ_SPAN_EMPTY;
update_manifest->update_id.version = AZ_SPAN_EMPTY;
update_manifest->instructions.steps_count = 0;
update_manifest->files_count = 0;
update_manifest->create_date_time = AZ_SPAN_EMPTY;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
bool property_parsed = true;
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MANIFEST_VERSION)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->manifest_version = ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTRUCTIONS)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_STEPS)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_ARRAY);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_manifest->instructions.steps_count = 0;
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_ARRAY)
{
uint32_t step_index = update_manifest->instructions.steps_count;
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HANDLER)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->instructions.steps[step_index].handler
= ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILES)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_ARRAY);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_manifest->instructions.steps[step_index].files_count = 0;
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_ARRAY)
{
// If array isn't ended and we have reached max files allowed, next would overflow.
if (update_manifest->instructions.steps[step_index].files_count
== _az_IOT_ADU_CLIENT_MAX_FILE_COUNT_PER_STEP)
{
return AZ_ERROR_NOT_ENOUGH_SPACE;
}
uint32_t file_index = update_manifest->instructions.steps[step_index].files_count;
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->instructions.steps[step_index].files[file_index]
= ref_json_reader->token.slice;
update_manifest->instructions.steps[step_index].files_count++;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(
AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HANDLER_PROPERTIES)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_INSTALLED_CRITERIA)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->instructions.steps[step_index]
.handler_properties.installed_criteria
= ref_json_reader->token.slice;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_END_OBJECT);
}
else
{
return AZ_ERROR_JSON_INVALID_STATE;
}
}
else
{
return AZ_ERROR_JSON_INVALID_STATE;
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
update_manifest->instructions.steps_count++;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_END_OBJECT);
}
else
{
_az_LOG_WRITE(
AZ_LOG_IOT_ADU, AZ_SPAN_FROM_STR("Unexpected property found in ADU manifest steps:"));
_az_LOG_WRITE(AZ_LOG_IOT_ADU, ref_json_reader->token.slice);
return AZ_ERROR_JSON_INVALID_STATE;
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_UPDATE_ID)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_PROVIDER)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->update_id.provider = ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_NAME)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->update_id.name = ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_VERSION)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->update_id.version = ref_json_reader->token.slice;
}
else
{
_az_LOG_WRITE(
AZ_LOG_IOT_ADU,
AZ_SPAN_FROM_STR("Unexpected property found in ADU update id object:"));
_az_LOG_WRITE(AZ_LOG_IOT_ADU, ref_json_reader->token.slice);
return AZ_ERROR_JSON_INVALID_STATE;
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_COMPATIBILITY)))
{
/*
* According to ADU design, the ADU service compatibility properties
* are not intended to be consumed by the ADU agent.
* To save on processing, the properties are not being exposed.
*/
_az_RETURN_IF_FAILED(az_json_reader_skip_children(ref_json_reader));
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILES)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
uint32_t files_index = update_manifest->files_count;
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
// If object isn't ended and we have reached max files allowed, next would overflow.
if (files_index == _az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT)
{
return AZ_ERROR_NOT_ENOUGH_SPACE;
}
update_manifest->files[files_index].id = ref_json_reader->token.slice;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_FILE_NAME)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->files[files_index].file_name = ref_json_reader->token.slice;
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_SIZE_IN_BYTES)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_NUMBER);
_az_RETURN_IF_FAILED(az_json_token_get_int64(
&ref_json_reader->token, &update_manifest->files[files_index].size_in_bytes));
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_HASHES)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_BEGIN_OBJECT);
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
update_manifest->files[files_index].hashes_count = 0;
while (ref_json_reader->token.kind != AZ_JSON_TOKEN_END_OBJECT)
{
uint32_t hashes_count = update_manifest->files[files_index].hashes_count;
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_PROPERTY_NAME);
update_manifest->files[files_index].hashes[hashes_count].hash_type
= ref_json_reader->token.slice;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->files[files_index].hashes[hashes_count].hash_value
= ref_json_reader->token.slice;
update_manifest->files[files_index].hashes_count++;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
/*
* Embedded C SDK will not support delta updates at this time, so relatedFiles,
* downloadHandler, and mimeType are not exposed or processed.
*/
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_RELATED_FILES)))
{
_az_RETURN_IF_FAILED(az_json_reader_skip_children(ref_json_reader));
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_DOWNLOAD_HANDLER)))
{
_az_RETURN_IF_FAILED(az_json_reader_skip_children(ref_json_reader));
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_MIME_TYPE)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
else
{
return AZ_ERROR_JSON_INVALID_STATE;
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
update_manifest->files_count++;
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
}
else if (az_json_token_is_text_equal(
&ref_json_reader->token,
AZ_SPAN_FROM_STR(AZ_IOT_ADU_CLIENT_AGENT_PROPERTY_NAME_CREATED_DATE_TIME)))
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
RETURN_IF_JSON_TOKEN_NOT_TYPE((ref_json_reader), AZ_JSON_TOKEN_STRING);
update_manifest->create_date_time = ref_json_reader->token.slice;
}
else
{
property_parsed = false;
}
if (!property_parsed)
{
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
_az_RETURN_IF_FAILED(az_json_reader_skip_children(ref_json_reader));
}
_az_RETURN_IF_FAILED(az_json_reader_next_token(ref_json_reader));
}
return AZ_OK;
}

607
src/az_iot_adu_client.h Normal file
Просмотреть файл

@ -0,0 +1,607 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Definition for the Azure IoT ADU Client
*
* @note More details about Azure Device Update can be found online
* at https://docs.microsoft.com/azure/iot-hub-device-update/understand-device-update
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*
*/
#ifndef _az_IOT_ADU_H
#define _az_IOT_ADU_H
#include <az_json.h>
#include <az_result.h>
#include <az_span.h>
#include <az_iot_hub_client.h>
#include <az_iot_adu_internal.h>
#include <stdbool.h>
#include <stdint.h>
#include <_az_cfg_prefix.h>
/**
* @brief Define the ADU agent model ID.
*
* https://github.com/Azure/iot-plugandplay-models/blob/main/dtmi/azure/iot/deviceupdatecontractmodel-2.json
*/
#define AZ_IOT_ADU_CLIENT_AGENT_MODEL_ID "dtmi:azure:iot:deviceUpdateContractModel;2"
/**
* @brief ADU Agent Version
*/
#define AZ_IOT_ADU_CLIENT_AGENT_VERSION "DU;agent/1.0.0"
/**
* @brief ADU PnP Component Name
*/
#define AZ_IOT_ADU_CLIENT_PROPERTIES_COMPONENT_NAME "deviceUpdate"
/**
* @brief Default Agent Compatibility Properties
*/
#define AZ_IOT_ADU_CLIENT_AGENT_DEFAULT_COMPATIBILITY_PROPERTIES "manufacturer,model"
/**
* @brief Decision codes to accept or reject a received update deployment.
*/
typedef enum
{
/// ADU Service Response (Accept)
AZ_IOT_ADU_CLIENT_REQUEST_DECISION_ACCEPT = 200,
/// ADU Service Response (Reject)
AZ_IOT_ADU_CLIENT_REQUEST_DECISION_REJECT = 406
} az_iot_adu_client_request_decision;
/**
* @brief Agent states used to notify the ADU service of current state.
*/
typedef enum
{
/// ADU Agent State (Idle)
AZ_IOT_ADU_CLIENT_AGENT_STATE_IDLE = 0,
/// ADU Agent State (In Progress)
AZ_IOT_ADU_CLIENT_AGENT_STATE_DEPLOYMENT_IN_PROGRESS = 6,
/// ADU Agent State (Failed)
AZ_IOT_ADU_CLIENT_AGENT_STATE_FAILED = 255
} az_iot_adu_client_agent_state;
/**
* @brief Actions specified by the service for the agent to process.
*/
typedef enum
{
/// ADU Service Action (Apply)
AZ_IOT_ADU_CLIENT_SERVICE_ACTION_APPLY_DEPLOYMENT = 3,
/// ADU Service Action (Cancel)
AZ_IOT_ADU_CLIENT_SERVICE_ACTION_CANCEL = 255
} az_iot_adu_client_service_action;
/**
* @brief Identity of the update request.
* @remark This version refers to the update request itself.
* For verifying if an update request is applicable to an
* ADU agent, use the
* #az_iot_adu_client_update_manifest_instructions_step_handler_properties.installed_criteria.
*/
typedef struct
{
/**
* The provider for the update.
*/
az_span provider;
/**
* The name for the update.
*/
az_span name;
/**
* The version for the update.
*/
az_span version;
} az_iot_adu_update_id;
/**
* @brief Holds any user-defined custom properties for the device.
* @remark Implementer can define other device properties to be used
* for the compatibility check when targeting the update deployment.
*/
typedef struct
{
/**
* An array holding the custom names for the device properties.
*/
az_span names[_az_IOT_ADU_CLIENT_MAX_DEVICE_CUSTOM_PROPERTIES];
/**
* An array holding the custom values for the device properties.
*/
az_span values[_az_IOT_ADU_CLIENT_MAX_DEVICE_CUSTOM_PROPERTIES];
/**
* The number of custom names and values.
*/
int32_t count;
} az_iot_adu_device_custom_properties;
/**
* @brief Holds the ADU agent device properties.
*
* az_iot_adu_client_device_properties_default() must be called to first initialize the
* device properties.
*
* @remarks These properties are used by the ADU service for matching
* update groups and verifying the current update deployed.
* https://docs.microsoft.com/azure/iot-hub-device-update/device-update-plug-and-play
*/
typedef struct
{
/**
* The device manufacturer of the device, reported through deviceProperties.
*/
az_span manufacturer;
/**
* The device model of the device, reported through deviceProperties.
*/
az_span model;
/**
* Implementer can define other device properties to be used for the
* compatibility check while targeting the update deployment.
*/
az_iot_adu_device_custom_properties* custom_properties;
/**
* Version of the Device Update agent running on the device.
* @remark Must be set to AZ_IOT_ADU_CLIENT_AGENT_VERSION.
*/
az_span adu_version;
/**
* Version of the Delivery Optimization agent.
* @remark Please see Azure Device Update documentation on how to use
* the delivery optimization agent. If unused, set to #AZ_SPAN_EMPTY.
*
* https://docs.microsoft.com/azure/iot-hub-device-update/device-update-plug-and-play#device-properties
*/
az_span delivery_optimization_agent_version;
/**
* An ID of the update that is currently installed.
*/
az_span update_id;
} az_iot_adu_client_device_properties;
/**
* @brief The update step result reported by the agent.
*
* This helps provide detailed results for a specific step of the update process.
*
*/
typedef struct
{
/**
* A code that contains information about the result of the last update action.
* Example: 700
*/
int32_t result_code;
/**
* A code that contains additional information about the result.
* Example: 0x80004005
*/
int32_t extended_result_code;
/**
* Customer-defined free form string to provide additional result details.
*/
az_span result_details;
} az_iot_adu_client_step_result;
/**
* @brief The update result reported by the agent.
*
* This details the result for the overall update.
*/
typedef struct
{
/**
* A code that contains information about the result of the last update action.
* Example: 700
*/
int32_t result_code;
/**
* A code that contains additional information about the result.
* Example: 0x80004006
*/
int32_t extended_result_code;
/**
* Customer-defined free form string to provide additional result details.
*/
az_span result_details;
/**
* Number of items in \p step_results.
*/
int32_t step_results_count;
/**
* The results for each step in the update manifest instructions.
* The number of steps MUST match the number of steps in the
* update manifest for the resulting state to be property generated.
*/
az_iot_adu_client_step_result step_results[_az_IOT_ADU_CLIENT_MAX_INSTRUCTIONS_STEPS];
} az_iot_adu_client_install_result;
/**
* @brief A set of values that indicate which deployment the agent is currently working on.
*
*/
typedef struct
{
/**
* An integer that corresponds to an action the agent should perform.
* @remark Refer to the #az_iot_adu_client_service_action for possible values
*/
az_iot_adu_client_service_action action;
/**
* ID of current deployment.
*/
az_span id;
/**
* Time of last deployment retry.
*/
az_span retry_timestamp;
} az_iot_adu_client_workflow;
/**
* @brief A map of file ID to download url.
*/
typedef struct
{
/**
* File ID, mapped in the updated manifest.
*/
az_span id;
/**
* Complete url to a file.
*/
az_span url;
} az_iot_adu_client_file_url;
/**
* @brief Structure that holds the parsed contents of the ADU
* request in the Plug and Play writable properties sent
* by the ADU service.
*/
typedef struct
{
/**
* A set of values that indicate which deployment the agent is currently working on.
*/
az_iot_adu_client_workflow workflow;
/**
* Description of the content of an update.
* @note This will come as an escaped string. This is done to guarantee ordering
* of JSON values so that we may verify a signature over the payload.
* The user must unescape it using an API such as az_json_string_unescape() before
* subsequently calling az_iot_adu_client_parse_update_manifest() with it.
*/
az_span update_manifest;
/**
* A JSON Web Signature (JWS) with JSON Web Keys used for source verification.
*/
az_span update_manifest_signature;
/**
* An array of files associated with the deployment. These are then correlated
* with specific steps of the update via their IDs.
*/
az_iot_adu_client_file_url file_urls[_az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT];
/**
* Number of items in \p file_urls.
*/
uint32_t file_urls_count;
} az_iot_adu_client_update_request;
/**
* @brief User-defined properties for handling an update request.
*
*/
typedef struct
{
/**
* The installed criteria which defines a successful installation for a given step.
*/
az_span installed_criteria;
} az_iot_adu_client_update_manifest_instructions_step_handler_properties;
/**
* @brief Step in the instructions of an update manifest.
*
*/
typedef struct
{
/**
* Name of the update agent handler type that is expected to handle the step.
* @remark For more details on handler types, please see here:
* https://learn.microsoft.com/azure/iot-hub-device-update/device-update-agent-overview#update-handlers
*
* Generally, for full image updates on embedded devices, the update handler type will
* be `microsoft/swupdate:<version-number>`.
*/
az_span handler;
/**
* Files needed for this update step, as an array of file ids. These ids
* can also be found in #az_iot_adu_client_update_manifest.files
* with their respective urls.
*/
az_span files[_az_IOT_ADU_CLIENT_MAX_FILE_COUNT_PER_STEP];
/**
* Number of items in \p files.
*/
uint32_t files_count;
/**
* Additional user-defined properties for the update step handler.
*/
az_iot_adu_client_update_manifest_instructions_step_handler_properties handler_properties;
} az_iot_adu_client_update_manifest_instructions_step;
/**
* @brief Instructions in the update manifest.
*/
typedef struct
{
/**
* Steps for the instructions in an update request.
*/
az_iot_adu_client_update_manifest_instructions_step
steps[_az_IOT_ADU_CLIENT_MAX_INSTRUCTIONS_STEPS];
/**
* Number of items in \p steps.
*/
uint32_t steps_count;
} az_iot_adu_client_update_manifest_instructions;
/**
* @brief Hash value for a given file.
*
*/
typedef struct
{
/**
* The hash type for the file (Example: sha256).
*/
az_span hash_type;
/**
* The value of the hash.
*/
az_span hash_value;
} az_iot_adu_client_update_manifest_file_hash;
/**
* @brief Details of a file referenced in the update request.
*
* @note C SDK will not support delta updates at this time, so relatedFiles,
* downloadHandler, and mimeType are not exposed or processed.
*/
typedef struct
{
/**
* Identity of a file, correlated with the same id in #az_iot_adu_client_file_url
* and #az_iot_adu_client_update_manifest_instructions_step.files.
*/
az_span id;
/**
* Name of the file.
*/
az_span file_name;
/**
* Size of a file, in bytes.
*/
int64_t size_in_bytes;
/**
* Hashes provided for a given file in the update request.
*/
az_iot_adu_client_update_manifest_file_hash hashes[_az_IOT_ADU_CLIENT_MAX_FILE_HASH_COUNT];
/**
* Number of items in \p hashes.
*/
uint32_t hashes_count;
} az_iot_adu_client_update_manifest_file;
/**
* @brief Structure that holds the parsed contents of the update manifest
* sent by the ADU service.
*/
typedef struct
{
/**
* Version of the update manifest schema.
*/
az_span manifest_version;
/**
* User-defined identity of the update manifest.
*/
az_iot_adu_update_id update_id;
/**
* Instructions of the update manifest.
*/
az_iot_adu_client_update_manifest_instructions instructions;
/**
* Download urls for the files referenced in the update manifest instructions.
*/
az_iot_adu_client_update_manifest_file files[_az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT];
/**
* Number of items in \p files.
*/
uint32_t files_count;
/**
* The creation date and time.
*/
az_span create_date_time;
} az_iot_adu_client_update_manifest;
/**
* @brief User-defined options for the Azure IoT ADU client.
*
*/
typedef struct
{
/**
* The custom device compatibility properties for the device.
*/
az_span device_compatibility_properties;
} az_iot_adu_client_options;
/**
* @brief Structure that holds the state of the Azure IoT ADU client.
*
*/
typedef struct
{
struct
{
az_iot_adu_client_options options;
} _internal;
} az_iot_adu_client;
/**
* @brief Gets the default Azure IoT ADU Client options.
* @details Call this to obtain an initialized #az_iot_adu_client_options structure that can be
* afterwards modified and passed to #az_iot_adu_client_init.
*
* @return #az_iot_adu_client_options.
*/
AZ_NODISCARD az_iot_adu_client_options az_iot_adu_client_options_default();
/**
* @brief Gets the default #az_iot_adu_client_device_properties.
* @details Call this to obtain an initialized #az_iot_adu_client_device_properties structure that
* can be afterwards modified and passed to necessary APIs.
*
* @return #az_iot_adu_client_device_properties.
*/
AZ_NODISCARD az_iot_adu_client_device_properties az_iot_adu_client_device_properties_default();
/**
* @brief Initializes an Azure IoT ADU Client.
*
* @param client The #az_iot_adu_client to use for this call.
* @param options A reference to an #az_iot_adu_client_options structure. If `NULL` is passed,
* the adu client will use the default options. If using custom options, please initialize first by
* calling az_iot_adu_client_options_default() and then populating relevant options with your own
* values.
* @pre \p client must not be `NULL`.
* @return An #az_result value indicating the result of the operation.
*/
AZ_NODISCARD az_result
az_iot_adu_client_init(az_iot_adu_client* client, az_iot_adu_client_options* options);
/**
* @brief Verifies if the Azure Plug-and-Play writable properties component
* is for ADU device update.
*
* @param[in] client The #az_iot_adu_client to use for this call.
* @param[in] component_name #az_span pointing to the component name in the
* writable properties.
* @return A boolean indicating if the component name is for ADU device update.
*/
AZ_NODISCARD bool az_iot_adu_client_is_component_device_update(
az_iot_adu_client* client,
az_span component_name);
/**
* @brief Generates the Azure Plug-and-Play (reported) properties payload
* with the state of the ADU agent.
*
* @param[in] client The #az_iot_adu_client to use for this call.
* @param[in] device_properties A pointer to a #az_iot_adu_client_device_properties
* structure with all the details of the device,
* as required by the ADU service.
* @param[in] agent_state An integer value indicating the current state of
* the ADU agent. Use the values defined by the
* #az_iot_adu_client_agent_state.
* Please see the ADU online documentation for more
* details.
* @param[in] workflow A pointer to a #az_iot_adu_client_workflow instance
* indicating the current ADU workflow being processed,
* if an ADU service workflow was received. Use NULL
* if no device update is in progress.
* @param[in] last_install_result A pointer to a #az_iot_adu_client_install_result
* instance with the results of the current or past
* device update workflow, if available. Use NULL
* if no results are available.
* @param[in,out] ref_json_writer An #az_json_writer initialized with the memory where
* to write the property payload.
* @return An #az_result value indicating the result of the operation.
*/
AZ_NODISCARD az_result az_iot_adu_client_get_agent_state_payload(
az_iot_adu_client* client,
az_iot_adu_client_device_properties* device_properties,
az_iot_adu_client_agent_state agent_state,
az_iot_adu_client_workflow* workflow,
az_iot_adu_client_install_result* last_install_result,
az_json_writer* ref_json_writer);
/**
* @brief Parses the json content from the ADU service writable properties into
* a pre-defined structure.
*
* @param[in] client The #az_iot_adu_client to use for this call.
* @param[in] ref_json_reader A #az_json_reader initialized with the ADU
* service writable properties json, set to the
* beginning of the json object that is the value
* of the ADU component.
* @param[out] update_request A pointer to the #az_iot_adu_client_update_request
* structure where to store the parsed contents
* read from the `ref_json_reader` json reader.
* In summary, this structure holds #az_span
* instances that point to the actual data
* parsed from `ref_json_reader` and copied to `buffer`.
* @return An #az_result value indicating the result of the operation.
*/
AZ_NODISCARD az_result az_iot_adu_client_parse_service_properties(
az_iot_adu_client* client,
az_json_reader* ref_json_reader,
az_iot_adu_client_update_request* update_request);
/**
* @brief Generates the payload necessary to respond to the service
after receiving incoming properties.
*
* @param[in] client The #az_iot_adu_client to use for this call.
* @param[in] version Version of the writable properties.
* @param[in] status Azure Plug-and-Play status code for the
* writable properties acknowledgement.
* @param[in] ref_json_writer An #az_json_writer pointing to the memory buffer where to
* write the resulting Azure Plug-and-Play properties.
* @return An #az_result value indicating the result of the operation.
*/
AZ_NODISCARD az_result az_iot_adu_client_get_service_properties_response(
az_iot_adu_client* client,
int32_t version,
az_iot_adu_client_request_decision status,
az_json_writer* ref_json_writer);
/**
* @brief Parses the json content from the ADU service update manifest into
* a pre-defined structure.
*
* @param[in] client The #az_iot_adu_client to use for this call.
* @param[in] ref_json_reader ADU update manifest, as initialized json reader.
* @param[out] update_manifest The structure where the parsed values of the
* manifest are stored. Values are not copied from
* `payload`, the fields of the structure just
* point to the positions in `payload` where the
* data is present, except for numeric and boolean
* values (which are parsed into the respective
* data types).
* @return An #az_result value indicating the result of the operation.
*/
AZ_NODISCARD az_result az_iot_adu_client_parse_update_manifest(
az_iot_adu_client* client,
az_json_reader* ref_json_reader,
az_iot_adu_client_update_manifest* update_manifest);
#include <_az_cfg_suffix.h>
#endif // _az_IOT_ADU_H

39
src/az_iot_adu_internal.h Normal file
Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines internal macros used by the ADU client.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
// Maximum Number of Files Handled by this ADU Agent (per step)
// This must be no larger than #_az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT.
#ifndef _az_IOT_ADU_CLIENT_MAX_FILE_COUNT_PER_STEP
#define _az_IOT_ADU_CLIENT_MAX_FILE_COUNT_PER_STEP (2)
#endif // _az_IOT_ADU_CLIENT_MAX_FILE_COUNT_PER_STEP
// Maximum Number of Files Handled by this ADU Agent (total files for this deployment)
#ifndef _az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT
#define _az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT (2)
#endif // _az_IOT_ADU_CLIENT_MAX_TOTAL_FILE_COUNT
// Maximum Number of Steps Handled by this ADU Agent
#ifndef _az_IOT_ADU_CLIENT_MAX_INSTRUCTIONS_STEPS
#define _az_IOT_ADU_CLIENT_MAX_INSTRUCTIONS_STEPS (2)
#endif // _az_IOT_ADU_CLIENT_MAX_INSTRUCTIONS_STEPS
// Maximum Number of File Hashes Handled by this ADU Agent
#ifndef _az_IOT_ADU_CLIENT_MAX_FILE_HASH_COUNT
#define _az_IOT_ADU_CLIENT_MAX_FILE_HASH_COUNT (2)
#endif // _az_IOT_ADU_CLIENT_MAX_FILE_HASH_COUNT
// Maximum Number of Custom Device Properties
#ifndef _az_IOT_ADU_CLIENT_MAX_DEVICE_CUSTOM_PROPERTIES
#define _az_IOT_ADU_CLIENT_MAX_DEVICE_CUSTOM_PROPERTIES (5)
#endif // _az_IOT_ADU_CLIENT_MAX_DEVICE_CUSTOM_PROPERTIES

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

@ -57,6 +57,9 @@ enum az_log_classification_iot
AZ_LOG_IOT_AZURERTOS
= _az_LOG_MAKE_CLASSIFICATION(_az_FACILITY_IOT, 3), ///< Azure IoT classification for Azure RTOS.
AZ_LOG_IOT_ADU
= _az_LOG_MAKE_CLASSIFICATION(_az_FACILITY_IOT, 4), ///< Azure IoT classification for ADU APIs.
};
enum
@ -162,14 +165,8 @@ typedef struct
/**
* @brief Initializes the Telemetry or C2D properties.
*
* @note The properties init API will not encode properties. In order to support
* the following characters, they must be percent-encoded (RFC3986) as follows:
* - `/` : `%2F`
* - `%` : `%25`
* - `#` : `%23`
* - `&` : `%26`
* Only these characters would have to be encoded. If you would like to avoid the need to
* encode the names/values, avoid using these characters in names and values.
* @note The properties must adhere to the character restrictions listed in the below link.
* https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-construct
*
* @param[in] properties The #az_iot_message_properties to initialize.
* @param[in] buffer Can either be an unfilled (but properly sized) #az_span or an #az_span
@ -190,14 +187,8 @@ AZ_NODISCARD az_result az_iot_message_properties_init(
/**
* @brief Appends a name-value property to the list of properties.
*
* @note The properties append API will not encode properties. In order to support
* the following characters, they must be percent-encoded (RFC3986) as follows:
* `/` : `%2F`
* `%` : `%25`
* `#` : `%23`
* `&` : `%26`
* Only these characters would have to be encoded. If you would like to avoid the need to
* encode the names/values, avoid using these characters in names and values.
* @note The properties must adhere to the character restrictions listed in the below link.
* https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-construct
*
* @param[in] properties The #az_iot_message_properties to use for this call.
* @param[in] name The name of the property. Must be a valid, non-empty span.

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

@ -21,13 +21,15 @@ static const az_span hub_client_param_equals_span = AZ_SPAN_LITERAL_FROM_STR("="
static const az_span hub_digital_twin_model_id = AZ_SPAN_LITERAL_FROM_STR("model-id");
static const az_span hub_service_api_version = AZ_SPAN_LITERAL_FROM_STR("/?api-version=2020-09-30");
static const az_span client_sdk_version
= AZ_SPAN_LITERAL_FROM_STR("DeviceClientType=c%2F" AZ_SDK_VERSION_STRING);
static const az_span client_sdk_device_client_type_name
= AZ_SPAN_LITERAL_FROM_STR("DeviceClientType");
static const az_span client_sdk_version_default_value
= AZ_SPAN_LITERAL_FROM_STR("azsdk-c%2F" AZ_SDK_VERSION_STRING);
AZ_NODISCARD az_iot_hub_client_options az_iot_hub_client_options_default()
{
return (az_iot_hub_client_options){ .module_id = AZ_SPAN_EMPTY,
.user_agent = client_sdk_version,
.user_agent = client_sdk_version_default_value,
.model_id = AZ_SPAN_EMPTY,
.component_names = NULL,
.component_names_length = 0 };
@ -76,7 +78,9 @@ AZ_NODISCARD az_result az_iot_hub_client_get_user_name(
}
if (az_span_size(*user_agent) > 0)
{
required_length += az_span_size(*user_agent) + az_span_size(hub_client_param_separator_span);
required_length += az_span_size(hub_client_param_separator_span)
+ az_span_size(client_sdk_device_client_type_name)
+ az_span_size(hub_client_param_equals_span) + az_span_size(*user_agent);
}
// Note we skip the length of the model id since we have to url encode it. Bound checking is done
// later.
@ -104,6 +108,8 @@ AZ_NODISCARD az_result az_iot_hub_client_get_user_name(
if (az_span_size(*user_agent) > 0)
{
remainder = az_span_copy_u8(remainder, *az_span_ptr(hub_client_param_separator_span));
remainder = az_span_copy(remainder, client_sdk_device_client_type_name);
remainder = az_span_copy_u8(remainder, *az_span_ptr(hub_client_param_equals_span));
remainder = az_span_copy(remainder, *user_agent);
}

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

@ -43,6 +43,10 @@ typedef struct
{
/**
* The module name (if a module identity is used).
* Must conform to the requirements of the MQTT spec for topic
* names (listed below) and of the IoT Hub (listed below)
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106
* https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#device-identity-properties
*/
az_span module_id;
@ -94,12 +98,10 @@ AZ_NODISCARD az_iot_hub_client_options az_iot_hub_client_options_default();
*
* @param[out] client The #az_iot_hub_client to use for this call.
* @param[in] iot_hub_hostname The IoT Hub Hostname.
* @param[in] device_id The Device ID. If the ID contains any of the following characters, they must
* be percent-encoded as follows:
* - `/` : `%2F`
* - `%` : `%25`
* - `#` : `%23`
* - `&` : `%26`
* @param[in] device_id The Device ID. Must conform to the requirements of the MQTT spec for
* topic names (listed below) and of the IoT Hub (listed below)
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106
* https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#device-identity-properties
* @param[in] options A reference to an #az_iot_hub_client_options structure. If `NULL` is passed,
* the hub client will use the default options. If using custom options, please initialize first by
* calling az_iot_hub_client_options_default() and then populating relevant options with your own

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

@ -65,7 +65,6 @@ AZ_NODISCARD az_result az_iot_hub_client_properties_writer_begin_component(
az_json_writer* ref_json_writer,
az_span component_name)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
_az_PRECONDITION_VALID_SPAN(component_name, 1, false);
@ -85,7 +84,6 @@ AZ_NODISCARD az_result az_iot_hub_client_properties_writer_end_component(
az_iot_hub_client const* client,
az_json_writer* ref_json_writer)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
(void)client;
@ -101,7 +99,6 @@ AZ_NODISCARD az_result az_iot_hub_client_properties_writer_begin_response_status
int32_t version,
az_span description)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
_az_PRECONDITION_VALID_SPAN(property_name, 1, false);
@ -133,7 +130,6 @@ AZ_NODISCARD az_result az_iot_hub_client_properties_writer_end_response_status(
az_iot_hub_client const* client,
az_json_writer* ref_json_writer)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_writer);
(void)client;
@ -201,7 +197,6 @@ AZ_NODISCARD az_result az_iot_hub_client_properties_get_properties_version(
az_iot_hub_client_properties_message_type message_type,
int32_t* out_version)
{
_az_PRECONDITION_NOT_NULL(client);
_az_PRECONDITION_NOT_NULL(ref_json_reader);
_az_PRECONDITION(
(message_type == AZ_IOT_HUB_CLIENT_PROPERTIES_MESSAGE_TYPE_WRITABLE_UPDATED)

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

@ -173,8 +173,17 @@ AZ_NODISCARD az_result az_iot_hub_client_twin_parse_received_topic(
{
// Is a reported prop response
out_response->response_type = AZ_IOT_HUB_CLIENT_TWIN_RESPONSE_TYPE_REPORTED_PROPERTIES;
_az_RETURN_IF_FAILED(az_iot_message_properties_find(
&props, az_iot_hub_twin_version_prop, &out_response->version));
result = az_iot_message_properties_find(
&props, az_iot_hub_twin_version_prop, &out_response->version);
if (result == AZ_ERROR_ITEM_NOT_FOUND)
{
out_response->version = AZ_SPAN_EMPTY;
}
else
{
_az_RETURN_IF_FAILED(result);
}
}
else // 200 or 202
{

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

@ -75,7 +75,8 @@ AZ_NODISCARD az_iot_provisioning_client_options az_iot_provisioning_client_optio
* @param[in] global_device_hostname The device provisioning services global host name.
* @param[in] id_scope The ID Scope.
* @param[in] registration_id The Registration ID. This must match the client certificate name (CN
* part of the certificate subject).
* part of the certificate subject). Must conform to the limitations listed in the link below:
* https://docs.microsoft.com/azure/iot-dps/concepts-service#registration-id
* @param[in] options __[nullable]__ A reference to an #az_iot_provisioning_client_options
* structure. Can be `NULL` for default options.
* @pre \p client must not be `NULL`.
@ -83,6 +84,7 @@ AZ_NODISCARD az_iot_provisioning_client_options az_iot_provisioning_client_optio
* @pre \p id_scope must be a valid span of size greater than 0.
* @pre \p registration_id must be a valid span of size greater than 0.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The client was initialized successfully.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_init(
az_iot_provisioning_client* client,
@ -105,6 +107,8 @@ AZ_NODISCARD az_result az_iot_provisioning_client_init(
* @pre \p mqtt_user_name must not be `NULL`.
* @pre \p mqtt_user_name_size must be greater than 0.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The user name was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_get_user_name(
az_iot_provisioning_client const* client,
@ -126,6 +130,8 @@ AZ_NODISCARD az_result az_iot_provisioning_client_get_user_name(
* @pre \p mqtt_client_id must not be `NULL`.
* @pre \p mqtt_client_id_size must be greater than 0.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The client id was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_get_client_id(
az_iot_provisioning_client const* client,
@ -163,6 +169,8 @@ AZ_NODISCARD az_result az_iot_provisioning_client_get_client_id(
* @pre \p signature must be a valid span of size greater than 0.
* @pre \p out_signature must not be `NULL`.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The signature was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_signature(
az_iot_provisioning_client const* client,
@ -193,7 +201,9 @@ AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_signature(
* @pre \p token_expiration_epoch_time must be greater than 0.
* @pre \p mqtt_password must not be `NULL`.
* @pre \p mqtt_password_size must be greater than 0.
* @return An #az_result value indicating the result of the operation..
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The password was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_sas_get_password(
az_iot_provisioning_client const* client,
@ -346,6 +356,7 @@ typedef struct
* @pre \p received_payload must be a valid span of size greater than or equal to 0.
* @pre \p out_response must not be `NULL`.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The topic and payload were parsed successfully.
* @retval #AZ_ERROR_IOT_TOPIC_NO_MATCH If the topic is not matching the expected format.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_parse_received_topic_and_payload(
@ -390,6 +401,8 @@ AZ_INLINE bool az_iot_provisioning_client_operation_complete(
* @pre \p mqtt_topic must not be `NULL`.
* @pre \p mqtt_topic_size must be greater than 0.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The topic was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_register_get_publish_topic(
az_iot_provisioning_client const* client,
@ -415,6 +428,8 @@ AZ_NODISCARD az_result az_iot_provisioning_client_register_get_publish_topic(
* @pre \p mqtt_topic must not be `NULL`.
* @pre \p mqtt_topic_size must be greater than 0.
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The topic was created successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
*/
AZ_NODISCARD az_result az_iot_provisioning_client_query_status_get_publish_topic(
az_iot_provisioning_client const* client,

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

@ -285,7 +285,7 @@ typedef struct
*/
AZ_NODISCARD AZ_INLINE az_json_writer_options az_json_writer_options_default()
{
az_json_writer_options options = (az_json_writer_options) {
az_json_writer_options options = {
._internal = {
.unused = false,
},
@ -303,17 +303,39 @@ AZ_NODISCARD AZ_INLINE az_json_writer_options az_json_writer_options_default()
*/
typedef struct
{
/// The total number of bytes written by the #az_json_writer to the output destination buffer(s).
/// This read-only field tracks the number of bytes of JSON written so far, and it shouldn't be
/// modified by the caller.
int32_t total_bytes_written;
struct
{
/// The destination to write the JSON into.
az_span destination_buffer;
int32_t bytes_written;
// For single contiguous buffer, bytes_written == total_bytes_written
int32_t total_bytes_written; // Currently, this is primarily used for testing.
/// The bytes written in the current destination buffer.
int32_t bytes_written; // For single contiguous buffer, bytes_written == total_bytes_written
/// Allocator used to support non-contiguous buffer as a destination.
az_span_allocator_fn allocator_callback;
/// Any struct that was provided by the user for their specific implementation, passed through
/// to the #az_span_allocator_fn.
void* user_context;
/// A state to remember when to emit a comma between JSON array and object elements.
bool need_comma;
/// The current state of the writer based on the last token written, used for validating the
/// correctness of the JSON being written.
az_json_token_kind token_kind; // needed for validation, potentially #if/def with preconditions.
/// The current state of the writer based on the last JSON container it is in (whether array or
/// object), used for validating the correctness of the JSON being written, and so it doesn't
/// overflow the maximum supported depth.
_az_json_bit_stack bit_stack; // needed for validation, potentially #if/def with preconditions.
/// A copy of the options provided by the user.
az_json_writer_options options;
} _internal;
} az_json_writer;
@ -390,18 +412,17 @@ az_json_writer_get_bytes_used_in_destination(az_json_writer const* json_writer)
/**
* @brief Appends the UTF-8 text value (as a JSON string) into the buffer.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* theoretically space, note that the JSON writer requires at least 64-bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires at least 64-bytes of space when writing any chunk of data larger than 10 characters
* because it tries to write in 64 byte chunks (10 character * 6 if all need to be escaped into the
* unicode form).
*
* @param[in,out] ref_json_writer A pointer to an #az_json_writer instance containing the buffer to
* append the string value to.
* @param[in] value The UTF-8 encoded value to be written as a JSON string. The value is escaped
* before writing.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* sufficient space, note that the JSON writer requires at least 64 bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires this extra space because it tries to write formatted text in chunks rather than one
* character at a time, whenever the input data is dynamic in size.
*
* @remarks If \p value is #AZ_SPAN_EMPTY, the empty JSON string value is written (i.e. "").
*
* @return An #az_result value indicating the result of the operation.
@ -420,6 +441,12 @@ AZ_NODISCARD az_result az_json_writer_append_string(az_json_writer* ref_json_wri
* is, without any formatting or spacing changes. No modifications are made to this text, including
* escaping.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* sufficient space, note that the JSON writer requires at least 64 bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires this extra space because it tries to write formatted text in chunks rather than one
* character at a time, whenever the input data is dynamic in size.
*
* @remarks A single, possibly nested, JSON value is one that starts and ends with {} or [] or is a
* single primitive token. The JSON cannot start with an end object or array, or a property name, or
* be incomplete.
@ -427,13 +454,6 @@ AZ_NODISCARD az_result az_json_writer_append_string(az_json_writer* ref_json_wri
* @remarks The function validates that the provided JSON to be appended is valid and properly
* escaped, and fails otherwise.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* theoretically space, note that the JSON writer requires at least 64-bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires at least 64-bytes of space when writing any chunk of data larger than 10 characters
* because it tries to write in 64 byte chunks (10 character * 6 if all need to be escaped into the
* unicode form).
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The provided \p json_text was appended successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The destination is too small for the provided \p json_text.
@ -456,6 +476,12 @@ az_json_writer_append_json_text(az_json_writer* ref_json_writer, az_span json_te
* @param[in] name The UTF-8 encoded property name of the JSON value to be written. The name is
* escaped before writing.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* sufficient space, note that the JSON writer requires at least 64 bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires this extra space because it tries to write formatted text in chunks rather than one
* character at a time, whenever the input data is dynamic in size.
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The property name was appended successfully.
* @retval #AZ_ERROR_NOT_ENOUGH_SPACE The buffer is too small.
@ -484,11 +510,10 @@ AZ_NODISCARD az_result az_json_writer_append_bool(az_json_writer* ref_json_write
* @param[in] value The value to be written as a JSON number.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* theoretically space, note that the JSON writer requires at least 64-bytes of slack within the
* sufficient space, note that the JSON writer requires at least 64 bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires at least 64-bytes of space when writing any chunk of data larger than 10 characters
* because it tries to write in 64 byte chunks (10 character * 6 if all need to be escaped into the
* unicode form).
* requires this extra space because it tries to write formatted text in chunks rather than one
* character at a time, whenever the input data is dynamic in size.
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The number was appended successfully.
@ -506,11 +531,10 @@ AZ_NODISCARD az_result az_json_writer_append_int32(az_json_writer* ref_json_writ
* point and truncate the rest.
*
* @note If you receive an #AZ_ERROR_NOT_ENOUGH_SPACE result while appending data for which there is
* theoretically space, note that the JSON writer requires at least 64-bytes of slack within the
* sufficient space, note that the JSON writer requires at least 64 bytes of slack within the
* output buffer, above the theoretical minimal space needed. The JSON writer pessimistically
* requires at least 64-bytes of space when writing any chunk of data larger than 10 characters
* because it tries to write in 64 byte chunks (10 character * 6 if all need to be escaped into the
* unicode form).
* requires this extra space because it tries to write formatted text in chunks rather than one
* character at a time, whenever the input data is dynamic in size.
*
* @return An #az_result value indicating the result of the operation.
* @retval #AZ_OK The number was appended successfully.
@ -621,7 +645,7 @@ typedef struct
*/
AZ_NODISCARD AZ_INLINE az_json_reader_options az_json_reader_options_default()
{
az_json_reader_options options = (az_json_reader_options) {
az_json_reader_options options = {
._internal = {
.unused = false,
},
@ -768,6 +792,29 @@ AZ_NODISCARD az_result az_json_reader_next_token(az_json_reader* ref_json_reader
*/
AZ_NODISCARD az_result az_json_reader_skip_children(az_json_reader* ref_json_reader);
/**
* @brief Unescapes the JSON string within the provided #az_span.
*
* @param[in] json_string The #az_span that contains the string to be unescaped.
* @param destination The destination buffer used to write the unescaped output into.
*
* @return An #az_span that is a slice of the \p destination #az_span containing the unescaped JSON
* string, which denotes the length of the unescaped string.
*
* @remarks For user-defined or unknown input, the buffer referred to by \p destination must be at
* least as large as the \p json_string #az_span. Content is copied from the source buffer, while
* unescaping.
*
* @remarks This function assumes that the \p json_string input is well-formed JSON.
*
* @remarks This function assumes that the \p destination has a large enough size to hold the
* unescaped \p json_string.
*
* @remarks This API can also be used to perform in place unescaping. However, doing so, is
* destructive and the input JSON may no longer be valid or parsable.
*/
AZ_NODISCARD az_span az_json_string_unescape(az_span json_string, az_span destination);
#include <_az_cfg_suffix.h>
#endif // _az_JSON_H

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

@ -1,6 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
*
* @brief Defines private implementation used by json.
*
* @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.)
* prefixed with an underscore ('_') directly in your application code. These symbols
* are part of Azure SDK's internal implementation; we do not document these symbols
* and they are subject to change in future versions of the SDK which would break your code.
*/
#ifndef _az_JSON_PRIVATE_H
#define _az_JSON_PRIVATE_H
@ -109,6 +120,24 @@ AZ_NODISCARD AZ_INLINE _az_json_stack_item _az_json_stack_peek(_az_json_bit_stac
: _az_JSON_STACK_ARRAY;
}
AZ_NODISCARD AZ_INLINE bool _az_is_valid_escaped_character(uint8_t byte)
{
switch (byte)
{
case '\\':
case '"':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
return true;
default:
return false;
}
}
#include <_az_cfg_suffix.h>
#endif // _az_SPAN_PRIVATE_H

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше