本文讲解模组商在支持MQTT的通信模组上如何集成阿里云IoT提供的Link SDK,以及完成相关需要完成的工作。本文档基于Link SDK 3.0.1进行编写。

场景说明

Link SDK是阿里云IoT提供的用于将设备连接到阿里云IoT的设备端SDK,用于完成设备认证、数据通信等功能。将Link SDK集成到通信模组中,可以带来以下好处:

  • 设备厂商在MCU上无需关心如何连接阿里云IoT,只是通过调用模组提供的AT指令就可以连接阿里云IoT,因此对MCU的资源消耗没有增加
  • 阿里将在认证合作伙伴页面露出通过认证的模组型号、购买链接、开发指导等文档,引导设备商以及服务提供商购买通过认证的通信模组连接阿里云IoT。

使用集成了SDK的模组开发设备的示意图如下所示:

设备商开发设备的流程为:

  • 购买集成了阿里云Link SDK的模组
  • 在MCU上通过模组提供的AT指令连接阿里云,以及从阿里云IoT收发数据
  • 在阿里云IoT上部署云端服务,对设备进行管理

对于模组商而言,在模组上需要完成的工作包括:

  • 将Link SDK正确的集成到模组上
  • 提供相应的连接阿里云IoT物联网的AT指令供MCU调用

文档目标

本文讲解如何将Link SDK集成到已支持MQTT协议的模组上,让模组商了解集成SDK的大概过程。为了简化难度,本文只讲解如何集成Link SDK的设备签名模块,从而让模组可以接入到阿里云物联网平台。

建议模组商尽量多的集成Link SDK的功能,比如物模型、OTA、设备影子等功能,因为模组上集成的Link SDK的功能越多,意味着设备商连接阿里云物联网平台时可以使用的功能越多。

由于模组已支持MQTT协议,SDK将会利用模组上已具备的MQTT功能模块,Link SDK提供API生成连接阿里云物联网平台MQTT Broker所需要的ClientID、UserName、Password等信息,然后模组商使用这些数据与阿里云物联网平台的Broker建立连接,并继而实现对MQTT Topic的订阅或者向指定Topic发送数据。

在阿里云物联网平台创建基础版产品

模组商在调试的时候,需要创建一个测试产品和一个测试设备,用于验证SDK是否已正常工作。请按照下面的过程创建设备:

  1. 点击阿里云物联网平台登录控制台,模组商需要注册一个阿里云账号,注册阿里云账号是免费的
  2. 登录阿里云物联网平台的控制台之后,按照“创建产品(基础版)”描述的步骤创建一个基础版产品。在产品页面可以获取到产品的product_key和product_secret
  3. 参照“单个创建设备”描述的步骤添加一个测试设备,在设备页面可以获取到设备的device_name和device_secret

下面讲解模组商在模组上集成SDK的开发过程:

集成SDK的开发过程

模组商集成SDK时,需要进行下面几个开发过程:

SDK配置与代码抽取

SDK包含的功能较多,为了避免SDK消耗过多的RAM和Flash,SDK提供配置和抽取工具让开发者只使用需要的组件。

配置SDK

下面讲解模组商如何配置需要的软件功能。

运行配置命令

  • Linux系统

进入SDK的根目录下,运行命令

make menuconfig
			
  • Windows系统

运行SDK根目录下的config.bat

config.bat
			

上面的两种方式都会启动SDK的配置工具,界面如下所示:

注:

  • 功能配置选项前的号表示该功能被使能,没有号表示该功能未被使能
  • 表格中显示的是可配置的选项,因此即使将所有选项都失效,SDK中不能失效的功能仍然有效
  • 按下空格键可以选中或者失效某个功能,使用小键盘的上下键来在不同功能之间切换;
  • 如果想知道每个选项的具体含义,先用方向键将高亮光条移到那个选项上,再按键盘上的“h”按键,将出现帮助文本,说明选项是什么含义,打开了和关闭了意味着什么。

对于本场景而言:

  • 如果开发环境支持stdint.h,那么使能PLATFORM_HAS_STDINT
  • 如果开发环境支持malloc/free,那么使能PLATFORM_HAS_DYNMEM
  • 如果运行环境有OS,那么使能PLATFORM_HAS_OS

其它选项均不使能。配置完成之后,通过“Exit”按键退出,并根据提示保存配置。

抽取SDK代码

下面讲解如何抽取SDK代码

运行抽取命令

  • Linux系统

进入SDK的根目录下,运行命令

sh ./extract.sh
			
  • Windows系统

运行SDK根目录下的config.bat

extract.bat
			
上面的两种方式都会启动SDK的代码抽取工具,抽取工具将会把必要的文件抽取之后放置到output目录下,如下所示:

SDK文件加入用户工程

用户可以将output目录下的eng文件夹复制到自己的工程目录,然后将代码文件添加到工程中。

需要添加的文件位于目录eng/dev_sign,eng/infra,eng/wrappers中,编译的时候也需要指定头文件查找路径包含这几个目录

HAL对接

将Link SDK与已有MQTT融合

生成MQTT ClientID、UserName、Password

MQTT Client连接MQTT Broker时需要指定ClientID、UserName、Password等信息,Link SDK提供API IOT_Sign_MQTT()用于生成这些数据:

int32_t IOT_Sign_MQTT(iotx_mqtt_region_types_t region, iotx_dev_meta_info_t *meta, iotx_sign_mqtt_t *signout)
			

注:使用该函数需要包含头文件dev_sign_internal.h:

#include "dev_sign_internal.h"
			

入参说明:

  • region

指定连接的阿里云IoT的云端站点,可选值定义在文件eng/infra/infra_defs.h中:

typedef enum {
    IOTX_CLOUD_REGION_SHANGHAI,   /* Shanghai */
    IOTX_CLOUD_REGION_SINGAPORE,  /* Singapore */
    IOTX_CLOUD_REGION_JAPAN,      /* Japan */
    IOTX_CLOUD_REGION_USA_WEST,   /* America */
    IOTX_CLOUD_REGION_GERMANY,    /* Germany */
    IOTX_CLOUD_REGION_CUSTOM,     /* Custom setting */
    IOTX_CLOUD_DOMAIN_MAX         /* Maximum number of domain */
} iotx_mqtt_region_types_t;
			
  • 如果模组在国内售卖,将该参数设置为IOTX_CLOUD_REGION_SHANGHAI

  • meta

指定设备的身份信息,数据结构定义如下:

typedef struct {
    char product_key[IOTX_PRODUCT_KEY_LEN + 1];
    char product_secret[IOTX_PRODUCT_SECRET_LEN + 1];
    char device_name[IOTX_DEVICE_NAME_LEN + 1];
    char device_secret[IOTX_DEVICE_SECRET_LEN + 1];
} iotx_dev_meta_info_t;
			

其中的四个变量是产品在阿里云物联网平台定义完毕之后,由设备商为每个设备申请的;在实际产品开发时,这几个参数需要由MCU通过AT指令传递给模组

出参说明

  • signout

该参数输出连接MQTT Broker时需要的ClientID、Username、Password等信息,结构定义如下:

typedef struct {
    char hostname[DEV_SIGN_HOSTNAME_MAXLEN];
    uint16_t port;
    char clientid[DEV_SIGN_CLIENT_ID_MAXLEN];
    char username[DEV_SIGN_USERNAME_MAXLEN];
    char password[DEV_SIGN_PASSWORD_MAXLEN];
} iotx_sign_mqtt_t;
			

其中hostname是阿里云物联网云端站点MQTT Broker的域名,port是阿里云物联网云端站点的MQTT Broker的端口号。

返回值说明

成功该函数将返回0,错误将返回-1。

使用示例

eng\examples\dev_sign_example.c中示范了如何使用IOT_Sign_MQTT()函数,下面是代码片段示例:

#define EXAMPLE_PRODUCT_KEY     "a1X2bEnP82z"
#define EXAMPLE_PRODUCT_SECRET  "7jluWm1zql7bt8qK"
#define EXAMPLE_DEVICE_NAME     "example1"
#define EXAMPLE_DEVICE_SECRET   "ga7XA6KdlEeiPXQPpRbAjOZXwG8ydgSe"

/* Implenment this HAL or using "printf" of your own system if you want to print something in example*/
void HAL_Printf(const char *fmt, ...);

int main(int argc, char *argv[])
{
    iotx_mqtt_region_types_t region = IOTX_CLOUD_REGION_SHANGHAI;
    iotx_dev_meta_info_t meta;
    iotx_sign_mqtt_t sign_mqtt;

    memset(&meta,0,sizeof(iotx_dev_meta_info_t));
    memcpy(meta.product_key,EXAMPLE_PRODUCT_KEY,strlen(EXAMPLE_PRODUCT_KEY));
    memcpy(meta.product_secret,EXAMPLE_PRODUCT_SECRET,strlen(EXAMPLE_PRODUCT_SECRET));
    memcpy(meta.device_name,EXAMPLE_DEVICE_NAME,strlen(EXAMPLE_DEVICE_NAME));
    memcpy(meta.device_secret,EXAMPLE_DEVICE_SECRET,strlen(EXAMPLE_DEVICE_SECRET));

    if (IOT_Sign_MQTT(region,&meta,&sign_mqtt) < 0) {
        return -1;
    }
    ...
}

			

在该示例中,设备的product_key、product_secret、device_name、device_secret都使用了固定的数值,在实际产品运行时需要由MCU告知模组。

在模组商调试时,模组商需要自己到阿里云物联网平台去创建产品和测试设备,然后使用平台为设备生成的product_key、product_secret、device_name、device_secret。

上传模组商编码和模组型号

如果模组商希望将模组送到阿里云IoT进行模组认证,那么模组商需要将模组商编码和模组型号进行上报,这样阿里云物联网平台可以统计通过指定模组商连接到平台的设备数量,也可以统计通过模组商的某个型号模组连接设备的数量。

模组商编码和模组型号请在集成SDK前联系阿里进行获取,请将邮件发送到邮箱 linkcertification@list.alibaba-inc.com,并在主题中标注“模组/芯片型号申请”。注:非模组商无需申请编__码

当模组与阿里云物联网平台建立连接之后,请复制并调用下面的函数进行信息上报,其中参数pid是模组商编码、mid是型号编码:

#define PID_STRING_LEN_MAX          32  /* PID字符串最大长度 */
#define MID_STRING_LEN_MAX          32  /* MID字符串最大长度 */


int example_report_pid_mid(void *pclient, const char *product_key, const char *device_name, const char *pid, const char *mid)
{
    int res = 0;
    iotx_mqtt_topic_info_t topic_msg;

    const char topic_frag1[] = "/sys/";
    const char topic_frag2[] = "/thing/deviceinfo/update";
    char topic[sizeof(topic_frag1) + sizeof(topic_frag2) + IOTX_PRODUCT_KEY_LEN + IOTX_DEVICE_NAME_LEN] = {0};

    const char payload_frag1[] = "{\"id\":\"0\",\"version\":\"1.0\",\"params\":[{\"attrKey\":\"SYS_MODULE_ID\",\"attrValue\":\"";
    const char payload_frag2[] = "\",\"domain\":\"SYSTEM\"},{\"attrKey\":\"SYS_PARTNER_ID\",\"attrValue\":\"";
    const char payload_frag3[] = "\",\"domain\":\"SYSTEM\"}],\"method\": \"thing.deviceinfo.update\"}";
    char payload[sizeof(payload_frag1) + sizeof(payload_frag2) + sizeof(payload_frag3) + PID_STRING_LEN_MAX + MID_STRING_LEN_MAX] = {0};

    if (strlen(pid) > PID_STRING_LEN_MAX || strlen(mid) > MID_STRING_LEN_MAX) {
        return -1;
    }

    /* 组装MQTT topic字符串 */
    memcpy(topic, topic_frag1, strlen(topic_frag1));
    memcpy(topic + strlen(topic), product_key, strlen(product_key));
    memcpy(topic + strlen(topic), "/", 1);
    memcpy(topic + strlen(topic), device_name, strlen(device_name));
    memcpy(topic + strlen(topic), topic_frag2, strlen(topic_frag2));

    /* 组装MQTT payload字符串, payload中包含了PID, MID字符串 */
    memcpy(payload, payload_frag1, strlen(payload_frag1));
    memcpy(payload + strlen(payload), mid, strlen(mid));
    memcpy(payload + strlen(payload), payload_frag2, strlen(payload_frag2));
    memcpy(payload + strlen(payload), pid, strlen(pid));
    memcpy(payload + strlen(payload), payload_frag3, strlen(payload_frag3));

    topic_msg.qos = IOTX_MQTT_QOS0;
    topic_msg.retain = 0;
    topic_msg.dup = 0;
    topic_msg.payload = (void *)payload;
    topic_msg.payload_len = strlen(payload);

    /* 使用MQTT publish API发送PID,MID信息报文, 由于使用模组上自带的MQTT功能,
       模组商集成时需要将下面的publish函数换为实际的publish函数*/
    res = IOT_MQTT_Publish(pclient, topic, &topic_msg);
    if (res < 0) {
        return -1;
    }

    return 0;
}

			

调试

将模组连接到阿里云物联网平台

模组商需要编写函数用于将模组连接到阿里云物联网平台,模组上的MQTT应该会提供一个连接MQTT Broker的函数,在其中需要输入通过调用Link SDK提供的API IOT_Sign_MQTT()获取到的域名、端口、ClientID、usename、password等信息。

下面是将SDK与一个mosquitto集成(一个开源的MQTT库)的示例伪代码:

        //调用签名函数获取MQTT username、password、clientID等信息
        IOT_Sign_MQTT(region,&meta,&sign_mqtt);

        mosquitto_lib_init();

        //下面的代码设置了mqtt clientID和cleansession参数
        mosq = mosquitto_new(sign_mqtt.clientid,0/*dont clean session*/,NULL);
        if(mosq==NULL){
                printf("Error:Failed creating mosquitto client\n\r");
                return(-1);
        }
        //下面的代码设置了MQTT连接时使用的username和password
        if(0 != mosquitto_username_pw_set(mosq, sign_mqtt.username, sign_mqtt.password)){
                printf("Error:Failed setting username or password\n\r");
                return(-1);
        }

        ...
        //下面的代码用于建立到MQTT Broker的连接,其中使用到了域名、端口
        if(mosquitto_connect(mosq, sign_mqtt.hostname, sign_mqtt.port, kaInterval)){
                printf("Error: Failed connecting cloud.\n\r");
                sleep(1);
                return -1;
        }
			

注:MQTT建立连接时还需要指定keepalive间隔,该值的推荐值为60秒,模组商也可以将该参数的设置提供AT指令给MCU进行动态修改,阿里云物联网平台接受的keepalive间隔范围为30~1200秒。

模组正常连接到物联网平台之后,如果模组没有断开MQTT连接,模组将处于在线状态。可以在阿里云物联网平台的控制台找到测试设备,查看其状态(下图展示了一个在线设备的状态):

如何判断程序可正常发送数据到物联网平台

当模组与阿里云物联网平台已建立连接之后,可以向topic /${productKey}/${deviceName}/get发送一个消息,来检查数据是否已可以正常发送到物联网平台。

注意:/${productKey}/${deviceName}/get这个topic默认只有“订阅”权限,请在物联网平台的控制台将其修改为“发布和订阅”,避免消息发送到云端后被云端丢弃; 把该topic修改为“发布和订阅”,主要是为了让example程序运行不出错。

下面是一个参考实现示例:

    int res = 0;
    iotx_mqtt_topic_info_t topic_msg;
    const char *fmt = "/%s/%s/get";
    char *topic = NULL;
    int topic_len = 0;
    char *payload = "hello,world";

    topic_len = strlen(fmt) + strlen(product_key) + strlen(device_name) + 1;
    topic = HAL_Malloc(topic_len);
    if (topic == NULL) {
        HAL_Printf("memory not enough\n");
        return -1;
    }
    memset(topic, 0, topic_len);
    //此处生成topic
    HAL_Snprintf(topic, topic_len, fmt, product_key, device_name);

        //下面的代码生成一个消息
    memset(&topic_msg, 0x0, sizeof(iotx_mqtt_topic_info_t));
    topic_msg.qos = IOTX_MQTT_QOS0;
    topic_msg.retain = 0;
    topic_msg.dup = 0;
    topic_msg.payload = (void *)payload;
    topic_msg.payload_len = strlen(payload);

        //下面的代码向指定的topic发送消息,请使用模组上的MQTT Publish函数替换下面的发送函数
    res = IOT_MQTT_Publish(handle, topic, &topic_msg);
			

在物联网平台的控制台上,在具体的设备的日志服务中,可以查看物联网平台是否已接收到该数据:

查看是否接收到数据

注:

  • 日志会显示在什么时间收到了来自设备的消息,也会显示收到消息的topic,并不会显示消息的内容
  • 阿里云物联网平台目前不支持QoS2

如何判断程序已正确订阅TOPIC

模组商可以向topic /{productKey}/${deviceName}/get订阅数据,这样当设备上报一个数据到物联网平台后,平台又会将数据发送回设备,从而让模组商得知订阅是否工作正常。下面是示例伪代码:

    int res = 0;
    const char *fmt = "/%s/%s/get";
    char *topic = NULL;
    int topic_len = 0;

    topic_len = strlen(fmt) + strlen(product_key) + strlen(device_name) + 1;
    topic = HAL_Malloc(topic_len);
    if (topic == NULL) {
        HAL_Printf("memory not enough\n");
        return -1;
    }
    //下面的代码用于组装topic
    memset(topic, 0, topic_len);
    snprintf(topic, topic_len, fmt, product_key, device_name);

    /*下面的代码用于订阅topic,以及指定消息处理函数。
      请使用模组上实际的MQTT subscribe函数替换下面的代码*/
    res = IOT_MQTT_Subscribe(handle, topic, IOTX_MQTT_QOS0, example_message_arrive, NULL);
			

在物联网平台的控制台,模组商可以查看平台是否将数据发送给了设备:

注:

  • 日志会显示在什么时间发送了消息到设备端,不会显示消息内容
  • 模组商需要在模组上查看收到的数据是否是自己上传的数据,来确保接收数据工作正常

AT指令实现

模组商还需要提供AT指令给MCU调用,由于模组以前已支持MQTT,所以应该已经提供MQTT连接配置、发起连接、断开连接、订阅、Publish等接口。集成了Link SDK之后,模组商可以新增针对阿里云的AT指令,也可以修改目前已有的AT指令。

下面是推荐增加的AT指令:

指令项 说明
阿里设备身份信息设置 设置设备的product_key, product_secret, device_name, device_secret
阿里云端region设置 阿里云IoT提供中国、美国、日本等多个云端站点,可以让MCU指定需要连接的阿里云IoT的站点以及端口信息

其余的MQTT指令可以继续使用模组商已有的指令