适用于 C 的 Azure IoT 设备 SDK
Azure IoT 设备 SDK 是一个库集,旨在简化从 Azure IoT 中心服务发送和接收消息的过程。 有各种不同的 SDK,每个 SDK 都以特定的平台为目标,而本文说明的是适用于 C 语言的 Azure IoT 设备 SDK。
注意
嵌入式 C SDK 是支持自带网络 (BYON) 方法的受限制设备的替代项。 IoT 开发人员可以自由选择使用 MQTT 客户端、TLS 和套接字来创建设备解决方案。 详细了解嵌入式 C SDK。
注意
本文中提到的某些功能(例如云到设备消息传递、设备孪生、设备管理)仅在 IoT 中心的标准层中提供。 有关 IoT 中心基本层和标准/免费层的详细信息,请参阅选择适合你的解决方案的 IoT 中心层。
适用于 C 语言的 Azure IoT 设备 SDK 以 ANSI C (C99) 编写,以获得最大可移植性。 此功能使得这些库很适合在多个平台和设备上运行,尤其是在以将磁盘和内存占用量降到最低作为优先考虑的情况下。
SDK 已在许多平台上进行了测试(有关详细信息,请参阅 Azure IoT 认证设备目录)。 尽管本文包含的是在 Windows 平台上运行的示例代码演示,但本文所述的代码在各种支持的平台上都完全相同。
本文介绍适用于 C 语言的 Azure IoT 设备 SDK 的体系结构,将演示如何初始化设备库,将数据发送到 IoT 中心,以及从 IoT 中心接收消息。 本文中的信息应足以让你开始使用 SDK,但同时也提供了有关库的其他信息的链接。
SDK 体系结构
可在 GitHub 存储库中找到适用于 C 语言的 Azure IoT 设备 SDK,还可在 C API 参考中查看 API 的详细信息。
在此存储库的主分支中可找到最新版本的库:
此 SDK 的核心实现可在 iothub_client 文件夹中找到,此文件夹包含 SDK 的最低 API 层的实现:IoTHubClient 库。 此 IoTHubClient 库包含实现原始消息传送的 API,即将消息发送到 IoT 中心以及从 IoT 中心接收消息。 使用此库时,需要负责实现消息序列化,但与 IoT 中心通信的其他细节则由系统处理。
IoTHubClient 库依赖于其他开放源代码库:
Azure C 共享实用程序库,其常用功能用于很多 Azure 相关的 C SDK 中所需的基本任务(如字符串、列表操作和 IO 等)。
Azure uAMQP 库,此库是针对资源约束设备的 AMQP 客户端实现的优化。
Azure uMQTT 库,它是实现 MQTT 协议并针对资源约束设备进行了优化的通用型库。
查看示例代码可以更方便地了解这些库的用法。 以下部分演练 SDK 中包含的几个示例应用程序。 此演练应可让你轻松了解 SDK 体系结构层的各种功能以及 API 工作原理的简介。
运行示例之前
在面向 C 的 Azure IoT 设备 SDK 中运行示例之前,必须在 Azure 订阅中创建 IoT 中心服务的实例。 然后完成以下任务:
- 准备开发环境
- 获取设备凭据。
准备开发环境
为常用平台提供了包(例如适用于 Windows 的 NuGet 包或者适用于 Debian 和 Ubuntu 的 apt_get),示例将使用这些包(如果适用)。 在某些情况下,需要为设备编译 SDK,或者在设备上编译 SDK。 如果需要编译 SDK,请参阅 GitHub 存储库中的准备开发环境。
若要获取示例应用程序代码,请从 GitHub 下载 SDK 的副本。 从 GitHub 存储库的主分支获取源的副本。
获取设备凭据
获取示例源代码后,下一步是获取一组设备凭据。 要使设备能够访问 IoT 中心,必须先将该设备添加到 IoT 中心标识注册表。 添加设备时,需要获取一组所需的设备凭据,以便设备能够连接到 IoT 中心。 下一部分所述示例应用程序的预期凭据格式为设备连接字符串。
有几个开源工具可帮助你管理 IoT 中心。
一个是称为 Azure IoT 资源管理器的 Windows 应用程序。
一个称为 Azure IoT 工具的跨平台 Visual Studio Code 扩展。
一个称为适用于 Azure CLI 的 IoT 扩展的跨平台 Python CLI。
本教程使用图形设备资源管理器工具。 如果在 VS Code 中进行开发,可以使用适用于 VS Code 的 Azure IoT 工具 。 如果喜欢使用 CLI 工具,也可以使用适用于 Azure CLI 2.0 的 IoT 扩展 工具。
设备资源管理器工具使用 Azure IoT 服务库在 IoT 中心执行各种功能(包括添加设备)。 若使用设备资源管理器工具添加设备,会获得设备的连接字符串。 需要此连接字符串才能运行示例应用程序。
如果不熟悉设备资源管理器工具,请参阅以下过程,了解如何使用该工具来添加设备和获取设备连接字符串。
若要安装设备资源管理器工具,请参阅如何对 IoT 中心设备使用设备资源管理器。
运行该程序时,可以看到以下界面:
在第一个字段中输入用户的 IoT 中心连接字符串,并单击“更新”。 此步骤配置该工具,以便与 IoT 中心通信。
可以在“IoT 中心服务”>“设置”>“共享访问策略”>“iothubowner”下找到连接字符串。
配置 IoT 中心连接字符串后,请单击“管理”选项卡:
可在此选项卡中管理已注册到 IoT 中心的设备。
单击“创建”按钮创建设备。 会显示一个已预先填充一组密钥(主密钥和辅助密钥)的对话框。 输入“设备 ID”,并单击“创建”。
创建设备后,“设备”列表会更新,其中包含所有已注册的设备(包括刚刚创建的设备)。 如果在新设备上单击右键,会看到此菜单:
如果选择“复制所选设备的连接字符串”,会将设备连接字符串复制到剪贴板。 请保留设备连接字符串的副本。 在运行后续部分中所述的示例应用程序时,将要用到它。
完成上述步骤后,可以开始运行一些代码。 大多数示例的主源文件顶部都有一个常量,可让你输入连接字符串。 例如,iothub_client_samples_iothub_convenience_sample 应用程序中的相应行如下所示。
static const char* connectionString = "[device connection string]";
使用 IoTHubClient 库
azure-iot-sdk-c 存储库的 iothub_client 文件夹中有一个 samples 文件夹,其中包含名为 iothub_client_sample_mqtt 的应用程序。
Windows 版本的 iothub_client_samples_iothub_convenience_sample 应用程序包含以下 Visual Studio 解决方案:
注意
如果 Visual Studio 要求你将项目重新定位到最新版本,请接受提示。
此解决方案只包含一个项目。 此解决方案中安装了四个 NuGet 包:
- Microsoft.Azure.C.SharedUtility
- Microsoft.Azure.IoTHub.MqttTransport
- Microsoft.Azure.IoTHub.IoTHubClient
- Microsoft.Azure.umqtt
在使用 SDK 时始终需要 Microsoft.Azure.C.SharedUtility 包。 本示例使用 MQTT 协议,因此,必须包括 Microsoft.Azure.umqtt 和 Microsoft.Azure.IoTHub.MqttTransport 包(AMQP 和 HTTPS 有对应的包)。 由于此示例使用 IoTHubClient 库,因此还必须在解决方案中包含 Microsoft.Azure.IoTHub.IoTHubClient 包。
可以在 iothub_client_samples_iothub_convenience_sample 源文件中找到示例应用程序的实现。
以下步骤使用此示例应用程序来演示使用 IoTHubClient 库时所需的项目。
初始化库
注意
在开始使用库之前,可能需要执行一些特定于平台的初始化。 例如,如果打算在 Linux 上使用 AMQP,则必须初始化 OpenSSL 库。 GitHub 存储库中的示例会在客户端启动时调用实用工具函数 platform_init,并在退出之前调用 platform_deinit 函数。 这些函数在 platform.h 标头文件中声明。 应该在存储库中为目标平台检查这些函数定义,以确定是否需要在客户端中包含任何特定于平台的初始化代码。
只有在分配 IoT 中心客户端句柄之后,才可以开始使用库:
if ((iotHubClientHandle =
IoTHubClient_LL_CreateFromConnectionString(connectionString, MQTT_Protocol)) == NULL)
{
(void)printf("ERROR: iotHubClientHandle is NULL!\r\n");
}
else
{
...
将设备资源管理器工具获取的设备连接字符串传递给此函数。 还需指定要使用的通信协议。 本示例使用 MQTT,但也可以选择 AMQP 和 HTTPS。
获取有效的 IOTHUB_CLIENT_HANDLE 后,可以开始调用 API 来与 IoT 中心相互发送和接收消息。
发送消息
示例应用程序将设置一个循环用于向 IoT 中心发送消息。 以下代码片段:
- 创建消息。
- 将属性添加到消息。
- 发送消息。
首先创建一条消息:
size_t iterator = 0;
do
{
if (iterator < MESSAGE_COUNT)
{
sprintf_s(msgText, sizeof(msgText), "{\"deviceId\":\"myFirstDevice\",\"windSpeed\":%.2f}", avgWindSpeed + (rand() % 4 + 2));
if ((messages[iterator].messageHandle = IoTHubMessage_CreateFromByteArray((const unsigned char*)msgText, strlen(msgText))) == NULL)
{
(void)printf("ERROR: iotHubMessageHandle is NULL!\r\n");
}
else
{
messages[iterator].messageTrackingId = iterator;
MAP_HANDLE propMap = IoTHubMessage_Properties(messages[iterator].messageHandle);
(void)sprintf_s(propText, sizeof(propText), "PropMsg_%zu", iterator);
if (Map_AddOrUpdate(propMap, "PropName", propText) != MAP_OK)
{
(void)printf("ERROR: Map_AddOrUpdate Failed!\r\n");
}
if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle, messages[iterator].messageHandle, SendConfirmationCallback, &messages[iterator]) != IOTHUB_CLIENT_OK)
{
(void)printf("ERROR: IoTHubClient_LL_SendEventAsync..........FAILED!\r\n");
}
else
{
(void)printf("IoTHubClient_LL_SendEventAsync accepted message [%d] for transmission to IoT Hub.\r\n", (int)iterator);
}
}
}
IoTHubClient_LL_DoWork(iotHubClientHandle);
ThreadAPI_Sleep(1);
iterator++;
} while (g_continueRunning);
每次发送消息时,指定发送数据时所调用的回调函数的引用。 在此示例中,回调函数名为 SendConfirmationCallback。 以下代码片段演示此回调函数:
static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
{
EVENT_INSTANCE* eventInstance = (EVENT_INSTANCE*)userContextCallback;
(void)printf("Confirmation[%d] received for message tracking id = %zu with result = %s\r\n", callbackCounter, eventInstance->messageTrackingId, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
/* Some device specific action code goes here... */
callbackCounter++;
IoTHubMessage_Destroy(eventInstance->messageHandle);
}
处理完消息后,请注意对 IoTHubMessage_Destroy 函数的调用。 此函数释放创建消息时分配的资源。
接收消息
接收消息是一个异步操作。 首先,请注册当设备接收消息时所要调用的回调:
if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle, ReceiveMessageCallback, &receiveContext) != IOTHUB_CLIENT_OK)
{
(void)printf("ERROR: IoTHubClient_LL_SetMessageCallback..........FAILED!\r\n");
}
else
{
(void)printf("IoTHubClient_LL_SetMessageCallback...successful.\r\n");
...
最后一个参数是指向所需对象的 void 指针。 在本示例中,这是一个指向整数的指针,但也可以是指向更复杂数据结构的指针。 此参数使回调函数可与此函数的调用方以共享状态运行。
当设备接收消息时,将调用注册的回调函数。 此回调函数:
- 从消息中检索消息 ID 和相关 ID。
- 检索消息内容。
- 从消息中检索任何自定义属性。
static IOTHUBMESSAGE_DISPOSITION_RESULT ReceiveMessageCallback(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
int* counter = (int*)userContextCallback;
const char* buffer;
size_t size;
MAP_HANDLE mapProperties;
const char* messageId;
const char* correlationId;
// Message properties
if ((messageId = IoTHubMessage_GetMessageId(message)) == NULL)
{
messageId = "<null>";
}
if ((correlationId = IoTHubMessage_GetCorrelationId(message)) == NULL)
{
correlationId = "<null>";
}
// Message content
if (IoTHubMessage_GetByteArray(message, (const unsigned char**)&buffer, &size) != IOTHUB_MESSAGE_OK)
{
(void)printf("unable to retrieve the message data\r\n");
}
else
{
(void)printf("Received Message [%d]\r\n Message ID: %s\r\n Correlation ID: %s\r\n Data: <<<%.*s>>> & Size=%d\r\n", *counter, messageId, correlationId, (int)size, buffer, (int)size);
// If we receive the work 'quit' then we stop running
if (size == (strlen("quit") * sizeof(char)) && memcmp(buffer, "quit", size) == 0)
{
g_continueRunning = false;
}
}
// Retrieve properties from the message
mapProperties = IoTHubMessage_Properties(message);
if (mapProperties != NULL)
{
const char*const* keys;
const char*const* values;
size_t propertyCount = 0;
if (Map_GetInternals(mapProperties, &keys, &values, &propertyCount) == MAP_OK)
{
if (propertyCount > 0)
{
size_t index;
printf(" Message Properties:\r\n");
for (index = 0; index < propertyCount; index++)
{
(void)printf("\tKey: %s Value: %s\r\n", keys[index], values[index]);
}
(void)printf("\r\n");
}
}
}
/* Some device specific action code goes here... */
(*counter)++;
return IOTHUBMESSAGE_ACCEPTED;
}
使用 IoTHubMessage_GetByteArray 函数来检索消息(在本示例中是一个字符串)。
取消初始化库
完成发送事件和接收消息后,可以取消初始化 IoT 库。 为此,请发出以下函数调用:
IoTHubClient_LL_Destroy(iotHubClientHandle);
此调用释放 IoTHubClient_CreateFromConnectionString 函数以前分配的资源。
可以看到,使用 IoTHubClient 库可以轻松发送和接收消息。 该库处理与 IoT 中心通信的详细信息,包括要使用哪个协议(从开发人员的视角来看,这是一个简单的配置选项)。
后续步骤
本文介绍了有关使用适用于 C 语言的 Azure IoT 设备 SDK 中的库的基本知识。其中针对 SDK 中包含的组件及其体系结构,以及如何开始使用 Windows 示例等进行了详细说明。 下一篇文章通过讲解有关 IoTHubClient 库的详细信息来继续介绍该 SDK。
若要详细了解如何针对 IoT 中心进行开发,请参阅 Azure IoT SDK。
若要进一步探索 IoT 中心的功能,请参阅: