4. RT-Thread连接阿里云物联网平台

任务目标

在阿里云物联网平台创建产品和设备,使用RT-Thread的MQTT组件完成设备接入,实现设备注册与认证(基于三元组信息的直连认证),并验证设备与平台之间的消息收发。

前置准备

  • 阿里云物联网平台账号,已开通公共实例
  • RT-Thread Studio或基于RT-Thread的嵌入式工程(支持AT指令或以太网)
  • 开发板具备网络连接能力(如ESP8266、以太网或4G模组)

阿里云平台配置

  1. 登录阿里云物联网平台,创建产品(如“RT-Thread_Demo”),选择直连设备、MQTT协议。
  2. 在产品下添加设备,获取设备证书三元组:ProductKey、DeviceName、DeviceSecret。
  3. 在产品详情页的“Topic类列表”中,默认已有 /sys/{ProductKey}/{DeviceName}/thing/event/property/post 和 /sys/{ProductKey}/{DeviceName}/thing/service/property/set 等Topic,可直接使用。

RT-Thread MQTT接入实现

RT-Thread的MQTT软件包基于paho.mqtt.embedded-c实现,支持阿里云要求的MQTT连接参数。设备认证通过MQTT Connect报文中的username和password字段完成,具体规则如下:

  • clientId: {DeviceName}|securemode=3,signmethod=hmacsha1,timestamp=789
  • username: {DeviceName}&{ProductKey}
  • password: 使用HMAC-SHA1对 clientId{DeviceName}deviceName{DeviceName}productKey{ProductKey}timestamp789 进行签名(注意:实际签名串中clientId的值需去掉竖线后的参数部分)

以下代码在RT-Thread中完成MQTT连接与属性上报:


#include <rtthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mqtt_client.h"

#define ALIYUN_PRODUCT_KEY     "a1XXXXXX"
#define ALIYUN_DEVICE_NAME     "device01"
#define ALIYUN_DEVICE_SECRET   "xxxxxxxxxxxxxxxx"

#define ALIYUN_HOST            ALIYUN_PRODUCT_KEY ".iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define ALIYUN_PORT            1883

/* 生成MQTT连接参数 */
static void aliyun_gen_conn_param(char *client_id, char *username, char *password, int buf_len)
{
    char sign_content[256];
    char timestamp[] = "789";
    const char *hmac_key = ALIYUN_DEVICE_SECRET;

    /* clientId: DeviceName|securemode=3,signmethod=hmacsha1,timestamp=789 */
    rt_snprintf(client_id, buf_len, "%s|securemode=3,signmethod=hmacsha1,timestamp=%s",
                ALIYUN_DEVICE_NAME, timestamp);

    /* username: DeviceName&ProductKey */
    rt_snprintf(username, buf_len, "%s&%s", ALIYUN_DEVICE_NAME, ALIYUN_PRODUCT_KEY);

    /* 签名串: clientId{DeviceName}deviceName{DeviceName}productKey{ProductKey}timestamp789 */
    rt_snprintf(sign_content, sizeof(sign_content),
                "clientId%sdeviceName%sproductKey%stimestamp%s",
                ALIYUN_DEVICE_NAME, ALIYUN_DEVICE_NAME, ALIYUN_PRODUCT_KEY, timestamp);

    /* 使用HMAC-SHA1计算签名,结果转为十六进制字符串 */
    aliyun_hmac_sha1_hex(hmac_key, sign_content, password, buf_len);
}

/* MQTT事件回调 */
static void mqtt_callback(MQTTClient *client, MQTTMessage *msg, void *arg)
{
    if (msg->qos == 0)
        rt_kprintf("recv msg: %.*s\n", msg->payloadlen, (char *)msg->payload);
}

/* 属性上报 */
static void aliyun_post_property(MQTTClient *client)
{
    char topic[128];
    char payload[128];
    int msg_id = 1;

    rt_snprintf(topic, sizeof(topic), "/sys/%s/%s/thing/event/property/post",
                ALIYUN_PRODUCT_KEY, ALIYUN_DEVICE_NAME);
    rt_snprintf(payload, sizeof(payload),
                "{\"id\":%d,\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":{\"temperature\":25.5}}",
                msg_id);

    MQTTMessage msg;
    memset(&msg, 0, sizeof(msg));
    msg.qos = 1;
    msg.payload = payload;
    msg.payloadlen = strlen(payload);

    if (MQTTPublish(client, topic, &msg) < 0)
        rt_kprintf("publish failed\n");
    else
        rt_kprintf("publish ok: %s\n", payload);
}

/* 主线程入口 */
static void aliyun_mqtt_thread(void *param)
{
    MQTTClient client;
    MQTTString clientId, username, password;
    char client_id[128], user_name[128], pass_word[64];
    Network n;
    int rc;

    /* 生成阿里云认证参数 */
    aliyun_gen_conn_param(client_id, user_name, pass_word, sizeof(pass_word));

    /* 初始化网络与MQTT客户端 */
    NewNetwork(&n);
    ConnectNetwork(&n, ALIYUN_HOST, ALIYUN_PORT);
    MQTTClientInit(&client, &n, 1024, 1024);

    clientId.cstring = client_id;
    username.cstring = user_name;
    password.cstring = pass_word;

    MQTTConnectData conn_data = MQTTConnectDataDefault;
    conn_data.clientID = clientId;
    conn_data.username = username;
    conn_data.password = password;
    conn_data.keepAliveInterval = 60;
    conn_data.cleansession = 1;

    /* 建立连接 */
    rc = MQTTConnect(&client, &conn_data);
    if (rc != 0) {
        rt_kprintf("MQTT connect failed, rc=%d\n", rc);
        return;
    }
    rt_kprintf("MQTT connected to Aliyun IoT\n");

    /* 订阅云端下行Topic(属性设置) */
    char sub_topic[128];
    rt_snprintf(sub_topic, sizeof(sub_topic), "/sys/%s/%s/thing/service/property/set",
                ALIYUN_PRODUCT_KEY, ALIYUN_DEVICE_NAME);
    MQTTSubscribe(&client, sub_topic, 0, mqtt_callback);

    /* 循环上报属性 */
    while (1) {
        aliyun_post_property(&client);
        rt_thread_mdelay(5000);  /* 5秒上报一次 */
    }
}

/* 创建线程 */
static int aliyun_mqtt_start(void)
{
    rt_thread_t tid = rt_thread_create("aliyun_mqtt",
                                       aliyun_mqtt_thread, RT_NULL,
                                       4096, 25, 10);
    if (tid)
        rt_thread_startup(tid);
    return 0;
}
INIT_APP_EXPORT(aliyun_mqtt_start);

HMAC-SHA1辅助函数

上述代码中的 aliyun_hmac_sha1_hex 需要实现。RT-Thread的mbedtls软件包提供了现成的HMAC-SHA1实现,在工程中添加该软件包后,实现如下:


#include <mbedtls/md.h>

void aliyun_hmac_sha1_hex(const char *key, const char *data, char *output, int out_len)
{
    unsigned char hmac[20];
    mbedtls_md_context_t ctx;

    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1);
    mbedtls_md_hmac_starts(&ctx, (const unsigned char *)key, strlen(key));
    mbedtls_md_hmac_update(&ctx, (const unsigned char *)data, strlen(data));
    mbedtls_md_hmac_finish(&ctx, hmac);
    mbedtls_md_free(&ctx);

    /* 转为小写十六进制字符串 */
    for (int i = 0; i < 20 && i * 2 + 1 < out_len; i++)
        rt_snprintf(output + i * 2, 3, "%02x", hmac[i]);
}

编译与运行

  1. 在RT-Thread Studio中,通过包管理器添加 mbedtls 和 paho-mqtt 软件包。
  2. 将上述代码保存为 aliyun_mqtt.c,放入工程的 applications 目录。
  3. 将代码开头的三元组宏替换为你在阿里云平台创建的实际值。
  4. 编译并烧录到开发板,确保网络连接正常。

验证方法

  • 串口日志:系统启动后,串口输出 MQTT connected to Aliyun IoT,随后每5秒输出一次 publish ok,说明连接与上报成功。
  • 阿里云控制台:在“设备管理”页面,对应设备状态显示为“在线”;在“日志服务”中可看到设备上报的属性数据(temperature=25.5)。
  • 下行验证:在阿里云控制台的“在线调试”中,选择属性设置(如设置temperature为30),开发板串口应打印 recv msg: {...},说明订阅成功。

常见问题

  • 连接失败(rc=-2或-3):检查三元组是否正确,设备是否已激活;确认网络能访问 *.aliyuncs.com:1883。
  • 签名错误:HMAC-SHA1的key为DeviceSecret,签名串中不要包含clientId的竖线后缀参数。
  • 时间戳:示例使用固定值789,实际产品中建议使用当前毫秒时间戳,但需保证签名串与clientId中的timestamp一致。