你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程 - 在不使用设备 SDK 的情况下使用 MQTT 开发 IoT 设备客户端

如果可能,应使用 Azure IoT 设备 SDK 之一以构建 IoT 设备客户端。 但是,在某些情形下(如使用内存有限的设备),可能需要使用 MQTT 库与 IoT 中心进行通信。

本教程中的示例使用 Eclipse Mosquitto MQTT 库。

本教程介绍如何执行下列操作:

  • 生成 C 语言设备客户端示例应用程序。
  • 运行使用 MQTT 库发送遥测数据的示例。
  • 运行使用 MQTT 库以处理从 IoT 中心发送的云到设备消息的示例。
  • 运行使用 MQTT 库管理设备上的设备孪生的示例。

可以使用 Windows 或 Linux 开发计算机以完成本教程中的步骤。

如果没有 Azure 订阅,请在开始之前创建一个免费帐户

先决条件

为 Azure CLI 准备环境

开发计算机先决条件

如果使用的是 Windows:

  1. 安装 Visual Studio(Community、Professional 或 Enterprise 版)。 请务必启用 以 C++ 进行桌面开发 工作负载。

  2. 安装 CMake。 启用“将 CMake 添加到所有用户的系统路径”选项。

  3. 安装 x64 版本Mosquitto

如果使用的是 Linux:

  1. 运行以下命令以安装生成工具:

    sudo apt install cmake g++
    
  2. 运行以下命令以安装 Mosquitto 客户端库:

    sudo apt install libmosquitto-dev
    

设置你的环境

如果尚无 IoT 中心,请运行以下命令以在名为 mqtt-sample-rg 的资源组中创建免费层 IoT 中心。 该命令将名称 my-hub 用作要创建的 IoT 中心的名称示例。 为 IoT 中心选择一个唯一名称来替代 my-hub

az group create --name mqtt-sample-rg --location eastus
az iot hub create --name my-hub --resource-group mqtt-sample-rg --sku F1 

记下 IoT 中心的名称,稍后需要它。

在 IoT 中心内注册设备。 以下命令在名为 my-hub 的 IoT 中心内注册名为 mqtt-dev-01 的设备。 请务必使用 IoT 中心的名称:

az iot hub device-identity create --hub-name my-hub --device-id mqtt-dev-01

使用以下命令创建 SAS 令牌,从而授予设备对 IoT 中心的访问权限。 请务必使用 IoT 中心的名称:

az iot hub generate-sas-token --device-id mqtt-dev-01 --hub-name my-hub --du 7200

记下命令输出的 SAS 令牌,因为稍后需要它。 SAS 令牌如下所示 SharedAccessSignature sr=my-hub.azure-devices.net%2Fdevices%2Fmqtt-dev-01&sig=%2FnM...sNwtnnY%3D&se=1677855761

提示

默认情况下,SAS 令牌的有效期为 60 分钟。 上一个命令中的 --du 7200 选项将令牌持续时间延长到两小时。 如果令牌在你准备使用之前过期,请生成新令牌。 还可以创建持续时间较长的令牌。 要了解详细信息,请参阅 az iot hub generate-sas-token

克隆示例存储库

使用以下命令将示例存储库克隆到本地计算机上的合适位置:

git clone https://github.com/Azure-Samples/IoTMQTTSample.git

存储库还包括:

  • 使用 paho-mqtt 库的 Python 示例。
  • 有关使用 mosquitto_pub CLI 与 IoT 中心交互的说明。

生成 C 示例

在生成示例之前,需要添加 IoT 中心和设备详细信息。 在克隆的 IoTMQTTSample 存储库中,打开 mosquitto/src/config.h 文件。 按如下所示添加 IoT 中心名称、设备 ID 和 SAS 令牌。 请务必使用 IoT 中心的名称:

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#define IOTHUBNAME "my-hub"
#define DEVICEID   "mqtt-dev-01"
#define SAS_TOKEN  "SharedAccessSignature sr=my-hub.azure-devices.net%2Fdevices%2Fmqtt-dev-01&sig=%2FnM...sNwtnnY%3D&se=1677855761"

#define CERTIFICATEFILE CERT_PATH "IoTHubRootCA.crt.pem"

注意

IoTHubRootCA.crt.pem 文件包括 TLS 连接的 CA 根证书。

将更改保存到 mosquitto/src/config.h 文件。

要生成示例,请在 shell 中运行以下命令:

cd mosquitto
cmake -Bbuild
cmake --build build

在 Linux 中,二进制文件位于 mosquitto 文件夹下的 ./build 文件夹中。

在 Windows 中,二进制文件位于 mosquitto 文件夹下的 .\build\Debug 文件夹中。

发送遥测

mosquitto_telemetry 示例展示了如何使用 MQTT 库将设备到云遥测消息发送到 IoT 中心。

在运行示例应用程序之前,运行以下命令以启动 IoT 中心的事件监视器。 请务必使用 IoT 中心的名称:

az iot hub monitor-events --hub-name my-hub

运行 mosquitto_telemetry 示例。 例如,在 Linux 上:

./build/mosquitto_telemetry

az iot hub monitor-events 生成以下输出以显示设备发送的有效负载:

Starting event monitor, use ctrl-c to stop...
{
    "event": {
        "origin": "mqtt-dev-01",
        "module": "",
        "interface": "",
        "component": "",
        "payload": "Bonjour MQTT from Mosquitto"
    }
}

现在可以停止事件监视器。

查看代码

以下代码片段取自 mosquitto/src/mosquitto_telemetry.cpp 文件。

以下语句定义了连接信息和用于发送遥测消息的 MQTT 主题的名称:

#define HOST IOTHUBNAME ".azure-devices.net"
#define PORT 8883
#define USERNAME HOST "/" DEVICEID "/?api-version=2020-09-30"

#define TOPIC "devices/" DEVICEID "/messages/events/"

main 函数设置用户名和密码以对 IoT 中心进行身份验证。 密码是为设备创建的 SAS 令牌:

mosquitto_username_pw_set(mosq, USERNAME, SAS_TOKEN);

此示例使用 MQTT 主题将遥测消息发送到 IoT 中心:

int msgId  = 42;
char msg[] = "Bonjour MQTT from Mosquitto";

// once connected, we can publish a Telemetry message
printf("Publishing....\r\n");
rc = mosquitto_publish(mosq, &msgId, TOPIC, sizeof(msg) - 1, msg, 1, true);
if (rc != MOSQ_ERR_SUCCESS)
{
    return mosquitto_error(rc);
}
printf("Publish returned OK\r\n");

要了解详细信息,请参阅 发送设备到云消息

接收云到设备消息

mosquitto_subscribe 示例展示了如何订阅 MQTT 主题,并使用 MQTT 库从 IoT 中心接收云到设备消息。

运行 mosquitto_subscribe 示例。 例如,在 Linux 上:

./build/mosquitto_subscribe

运行以下命令以从 IoT 中心发送云到设备消息。 请务必使用 IoT 中心的名称:

az iot device c2d-message send --hub-name my-hub --device-id mqtt-dev-01 --data "hello world"

来自 mosquito to_subscribe 的输出如以下示例所示:

Waiting for C2D messages...
C2D message 'hello world' for topic 'devices/mqtt-dev-01/messages/devicebound/%24.mid=d411e727-...f98f&%24.to=%2Fdevices%2Fmqtt-dev-01%2Fmessages%2Fdevicebound&%24.ce=utf-8&iothub-ack=none'
Got message for devices/mqtt-dev-01/messages/# topic

查看代码

以下代码片段取自 mosquitto/src/mosquitto_subscribe.cpp 文件。

以下语句定义了设备用于接收云到设备消息的主题筛选器。 # 是多级通配符:

#define DEVICEMESSAGE "devices/" DEVICEID "/messages/#"

main 函数使用 mosquitto_message_callback_set 函数设置回调以处理从 IoT 中心发送的消息,并使用 mosquitto_subscribe 函数订阅所有消息。 以下代码片段展示了该回调函数:

void message_callback(struct mosquitto* mosq, void* obj, const struct mosquitto_message* message)
{
    printf("C2D message '%.*s' for topic '%s'\r\n", message->payloadlen, (char*)message->payload, message->topic);

    bool match = 0;
    mosquitto_topic_matches_sub(DEVICEMESSAGE, message->topic, &match);

    if (match)
    {
        printf("Got message for " DEVICEMESSAGE " topic\r\n");
    }
}

要了解详细信息,请参阅 使用 MQTT 接收云到设备消息

更新设备孪生

mosquitto_device_twin 示例展示了如何在设备孪生中设置报告的属性,然后读回该属性。

运行 mosquitto_device_twin 示例。 例如,在 Linux 上:

./build/mosquitto_device_twin

来自 mosquitto_device_twin 的输出如以下示例所示:

Setting device twin reported properties....
Device twin message '' for topic '$iothub/twin/res/204/?$rid=0&$version=2'
Setting device twin properties SUCCEEDED.

Getting device twin properties....
Device twin message '{"desired":{"$version":1},"reported":{"temperature":32,"$version":2}}' for topic '$iothub/twin/res/200/?$rid=1'
Getting device twin properties SUCCEEDED.

查看代码

以下代码片段取自 mosquitto/src/mosquitto_device_twin.cpp 文件。

以下语句定义了设备用于订阅设备孪生更新、读取设备孪生和更新设备孪生的主题:

#define DEVICETWIN_SUBSCRIPTION  "$iothub/twin/res/#"
#define DEVICETWIN_MESSAGE_GET   "$iothub/twin/GET/?$rid=%d"
#define DEVICETWIN_MESSAGE_PATCH "$iothub/twin/PATCH/properties/reported/?$rid=%d"

main 函数使用 mosquitto_connect_callback_set 函数设置回调以处理从 IoT 中心发送的消息,并使用 mosquitto_subscribe 函数订阅 $iothub/twin/res/# 主题。

以下代码片段展示了 connect_callback 函数使用 mosquitto_publish 在设备孪生中设置报告的属性。 设备将消息发布到 $iothub/twin/PATCH/properties/reported/?$rid=%d 主题。 每次设备将消息发布到主题时,%d 值都会递增:

void connect_callback(struct mosquitto* mosq, void* obj, int result)
{
    // ... other code ...  

    printf("\r\nSetting device twin reported properties....\r\n");

    char msg[] = "{\"temperature\": 32}";
    char mqtt_publish_topic[64];
    snprintf(mqtt_publish_topic, sizeof(mqtt_publish_topic), DEVICETWIN_MESSAGE_PATCH, device_twin_request_id++);

    int rc = mosquitto_publish(mosq, NULL, mqtt_publish_topic, sizeof(msg) - 1, msg, 1, true);
    if (rc != MOSQ_ERR_SUCCESS)

    // ... other code ...  
}

设备订阅 $iothub/twin/res/# 主题,当它收到来自 IoT 中心的消息时,message_callback 函数会处理该消息。 运行示例时,message_callback 函数调用两次。 设备第一次收到来自 IoT 中心的响应以响应报告的属性更新。 然后,设备请求设备孪生。 设备第二次收到请求的设备孪生。 以下代码片段展示了 message_callback 函数:

void message_callback(struct mosquitto* mosq, void* obj, const struct mosquitto_message* message)
{
    printf("Device twin message '%.*s' for topic '%s'\r\n", message->payloadlen, (char*)message->payload, message->topic);

    const char patchTwinTopic[] = "$iothub/twin/res/204/?$rid=0";
    const char getTwinTopic[]   = "$iothub/twin/res/200/?$rid=1";

    if (strncmp(message->topic, patchTwinTopic, sizeof(patchTwinTopic) - 1) == 0)
    {
        // Process the reported property response and request the device twin
        printf("Setting device twin properties SUCCEEDED.\r\n\r\n");

        printf("Getting device twin properties....\r\n");

        char msg[] = "{}";
        char mqtt_publish_topic[64];
        snprintf(mqtt_publish_topic, sizeof(mqtt_publish_topic), DEVICETWIN_MESSAGE_GET, device_twin_request_id++);

        int rc = mosquitto_publish(mosq, NULL, mqtt_publish_topic, sizeof(msg) - 1, msg, 1, true);
        if (rc != MOSQ_ERR_SUCCESS)
        {
            printf("Error: %s\r\n", mosquitto_strerror(rc));
        }
    }
    else if (strncmp(message->topic, getTwinTopic, sizeof(getTwinTopic) - 1) == 0)
    {
        // Process the device twin response and stop the client
        printf("Getting device twin properties SUCCEEDED.\r\n\r\n");

        mosquitto_loop_stop(mosq, false);
        mosquitto_disconnect(mosq); // finished, exit program
    }
}

要了解详细信息,请参阅 使用 MQTT 更新设备孪生报告属性使用 MQTT 检索设备孪生属性

清理资源

如果计划继续学习其他设备开发者文章,可保留并重复使用在本文中使用的资源。 否则,可删除在本文中创建的资源,以免产生额外费用。

可通过使用以下 Azure CLI 命令删除整个资源组来一次性删除中心和注册设备。 如果这些资源与你想要保留的其他资源共享一个资源组,则不要使用此命令。

az group delete --name <YourResourceGroupName>

若要仅删除 IoT 中心,请使用 Azure CLI 运行以下命令:

az iot hub delete --name <YourIoTHubName>

若要仅删除注册到 IoT 中心的设备标识,请使用 Azure CLI 运行以下命令:

az iot hub device-identity delete --hub-name <YourIoTHubName> --device-id <YourDeviceID>

你可能还需要从开发计算机中删除克隆的示例文件。

后续步骤

现在,你已经了解如何使用 Mosquitto MQTT 库与 IoT 中心通信,建议接下来查看: