适用于 C 的 Azure IoT 设备 SDKAzure IoT device SDK for C

Azure IoT 设备 SDK 是一个库集,旨在简化从 Azure IoT 中心服务发送和接收消息的过程。The Azure IoT device SDK is a set of libraries designed to simplify the process of sending messages to and receiving messages from the Azure IoT Hub service. 有各种不同的 SDK,每个 SDK 都以特定的平台为目标,而本文说明的是适用于 C 语言的 Azure IoT 设备 SDKThere are different variations of the SDK, each targeting a specific platform, but this article describes the Azure IoT device SDK for C.

备注

本文中提到的某些功能(例如云到设备消息传递、设备孪生、设备管理)仅在 IoT 中心的标准层中提供。Some of the features mentioned in this article, like cloud-to-device messaging, device twins, and device management, are only available in the standard tier of IoT hub. 有关基本和标准 IoT 中心层的详细信息,请参阅如何选择合适的 IoT 中心层For more information about the basic and standard IoT Hub tiers, see How to choose the right IoT Hub tier.

适用于 C 语言的 Azure IoT 设备 SDK 以 ANSI C (C99) 编写,以获得最大可移植性。The Azure IoT device SDK for C is written in ANSI C (C99) to maximize portability. 此功能使得这些库很适合在多个平台和设备上运行,尤其是在以将磁盘和内存占用量降到最低作为优先考虑的情况下。This feature makes the libraries well-suited to operate on multiple platforms and devices, especially where minimizing disk and memory footprint is a priority.

SDK 已在许多平台上进行了测试(有关详细信息,请参阅 Azure IoT 认证设备目录)。There are a broad range of platforms on which the SDK has been tested (see the Azure Certified for IoT device catalog for details). 尽管本文包含的是在 Windows 平台上运行的示例代码演示,但本文所述的代码在各种支持的平台上都完全相同。Although this article includes walkthroughs of sample code running on the Windows platform, the code described in this article is identical across the range of supported platforms.

本文介绍适用于 C 语言的 Azure IoT 设备 SDK 的体系结构,将演示如何初始化设备库,将数据发送到 IoT 中心,以及从 IoT 中心接收消息。This article introduces you to the architecture of the Azure IoT device SDK for C. It demonstrates how to initialize the device library, send data to IoT Hub, and receive messages from it. 本文中的信息应足以让你开始使用 SDK,但同时也提供了有关库的其他信息的链接。The information in this article should be enough to get started using the SDK, but also provides pointers to additional information about the libraries.

SDK 体系结构SDK architecture

可在 GitHub 存储库中找到适用于 C 语言的 Azure IoT 设备 SDK,还可在 C API 参考中查看 API 的详细信息。You can find the Azure IoT device SDK for C GitHub repository and view details of the API in the C API reference.

在此存储库的 master 分支中可找到最新版本的库:The latest version of the libraries can be found in the master branch of the repository:

存储库主分支的屏幕截图

  • 此 SDK 的核心实现可在 iothub_client 文件夹中找到,此文件夹包含 SDK 的最低 API 层的实现:IoTHubClient 库。The core implementation of the SDK is in the iothub_client folder that contains the implementation of the lowest API layer in the SDK: the IoTHubClient library. IoTHubClient 库包含实现原始消息传送的 API,即将消息发送到 IoT 中心以及从 IoT 中心接收消息。The IoTHubClient library contains APIs implementing raw messaging for sending messages to IoT Hub and receiving messages from IoT Hub. 使用此库时,需要负责实现消息序列化,但与 IoT 中心通信的其他细节则由系统处理。When using this library, you are responsible for implementing message serialization, but other details of communicating with IoT Hub are handled for you.
  • serializer 文件夹包含帮助器函数和示例代码,演示了使用客户端库向 Azure IoT 中心发送消息之前如何序列化数据。The serializer folder contains helper functions and samples that show you how to serialize data before sending to Azure IoT Hub using the client library. 使用序列化程序不是必需的,仅为了提供便利。The use of the serializer is not mandatory and is provided as a convenience. 如果使用序列化程序库,需要定义一个模型,用于指定要发送到 IoT 中心的数据以及预期要从 IoT 中心接收的消息。To use the serializer library, you define a model that specifies the data to send to IoT Hub and the messages you expect to receive from it. 定义模型后,SDK 将提供一个 API 图面,让你轻松处理设备到云和云到设备的消息,而无需担心序列化细节。Once the model is defined, the SDK provides you with an API surface that enables you to easily work with device-to-cloud and cloud-to-device messages without worrying about the serialization details. 该库依赖于使用 MQTT 和 AMQP 等协议实现传输的其他开放源代码库。The library depends on other open source libraries that implement transport using protocols such as MQTT and AMQP.
  • IoTHubClient 库依赖于其他开放源代码库:The IoTHubClient library depends on other open source libraries:
    • Azure C 共享实用程序库,其常用功能用于很多 Azure 相关的 C SDK 中所需的基本任务(如字符串、列表操作和 IO 等)。The Azure C shared utility library, which provides common functionality for basic tasks (such as strings, list manipulation, and IO) needed across several Azure-related C SDKs.
    • Azure uAMQP 库,此库是针对资源约束设备的 AMQP 客户端实现的优化。The Azure uAMQP library, which is a client-side implementation of AMQP optimized for resource constrained devices.
    • Azure uMQTT 库,它是实现 MQTT 协议并针对资源约束设备进行了优化的通用型库。The Azure uMQTT library, which is a general-purpose library implementing the MQTT protocol and optimized for resource constrained devices.

查看示例代码可以更方便地了解这些库的用法。Use of these libraries is easier to understand by looking at example code. 以下部分演练 SDK 中包含的几个示例应用程序。The following sections walk you through several of the sample applications that are included in the SDK. 此演练应可让你轻松了解 SDK 体系结构层的各种功能以及 API 工作原理的简介。This walkthrough should give you a good feel for the various capabilities of the architectural layers of the SDK and an introduction to how the APIs work.

运行示例之前Before you run the samples

在面向 C 的 Azure IoT 设备 SDK 中运行示例之前,必须在 Azure 订阅中创建 IoT 中心服务的实例Before you can run the samples in the Azure IoT device SDK for C, you must create an instance of the IoT Hub service in your Azure subscription. 然后完成以下任务:Then complete the following tasks:

  • 准备开发环境Prepare your development environment
  • 获取设备凭据。Obtain device credentials.

准备开发环境Prepare your development environment

为常用平台提供了包(例如适用于 Windows 的 NuGet 包或者适用于 Debian 和 Ubuntu 的 apt_get),示例将使用这些包(如果适用)。Packages are provided for common platforms (such as NuGet for Windows or apt_get for Debian and Ubuntu) and the samples use these packages when available. 在某些情况下,需要为设备编译 SDK,或者在设备上编译 SDK。In some cases, you need to compile the SDK for or on your device. 如果需要编译 SDK,请参阅 GitHub 存储库中的准备开发环境If you need to compile the SDK, see Prepare your development environment in the GitHub repository.

若要获取示例应用程序代码,请从 GitHub 下载 SDK 的副本。To obtain the sample application code, download a copy of the SDK from GitHub. GitHub 存储库master 分支获取源的副本。Get your copy of the source from the master branch of the GitHub repository.

获取设备凭据Obtain the device credentials

获取示例源代码后,下一步是获取一组设备凭据。Now that you have the sample source code, the next thing to do is to get a set of device credentials. 要使设备能够访问 IoT 中心,必须先将该设备添加到 IoT 中心标识注册表。For a device to be able to access an IoT hub, you must first add the device to the IoT Hub identity registry. 添加设备时,需要获取一组所需的设备凭据,以便设备能够连接到 IoT 中心。When you add your device, you get a set of device credentials that you need for the device to be able to connect to the IoT hub. 下一部分所述示例应用程序的预期凭据格式为设备连接字符串The sample applications discussed in the next section expect these credentials in the form of a device connection string.

有几个开源工具可帮助你管理 IoT 中心。There are several open-source tools to help you manage your IoT hub.

本教程使用图形设备资源管理器工具。This tutorial uses the graphical device explorer tool. 如果在 VS Code 中进行开发,可以使用适用于 VS Code 的 Azure IoT 工具 。You can use the Azure IoT Tools for VS Code if you develop in VS Code. 如果喜欢使用 CLI 工具,也可以使用适用于 Azure CLI 2.0 的 IoT 扩展 工具。You can also use the IoT extension for Azure CLI 2.0 tool if you prefer to use a CLI tool.

设备资源管理器工具使用 Azure IoT 服务库在 IoT 中心执行各种功能(包括添加设备)。The device explorer tool uses the Azure IoT service libraries to perform various functions on IoT Hub, including adding devices. 若使用设备资源管理器工具添加设备,会获得设备的连接字符串。If you use the device explorer tool to add a device, you get a connection string for your device. 需要此连接字符串才能运行示例应用程序。You need this connection string to run the sample applications.

如果不熟悉设备资源管理器工具,请参阅以下过程,了解如何使用该工具来添加设备和获取设备连接字符串。If you're not familiar with the device explorer tool, the following procedure describes how to use it to add a device and obtain a device connection string.

  1. 若要安装设备资源管理器工具,请参阅如何对 IoT 中心设备使用设备资源管理器To install the device explorer tool, see How to use the Device Explorer for IoT Hub devices.

  2. 运行该程序时,可以看到以下界面:When you run the program, you see this interface:

Device Explorer 孪生屏幕截图

  1. 在第一个字段中输入用户的 IoT 中心连接字符串,并单击“更新”。Enter your IoT Hub Connection String in the first field and click Update. 此步骤配置该工具,以便与 IoT 中心通信。This step configures the tool so that it can communicate with IoT Hub.

可以在“IoT 中心服务” > “设置” > “共享访问策略” > “iothubowner”下找到连接字符串The Connection String can be found under IoT Hub Service > Settings > Shared Access Policy > iothubowner.

  1. 配置 IoT 中心连接字符串后,请单击“管理”选项卡: When the IoT Hub connection string is configured, click the Management tab:

    Device Explorer 孪生/管理屏幕截图

可在此选项卡中管理已注册到 IoT 中心的设备。This tab is where you manage the devices registered in your IoT hub.

  1. 单击“创建”按钮创建设备。 You create a device by clicking the Create button. 会显示一个已预先填充一组密钥(主密钥和辅助密钥)的对话框。A dialog displays with a set of pre-populated keys (primary and secondary). 输入“设备 ID”,并单击“创建”。 Enter a Device ID and then click Create.

    “创建设备”屏幕截图

  2. 创建设备后,“设备”列表会更新,其中包含所有已注册的设备(包括刚刚创建的设备)。When the device is created, the Devices list updates with all the registered devices, including the one you just created. 如果在新设备上单击右键,会看到此菜单:If you right-click your new device, you see this menu:

    Device Explorer 孪生右键单击结果

  3. 如果选择“复制所选设备的连接字符串”,会将设备连接字符串复制到剪贴板。 If you choose Copy connection string for selected device, the device connection string is copied to the clipboard. 请保留设备连接字符串的副本。Keep a copy of the device connection string. 在运行后续部分中所述的示例应用程序时,将要用到它。You need it when running the sample applications described in the following sections.

完成上述步骤后,可以开始运行一些代码。When you've completed the steps above, you're ready to start running some code. 大多数示例的主源文件顶部都有一个常量,可让你输入连接字符串。Most samples have a constant at the top of the main source file that enables you to enter a connection string. 例如,iothub_client_samples_iothub_convenience_sample 应用程序中的相应行如下所示。For example, the corresponding line from the iothub_client_samples_iothub_convenience_sample application appears as follows.

static const char* connectionString = "[device connection string]";

使用 IoTHubClient 库Use the IoTHubClient library

azure-iot-sdk-c 存储库的 iothub_client 文件夹中有一个 samples 文件夹,其中包含名为 iothub_client_sample_mqtt 的应用程序。Within the iothub_client folder in the azure-iot-sdk-c repository, there is a samples folder that contains an application called iothub_client_sample_mqtt.

Windows 版本的 iothub_client_samples_iothub_convenience_sample 应用程序包含以下 Visual Studio 解决方案:The Windows version of the iothub_client_samples_iothub_convenience_sample application includes the following Visual Studio solution:

Visual Studio 解决方案资源管理器

备注

如果 Visual Studio 要求你将项目重新定位到最新版本,请接受提示。If Visual Studio asks you to retarget the project to the latest version, accept the prompt.

此解决方案只包含一个项目。This solution contains a single project. 此解决方案中安装了四个 NuGet 包:There are four NuGet packages installed in this solution:

  • Microsoft.Azure.C.SharedUtilityMicrosoft.Azure.C.SharedUtility
  • Microsoft.Azure.IoTHub.MqttTransportMicrosoft.Azure.IoTHub.MqttTransport
  • Microsoft.Azure.IoTHub.IoTHubClientMicrosoft.Azure.IoTHub.IoTHubClient
  • Microsoft.Azure.umqttMicrosoft.Azure.umqtt

在使用 SDK 时始终需要 Microsoft.Azure.C.SharedUtility 包。You always need the Microsoft.Azure.C.SharedUtility package when you are working with the SDK. 本示例使用 MQTT 协议,因此,必须包括 Microsoft.Azure.umqtt 和 Microsoft.Azure.IoTHub.MqttTransport 包(AMQP 和 HTTPS 有对应的包)。This sample uses the MQTT protocol, therefore you must include the Microsoft.Azure.umqtt and Microsoft.Azure.IoTHub.MqttTransport packages (there are equivalent packages for AMQP and HTTPS). 由于此示例使用 IoTHubClient 库,因此还必须在解决方案中包含 Microsoft.Azure.IoTHub.IoTHubClient 包。Because the sample uses the IoTHubClient library, you must also include the Microsoft.Azure.IoTHub.IoTHubClient package in your solution.

可以在 iothub_client_samples_iothub_convenience_sample 源文件中找到示例应用程序的实现。You can find the implementation for the sample application in the iothub_client_samples_iothub_convenience_sample source file.

以下步骤使用此示例应用程序来演示使用 IoTHubClient 库时所需的项目。The following steps use this sample application to walk you through what's required to use the IoTHubClient library.

初始化库Initialize the library

备注

在开始使用库之前,可能需要执行一些特定于平台的初始化。Before you start working with the libraries, you may need to perform some platform-specific initialization. 例如,如果打算在 Linux 上使用 AMQP,则必须初始化 OpenSSL 库。For example, if you plan to use AMQP on Linux you must initialize the OpenSSL library. GitHub 存储库中的示例在客户端启动时调用实用工具函数 platform_init,并在退出之前调用 platform_deinit 函数。The samples in the GitHub repository call the utility function platform_init when the client starts and call the platform_deinit function before exiting. 这些函数在 platform.h 标头文件中声明。These functions are declared in the platform.h header file. 应该在存储库中为目标平台检查这些函数定义,以确定是否需要在客户端中包含任何特定于平台的初始化代码。Examine the definitions of these functions for your target platform in the repository to determine whether you need to include any platform-specific initialization code in your client.

只有在分配 IoT 中心客户端句柄之后,才可以开始使用库:To start working with the libraries, first allocate an IoT Hub client handle:

if ((iotHubClientHandle = 
  IoTHubClient_LL_CreateFromConnectionString(connectionString, MQTT_Protocol)) == NULL)
{
    (void)printf("ERROR: iotHubClientHandle is NULL!\r\n");
}
else
{
    ...

将设备资源管理器工具获取的设备连接字符串传递给此函数。You pass a copy of the device connection string you obtained from the device explorer tool to this function. 还需指定要使用的通信协议。You also designate the communications protocol to use. 本示例使用 MQTT,但也可以选择 AMQP 和 HTTPS。This example uses MQTT, but AMQP and HTTPS are also options.

获取有效的 IOTHUB_CLIENT_HANDLE 后,可以开始调用 API 来与 IoT 中心相互发送和接收消息。When you have a valid IOTHUB_CLIENT_HANDLE, you can start calling the APIs to send and receive messages to and from IoT Hub.

发送消息Send messages

示例应用程序将设置一个循环用于向 IoT 中心发送消息。The sample application sets up a loop to send messages to your IoT hub. 以下代码片段:The following snippet:

  • 创建消息。Creates a message.
  • 将属性添加到消息。Adds a property to the message.
  • 发送消息。Sends a message.

首先创建一条消息:First, create a message:

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);

每次发送消息时,指定发送数据时所调用的回调函数的引用。Every time you send a message, you specify a reference to a callback function that's invoked when the data is sent. 在此示例中,回调函数名为 SendConfirmationCallbackIn this example, the callback function is called SendConfirmationCallback. 以下代码片段演示此回调函数:The following snippet shows this callback function:

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 函数的调用。Note the call to the IoTHubMessage_Destroy function when you're done with the message. 此函数释放创建消息时分配的资源。This function frees the resources allocated when you created the message.

接收消息Receive messages

接收消息是一个异步操作。Receiving a message is an asynchronous operation. 首先,请注册当设备接收消息时所要调用的回调:First, you register the callback to invoke when the device receives a message:

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 指针。The last parameter is a void pointer to whatever you want. 在本示例中,这是一个指向整数的指针,但也可以是指向更复杂数据结构的指针。In the sample, it's a pointer to an integer but it could be a pointer to a more complex data structure. 此参数使回调函数可与此函数的调用方以共享状态运行。This parameter enables the callback function to operate on shared state with the caller of this function.

当设备接收消息时,将调用注册的回调函数。When the device receives a message, the registered callback function is invoked. 此回调函数:This callback function retrieves:

  • 从消息中检索消息 ID 和相关 ID。The message id and correlation id from the message.
  • 检索消息内容。The message content.
  • 从消息中检索任何自定义属性。Any custom properties from the message.
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 函数来检索消息(在本示例中是一个字符串)。Use the IoTHubMessage_GetByteArray function to retrieve the message, which in this example is a string.

取消初始化库Uninitialize the library

完成发送事件和接收消息后,可以取消初始化 IoT 库。When you're done sending events and receiving messages, you can uninitialize the IoT library. 为此,请发出以下函数调用:To do so, issue the following function call:

IoTHubClient_LL_Destroy(iotHubClientHandle);

此调用释放 IoTHubClient_CreateFromConnectionString 函数以前分配的资源。This call frees up the resources previously allocated by the IoTHubClient_CreateFromConnectionString function.

可以看到,使用 IoTHubClient 库可以轻松发送和接收消息。As you can see, it's easy to send and receive messages with the IoTHubClient library. 该库处理与 IoT 中心通信的详细信息,包括要使用哪个协议(从开发人员的视角来看,这是一个简单的配置选项)。The library handles the details of communicating with IoT Hub, including which protocol to use (from the perspective of the developer, this is a simple configuration option).

在如何对设备发送到 IoT 中心的数据进行序列化方面,IoTHubClient 库也可提供精确的控制。The IoTHubClient library also provides precise control over how to serialize the data your device sends to IoT Hub. 在某些情况下,这种控制级别是一项优点,但在其他情况下,这可能不是你想要看到的实现细节。In some cases this level of control is an advantage, but in others it is an implementation detail that you don't want to be concerned with. 如果是这样,可以考虑使用下一部分中介绍的序列化程序库。If that's the case, you might consider using the serializer library, which is described in the next section.

使用序列化程序库Use the serializer library

从概念上讲,序列化程序库位于 SDK 中的 IoTHubClient 库之上。Conceptually the serializer library sits on top of the IoTHubClient library in the SDK. 它使用 IoTHubClient 库来与 IoT 中心进行底层通信,但它添加了建模功能,消除了开发人员处理消息序列化的负担。It uses the IoTHubClient library for the underlying communication with IoT Hub, but it adds modeling capabilities that remove the burden of dealing with message serialization from the developer. 我们通过一个示例充分演示此库的工作原理。How this library works is best demonstrated by an example.

azure-iot-sdk-c 存储库serializer 文件夹中有一个 samples 文件夹,其中包含名为 simplesample_mqtt 的应用程序。Inside the serializer folder in the azure-iot-sdk-c repository, is a samples folder that contains an application called simplesample_mqtt. 此示例的 Windows 版本包含以下 Visual Studio 解决方案:The Windows version of this sample includes the following Visual Studio solution:

用于 mqtt 的 Visual Studio 解决方案示例

备注

如果 Visual Studio 要求你将项目重新定位到最新版本,请接受提示。If Visual Studio asks you to retarget the project to the latest version, accept the prompt.

如同前面的示例,此示例也包含多个 NuGet 包:As with the previous sample, this one includes several NuGet packages:

  • Microsoft.Azure.C.SharedUtilityMicrosoft.Azure.C.SharedUtility
  • Microsoft.Azure.IoTHub.MqttTransportMicrosoft.Azure.IoTHub.MqttTransport
  • Microsoft.Azure.IoTHub.IoTHubClientMicrosoft.Azure.IoTHub.IoTHubClient
  • Microsoft.Azure.IoTHub.SerializerMicrosoft.Azure.IoTHub.Serializer
  • Microsoft.Azure.umqttMicrosoft.Azure.umqtt

其中的大多数包已在前面的示例中出现过,但 Microsoft.Azure.IoTHub.Serializer 是新的。You've seen most of these packages in the previous sample, but Microsoft.Azure.IoTHub.Serializer is new. 使用序列化程序库时需要此包。This package is required when you use the serializer library.

可以在 iothub_client_samples_iothub_convenience_sample 文件中找到示例应用程序的实现。You can find the implementation of the sample application in the iothub_client_samples_iothub_convenience_sample file.

以下部分演练本示例的重要组成部分。The following sections walk you through the key parts of this sample.

初始化库Initialize the library

若要开始使用序列化程序库,请调用初始化 API:To start working with the serializer library, call the initialization APIs:

if (serializer_init(NULL) != SERIALIZER_OK)
{
    (void)printf("Failed on serializer_init\r\n");
}
else
{
    IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle = IoTHubClient_LL_CreateFromConnectionString(connectionString, MQTT_Protocol);
    srand((unsigned int)time(NULL));
    int avgWindSpeed = 10;

    if (iotHubClientHandle == NULL)
    {
        (void)printf("Failed on IoTHubClient_LL_Create\r\n");
    }
    else
    {
        ContosoAnemometer* myWeather = CREATE_MODEL_INSTANCE(WeatherStation, ContosoAnemometer);
        if (myWeather == NULL)
        {
            (void)printf("Failed on CREATE_MODEL_INSTANCE\r\n");
        }
        else
        {
...

serializer_init 函数进行的调用是一次性调用,用于初始化底层库。The call to the serializer_init function is a one-time call and initializes the underlying library. 然后,需调用 IoTHubClient_LL_CreateFromConnectionString 函数,这是 IoTHubClient 示例中的同一 API。Then, you call the IoTHubClient_LL_CreateFromConnectionString function, which is the same API as in the IoTHubClient sample. 此调用将设置设备连接字符串(也可用于选择要使用的协议)。This call sets your device connection string (this call is also where you choose the protocol you want to use). 本示例使用 MQTT 作为传输方式,但也可以使用 AMQP 或 HTTPS。This sample uses MQTT as the transport, but could use AMQP or HTTPS.

最后,调用 CREATE_MODEL_INSTANCE 函数。Finally, call the CREATE_MODEL_INSTANCE function. WeatherStation 是模型的命名空间,ContosoAnemometer 是模型的名称。WeatherStation is the namespace of the model and ContosoAnemometer is the name of the model. 创建模型实例后,可以使用它来开始发送和接收消息。Once the model instance is created, you can use it to start sending and receiving messages. 但是,必须了解模型是什么。However, it's important to understand what a model is.

定义模型Define the model

序列化程序库中的模型定义了设备可发送到 IoT 中心的消息以及可接收的消息(在建模语言中称为操作)。A model in the serializer library defines the messages that your device can send to IoT Hub and the messages, called actions in the modeling language, which it can receive. iothub_client_samples_iothub_convenience_sample 示例应用程序中所示,你使用一组 C 宏定义了一个模块:You define a model using a set of C macros as in the iothub_client_samples_iothub_convenience_sample sample application:

BEGIN_NAMESPACE(WeatherStation);

DECLARE_MODEL(ContosoAnemometer,
WITH_DATA(ascii_char_ptr, DeviceId),
WITH_DATA(int, WindSpeed),
WITH_ACTION(TurnFanOn),
WITH_ACTION(TurnFanOff),
WITH_ACTION(SetAirResistance, int, Position)
);

END_NAMESPACE(WeatherStation);

BEGIN_NAMESPACEEND_NAMESPACE 这两个宏都以模型的命名空间作为参数。The BEGIN_NAMESPACE and END_NAMESPACE macros both take the namespace of the model as an argument. 介于这两个宏之间的内容应该就是模型的定义和模型使用的数据结构。It's expected that anything between these macros is the definition of your model or models, and the data structures that the models use.

在本示例中,有一个名为 ContosoAnemometer 的模型。In this example, there is a single model called ContosoAnemometer. 此模型定义了设备可以发送到 IoT 中心的两个数据片段:DeviceIdWindSpeedThis model defines two pieces of data that your device can send to IoT Hub: DeviceId and WindSpeed. 它还定义了设备可以接收的三个操作(消息):TurnFanOnTurnFanOffSetAirResistanceIt also defines three actions (messages) that your device can receive: TurnFanOn, TurnFanOff, and SetAirResistance. 每个数据元素都有一个类型,而每个操作都有一个名称(以及一组可选参数)。Each data element has a type, and each action has a name (and optionally a set of parameters).

模型中定义的数据和操作可定义 API 接口,此接口可用于将消息发送到 IoT 中心,以及响应发送到设备的消息。The data and actions defined in the model define an API surface that you can use to send messages to IoT Hub, and respond to messages sent to the device. 最好通过示例了解此模型的用法。Use of this model is best understood through an example.

发送消息Send messages

模型定义了可以发送到 IoT 中心的数据。The model defines the data you can send to IoT Hub. 在本示例中,这是指使用 WITH_DATA 宏来定义的两个数据项之一。In this example, that means one of the two data items defined using the WITH_DATA macro. 要将 DeviceIdWindSpeed 值发送到 IoT 中心,需要执行几个步骤。There are several steps required to send DeviceId and WindSpeed values to an IoT hub. 第一个步骤是设置要发送的数据:The first is to set the data you want to send:

myWeather->DeviceId = "myFirstDevice";
myWeather->WindSpeed = avgWindSpeed + (rand() % 4 + 2);

使用前面定义的模型可以通过设置 struct 的成员来设置值。The model you defined earlier enables you to set the values by setting members of a struct. 接下来,序列化想要发送的消息:Next, serialize the message you want to send:

unsigned char* destination;
size_t destinationSize;
if (SERIALIZE(&destination, &destinationSize, myWeather->DeviceId, myWeather->WindSpeed) != CODEFIRST_OK)
{
    (void)printf("Failed to serialize\r\n");
}
else
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
    free(destination);
}

此代码将设备到云的消息序列化到缓冲区(由 destination 引用)。This code serializes the device-to-cloud to a buffer (referenced by destination). 然后,代码调用 sendMessage 函数将消息发送到 IoT 中心:The code then invokes the sendMessage function to send the message to IoT Hub:

static void sendMessage(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, const unsigned char* buffer, size_t size)
{
    static unsigned int messageTrackingId;
    IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, size);
    if (messageHandle == NULL)
    {
        printf("unable to create a new IoTHubMessage\r\n");
    }
    else
    {
        if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle, messageHandle, sendCallback, (void*)(uintptr_t)messageTrackingId) != IOTHUB_CLIENT_OK)
        {
            printf("failed to hand over the message to IoTHubClient");
        }
        else
        {
            printf("IoTHubClient accepted the message for delivery\r\n");
        }
        IoTHubMessage_Destroy(messageHandle);
    }
    messageTrackingId++;
}

IoTHubClient_LL_SendEventAsync 的倒数第二个参数是对成功发送数据后所调用的回调函数的引用。The second to last parameter of IoTHubClient_LL_SendEventAsync is a reference to a callback function that's called when the data is successfully sent. 下面是本示例中的回调函数:Here's the callback function in the sample:

void sendCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
{
    unsigned int messageTrackingId = (unsigned int)(uintptr_t)userContextCallback;

    (void)printf("Message Id: %u Received.\r\n", messageTrackingId);

    (void)printf("Result Call Back Called! Result is: %s \r\n", MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
}

第二个参数是指向用户上下文的指针,即传递给 IoTHubClient_LL_SendEventAsync 的同一个指针。The second parameter is a pointer to user context; the same pointer passed to IoTHubClient_LL_SendEventAsync. 在本例中,该上下文是一个简易计数器,但也可以是所需的任何组件。In this case, the context is a simple counter, but it can be anything you want.

这就是发送设备到云的消息所要执行的所有操作。That's all there is to sending device-to-cloud messages. 最后要介绍的内容是如何接收消息。The only thing left to cover is how to receive messages.

接收消息Receive messages

接收消息的方式类似于在 IoTHubClient 库中处理消息。Receiving a message works similarly to the way messages work in the IoTHubClient library. 首先,需要注册消息回调函数:First, you register a message callback function:

if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle, 
  IoTHubMessage, myWeather) != IOTHUB_CLIENT_OK)
{
    printf("unable to IoTHubClient_SetMessageCallback\r\n");
}
else
{
...

然后编写在接收消息时要调用的回调函数:Then, you write the callback function that's invoked when a message is received:

static IOTHUBMESSAGE_DISPOSITION_RESULT IoTHubMessage(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
    IOTHUBMESSAGE_DISPOSITION_RESULT result;
    const unsigned char* buffer;
    size_t size;
    if (IoTHubMessage_GetByteArray(message, &buffer, &size) != IOTHUB_MESSAGE_OK)
    {
        printf("unable to IoTHubMessage_GetByteArray\r\n");
        result = IOTHUBMESSAGE_ABANDONED;
    }
    else
    {
        /*buffer is not zero terminated*/
        char* temp = malloc(size + 1);
        if (temp == NULL)
        {
            printf("failed to malloc\r\n");
            result = IOTHUBMESSAGE_ABANDONED;
        }
        else
        {
            (void)memcpy(temp, buffer, size);
            temp[size] = '\0';
            EXECUTE_COMMAND_RESULT executeCommandResult = EXECUTE_COMMAND(userContextCallback, temp);
            result =
                (executeCommandResult == EXECUTE_COMMAND_ERROR) ? IOTHUBMESSAGE_ABANDONED :
                (executeCommandResult == EXECUTE_COMMAND_SUCCESS) ? IOTHUBMESSAGE_ACCEPTED :
                IOTHUBMESSAGE_REJECTED;
            free(temp);
        }
    }
    return result;
}

此代码是一个样板 - 对任何解决方案都是相同的。This code is boilerplate -- it's the same for any solution. 此函数将接收消息并通过调用 EXECUTE_COMMAND 将它路由到相应的函数。This function receives the message and takes care of routing it to the appropriate function through the call to EXECUTE_COMMAND. 此时调用的函数取决于模型中的操作定义。The function called at this point depends on the definition of the actions in your model.

在模型中定义操作时,需要实现当设备接收相应的消息时调用的函数。When you define an action in your model, you're required to implement a function that's called when your device receives the corresponding message. 例如,如果模型定义了此操作:For example, if your model defines this action:

WITH_ACTION(SetAirResistance, int, Position)

使用此签名定义函数:Define a function with this signature:

EXECUTE_COMMAND_RESULT SetAirResistance(ContosoAnemometer* device, int Position)
{
    (void)device;
    (void)printf("Setting Air Resistance Position to %d.\r\n", Position);
    return EXECUTE_COMMAND_SUCCESS;
}

请注意,函数的名称与模型中的操作名称匹配,而函数的参数与为该操作指定的参数匹配。Note how the name of the function matches the name of the action in the model and that the parameters of the function match the parameters specified for the action. 第一个参数始终是必需的,包含指向模型实例的指针。The first parameter is always required and contains a pointer to the instance of your model.

当设备收到与此签名匹配的消息时,会调用相应的函数。When the device receives a message that matches this signature, the corresponding function is called. 因此,除了必须包含 IoTHubMessage 中的样板代码以外,接收消息所涉及的操作只是为模型中定义的每个操作定义一个简单的函数。Therefore, aside from having to include the boilerplate code from IoTHubMessage, receiving messages is just a matter of defining a simple function for each action defined in your model.

取消初始化库Uninitialize the library

完成发送数据和接收消息后,可以取消初始化 IoT 库。When you're done sending data and receiving messages, you can uninitialize the IoT library:

...
        DESTROY_MODEL_INSTANCE(myWeather);
    }
    IoTHubClient_LL_Destroy(iotHubClientHandle);
}
serializer_deinit();

上述 3 个函数均符合以前所述的 3 个初始化函数。Each of these three functions aligns with the three initialization functions described previously. 调用这些 API 可确保释放以前分配的资源。Calling these APIs ensures that you free previously allocated resources.

后续步骤Next Steps

本文介绍了有关使用适用于 C 语言的 Azure IoT 设备 SDK 中的库的基本知识。其中针对 SDK 中包含的组件及其体系结构,以及如何开始使用 Windows 示例等进行了详细说明。This article covered the basics of using the libraries in the Azure IoT device SDK for C. It provided you with enough information to understand what's included in the SDK, its architecture, and how to get started working with the Windows samples. 下一篇文章通过讲解有关 IoTHubClient 库的详细信息来继续介绍该 SDK。The next article continues the description of the SDK by explaining more about the IoTHubClient library.

若要详细了解如何针对 IoT 中心进行开发,请参阅 Azure IoT SDKTo learn more about developing for IoT Hub, see the Azure IoT SDKs.

若要进一步探索 IoT 中心的功能,请参阅:To further explore the capabilities of IoT Hub, see: