流程引导
如何让您的设备连接上云,请参考一下路径。
云
端
产品版本
2020-03-31
V 1.0.0 版本发布
发布时间时间:2020-03-31
云端
一、产品管理 新增产品管理、云端订阅、优模型定义等功能
- 产品管理:提供产品的三元组创建及管理功能,提供产品的增删改查,并且针对产品进行优模型的定义
- 优模型管理:提供优模型的文件上传,在线编辑等能力
二、设备管理 新增设备的创建设备管理、批量创建设备管理
- 设备管理:提供设备的新增功能,实现对设备上下线管理,删除管理等能力
- 批量新增设备管理:实现对设备的批量新增,批量删除等功能
设备端
提供云端直连设备的硬件SDK,可以进行设备连云、设备身份认证、属性上报、设置服务调用、事件上报等功能。 SDK版本包括:
- C SDK
2020-06-01
V 1.00.04.00 版本发布
发布时间时间:2020-06-01
云端
二、设备管理
- 新增批量查询设备属性功能
平台简介
平台优势
设备接入:
- 提供不同协议类型的设备接入的SDK
- 集成设备配网方案
硬件云平台:
- 产品和设备的生命周期管理
- 设备数据存储
- 运维监控,实时状态和历史状态
设备权限:
- 结合权限系统,提供互联网级方案和企业级方案
灵活扩展:
- 可根据提供的API,自行定制业务需求
平台架构
名词解释
以下主要介绍硬件云的相关名词解释:
名称 | 描述 |
产品 | 设备的集合,通常指一组具有相同功能的设备。物联网平台为每个产品颁发全局唯一的ProductKey |
设备 | 归属于某个产品下的具体设备。物联网平台为设备颁发产品内唯一的证书DeviceName。设备可以直接连接物联网平台,也可以作为子设备通过网关连接物联网平台。 |
网关 | 能够直接连接物联网平台的设备,且具有子设备管理功能,能够代理子设备连接云端。 |
子设备 | 本质上也是设备。子设备不能直接连接物联网平台,只能通过网关连接。 |
设备证书(三元组) | 设备证书指ProductKey、DeviceName、DeviceSecret。 ProductKey:是物联网平台为产品颁发的全局唯一标识。该参数很重要,在设备认证以及通信中都会用到,因此需要您保管好。 DeviceName:在注册设备时,自定义的或自动生成的设备名称,具备产品维度内的唯一性。该参数很重要,在设备认证以及通信中都会用到,因此需要您保管好。 DeviceSecret:物联网平台为设备颁发的设备密钥,和DeviceName成对出现。该参数很重要,在设备认证时会用到,因此需要您保管好并且不能泄露。 |
ProductSecret | 由物联网平台颁发的产品密钥,通常与ProductKey成对出现,可用于一型一密的认证方案。该参数很重要,需要您保管好,不能泄露 |
标签 | 标签分为产品标签、设备标签。 产品标签:描述同一个产品下,所有设备所具有的共性信息。 设备标签:通常根据设备的特性为设备添加的特有的标记,您可灵活自定义标签内容。 |
优模型 | 是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为 TSL(即 Thing Specification Language),采用JSON格式,您可以根据TSL组装上报设备的数据。 |
属性 | 设备的模型之一,一般用于描述设备运行时的状态,如环境监测设备所读取的当前环境温度等。属性支持 GET 和 SET 请求方式。应用系统可发起对属性的读取和设置请求。 |
服务 | 设备的模型之一,设备可被外部调用的能力或方法,可设置输入参数和输出参数。相比于属性,服务可通过一条指令实现更复杂的业务逻辑,如执行某项特定的任务。 |
事件 | 设备的模型之一,设备运行时的事件,事件一般包含需要被外部感知和处理的通知信息,可包含多个输出参数。如,某项任务完成的信息,或者设备发生故障或告警时的温度等,事件可以被订阅和推送。 |
数据解析脚本 | 针对采用透传格式/自定义数据格式的设备,需要在云端编写数据解析脚本,将设备上报的二进制数据或自定义的JSON数据,转换为平台上的Alink JSON数据格式。 |
场景联动 | 场景联动是一种开发自动化业务逻辑的可视化编程方式,您可以通过可视化的方式定义设备之间联动规则,并将规则部署至云端或者边缘端。 |
一型一密 | 同一产品下所有设备可以烧录相同产品证书(即ProductKey和ProductSecret)。设备发送激活请求时,物联网平台进行产品身份确认,认证通过,下发该设备对应的DeviceSecret。 |
一机一密 | 每个设备烧录其唯一的设备证书(ProductKey、DeviceName和DeviceSecret)。当设备与物联网平台建立连接时,物联网平台对其携带的设备证书信息进行认证。 |
使用限制
二、快速入门
业务使用全过程
产品开发阶段
关键操作 | 说明 |
开发者注册账号 | 开发者注册入口。 |
开通硬件注册账号 | 邮件将您的账号发送到【chenwei@ut.cn】申请开通硬件云服务。 |
创建产品 | 在开发者中心前端页面创建产品档案,产品信息包括产品名称、节点类型、联网方式等信息。 |
产品优模型开发 | 定义产品的通讯协议,系统封装成属性、服务和事件。 |
开通云端订阅服务 | 应用服务端需要订阅设备上报的信息,需要申请开通订阅服务,在开发者中心的前端操作台就能申请开通。 |
应用服务端开发 | 基于硬件云平台提供的API接口,便于设备的远程监控和控制。 |
设备开发 | 设备接入硬件云平台提供的设备端 SDK ,便于设备能够接入到IoT平台,并能正常上报数据和接收命令。 |
设备接入
关键操作 | 说明 |
注册设备 | 设备在接入前,需要在硬件云平台为设备开户,将设备接入需要的信息提前录入到硬件云平台,也就是得闲生成设备的三元组。 |
接入设备 | 设备通电,被用户激活后,设备能够正常接入到硬件云平台。 |
应用后台接入
关键操作 | 说明 |
调测应用后台 | 应用接入硬件云平台后,查看应用服务器是否能够远程控制设备。 |
步骤一: 成为开发者
在使用硬件云平台之前,开发者需要先注册一个平台的开发者账号,目前平台注册是基于用户中心的。
注册
打开 开发者中心,会自动跳转到用户中心登录注册页面,按指引完成用户中心的注册流程。
登陆
注册完成后,会自动登陆开发者中心。目前硬件云的自动授权功能正在开发中,开发者注册完成之后,需要发邮件到【chenwei@ut.cn】申请开通硬件云的使用权限。
步骤二: 创建产品
创建产品的方式:
目前只支持开发者登陆平台提供的开发者中心,在线创建并开发产品。
前提条件:
- 开发者已经在开发者中心完成注册
- 开发者的账号已经完成硬件云的使用授权
- 开发者登陆开发者中心
创建产品操作步骤:
- 进入菜单 【IoT 硬件云】 - 【产品】管理页面:
- 点击右上角【创建产品】的按钮,在弹窗中按照提示填写产品的基本信息,然后点击“完成”进行产品创建。
- 创建成功,会生成产品的唯一标识符 ProductKey。
注意事项:
- 每个开发者账号最多创建 100 款产品。
- 每款产品的 ProductKey 都是唯一的,由云平台自动生成的,如果云端的产品被删除了,要重新创建,那么原来已经烧录了三元组的设备要重新烧录,请谨慎删除产品。
步骤三:产品优模型定义
如何定义优模型
优模型,将实际产品抽象成由属性、服务、事件所组成的数据模型,便于云端管理和数据交互。产品创建完成后,开发者可以为它定义优模型,产品下的设备将自动继承优模型内容。
为了方便开发者定义产品的物模型,以下用取餐柜作为一个示例(仅提供参考),协助理解属性、服务、事件的定义:
属性:
服务:
事件:
开发优模型的操作步骤
- 点击已经创建的产品,进入产品的详情页面,点击左侧菜单【优模型】
- 点击右上角的【新增】按钮,按照提醒填写信息
步骤四:创建设备
创建设备的方式:
- 支持开发者登陆平台提供的开发者中心,在线创建设备。
- 支持开发者直接调用创建设备的后台接口进行创建
- 创建设备可以选择创建单台设备或批量创建多台设备
前提条件:
- 开发者已经在线完成产品的开发
开发者中心操作步骤:
新增单台设备
- 进入菜单 【IoT 硬件云】 - 【设备】管理页面:
- 点击右上角【新建设备】的按钮,在弹窗中按照提示填写设备的基本信息,然后点击“完成”进行设备创建。
- 创建成功,会生成设备的 DeviceSecret。 DeviceSecret :指设备的密钥,和DeviceName成对出现。该参数很重要,在设备认证时会用到,因此需要您保管好并且不能泄露。设备三元组之一。
批量创建设备
- 进入菜单 【IoT 硬件云】 - 【设备】管理页面:
- 点击右上角【批量新建设备】的按钮,在弹窗中按照提示填写设备的基本信息,然后点击“完成”进行设备创建。
- 切换设备列表顶部的 Tab 到【批次管理】,可以查询到批量创建设备的记录。
- 点击批次记录右侧的【下载设备证书】的按钮,可以下载该批次下的所有设备的三元组,可用于烧录到设备中。
调用接口操作步骤:
- 创建单个设备 path : /device/create
- 批量创建设备 path : /device/batchCreate
- 查询批量创建设备详情 path : /device/queryBatchInfo
注意事项:
- 单款产品最多可以添加 50000 台设备。
- 批量创建设备,每次最多可以创建 1000 台设备。
建立设备与平台的连接
平台提供设备端SDK,设备使用SDK与平台建立通信。在这一步里,我们使用平台提供的样例程序
data_model_basic_demo
模拟设备进行开发,实现设备与物联网平台的通信。背景信息
- 本示例使用Linux下的设备端C语言SDK4.x版。该SDK的编译环境推荐使用64位的Ubuntu16.04。
- SDK的开发编译环境会用到以下软件:make-4.1、git-2.7.4、gcc-5.4.0、gcov-5.4.0、lcov-1.12、bash-4.3.48、tar-1.28和mingw-5.3.1。可以使用如下命令行安装:
sudo apt-get install -y build-essential make git gcc
操作步骤
- 登录Linux虚拟机。
- 获取设备端C SDK 4.x版。
- 使用unzip命令解压压缩包。
- 设备身份信息将通过demo/data_model_basic_demo.c的main函数设置。因此,需要将main函数中的设备证书信息修改为创建产品与设备步骤中创建的设备证书,完成后保存退出。如下所示,填入ProductKey、DeviceName和DeviceSecret。设备使用该证书进行身份认证并连接物联网平台。
- 在SDK根目录,执行make命令,完成样例程序的编译。
$ make distclean $ make
生成的样例程序data_model_basic_demo
存放在./output/目录下。 - 运行样例程序。
./output/data_model_basic_demo
登录平台,在对应实例下,设备状态显示为在线,则表示设备与物联网平台成功连接。设备上线成功后,会自动向物联网平台上报消息。您可以通过查看日志,获取具体内容。
步骤五:订阅设备消息
物联网平台将设备上报的数据流转至云端的消息队列 RocketMQ 的 Topic 中,然后,应用服务后台订阅了 Topic 后 RocketMQ 将数据流转到您的服务器。通过消息队列 RocketMQ 消峰去谷,缓冲消息,可以减轻服务器同时接收大量设备消息的压力。
目前,硬件云平台设计的规则是:
- 一款产品对应一个 Topic
- 设备上报的属性、事件和设备上下线状态都会转发到对应的产品Topic中
适用场景
服务后台需要有监听设备信息的需求,例如设备的上下线通知、设备的异常告警事件等
操作步骤:
●开通云端订阅
- 左侧菜单栏选中【IoT 硬件云】-【产品】,找到需要订阅的产品,点击详情,点击【我要开通云端订阅】
- 申请开通云端订阅后,请把产品的 Productkey ,邮件发送到 yangguangqing@ut.cn,并且要说明需要订阅的后台服务数量。
- 硬件云运维人员会为开发者提供产品的 Topic、Group ID(GID)、接入点(Endpoint),以及 AccessKeyId 和 AccessKeySecret 信息
●服务后台订阅设备消息
- 添加 Maven 依赖,请在 pom.xml 文件中添加以下依赖:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>4.5.1</version>
</dependency>
- 生产者示例代码
//配置参数
String accesskey ="accesskey";
String secretkey ="secretkey";
String gid = "GID_XXXX";
String topic ="topic";
String endpoint ="endpoint";
//设置为云上创建的 GID, 以及替换为自己的 AccessKeyId 和 AccessKeySecret
DefaultMQProducer producer = new DefaultMQProducer(gid, new AclClientRPCHook(new SessionCredentials(accesskey,secretkey)));
//设置为自己的云上接入点
producer.setNamesrvAddr(endpoint);
// 云上消息轨迹需要设置为 CLOUD
producer.setAccessChannel(AccessChannel.CLOUD);
producer.start();
// 设置为云上创建的 Topic 名字
Message msg = new Message(topic, "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.send(msg);
- 消费者示例代码
//配置参数
String accesskey ="accesskey";
String secretkey ="secretkey";
String topic ="topic";
String gid = "GID_XXXX";
String endpoint ="endpoint";
//设置为云上创建的 GID, 以及替换为自己的 AccessKeyId 和 AccessKeySecret
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(gid, new AclClientRPCHook(new SessionCredentials(accesskey,secretkey)), new AllocateMessageQueueAveragely());
//设置为云上接入点
consumer.setNamesrvAddr(endpoint);
// 云上消息轨迹需要设置为 CLOUD
consumer.setAccessChannel(AccessChannel.CLOUD);
// 设置为云上创建的 Topic
consumer.subscribe(topic, "*");
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
System.out.printf("Receive New Messages: %s %n", msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
- 通用消息返回结构,以下提供的例子是:
//设备上下线消息结构
{
"lastTime": "最后上线时间,例:2020-01-09 10:39:45.229", //最后上线时间
"utcLastTime": "2020-01-09T02:39:45.229Z", //最后上线时间(UTC时间)
"clientIp": "218.13.182.108", //设备IP
"utcTime": "2020-01-09T02:40:41.509Z ", //上线/下线时间(UTC时间)
"time": "2020-01-09 10:40:41.509", //上线/下线时间
"productKey": "a1GG6XPSV2I", //产品KEY
"deviceName": "hum_temp_device", //设备名称
"status": "offline" //状态, offline:上线,online: 下线
}
//属性变更上报消息结构
{
"productKey": "a1GG6XPSV2I", //产品key
"gmtCreate": 1578538943286,
"deviceName": "hum_temp_device", //设备名称
"items": { //变更属性信息
"light_switch": { //变更属性标识符
"time": 1578538943289, //时间
"value": 1 //变更后值
}
}
}
//事件上报消息结构
{
"identifier": "key_event", //事件标识符
"name": "按键动作", //事件名称
"type": "info", //事件类型
"productKey": "a1dDBQ8ipQr", //产品key
"deviceName": "TEST_EVENT", //设备名称
"value": { //设备上报值
"key_type": 1,
"key_value": 1
}
步骤六: 云端指令下发
云端下发指令的方式
- 调用接口【调用设备服务】、【批量调用服务】、【设置设备属性值】、【批量设置设备属性值】接口下下发指令到设备调用设备服务 path: /device/invokeThingService批量调用服务 path: /device/invokeThingsService设置设备属性值 path: /device/setDeviceProperty批量设置设备属性值 path: /device/setDevicesProperty
- 调用设备服务 path: /device/invokeThingService
- 批量调用服务 path: /device/invokeThingsService
- 设置设备属性值 path: /device/setDeviceProperty
- 批量设置设备属性值 path: /device/setDevicesProperty
案例
●下面主要是以【取餐柜】作为一个例子,解析云端调用设备服务的系统的出来流程:
- 设置设备属性,可以调用 接口 【设置设备属性值】,下图以设置取餐柜所在位置为例,说明设备接收云端设置属性的流程
- 下发服务指令,可以调用 接口 【调用设备服务】,下图以云端下发开柜指令为例,说明设备接收云端指令的流程:
三、使用手册
产品管理
产品列表
进入菜单 【IoT 硬件云】 - 【产品】管理页面,可以看到开发者创建的所有产品记录:
列表顶部可以根据产品的类型筛选数据
产品详情
在产品列表中找到需要查看的产品记录,点击进入详情:
开通云端订阅
开通云端订阅后,应用服务后台就能订阅该产品下的所有设备上报的信息,包括:设备上报的属性、事件和设备上下线状态。 可参考:./iotcloud-1blfm63fp0c7j
新增产品
教程请参考:创建产品指南
编辑产品
入口:【IoT 硬件云】 - 【产品】 - 【产品详情】 -【基本信息】,点击右上角的【编辑】按钮。 目前产品支持编辑的字段有:
- 产品名称
- 产品分组
- 产品类型
- 描述
设备管理
设备列表
进入菜单 【IoT 硬件云】 - 【设备】管理页面,可以看到开发者创建的所有设备记录:
设备详情
在设备列表中找到需要查看的设备记录,点击进入详情,下图圈中的就是设备的三元组,用于通讯:
新增一个设备
教程请参考:
二、快速入门:步骤四:创建设备
批量创建设备
教程请参考:
二、快速入门:步骤四:创建设备
四、开发指南
云端对接指引
外部应用服务器接入指引
通常来讲,开发者的应用后台要接入硬件云有以下几个关键的步骤:
外部应用服务端开发 | 操作指引 |
开发者注册 | 参考【二、快速入门】-【成为开发者】 |
用户登录授权 | 登录授权,使用户名/密码模式,调用用户中心获取授权地址:https://oauth.utcook.com/uaa/oauth/login ,参数为用户名、密码,返回授权token数据,操作指引 |
调用API列表 | 通过统一网关接入硬件云API列表,调用网关地址https://web.utcook.com/iotapp/接口url ,接口url为具体API的访问路径,,每次API调用须在请求头传递token作为凭证。API列表中对访问路径、请求参数、响应数格式均有定义,具体参考API列表 |
Swagger访问地址 | 请点击进入 使用您注册的开发者账号登陆即可 |
API 性能测试报告
●测试环境
- 运行环境:java、mongodb、k8s
- 测试工具:jmeter
- 并发数量:100/200
- 服务部署数量:单机
●测试报告
●测试结论
- 性能指标分析:平均响应时间小于1秒,错误率为0,tps在100左右
- 改进方案:目前开发环境硬件资源(CPU核心数、内存大写、带宽等)有限,未进行极限测试,理论上扩展服务器结点数(k8s已支持)、升级硬件资源配置,性能指标均能大幅提升
单元测试报告
●测试报告
3.2版本的设备端开发指引
概述
C语言Link Kit SDK适用于使用C语言开发业务处理逻辑的设备, 由于C语言运行速度快、需要的运行内存较少, 目前大多数的IoT设备使用C语言进行产品开发。
SDK文件
请点击链接下载SDK文件
SDK使用说明
SDK提供了API供设备厂商调用,用于实现与阿里云IoT平台通信以及一些其它的辅助功能,比如WiFi配网、本地控制等。 另外,C语言版本的SDK被设计为可以在不同的操作系统上运行,比如Linux、FreeRTOS、Windows,因此SDK需要OS或者硬件支持的操作被定义为一些HAL函数,设备厂商在使用SDK开发产品时需要将这些HAL函数进行实现。 产品的业务逻辑、SDK、HAL的关系如下图所示:
其中产品业务逻辑和HAL需要设备厂商实现,SDK的目录wrappers\os下提供了针对Linux、FreeRTOS的部分HAL参考实现供参考。
SDK裁剪
一、基于嵌入式Linux开发的Make的编译说明
常用命令
命令 | 解释 |
make distclean | 清除一切构建过程产生的中间文件, 使当前目录仿佛和刚刚clone下来一样 |
make | 使用默认的或已选中的平台配置文件平台配置文件开始编译 |
make env | 显示当前编译配置, 非常有用, 比如可显示交叉编译链, 编译CFLAGS等 |
make reconfig | 弹出多平台选择菜单, 用户可按数字键选择, 然后根据相应的硬件平台配置开始编译 |
make config | 显示当前被选择的平台配置文件 |
make menuconfig | 以图形化的方式编辑和生成功能配置文件make.settings |
make help | 打印帮助文本 |
SDK裁剪
步骤一:下面是运行图形化配置工具之后的图示:
步骤二:在上面的界面中
- 按下空格键可以选中或者失效某个功能, 使用小键盘的上下键来在不同功能之间选择
- 如果想知道每个选项的具体含义, 先用方向键将高亮光条移到那个选项上, 再按键盘上的 “h”按键, 将出现帮助文本, 对选项进行详细说明。
注意: 不建议手动编辑 make.settings 文件改动配置, 一切配置都需通过上面的图形界面进行,配置选项说明:
配置选项 | 说明 |
FEATURE_MQTT_COMM_ENABLED | MQTT上云功能开关, 所谓MQTT上云是指搭载了C-SDK的嵌入式设备和阿里云服务器之间使用 MQTT 协议进行连接和交互。本选项使能之后,SDK将提供MQTT相关的API。 |
FEATURE_DYNAMIC_REGISTER | 一型一密/动态注册功能开关, 所谓动态注册是指不需要为同个品类下的不同设备烧录不同的三元组, 只需烧录相同的productSecret, 每个设备在网络通信中动态注册自己 |
FEATURE_DEPRECATED_LINKKIT | 高级版接口风格的开关, 配置进行高级版物模型相关的编程时, C-SDK是提供 linkkit_xxx_yyy() 风格的旧版接口, 还是提供 IOT_Linkkit_XXX() 风格的新版接口 |
FEATURE_DEVICE_MODEL_GATEWAY | 在V2.3.0以前的版本中, 这个开关的曾用名是 FEATURE_ENHANCED_GATEWAY,高级版网关能力的开关, 配置进行高级版物模型相关的编程时, C-SDK是提供 linkkit_xxx_yyy() 风格的单品接口, 还是提供 linkkit_gateway_xxx_yyy() 风格的网关接口 |
FEATURE_MQTT_DIRECT | MQTT直连功能开关, 所谓MQTT直连是指设备和阿里云服务器之间使用 MQTT 协议进行连接, 而不会前置基于 HTTP 协议认证的交互过程 |
FEATURE_DEVICE_MODEL_ENABLED | 在V2.3.0以前的版本中, 这个开关的曾用名是 FEATURE_SDK_ENHANCE。高级版物模型能力的功能开关, 所谓高级版物模型能力是指设备可使用基于服务/属性/事件三要素的Alink协议和服务端通信 |
FEATURE_SUPPORT_TLS | 在TLS层是否使用TLS的功能开关, 关闭则代表用不带TLS加密的TCP连接连云 |
输出说明
使用make进行成功编译, 将会打印类似如下的表格, 给出每个模块的ROM占用, 以及静态RAM占用的统计
RATE | MODULE NAME | ROM | RAM | BSS | DATA |
45.3% | src/dev_model | 28563 | 216 | 188 | 28 |
28.1% | src/mqtt | 17737 | 28 | 28 | 0 |
25.7% | src/infra | 16195 | 544 | 60 | 484 |
1.65% | src/dev_sign | 1045 | 48 | 0 | 48 |
100% | - IN TOTAL - | 63540 | 836 | 276 | 560 |
用户需要关注的输出产物都在 output/release 目录下:
- output/release/lib
产物文件名 | 说明 |
libiot_hal.a | HAL接口层的参考实现, 提供了 HAL_XXX() 接口 |
libiot_sdk.a | SDK的主库, 提供了 IOT_XXX 接口和 linkkit_xxx()接口 |
libiot_tls.a | 裁剪过的 mbedtls, 提供了 mbedtls_xxx() 接口, 支撑libiot_hal.a |
- output/release/include
产物文件名说明 mqtt_api.h当用户在配置环节选中”MQTT上云”时出现, 列出MQTT上云功能点提供的用户API dev_model_api.h 当用户在配置环节选中”物模型管理”时出现, 列出物模型管理功能点提供的用户API 选中了什么功能, 该功能的API就会以 <功能名字>_api.h 的形式出现, 依次类推
- output/release/bin |——-|—————-| 如果是在主机环境下不做交叉编译(Ubuntu/Windows), 是可以产生主机版本的demo程序, 可以直接运行的, 比如 产物文件名说明 linkkit-example-solo物模型管理功能的例程, 可演示 linkkit_xxx() 接口的使用 mqtt-exampleMQTT上云功能的例程, 可演示 IOT_XXX() 接口的使用
配置系统组成部分
用户输入
设备端C-SDK的构建配置系统, 有以下输入文件可接受用户的配置, 您可以通过编辑它们, 将配置输入到构建系统中 功能配置文件: 即顶层目录的 make.settings 文本文件 平台配置文件: 即目录 tools/board 下的 config.xxx.yyy 系列文件, 也称config文件
构建系统最终是依据 config.xxx.yyy 文件进行编译, 然而由于功能配置/裁剪更为常用, 我们将它额外抽取到了make.settings 中 config.xxx.yyy 主要关注目标嵌入式硬件平台的工具链程序和编译/链接选项的指定, 用于跨平台移植 config.xxx.yyy 此外也能以 CONFIG_ENV_CFLAGS += … 的语法新增自定义 CFLAGS, 同理 CONFIG_ENV_LDFLAGS += … 可以指定链接选项 make.settings 则是在已被确定的目标硬件平台上, 专注于C-SDK的功能模块裁剪或者配置, 用于裁剪功能模块
构建单元
从工程顶层目录以下, 每一个含有iot.mk的子目录, 都被构建系统认为是一个构建单元 每一个构建单元, 若相对顶级makefile的路径是bar, foo/bar1, 则可以用make bar, make foo/bar1这样的命令单独编译 tools/board/config.xxx.yyy 文件名形式为config..的文本文件, 会被构建系统认为是硬件平台配置文件, 每个文件对应一个嵌入式软硬件平台
- 其中部分, 一般是指明嵌入式平台的软件OS提供方, 如mxchip, ubuntu, win7等. 另外, 这也会导致构建系统到$(IMPORT_DIR)/目录下寻找预编译库的二进制库文件和头文件
- 其中部分, 一般是标明嵌入式平台的具体硬件型号, 如mtk7687, qcom4004等, 不过也可以写上其它信息, 因为构建系统不会去理解它, 比如mingw32, x86-64等
- 调试方式在make …命令行中, 设置TOP_Q变量为空, 可打印工程顶层的执行逻辑, 例如硬件平台的选择, SDK主库的生成等
make .... TOP_Q=
在make …命令行中, 设置Q变量为空, 可打印模块内部的构建过程, 例如目标文件的生成, 头文件搜寻路径的组成等make .... Q=
可以用make foo/bar单独对foo/bar进行构建, 不过, 这可能需要先执行make reconfig可以进入.O/foo/bar路径, 看到完整的编译临时目录, 有makefile和全部源码, 所以在这里执行make, 效果和make foo/bar等同 - 在make …命令行中, 设置TOP_Q变量为空, 可打印工程顶层的执行逻辑, 例如硬件平台的选择, SDK主库的生成等
make .... TOP_Q=
- 在make …命令行中, 设置Q变量为空, 可打印模块内部的构建过程, 例如目标文件的生成, 头文件搜寻路径的组成等
make .... Q=
- 可以用make foo/bar单独对foo/bar进行构建, 不过, 这可能需要先执行make reconfig
- 可以进入.O/foo/bar路径, 看到完整的编译临时目录, 有makefile和全部源码, 所以在这里执行make, 效果和make foo/bar等同
交叉编译相关
以下是常在 tools/board/config.xxx.yyy 平台配置文件中使用的变量:
变量 | 说明 |
CONFIG_ENV_CFLAGS | 指定全局的CFLAGS编译选项, 传给compiler, 例如CONFIG_ENV_CFLAGS += -DDEBUG |
CONFIG_ENV_LDFLAGS | 指定全局的LDFLAGS链接选项, 传给linker, 例如CONFIG_ENV_LDFLAGS += -lcrypto |
CROSS_PREFIX | 指定交叉编译工具链共有的前缀, 例如CROSS_PREFIX := arm-none-eabi-, 会导致构建系统使用arm-none-eabi-gcc和arm-none-eabi-ar, 以及arm-none-eabi-strip等 |
OVERRIDE_CC | 当交叉工具链没有共有的前缀或者前缀不符合prefix+gcc/ar/strip类型时, 例如armcc, 可用OVERRIDE_CC = armcc单独指定C编译器 |
OVERRIDE_AR | 当交叉工具链没有共有的前缀或者前缀不符合prefix+gcc/ar/strip类型时, 例如armar, 可用OVERRIDE_AR = armar单独指定库压缩器 |
OVERRIDE_STRIP | 当交叉工具链没有共有的前缀或者前缀不符合prefix+gcc/ar/strip类型时, 例如armcc没有对应的strip程序, 可用OVERRIDE_STRIP = true单独指定strip程序不执行 |
CONFIG_LIB_EXPORT | 指定SDK产生的二进制库的形式, 例如`CONFIG_LIB_EXPORT := dynamic可以指定产生linux上的libiot_sdk.so动态库文件, 默认为产生跨平台的libiot_sdk.a静态库 |
目录文件相关
变量 | 说明 |
CONFIG_mmm/nnn | 指定mmm/nnn目录是否需要编译的开关, 例如CONFIG_mmm/nnn :=的写法会导致该目录被跳过编译 |
二、基于嵌入式Linux开发的Make的交叉编译示例
交叉编译到嵌入式硬件平台
对于嵌入式硬件平台的情况, 对编译出目标平台的libiot_sdk.a, 需要经历如下几个步骤:
- 在tools/board/目录下添加一个对应的配置文件, 文件名规范为config.XXX.YYY, 其中XXX部分就对应后面wrappers/os/XXX目录的HAL层代码
- 在配置文件中, 至少要指定:交叉编译器 OVERRIDE_CC 的路径交叉链接器 OVERRIDE_LD 的路径静态库压缩器 OVERRIDE_AR 的路径编译选项 CONFIG_ENV_CFLAGS, 用于C文件的编译链接选项 CONFIG_ENV_LDFLAGS, 用于可执行程序的链接
- 交叉编译器 OVERRIDE_CC 的路径
- 交叉链接器 OVERRIDE_LD 的路径
- 静态库压缩器 OVERRIDE_AR 的路径
- 编译选项 CONFIG_ENV_CFLAGS, 用于C文件的编译
- 链接选项 CONFIG_ENV_LDFLAGS, 用于可执行程序的链接
- 尝试编译SDK, 对可能出现的跨平台问题进行修正, 直到成功产生目标格式的libiot_sdk.a
- 最后, 您需要以任何您喜欢的编译方式, 产生目标架构的libiot_hal.a
- 若目标平台尚未被适配, 则libiot_hal.a对应的源代码在C-SDK中并未包含, 需要您自行实现HAL_*()接口
下面以某款目前未官方适配的 arm-linux 目标平台为例, 演示如何编译出该平台上可用的libiot_sdk.a
安装交叉编译工具链 仍以Ubuntu16.04开发环境为例
$ sudo apt-get install -y gcc-arm-linux-gnueabihf
$ arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (Ubuntu/Linaro 4.8.4-2ubuntu1~14.04.1) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
添加配置文件
$ touch tools/board/config.arm-linux.demo
$ ls tools/board/
config.alios.mk3080 config.arm-linux.demo config.ubuntu.x86
编辑配置文件 在这一步, 需要设置编译选项和工具链, 以及跳过编译的目录
$ vim tools/board/config.arm-linux.demo
CONFIG_ENV_CFLAGS = \
-D_PLATFORM_IS_LINUX_ \
-Wall \
-DNO_EXECUTABLES
CONFIG_ENV_LDFLAGS = \
-lpthread -lrt
OVERRIDE_CC = arm-linux-gnueabihf-gcc
OVERRIDE_AR = arm-linux-gnueabihf-ar
OVERRIDE_LD = arm-linux-gnueabihf-ld
CONFIG_wrappers :=
注意, 上面的最后1行表示跳过对 wrappers 目录的编译, 以及 -DNO_EXECUTABLES 表示不要产生可执行程序。在编译未被适配平台的库时在最初是必要的, 这样可以避免产生过多的错误 注:上述配置文件的内容从某些浏览器复制出来之后将会丢失空行。
选择配置文件
$ make reconfig
SELECT A CONFIGURATION:
1) config.alios.mk3080
2) config.arm-linux.demo
3) config.ubuntu.x86
#? 2
SELECTED CONFIGURATION:
VENDOR : arm-linux
MODEL : demo
交叉编译产生库文件libiot_sdk.a 注: 本步骤不编译HAL, 只是为了验证配置文件中的交叉编译参数是否正确, 如果出现错误请对配置文件再次进行修改, 直到编译成功
$ make
BUILDING WITH EXISTING CONFIGURATION:
VENDOR : arm-linux
MODEL : demo
[CC] infra_timer.o <= ...
[CC] infra_json_parser.o <= ...
[CC] infra_preauth.o <= ...
获取交叉编译的产物, 包括静态库和头文件
$ ls -1 output/release/lib/
libiot_sdk.a
libiot_tls.a
这里, libiot_sdk.a文件就是编译好的物联网套件SDK, 已经是ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV)格式, 也就是arm-linux格式的交叉编译格式了 另外, libiot_tls.a是一个裁剪过的加解密库, 您可以选择使用它, 也可以选择使用平台自带的加解密库, 以减小最终固件的尺寸
$ ls -1 output/release/include/
dev_model_api.h
dev_sign_api.h
infra
mqtt_api.h
这里, dev_sign_api.h就是使用SDK中”设备签名”功能需要包含的头文件, 类似mqtt_api.h是使用SDK中”MQTT上云”功能需要的, infra下的头文件也请加入编译搜索路径
开发未适配平台的HAL层
对于实现平台抽象层接口 HAL_XXX_YYY() 的库 libiot_hal.a, 不限制其编译和产生的方式 但是如果你愿意的话, 当然仍然可以借助物联网套件设备端C-SDK的编译系统来开发和产生它
复制一份HAL层实现代码
注: 在 wrappers/os 下需要创建一个与 tools/board/confg.XXX.YYY 中的 XXX 一样的目录用于存放HAL实现 由于目标平台为arm-linux,因此可以复制Ubuntu下面的HAL实现,下面是操作示例:
$ cd wrappers/os/
$ ls
freertos nos nucleus ubuntu
wrappers/os$ cp -rf ubuntu arm-linux
wrappers/os$ rm -f arm-linux/HAL_UART_linux.c
wrappers/os$ ls
arm-linux freertos nos nucleus ubuntu
wrappers/os$ tree -A arm-linux/
arm-linux/
+-- HAL_AWSS_linux.c
+-- HAL_Crypt_Linux.c
+-- HAL_FS_Linux.c
+-- HAL_KV_linux.c
+-- HAL_OS_linux.c
+-- HAL_TCP_linux.c
+-- HAL_UDP_linux.c
打开之前被关闭的编译开关
$ vim tools/board/config.arm-linux.demo
CONFIG_ENV_CFLAGS = \
-D_PLATFORM_IS_LINUX_ \
-Wall\
-DNO_EXECUTABLES
CONFIG_ENV_LDFLAGS = \
-lpthread -lrt
OVERRIDE_CC = arm-linux-gnueabihf-gcc
OVERRIDE_AR = arm-linux-gnueabihf-ar
OVERRIDE_LD = arm-linux-gnueabihf-ld
# CONFIG_wrappers :=
可以看到在CONFIG_wrappers :=这一行前添加了一个#符号, 代表这一行被注释掉了, wrappers将会进入编译过程
尝试交叉编译被复制的HAL层代码
$ make reconfig
SELECT A CONFIGURATION:
1) config.alios.mk3080
2) config.arm-linux.demo
3) config.ubuntu.x86
#? 2
SELECTED CONFIGURATION:
VENDOR : arm-linux
MODEL : demo
...
$ make
可以看到我们进展的十分顺利, 被复制的代码 wrappers/os/arm-linux/*.c 直接编译成功了, 产生了 arm-linux 格式的
交叉编译样例程序
这样有了libiot_hal.a, libiot_tls.a, 以及libiot_sdk.a, 已经可以尝试交叉编译样例的可执行程序, 并在目标嵌入式硬件开发板上运行一下试试了 方法是去掉 config.arm-linux.demo 里面的 -DNO_EXECUTABLES开关, 使得*/examples/目录下的样例源码被编译出来 注:删掉-DNO_EXECUTABLES开关时记得把上面一行-Wall后面的’\’符号也删掉 修改后的config.arm-linux.demo内容如下所示:
$ vi tools/board/config.arm-linux.demo
CONFIG_ENV_CFLAGS = \
-D_PLATFORM_IS_LINUX_ \
-Wall
CONFIG_ENV_LDFLAGS = \
-lpthread -lrt
OVERRIDE_CC = arm-linux-gnueabihf-gcc
OVERRIDE_AR = arm-linux-gnueabihf-ar
OVERRIDE_LD = arm-linux-gnueabihf-ld
# CONFIG_wrappers :=
可以看到在 -DNO_EXECUTABLES 开关从 CONFIG_ENV_CFLAGS 中去掉了, 例子可执行程序进入了编译范围
重新载入配置文件, 交叉编译可执行程序
$ make reconfig
$ make
如果有如下的编译输出, 则代表 mqtt-example 等一系列样例程序已经被成功的编译出来, 它们存放在output/release/bin 目录下
[LD] dev-sign-example <= ...
[LD] mqtt-example <= ...
[LD] linkkit-example-solo <= ...
$ cd output/release/bin/
$ ls
dev-sign-example linkkit-example-solo mqtt-example
$ file *
dev-sign-example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked ...
linkkit-example-solo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked ...
mqtt-example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked ...
可以用file命令验证, 这些可执行程序确实是交叉编译到 arm-linux 架构上的
尝试运行样例程序
接下来, 您就可以把样例程序例如mqtt-example, 用SCP, TFTP或者其它方式, 拷贝下载到您的目标开发板上运行调试了
- 如果一切顺利, 样例程序和同样例程在 Ubuntu 上运行效果相同, 则证明 wrappers/os/arm-linux 部分的HAL层代码工作正常
- 如果样例程序运行起来, 和同样例程在 Ubuntu 上运行效果不同, 则需要再重点修改调试HAL实现
- 也就是指 wrappers/os/arm-linux 目录的HAL层代码, 因为这些代码是我们从 Ubuntu 主机部分复制的, 完全可能并不适合 arm-linux 如此反复直到确保 libiot_hal.a 的开发没问题为止
三、基于代码抽取的移植说明
对于使用KEIL、IAR进行设备开发的用户来说,不能使用make的方式编译SDK。此时用户可以配置需要的SDK功能,使用SDK提供的抽取工具将相应的代码抽取出来,然后将源文件添加到开发工具中的项目后进行编译; 对于使用Linux作为开发环境的用户,也可以使用本方式将代码抽取出来之后进行交叉编译。 本文将以获取 MQTT上云 和 OTA固件升级 能力为例, 描述在 Windows 开发主机上的SDK移植过程
设备端的整体开发过程如下所示
以下是详细步骤讲解
SDK功能配置
SDK中有各种功能模块, 用户需要决定需要使用哪些功能, 在本例中假设用户需要”MQTT上云”和”OTA固件下载”的功能
在SDK的根目录双击 config.bat 脚本运行, 弹出如下的功能选择界面(相当于Ubuntu16.04 64位主机上的 make menuconfig 命令)
- 按下空格键可以选中或者失效某个功能, 使用小键盘的上下键来在不同功能之间切换
- 如果想知道每个选项的具体含义, 先用方向键将高亮光条移到那个选项上, 再按键盘上的”h”按键, 将出现帮助文本, 对选项进行详细说明 比如:
- 如果编译环境有自带标准头文件, 请使能选项 PLATFORM_HAS_STDINT
- 如果目标系统上运行有嵌入式操作系统, 请使能选项 PLATFORM_HAS_OS
- 如果目标系统上支持 malloc 和 free 这样的动态内存管理能力, 请使能选项 PLATFORM_HAS_DYNMEM
在本例中, 我们需要”MQTT上云”和”OTA固件下载”的功能, 所以需要选中
- FEATURE_MQTT_COMM_ENABLED: 对应”MQTT上云”功能
- FEATURE_OTA_ENABLED: 对应”OTA固件下载”功能
然后将光标移动到窗口底部的 上, 在随后弹出的保存对话框中选 存盘退出配置界面
SDK代码抽取
经过上面的步骤, SDK根目录下的 make.settings 功能配置文件的内容就会发生变化, 对应到用户所选择的功能,如下所示:
FEATURE_MQTT_COMM_ENABLED=y
FEATURE_MQTT_DEFAULT_IMPL=y
FEATURE_MQTT_PRE_AUTH is not set
FEATURE_MQTT_DIRECT=y
...
FEATURE_OTA_ENABLED=y
可以注意到其中对应所选功能点的开关 FEATURE_MQTT_COMM_ENABLED 和 FEATURE_OTA_ENABLED 都已被设置为 y, 表示打开了 接下来运行代码抽取工具 extract.bat, 该脚本运行之后的界面如下所示:
抽取后的代码说明 如同 config.bat 脚本的输出是 make.settings 文件, extract.bat 脚本的输入是 make.settings, 输出是output 文件夹
点击进去, 观察它的内容。output目录结构如下
output/
+-- eng
| +-- certs
| +-- dev_model
| +-- dev_sign
| +-- infra
| +-- mqtt
| +-- ota
| +-- wrappers
| +-- external_libs
+-- examples
其中 output/eng 目录就对应的是SDK相关功能抽取出来的代码, output/example对应使用SDK中API接口的例子程序
文件或目录 说明 output/eng 对应SDK和支撑SDK的HAL接口 output/eng/infra 不对应到某个具体的功能点, 表示”基础”, 但是内容的多少会随着被选择的功能点多少而变化 output/eng/certs 不对应到某个具体的功能点, 存放着验证阿里云IoT服务端的根证书, 使能TLS传输时出现 output/eng/xxx 对应当前被选中的 xxx 功能的实现源码, 其中output/eng/xxx/xxx_api.h 列出了这个功能的API output/example/xxx 对应当前被选中的 xxx 功能的例子程序, 演示了如何用这个功能的API编写业务逻辑 在本例中, eng下面的目录如下所示:
这些功能点的例子程序都在 output/examples 文件夹
对接HAL接口或者wrapper接口 SDK运行时对外界所需要的依赖都以 HAL_XXX或者 wrapper_xxx进行定义,并放于文件output\eng\wrappers\wrapper.c中,用户需要对其中的函数进行实现。
对每个 HAL_XXX 接口都有详细的注释和文档, 用户可以参考SDK根目录下的wrappers子目录中的wrapper参考实现。
1.3.4.其它
抽取出来的代码使用make进行编译 用户也可能使用 GNU Make对抽取出来的代码进行编译, 因此在抽取出来的目录下仍然提供了一个示例的makefile, make之后将会生成以下内容: 二进制文件 说明 产生方式 libiot_sdk.a SDK主库文件, 提供所有形如IOT_XXX 的用户API 从 output/eng 下除了output/wrapper/* 的目录产生 libiot_hal.a 支撑SDK的HAL库, 提供形如HAL_XXX 的底层接口 从 output/eng/wrappers 下源文件产生, 其中 output/eng/wrappers/wrapper.c 需要用户填写 xxx-example 功能点的例子程序 从 output/examples/xxx 下源文件产生, 需要通过 output 目录下运行 make prog 命令编译 使用C++编译器编译时错误的处理 C++编译器在编译的时候判断比gcc判断更加严格,如果编译的时候出错,请编译时:
- 增加 -fpermissive -w, 去掉 -ansi -Wdeclaration-after-statement
1.3.5.总结
以图中红色文字表示用户执行的动作, 用绿色文字表示用户得到的产物, 则这个过程可用下图表示为
图中左上角是移植过程的开始, 到右下角是移植过程的结束, 以获得目标设备架构的二进制库 libiot_sdk.a 和 libiot_hal.a 为标志 到此为止整个SDK的抽取都已经讲解完毕, 您可以将 output/eng 下的所有目录加入自己的工程中编译和集成, 并参考 output/examples 下的例程开始调用SDK提供的API了
MCU上集成SDK
一、MCU+支持MQTT的模组
应用场景说明
应用场景: 设备的硬件由一个MCU加上一个通信模组构成, 设备的应用逻辑运行在MCU上, 通信模组支持MQTT功能并提供AT指令给MCU使用, MCU控制模组连接云端服务以及收发数据
对于这样的场景, 设备厂商需要将Link Kit SDK集成并运行在MCU上, 让Link Kit SDK通过通信模组连接到阿里云物联网平台
文档目标
下面的文档关注于讲解用户如何把SDK移植到MCU, 并与通信模组协作来与阿里云物联网平台通信. 为了简化移植过程, 下面的文档在MCU上以开发一个基础版产品作为案例进行讲解, 如果用户需要在MCU上使用SDK的其它功能, 可以在MCU上将基础版的example正确运行之后, 再重新配置SDK, 选中其它功能再进行产品功能开发。
设备端开发过程
设备端的开发过程如下所示:
SDK配置与代码抽取
SDK中有各种功能模块, 用户需要决定: • 需要使用哪些功能(SDK配置) SDK提供了配置工具用于配置需要使能哪些功能, 每个功能的配置选项名称类似FEATURE_MQTT_XXX, 下面的章节中会讲解具体有哪些功能可供配置 • SDK如何与外部模组进行数据交互
上图中的三根红色虚线代表SDK可以与MQTT模组进行数据交互的三种方式: • MQTT Wrapper MQTT Wrapper提供了接口函数定义用于与MQTT Client交互, 当MCU外接MQTT模组时可以通过实现相关接口函数来驱动MQTT模组中的MQTT Client与阿里云物联网平台上的MQTT Broker/Server建连/收发MQTT消息。开发者可以实现相关的wrapper函数来代码来驱动MQTT模组进行MQTT的连接, 无需使能ATM/AT MQTT/AT Parser等功能 • AT MQTT 当MQTT模组发送MQTT消息给MCU时, 如果模组发送给MCU的数据的速度超过了MCU上处理MQTT消息的速度, 可能导致丢包, 因此SDK中实现了一个AT MQTT模块用于对收到的MQTT消息进行缓存。开发者如果使能本模块, 本模块将提供MQTT Wrapper函数的实现, 开发者需要实现的函数将是AT MQTT HAL中定义的函数, 在这些函数中驱动MQTT模组。 • AT Parser MCU与模组之间通常使用UART进行连接, 因此开发者需要开发代码对UART进行初始化, 通过UART接收来自模组的数据。由于UART是一个字符一个字符的接收数据, 因此开发者还需要对收到的数据组装并判断AT指令是否承载MQTT数据, 如果是才能将MQTT数据发送给AT MQTT模块。SDK中提供了AT Parser模块用于完成这些功能, 如果开发者尚未实现在UART上的数据收发/解析等功能, 可以使能AT Parser功能来减少开发工作量 当开发者使能AT Parser后, AT Parser将会提供AT MQTT HAL的实现, 因此开发者需要实现的函数是AT Parser HAL中定义的函数
1)配置SDK SDK包含的功能较多, 为了节约对MCU RAM/Flash资源的消耗, 用户需要根据自己的产品功能定义需要SDK中的哪些功能
2)运行配置命令 • Linux系统 进入SDK的根目录下, 运行命令
1.make menuconfig
• Windows系统 运行SDK根目录下的config.bat
1.config.bat
3)使能需要的SDK功能 运行上面的命令之后, 将会跳出下面的功能配置界面. 按下空格键可以选中或者失效某个功能, 使用小键盘的上下键来在不同功能之间切换. 如果想知道每个选项的具体含义, 先用方向键将高亮光条移到那个选项上, 再按键盘上的”h”按键, 将出现帮助文本, 说明选项是什么含义, 打开了和关闭了意味着什么
如果编译环境有自带标准头文件, 请使能选项 • PLATFORM_HAS_STDINT 如果目标系统上运行有嵌入式操作系统, 请使能选项 • PLATFORM_HAS_OS
请务必使能: • FEATURE_MQTT_COMM_ENABLED, 用于让SDK提供MQTT API供应用程序调用, 并关闭 • FEATURE_MQTT_DEFAULT_IMPL, 该选项用于包含阿里提供的MQTT Client实现, 因为模组支持MQTT Client, 所以关闭该选项 SDK连接MQTT模组有几种不同的对接方法, 为了简化对接, 本文档中使能 • FEATURE_ATM_ENABLED 该选项使能之后具有下面的子选项可供选择, 需要使能 • FEATURE_AT_MQTT_ENABLED 如果用户没有用于AT命令收发/解析的框架, 可以选择(非必须)使用at_parser框架: • FEATURE_AT_PARSER_ENABLED
SDK基于at_parser提供了已对接示例, 如果模组是支持MQTT的sim800 2G模组或者支持ICA MQTT的WiFi模组, 可以进行进一步选择相应选项, 这样开发的工作量将进一步减少. 如果不需要对接示例, 请忽略该步骤
完整的配置开关说明表格如下, 但最终解释应以上面提到的”h”按键触发文本为准
配置开关 | 说明 |
PLATFORM_HAS_STDINT | 告诉SDK当前要移植的嵌入式平台是否有自带标准头文件 |
PLATFORM_HAS_OS | 目标系统是否运行某个操作系统FEATURE_MQTT_COMM_ENABLEDMQTT长连接功能, 打开后将使SDK提供MQTT网络收发的能力和接口 |
FEATURE_MQTT_DEFAULT_IMPLSDK | 内包含的MQTT Client实现, 打开则表示使用SDK内置的MQTT客户端实现 |
FEATURE_ASYNC_PROTOCOL_STACK | 对于使用SDK内置的MQTT客户端实现的时候, 需要用户实现TCP相关的HAL, 这些HAL的TCP发送数据/接收数据的定义是同步机制的, 如果目标系统的TCP基于异步机制, 可以使能该开关实现SDK从同步到异步机制的转换 |
FEATURE_DYNAMIC_REGISTER | 动态注册能力, 即设备端只保存了设备的ProductKey和ProductSecret和设备的唯一标识, 通过该功能从物联网平台换取DeviceSecret |
FEATURE_DEVICE_MODEL_ENABLE | 使能设备物模型编程相关的接口以及实现FEATURE_DEVICE_MODEL_GATEWAY网关的功能以及相应接口 |
FEATURE_THREAD_COST_INTERNAL | 为收包启动一个独立线程FEATURE_SUPPORT_TLS标准TLS连接, 打开后SDK将使用标准的TLS1.2安全协议连接服务器 |
FEATURE_SUPPORT_ITLS | 阿里iTLS连接, 打开后SDK将使用阿里自研的iTLS代替TLS建立安全连接 |
FEATURE_ATM_ENABLED | 如果系统是使用MCU+外接模组的架构, 并且SDK运行在MCU上, 必须打开该选项, 然后进行配置 |
FEATURE_AT_MQTT_ENABLED | 如果MCU连接的通信模组支持MQTT AT, 则使用该选项 |
FEATURE_AT_PARSER_ENABLED | 如果用户需要使用SDK提供的AT收发/解析的框架, 则可以使用该选项 |
FEATURE_AT_MQTT_HAL_ICA | 基于at_parser的ICA MQTT AT对接示例 |
FEATURE_AT_MQTT_HAL_SIM800 | 基于at_parser的SIM800 MQTT对接示例 |
使能需要的SDK配置后, 保持配置并退出SDK配置工具。
4)抽取选中功能的源代码 运行SDK根目录下的extract.bat, 客户选中的功能所对应的代码将会被放置到文件夹output:
实现HAL对接函数
Link Kit SDK被设计为可以在不同的操作系统上运行, 或者甚至在不支持操作系统的MCU上运行, 因此与系统相关的操作被定义成一些HAL函数, 需要客户进行实现. 另外, 由于不同的通信模组支持的AT指令集不一样, 所以与通信模组上TCP相关的操作也被定义成HAL函数需要设备开发者进行实现。 由于不同的用户使能的SDK的功能可能不一样, 因此需要对接的HAL函数会不一样, 设备开发者只需要实现位于文件output/eng/wrappers/wrapper.c中的HAL函数. 下面对可能出现在文件wrapper.c的HAL函数进行讲解:
1)MCU系统相关HAL 必须实现函数:
函数名 | 说明 |
HAL_Malloc | 对应标准C库中的malloc(), 按入参长度开辟一片可用内存, 并返回首地址 |
HAL_Free | 对应标准C库中的free(), 将入参指针所指向的内存空间释放 |
HAL_Printf | 对应标准C库中的printf(), 根据入参格式字符串将字符文本显示到终端,如果用户无需在串口上进行调试,该函数可以为空 |
HAL_Snprintf | 类似printf, 但输出的结果不再是显示到终端, 而是存入指定的缓冲区内存 |
HAL_UptimeMs | 返回一个uint64_t类型的数值, 表达设备启动后到当前时间点过去的毫秒数 |
HAL_SleepMs | 按照指定入参的数值, 睡眠相应的毫秒, 比如参数是10, 那么就会睡眠10毫秒 |
OS相关可选函数 如果MCU没有运行OS, 或者SDK的MQTT API并没有在多个线程中被调用, 以下函数可以不用修改wrapper.c中相关的函数实现. 在有OS场景下并且MQTT API被APP在多个线程中调用, 则需要用户对接以下函数:
函数名 | 说明 |
HAL_MutexCreate | 创建一个互斥锁, 返回值可以传递给HAL_MutexLock/Unlock |
HAL_MutexDestroy | 销毁一个互斥锁, 这个锁由入参标识 |
HAL_MutexLock | 申请互斥锁, 如果当前该锁由其它线程持有, 则当前线程睡眠, 否则继续 |
HAL_MutexUnlock | 释放互斥锁, 此后当前在该锁上睡眠的其它线程将取得锁并往下执行 |
HAL_SemaphoreCreate | 创建一个信号量, 返回值可以传递给HAL_SemaphorePost/Wait |
HAL_SemaphoreDestroy | 销毁一个信号量, 这个信号量由入参标识 |
HAL_SemaphorePost | 在指定的计数信号量上做自增操作, 解除其它线程的等待 |
HAL_SemaphoreWait | 在指定的计数信号量上等待并做自减操作 |
HAL_ThreadCreate | 根据配置参数创建thread |
2)AT MQTT相关HAL AT MQTT相关HAL函数位于抽取出来的文件wrapper.c中, 客户需要在这些函数中调用模组提供的AT指令和模组进行数据交互. 函数说明如下:
函数名 | 说明 |
HAL_AT_MQTT_Init | 初始化MQTT参数配置. 比如初始化MCU与通信模组之间的UART串口设置, 初始化MQTT配置参数: clientID/clean session/user name/password/timeout/MQTT Broker的地址和端口等数值. 返回值类型为iotx_err_t, 其定义位于文件infra_defs.h |
HAL_AT_MQTT_Deinit | 如果在HAL_AT_MQTT_Init创建了一些资源, 可以在本函数中相关资源释放掉 |
HAL_AT_MQTT_Connect | 连接MQTT服务器. 入参: proKey:产品密码devName:设备名devSecret:设备密码注: 只有通信模组集成了阿里的SDK的时候会使用到该函数的这几个入参, 如果模组上并没有集成阿里的SDK, 那么略过这几个参数. 该函数的入参并没有指定服务器的地址/端口, 这两个参数需要在HAL_AT_MQTT_Init()中记录下来 |
HAL_AT_MQTT_Disconnect | 断开MQTT服务器 |
HAL_AT_MQTT_Subscribe | 向服务器订阅指定的TOPIC. 入参: topic:主题qos:服务器质量mqtt_packet_id: 数据包的IDmqtt_status: mqtt状态timeout_ms:超时时间 |
HAL_AT_MQTT_Unsubscribe | 向服务器取消对指定topoic的订阅. 入参: topic:主题mqtt_packet_id:数据包的IDmqtt_status:mqtt状态 |
HAL_AT_MQTT_Publish | 向服务器指定的Topic发送消息 |
HAL_AT_MQTT_State | 返回MQTT的状态, 状态值定义在文件mal.h的数据结构iotx_mc_state_t中 |
调用接收函数 MCU从模组收到MQTT消息之后, 需要调用SDK提供的函数IOT_ATM_Input()(见atm/at_api.h)将MQTT 消息交付给SDK。下面的示例代码演示当MCU从模组收到MQTT消息后,如何调用IOT_ATM_Input函数:
void handle_recv_data()
{
struct at_mqtt_input param;
…
param.topic = topic_ptr;
param.topic_len = strlen(topic_ptr);
param.message = msg_ptr;
param.msg_len = strlen(msg_ptr);
if (IOT_ATM_Input(¶m) != 0) {
mal_err(“hand data to uplayer fail!\n”);
}
}
3)AT Parser相关HAL 如果选择了at_parser框架, 则需要对接以下四个UART HAL函数, 函数声明见at_wrapper.h. 如果用户不使用at_parser框架请忽略该步
函数名 | 说明 |
HAL_AT_Uart_Init | 该接口对UART进行配置(波特率/停止位等)并初始化 |
HAL_AT_Uart_Deinit | 该接口对UART去初始化 |
HAL_AT_Uart_Send | 该接口用于向指定的UART口发送数据 |
HAL_AT_Uart_Recv | 该接口用于从底层UART buffer接收数据 |
4)产品相关HAL 下面的HAL用于获取产品的身份认证信息, 设备厂商需要设计如何在设备上烧写设备身份信息, 并通过下面的HAL函数将其读出后提供给SDK:
函数名 | 说明 |
HAL_GetProductKey | 获取设备的ProductKey, 用于标识设备的产品型号 |
HAL_GetDeviceName | 获取设备的DeviceName, 用于唯一标识单个设备 |
HAL_GetDeviceSecret | 获取设备的DeviceSecret, 用于标识单个设备的密钥 |
代码集成
如果设备商的开发环境使用makefile编译代码, 可以将SDK抽取出来的代码加入其编译环境进行编译. 如果设备商使用KEIL/IAR这样的开发工具, 可以将SDK抽取出来的代码文件加入到IDE的工程中进行编译
参照example实现产品功能
如果要使用MQTT连云, 可参考抽取文件夹中的 eng/examples/mqtt_example_at.c . 设备厂商可以将该文件复制到产品工程中, 对其进行修改后使用 该example将连接设备到阿里云, 订阅一个指定的topic并发送数据给该topic, 即设备上报的消息会被物联网平台发送给设备, 下面是example的大概过程说明:
注意: 需要在云端将该topic从默认的权限从”订阅”修改为”发布和订阅”, 如下图所示:
从程序入口的 main() 函数看起, 第一步是调用AT模块初始化函数IoT_ATM_Init(), 使模组处于ready状态, 第二步是调用用户提供的HAL函数获取产品信息
int main(int argc, char *argv[])
{
void * pclient = NULL;
int res = 0;
int loop_cnt = 0;
iotx_mqtt_region_types_t region = IOTX_CLOUD_REGION_SHANGHAI;
iotx_sign_mqtt_t sign_mqtt;
iotx_dev_meta_info_t meta;
iotx_mqtt_param_t mqtt_params;
#ifdef ATM_ENABLED
if (IOT_ATM_Init() < 0) {
HAL_Printf(“IOT ATM init failed!\n”);
return -1;
}
#endif
HAL_Printf(“mqtt example\n”);
memset(&meta, 0, sizeof(iotx_dev_meta_info_t));
HAL_GetProductKey(meta.product_key);
HAL_GetDeviceName(meta.device_name);
HAL_GetDeviceSecret(meta.device_secret);
注: • 上面的三个HAL_GetXXX函数是获取设备的三元组信息, 设备厂商需要自己设计设备的三元组存放的位置/并将其从指定位置读取出来 • 由于设备的唯一标识DeviceName/设备密钥DeviceSecret都是机密信息, 设备厂商在设计时可以把相关信息加密后存放到Flash上, 在HAL函数里面将其解密后提供给SDK, 以避免黑客直接从Flash里面读取设备的身份信息 接下来对MQTT连接参数进行指定, 客户可以根据自己的需要对参数进行修改:
/* Initialize MQTT parameter */
memset(&mqtt_params, 0x0, sizeof(mqtt_params));
mqtt_params.port = sign_mqtt.port;
mqtt_params.host = sign_mqtt.hostname;
mqtt_params.client_id = sign_mqtt.clientid;
mqtt_params.username = sign_mqtt.username;
mqtt_params.password = sign_mqtt.password;
mqtt_params.request_timeout_ms = 2000;
mqtt_params.clean_session = 0;
mqtt_params.keepalive_interval_ms = 60000;
mqtt_params.read_buf_size = 1024;
mqtt_params.write_buf_size = 1024;
mqtt_params.handle_event.h_fp = example_event_handle;
mqtt_params.handle_event.pcontext = NULL;
pclient = IOT_MQTT_Construct(&mqtt_params);
通过调用接口 IOT_MQTT_Construct() 触发SDK连接云平台, 若接口返回值非NULL, 则连云成功之后调用example_subscribe对一个指定的topic进行数据订阅
res = example_subscribe(pclient);
example_subscribe的函数内容如下:
注: • 设备商需要根据自己的产品设计, 订阅自己希望订阅的TOPIC, 以及注册相应的处理函数 • 订阅的topic的格式需要指定产品型号(product_key)以及设备标识(device_name), 如上图中第一个橙色框中的格式 • 上图的第二个框展示了如何订阅一个指定的topic以及其处理函数 以下段落演示MQTT的发布功能, 即将业务报文上报到云平台:
while (1) {
if (0 == loop_cnt % 20) {
example_publish(pclient);
}
IOT_MQTT_Yield(pclient, 200);
loop_cnt += 1;
}
下面是example_publish函数体的部分内容:
注: • 上面的代码是周期性的将固定的消息发送给云端, 设备商需要根据自己的产品功能, 在必要的时候才上传数据给物联网平台 • 客户可以删除main函数中example_publish(pclient)语句, 避免周期发送无效数据给到云端 • IOT_MQTT_Yield是让SDK去接收来自MQTT Broker的数据, 其中200毫秒是等待时间, 如果用户的消息数量比较大/或者实时性要求较高, 可以将时间改小
功能调试
下面的信息截图以mqtt_example_at.c为例编写
1)如何判断设备已连接到阿里云
下面的打印是HAL_Printf函数将信息打印到串口后运行example的输出内容, 其中使用橙色圈选的信息表明设备已成功连接到阿里云物联网平台:
2)如何判断设备已成功发送数据到云端
登录阿里网物联网平台的商家后台, 选中指定的设备, 可以查看是否收到来自设备的消息, 如下图所示:
注: • 上图中的内容只能看见消息发送到了哪个topic, 消息的内容并不会显示出来
3)如何判断设备已可成功接收来自云端数据
在商家后台的”下行消息分析”分析中可以看见由物联网平台发送给设备的消息:
也可在设备端查看是否已收到来自云端的数据, exmaple代码中收到云端发送的数据的打印信息如下所示:
至此, SDK在MCU与模组之间的适配开发已结束, 用户可以进行产品业务功能的实现
二、MCU+支持TCP的模组
应用场景说明
应用场景: 设备的硬件由一个MCU加上一个通信模组构成, 设备的应用逻辑运行在MCU上, 模组上支持了TCP但是并不支持MQTT, MCU通过模组提供的AT指令来控制模组何时连接云端服务以及收发数据
对于这样的场景, 设备厂商需要将Link Kit SDK集成并运行在MCU上, 让Link Kit SDK通过通信模组连接到阿里云物联网平台
文档目标
下面的文档关注于讲解用户如何把SDK移植到MCU, 并与通信模组协作来与阿里云物联网平台通信. 为了简化移植过程, 下面的文档在MCU上以开发一个基础版产品作为案例进行讲解, ++如果用户需要在MCU上使用SDK的其它功能, 可以在MCU上将基础版的example正确运行之后, 再重新配置SDK, 选中其它功能再进行产品功能开发. ++
设备端开发过程
设备端的开发过程如下所示:
SDK配置与代码抽取
SDK中有各种功能模块, 用户需要决定: • 需要使用哪些功能(SDK配置) SDK提供了配置工具用于配置需要使能哪些功能, 每个功能的配置选项名称类似FEATURE_MQTT_XXX, 下面的章节中会讲解具体有哪些功能可供配置 • SDK如何与外部模组进行数据交互 SDK使用MQTT与阿里云物联网平台通信, 对于模组只支持TCP的情况, 意味着MCU上需要使能SDK自带的MQTT Client, 由MQTT Client将用户数据封装成MQTT协议之后通过通信模组上的TCP模块将数据发送到阿里云物联网平台,如下图所示:
MQTT Client与给模组协作的时候, 开发者需要编写一个TCP连接管理模块去实现如下功能: • 控制模组发起到阿里云物联网平台的TCP连接, 并记录模组返回的TCP连接ID • 当MQTT Client发送数据时, 将数据通过MQTT Client创建的模组TCP连接ID进行数据发送 • 接收来自模组的字符流数据, 只有是从MQTT Client建立的TCP连接ID中的数据才能发送给MQTT Client • 如果MQTT Client接收和处理数据的速度慢于模组发送数据给MCU的速度, 开发者还需要将数据进行缓存以避免数据丢失 该TCP连接管理模组与SDK/TCP模组关系如下图所示:
Link Kit SDK中包含了一个模组TCP连接和数据缓存的模块, 称为AT TCP, 如果开发者在MCU上尚未实现这样一个模组TCP连接和数据缓存的模块, 可以使用SDK提供的AT TCP MCU与模组之间通常使用UART进行连接, 因此开发者需要开发代码对UART进行初始化, 通过UART接收来自模组的数据, 由于UART是一个字符一个字符的接收数据, 因此开发者还需要对收到的数据组装并判断AT指令是否承载TCP数据, 如果是才能将TCP数据发送给TCP连接管理模块. 这个模块与SDK/模组的关系如下图所示:
Link Kit SDK中包含了一个AT解析的模块, 称为AT Parser, 如果开发者尚未实现这样的一个功能模块, 可以使能Link Kit SDK中的AT Parser模块以减少开发工作量
1)SDK与模组对接结构说明
下面是SDK与TCP模组的可能对接方式的图示, 以及相关的配置选项:
上图中SDK外部的蓝色配置选项表示该选项使能后将会使能的SDK功能模块, 每根红色虚线代表一种可能的SDK与模组的对接方式, HAL表示该模块与外部模块交互的函数定义:
配置选项 | 说明 | 可选项 |
FEATURE_MQTT_COMM_ENABLED | 是否需要为MQTT提供API | 必选 |
FEATURE_MQTT_DEFAULT_IMPL | 使能后将包含SDK中的MQTT实现, 并提供TCP HAL函数用于在TCP上收发MQTT数据 | 必选 |
FEATURE_MQTT_ATM_ENABLED | 是否使能SDK中的ATM模块 | 必选 |
FEATURE_AT_TCP_ENABLED | 是否使能AT TCP模块, 当本模块被使能后, AT TCP将会提供TCP HAL实现, 也就是说开发者无需再实现TCP HAL, 但是开发者需要实现AT TCP HAL | 必选 |
FEATURE_AT_PARSER_ENABLED | 是否使能AT Parser模块, 当本模块被使能后, AT Parser将会提供AT TCP HAL实现, 也就是说开发者无需再实现AT TCP HAL, 但是开发者需要实现AT Parser HAL | 必选 |
2)配置SDK
SDK包含的功能较多, 为了节约对MCU RAM/Flash资源的消耗, 用户需要根据自己的产品功能定义需要SDK中的哪些功能
运行配置命令 • Linux系统 进入SDK的根目录下, 运行命令
- make menuconfig
• Windows系统 运行SDK根目录下的config.bat
- config.bat
使能需要的SDK功能 运行上面的命令之后, 将会跳出下面的功能配置界面. 按下空格键可以选中或者失效某个功能, 使用小键盘的上下键来在不同功能之间切换. 如果想知道每个选项的具体含义, 先用方向键将高亮光条移到那个选项上, 再按键盘上的”h”按键, 将出现帮助文本, 说明选项是什么含义, 打开了和关闭了意味着什么
如果编译环境有自带标准头文件, 请使能选项 • PLATFORM_HAS_STDINT 如果目标系统上运行有嵌入式操作系统, 请使能选项 • PLATFORM_HAS_OS 本场景中由于模组支持TCP但是不支持MQTT, 因此必须使能下面两项配置: • FEATURE_MQTT_COMM_ENABLED, 使用阿里SDK提供的MQTT API与云端通信 • FEATURE_MQTT_DEFAULT_IMPL, 使用阿里SDK中自带的MQTT Client实现, 用户需要实现相关的TCP连接的创建/连接/数据收发过程 • FEATURE_ATM_ENABLED如果希望使用阿里提供的AT TCP或者AT Parser,请使能本选项。当本模块使能之后, 还会出现是否使能AT TCP的配置选项:
开发者可以根据产品的实际情况选择是否使能ATM以及AT TCP. 如果开发者使能了ATM, 但是开发者没有用于AT收发/解析的框架, 可以选择使用at_parser框架(非必须): • FEATURE_AT_PARSER_ENABLED
SDK基于at_parser提供了已对接示例, 如果模组是sim800 2G模组或者mk3060 Wi-Fi模组, 可以进行进一步选择模组的型号, 可以让SDK将相应的HAL实现也包含在抽取的代码中. 如果不需要对接示例, 请忽略该步骤
完整的配置开关说明表格如下, 但最终解释应以上面提到的”h”按键触发文本为准
配置开关 | 说明 |
PLATFORM_HAS_STDINT | 告诉SDK当前要移植的嵌入式平台是否有自带标准头文件 |
PLATFORM_HAS_OS | 目标系统是否运行某个操作系统 |
FEATURE_MQTT_COMM_ENABLED MQTT | 长连接功能, 打开后将使SDK提供MQTT网络收发的能力和接口 |
FEATURE_MQTT_DEFAULT_IMPL | SDK内包含的MQTT Client实现, 打开则表示使用SDK内置的MQTT客户端实现 |
FEATURE_ASYNC_PROTOCOL_STACK | 对于使用SDK内置的MQTT客户端实现的时候, 需要用户实现TCP相关的HAL, 这些HAL的TCP发送数据/接收数据的定义是同步机制的, 如果目标系统的TCP基于异步机制, 可以使能该开关实现SDK从同步到异步机制的转换 |
FEATURE_DYNAMIC_REGISTER | 动态注册能力, 即设备端只保存了设备的ProductKey和ProductSecret和设备的唯一标识, 通过该功能从物联网平台换取DeviceSecret |
FEATURE_DEVICE_MODEL_ENABLE | 使能设备物模型编程相关的接口以及实现 |
FEATURE_DEVICE_MODEL_GATEWAY | 网关的功能以及相应接口 |
FEATURE_THREAD_COST_INTERNAL | 为收包启动一个独立线程 |
FEATURE_SUPPORT_TLS | 标准TLS连接, 打开后SDK将使用标准的TLS1.2安全协议连接服务器 |
FEATURE_SUPPORT_ITLS | iTLS连接, 打开后SDK将使用自研的iTLS代替TLS建立安全连接 |
FEATURE_ATM_ENABLED | 如果系统是使用MCU+外接模组的架构, 并且SDK运行在MCU上, 必须打开该选项, 然后进行配置 |
FEATURE_AT_MQTT_ENABLED | 如果MCU连接的通信模组支持MQTT AT, 则使用该选项 |
FEATURE_AT_PARSER_ENABLED | 如果用户需要使用SDK提供的AT收发/解析的框架, 则可以使用该选项 |
FEATURE_AT_MQTT_HAL_ICA | 基于at_parser的ICA MQTT AT对接示例 |
FEATURE_AT_MQTT_HAL_SIM800 | 基于at_parser的SIM800 MQTT对接示例 |
使能需要的SDK配置后, 保持配置并退出SDK配置工具
抽取选中功能的源代码 运行SDK根目录下的extract.bat(Linux下运行extract.sh), 客户选中的功能所对应的代码将会被放置到文件夹output, 如下图所示:
实现HAL对接函数
Link Kit SDK被设计为可以在不同的操作系统上运行, 或者甚至在不支持操作系统的MCU上运行, 因此与系统相关的操作被定义成一些HAL函数, 需要客户进行实现. 另外, 由于不同的通信模组支持的AT指令集不一样, 所以与通信模组上TCP相关的操作也被定义成HAL函数需要设备开发者进行实现 由于不同的用户使能的SDK的功能可能不一样, 因此需要对接的HAL函数会不一样, 设备开发者只需要实现位于文件output/eng/wrappers/wrapper.c中的HAL函数. 下面对所有可能出现在文件wrapper.c的HAL函数进行讲解:
1)MCU系统相关HAL
必须实现函数:
函数名 | 说明 |
HAL_Malloc | 对应标准C库中的malloc(), 按入参长度开辟一片可用内存, 并返回首地址 |
HAL_Free | 对应标准C库中的free(), 将入参指针所指向的内存空间释放, 不再使用 |
HAL_Printf | 对应标准C库中的printf(), 根据入参格式字符串将字符文本显示到终端 |
HAL_Snprintf | 类似printf, 但输出的结果不再是显示到终端, 而是存入指定的缓冲区内存 |
HAL_UptimeMs | 返回一个uint64_t类型的数值, 表达设备启动后到当前时间点过去的毫秒数 |
HAL_SleepMs | 按照指定入参的数值, 睡眠相应的毫秒, 比如参数是10, 那么就会睡眠10毫秒 |
OS相关可选函数 如果MCU没有运行OS, 或者SDK的MQTT API并没有在多个线程中被调用, 以下函数可以不用修改wrapper.c中相关的函数实现. 在有OS场景下并且MQTT API被APP在多个线程中调用, 则需要用户对接以下函数:
函数名 | 说明 |
HAL_MutexCreate | 创建一个互斥锁, 返回值可以传递给HAL_MutexLock/Unlock |
HAL_MutexDestroy | 销毁一个互斥锁, 这个锁由入参标识 |
HAL_MutexLock | 申请互斥锁, 如果当前该锁由其它线程持有, 则当前线程睡眠, 否则继续 |
HAL_MutexUnlock | 释放互斥锁, 此后当前在该锁上睡眠的其它线程将取得锁并往下执行 |
HAL_SemaphoreCreate | 创建一个信号量, 返回值可以传递给HAL_SemaphorePost/Wait |
HAL_SemaphoreDestroy | 销毁一个信号量, 这个信号量由入参标识 |
HAL_SemaphorePost | 在指定的计数信号量上做自增操作, 解除其它线程的等待 |
HAL_SemaphoreWait | 在指定的计数信号量上等待并做自减操作 |
HAL_ThreadCreate | 根据配置参数创建thread |
2)TCP相关HAL
如果用户未选择ATM, 用户适配时调用模组提供的TCP AT指令实现四个TCP HAL函数. 下面是对这些函数的说明,
函数名 | 说明 |
HAL_TCP_Establish | 建立一个TCP连接 注意: + 入参host是一个域名, 需要转换为IP地址 + 返回值是tcp的socket号 |
HAL_TCP_Destroy | 关闭tcp连接, 入参是HAL_TCP_Establish的返回值, 返回值0表示成功 |
HAL_TCP_Write | 通过TCP连接发送数据 注意: + 该函数传入了一个超时时间, 如果超时仍未将数据发送结束那么函数也需要返回. + 如果TCP连接已断开, 需要返回一个小于0的负数 |
HAL_TCP_Read | 在指定的时间内读取数据并返回, 该函数的入参中指定了可接收的数据的最大长度, 如果从TCP中读取到该最大长度的数据, 那么可以立即返回 |
3)AT TCP相关HAL
如果用户选择使用ATM以及AT TCP, 并且未使能AT Parser, 用户需要实现下面表格中的HAL函数. 下面是对这些函数的说明:
函数名 | 说明 |
HAL_AT_CONN_Init | 该接口需要对通信模组进行相关初始化, 使通信模组达到可以工作的状态 |
HAL_AT_CONN_Deinit | 该接口需要提供对通信模组的去初始化操作 |
HAL_AT_CONN_Start | 该接口需要模组启动一次连接. 上层传给底层的参数为一个结构体指针at_conn_t, 其参数说明如下: fd: 每个连接对应的句柄. type: 建立连接的类型(如TCP_client), 见at_wrapper.h. addr: 对端ip或者域名. r_port: 远端端口号. l_port: 本地端口号. tcp_keep_alive: tcpkeep alive的时间 |
HAL_AT_CONN_Close | 该接口关闭模组的一个连接. 入参说明如下: fd: 需要关闭的socket句柄. remote_port: 对端端口号, 该参数为可选参数, 小于0时为无效参数. |
HAL_AT_CONN_Send | 该接口通过模块发送数据的接口, 该接口为阻塞接口, 直到模组通知底层控制模块数据发送成功才会返回. 入参说明如下: fd: 发送数据所操作的句柄 data: 待发送数据的指针 len: 待发送数据的长度 remote_ip[16], 对端ip地址, 为可选参数, 为NULL时无效 remote_port: 对端端口号, 为可选参数, 小于0时无效 |
HAL_AT_CONN_DomainToIp | 该接口提供获取对应域名ip地址的功能, 注意: 1/即使该域名对应多个ip, 也只会返回一个ip地址. 2/目前该接口只需要支持ipv4. 入参说明如下: domain: 域名信息 ip[16]: 点格式的ip字符串, 目前只支持ipv4 |
4)AT TCP HAL对接示例
(1)HAL_AT_CONN_Init 该函数完成HAL层数据初始化. 下面的代码示例中, 创建了容量为LINK_ID_MAX的TCP连接数组, 用于对应模组上创建的TCP连接. 用户还需要在此处完成与模组必要的交互, 例如sim800模组对接时需要附着网络/获取ip地址等
typedef struct link_s {
int fd;
….
} link_t;
static link_t g_link[LINK_ID_MAX];
int HAL_AT_CONN_Init(void)
{
int link;2019-11-12 09:27:11 星期二
memset(g_link, 0, sizeof(g_link));
for (link = 0; link < LINK_ID_MAX; link++) {
g_link[link].fd = -1;
}
…
inited = true;
return 0;
}
(2)HAL_AT_CONN_Deinit 该函数完去初始化, 将HAL_AT_CONN_Init()中分配的资源释放. 下面的示例中只是简单的将inited变量设置为false:
int HAL_AT_CONN_Deinit(void)
{
if (!inited) {
return 0;
}
inited = false;
return 0;
}
(3)HAL_AT_CONN_Start 该函数用于建立TCP连接. 下面的示例代码主要包括从g_link数组中获得空闲的TCP连接元素/记录该连接的地址/端口等信息, 向模组发送拼接生成的建立TCP连接的AT指令. 用户需要将该函数实现修改为实际使用的模组提供的AT指令, 并对返回数据进行相应处理
int HAL_AT_CONN_Start(at_conn_t *c)
{
int link_id;
for (link_id = 0; link_id < LINK_ID_MAX; link_id++) {
if (g_link[link_id].fd >= 0) {
continue;
} else {
g_link[link_id].fd = c->fd;
break;
}
}
…
/* 拼接AT命令 */
snprintf(cmd, START_CMD_LEN, “%s=%d,%s,%s,%d”,
START_CMD, link_id, start_cmd_type_str[c->type],
c->addr, c->r_port);
….
/* 发送AT命令 */
at_send_wait_reply(cmd, strlen(cmd), true, out, sizeof(out), NULL);
LOGD(TAG, “The AT response is: %s”, out);
if (strstr(out, CMD_FAIL_RSP) != NULL) {
goto err;
}
return 0;
err:
// error handle
}
(4)HAL_AT_CONN_Close 该函数用于关闭TCP连接. 下面的示例代码主要包括记录向模组发送动态生成的AT指令, 然后删除linkid与fd的映射. 其中, fd_to_linkid()是fd向linkid转换函数
int HAL_AT_CONN_Close(int fd, int32_t remote_port)
{
int link_id;
char cmd[STOP_CMD_LEN] = {0}, out[64];
link_id = fd_to_linkid(fd);
if (link_id < 0 || link_id >= LINK_ID_MAX) {
LOGE(TAG, “No connection found for fd (%d) in %s”, fd, func);
return -1;
}
snprintf(cmd, STOP_CMD_LEN - 1, “%s=%d”, STOP_CMD, link_id);
LOGD(TAG, “%s %d - AT cmd to run: %s”, func, LINE, cmd);
at_send_wait_reply(cmd, strlen(cmd), true, out, sizeof(out), NULL);
LOGD(TAG, “The AT response is: %s”, out);
if (strstr(out, CMD_FAIL_RSP) != NULL) {
LOGE(TAG, “%s %d failed”, func, LINE);
goto err;
}
…
g_link[link_id].fd = -1;
}
(5)HAL_AT_CONN_Send 该函数用于向模组发送数据, 主要工作是向模组发送拼接生成AT命令与数据. 下面的示例代码是向模组上指定的TCP socket发送数据, 用户需要将AT指令修改为实际连接模组对应的AT指令以及进行相应处理
int HAL_AT_CONN_Send(int fd,
uint8_t *data,
uint32_t len,
char remote_ip[16],
int32_t remote_port,
int32_t timeout)
{
int link_id;
char cmd[SEND_CMD_LEN] = {0}, out[128] = {0};
if (!data) {
return -1;
}
link_id = fd_to_linkid(fd);
if (link_id < 0 || link_id >= LINK_ID_MAX) {
LOGE(TAG, “No connection found for fd (%d) in %s”, fd, func);
return -1;
}
/* AT+CIPSEND=id, */
snprintf(cmd, SEND_CMD_LEN - 1, “%s=%d,”, SEND_CMD, link_id);
/* [remote_port,] */
if (remote_port >= 0) {
snprintf(cmd + strlen(cmd), 7, “%d,”, remote_port);
}
at_send_data_2stage((const char *)cmd, (const char *)data, len, out, sizeof(out));
}
(6)HAL_AT_CONN_DomainToIp 该函数用于域名解析, 向模组发送动态生成的查询命令后, 根据已知格式解析回复
int HAL_AT_CONN_DomainToIp(char *domain, char ip[16])
{
char cmd[DOMAIN_CMD_LEN] = {0}, out[256] = {0}, *head, *end;
snprintf(cmd, DOMAIN_CMD_LEN - 1, “%s=%s”, DOMAIN_CMD, domain);
/* 发送查询命令 */
at_send_wait_reply(cmd, strlen(cmd), true, out, sizeof(out), NULL);
LOGD(TAG, “The AT response is: %s”, out);
if (strstr(out, AT_RECV_SUCCESS_POSTFIX) == NULL) {
LOGE(TAG, “%s %d failed”, func, LINE);
return -1;
}
/* 根据已知格式解析回复 */
…
}
5)调用接收函数
用户在AT HAL层收到数据后需要调用API接口IOT_ATM_Input(见atm/at_api.h), 将数据交付给上层
void handle_recv_data()
{
struct at_conn_input param;
…
/* 读取AT指令中数据长度信息 */
len = atoi(reader);
if (len > MAX_DATA_LEN) {
LOGE(TAG, “invalid input socket data len %d \r\n”, len);
return;
}
/分配接收buffer, 用户也可以直接使用一个静态数组用于数据接收/
recvdata = (char *)aos_malloc(len);
if (!recvdata) {
LOGE(TAG, “Error: %s %d out of memory, len is %d. \r\n”, func, LINE, len);
return;
}
/* 读取数据 */
ret = at_read(recvdata, len);
if (ret != len) {
LOGE(TAG, “at read error recv %d want %d!\n”, ret, len);
goto err;
}
if (g_link[link_id].fd >= 0) {
param.fd = g_link[link_id].fd;
param.data = recvdata;
param.datalen = len;
param.remote_ip = NULL;
param.remote_port = 0;
/* 向上层交付数据 */
if (IOT_ATM_Input(¶m) != 0) {
at_conn_hal_err(“ %s socket %d get data len %d fail to post to at_conn, drop it\n”,
func, g_link[link_id].fd, len);
}
}
…
}
其中, struct at_conn_input定义见at_wrapper.h
struct at_conn_input {
int fd; /* 数据上送需要操作的句柄 */
void data; / 接收到的数据(该部分内存由底层自行释放)*/
uint32_t datalen; /* 接收到的数据长度 */
char remote_ip; / 该数据的源地址, 为可选参数, 可以传入NULL(该部分内存由底层自行释放)*/
uint16_t remote_port; /* 该数据的源端口, 为可选参数, 可以传入0 */
};
6)AT Parser相关HAL
如果选择了at_parser框架, 则需要对接以下四个UART HAL函数, 函数声明见at_wrapper.h. 如果用户不使用at_parser框架请忽略该步骤
函数名 | 说明 |
HAL_AT_Uart_Init | 该接口对UART进行配置(波特率/停止位等)并初始化 |
HAL_AT_Uart_Deinit | 该接口对UART去初始化 |
HAL_AT_Uart_Send | 该接口用于向指定的UART口发送数据 |
HAL_AT_Uart_Recv | 该接口用于从底层UART buffer接收数据 |
7)产品相关HAL
下面的HAL用于获取产品的身份认证信息, 设备厂商需要设计如何在设备上烧写设备身份信息, 并通过下面的HAL函数将其读出后提供给SDK:
函数名 | 说明 |
HAL_GetProductKey | 获取设备的ProductKey, 用于标识设备的产品型号 |
HAL_GetDeviceName | 获取设备的DeviceName, 用于唯一标识单个设备 |
HAL_GetDeviceSecret | 获取设备的DeviceSecret, 用于标识单个设备的密钥 |
8)代码集成
如果设备商的开发环境使用makefile编译代码, 可以将SDK抽取出来的代码加入其编译环境进行编译. 如果设备商使用KEIL/IAR这样的开发工具, 可以将SDK抽取出来的代码文件加入到IDE的工程中进行编译 下面是将抽取出来的output目录复制到Linux下, 并在output目录下创建的一个makefile的示例 注: 配置时使能了ATM模块/使用AT TCP/AT Parser/并选择使用SIM800作为外接模组
SDK_PWD = $(shell pwd)/eng
SDK_DIRS = $(SDK_PWD)/dev_sign $(SDK_PWD)/atm $(SDK_PWD)/infra $(SDK_PWD)/mqtt $(SDK_PWD)/wrappers
SDK_SOURCES = $(foreach dir,$(SDK_DIRS),$(wildcard $(dir)/*.c))
SDK_OBJS = $(patsubst %.c,%.o,$(SDK_SOURCES))
SDK_INC_DIRS = $(foreach dir, $(SDK_DIRS),-I$(dir) )
TARGET = testmqtt
all:eng/examples/mqtt_example_at.o $(SDK_OBJS)
$(CC) -o $(TARGET) $(SDK_OBJS) eng/examples/mqtt_example_at.o
clean:
rm -rf *.o $(TARGET) $(SDK_OBJS)
%.o:%.c
$(CC) -c $(SDK_INC_DIRS) $< -o $@
注: • 上面的makefile仅供参考, 用户配置SDK时选用的功能不一样会导致目录出现差别, 用户需要将除了eng/examples外的目录加入编译系统/工具 • 用户如果复制该makefile使用, 在复制/粘贴时all/clean/%.o:%.c下一行的命令语句可能出现多个空格/或者没有空格就直接是命令, 如果发现在命令前有空格需要将空格全部删除, 然后增加一个Tab键, 以避免make出错
参照example实现产品功能
如果要使用MQTT连云, 可参考抽取文件夹中的 eng/examples/mqtt_example_at.c . 设备厂商可以将该文件复制到产品工程中, 对其进行修改后使用 该example将连接设备到阿里云, 订阅一个指定的topic并发送数据给该topic, 即设备上报的消息会被物联网平台发送给设备, 下面是example的大概过程说明:
注意: 需要在云端将该topic从默认的权限从”订阅”修改为”发布和订阅”, 如下图所示:
从程序入口的 main() 函数看起, 第一步是调用AT模块初始化函数IoT_ATM_Init(), 使模组处于ready状态, 第二步是调用用户提供的HAL函数获取产品信息
int main(int argc, char *argv[])
{
void * pclient = NULL;
int res = 0;
int loop_cnt = 0;
iotx_mqtt_region_types_t region = IOTX_CLOUD_REGION_SHANGHAI;
iotx_sign_mqtt_t sign_mqtt;
iotx_dev_meta_info_t meta;
iotx_mqtt_param_t mqtt_params;
#ifdef ATM_ENABLED
if (IOT_ATM_Init() < 0) {
HAL_Printf(“IOT ATM init failed!\n”);
return -1;
}
#endif
HAL_Printf(“mqtt example\n”);
memset(&meta, 0, sizeof(iotx_dev_meta_info_t));
HAL_GetProductKey(meta.product_key);
HAL_GetDeviceName(meta.device_name);
HAL_GetDeviceSecret(meta.device_secret);
注: • 上面的三个HAL_GetXXX函数是获取设备的三元组信息, 设备厂商需要自己设计设备的三元组存放的位置/并将其从指定位置读取出来 • 由于设备的唯一标识DeviceName/设备密钥DeviceSecret都是机密信息, 设备厂商在设计时可以把相关信息加密后存放到Flash上, 在HAL函数里面将其解密后提供给SDK, 以避免黑客直接从Flash里面读取设备的身份信息 接下来对MQTT连接参数进行指定, 客户可以根据自己的需要对参数进行修改:
/* Initialize MQTT parameter */
memset(&mqtt_params, 0x0, sizeof(mqtt_params));
mqtt_params.port = sign_mqtt.port;
mqtt_params.host = sign_mqtt.hostname;
mqtt_params.client_id = sign_mqtt.clientid;
mqtt_params.username = sign_mqtt.username;
mqtt_params.password = sign_mqtt.password;
mqtt_params.request_timeout_ms = 2000;
mqtt_params.clean_session = 0;
mqtt_params.keepalive_interval_ms = 60000;
mqtt_params.read_buf_size = 1024;
mqtt_params.write_buf_size = 1024;
mqtt_params.handle_event.h_fp = example_event_handle;
mqtt_params.handle_event.pcontext = NULL;
pclient = IOT_MQTT_Construct(&mqtt_params);
通过调用接口 IOT_MQTT_Construct() 触发SDK连接云平台, 若接口返回值非NULL, 则连云成功 之后调用example_subscribe对一个指定的topic进行数据订阅:
res = example_subscribe(pclient);
example_subscribe的函数内容如下:
注: • 设备商需要根据自己的产品设计, 订阅自己希望订阅的TOPIC, 以及注册相应的处理函数 • 订阅的topic的格式需要指定产品型号(product_key)以及设备标识(device_name), 如上图中第一个橙色框中的格式 • 上图的第二个框展示了如何订阅一个指定的topic以及其处理函数 以下段落演示MQTT的发布功能, 即将业务报文上报到云平台:
while (1) {
if (0 == loop_cnt % 20) {
example_publish(pclient);
}
IOT_MQTT_Yield(pclient, 200);
loop_cnt += 1;
}
下面是example_publish函数体的部分内容:
注: • 上面的代码是周期性的将固定的消息发送给云端, 设备商需要根据自己的产品功能, 在必要的时候才上传数据给物联网平台 • 客户可以删除main函数中example_publish(pclient)语句, 避免周期发送无效数据给到云端 • IOT_MQTT_Yield是让SDK去接收来自MQTT Broker的数据, 其中200毫秒是等待时间, 如果用户的消息数量比较大/或者实时性要求较高, 可以将时间改小
功能调试
下面的信息截图以mqtt_example_at.c为例编写
1)如何判断设备已连接到阿里云
下面的打印是HAL_Printf函数将信息打印到串口后运行example的输出内容, 其中使用橙色圈选的信息表明设备已成功连接到阿里云物联网平台:
2)如何判断设备已成功发送数据到云端
登录阿里网物联网平台的商家后台, 选中指定的设备, 可以查看是否收到来自设备的消息, 如下图所示:
3)如何判断设备已可成功接收来自云端数据
在商家后台的”下行消息分析”分析中可以看见由物联网平台发送给设备的消息:
也可在设备端查看是否已收到来自云端的数据, exmaple代码中收到云端发送的数据的打印信息如下所示:
至此, SDK在MCU与模组之间的适配开发已结束, 用户可以进行产品业务功能的实现
4.x版本的设备端开发指引
概述
C语言Link Kit SDK适用于使用C语言开发业务处理逻辑的设备, 由于C语言运行速度快、需要的运行内存较少, 目前大多数的IoT设备使用C语言进行产品开发。
SDK文件
请点击链接下载SDK文件 C-SDK
SDK使用说明
SDK提供了API供设备厂商调用,用于实现与硬件云IoT平台通信。 另外,C语言版本的SDK被设计为可以在不同的操作系统上运行,比如Linux、FreeRTOS、Windows,rthread等,因此SDK需要OS或者硬件支持的操作被定义为一些HAL函数,设备厂商在使用SDK开发产品时需要将这些HAL函数进行实现。 产品的业务逻辑、SDK、HAL的关系如下图所示:
SDK移植指南
一、基于Linux开发环境集成SDK
SDK虽然提供了makefile用于编译SDK以及其中的示例文件,但是实际开发项目过程中,客户往往已经有自己的开发工程,因此实际的做法是将SDK添加到现有开发工程中进行编译。本文通过示例为您讲解如何将SDK添加到一个已有工程中并进行编译。
示例说明
1、本示例包含一个打印
Hello World!
的测试程序hello.c
,并有一个编译该测试程序的makefile。 hello.c
代码内容非常简单,只是打印一句输出。具体代码内容如下(附hello代码片段):#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("Hello World!\n\r");
return(0);
}
makefile的编码内容如下:
PROG_FILE := hello.c
PROG_OBJS := $(patsubst %.c,%.o,$(PROG_FILE))
PROG = hello
main:$(PROG_OBJS)
$(CC) -o $(PROG) $(PROG_OBJS)
clean:
rm -f *.o $(PROG)
2、
SDK下载之后,将压缩文件解压,并将LinkSDK目录复制到
hello.c
所在目录,如下内容所示: $ ls -l
-rwxrwxrwx 1 root root 183 5月 11 19:45 hello.c
drwxrwxrwx 1 root root 4096 5月 11 17:28 LinkSDK
-rwxrwxrwx 1 root root 672 5月 11 19:49 makefile
3、复制mqtt示例并进行修改,将
LinkSDK/demos/mqtt_basic_demo.c
复制到hello.c
所在目录,并将mqtt_basic_demo.c
文件中的main
函数修改为sdk_test
,修改后的代码如内容所示:int sdk_test(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
char *url = "iot-as-mqtt.cn-shanghai.aliyuncs.com"; /* 云平台上海站点(华东2站点)的域名后缀 */
...
}
4、修改makefile包含SDK ,修改makefile去编译SDK的源代码,以及复制出来的文件
mqtt_basic_demo.c
(附sdk_test代码片段):PROG_FILE := hello.c mqtt_basic_demo.c
PROG_OBJS := $(patsubst %.c,%.o,$(PROG_FILE))
PROG = hello
SDK_ROOT = $(shell pwd)/LinkSDK
SDK_DIR = $(SDK_ROOT)/core $(SDK_ROOT)/core/sysdep $(SDK_ROOT)/core/utils $(SDK_ROOT)/portfiles/aiot_port $(SDK_ROOT)/external $(SDK_ROOT)/external/mbedtls/library
SDK_INC = -I$(SDK_ROOT)/external/mbedtls/include $(foreach dir, $(SDK_DIR), -I$(dir) )
SDK_FILES = $(foreach dir, $(SDK_DIR), $(wildcard $(dir)/*.c))
SDK_OBJS = $(patsubst %.c,%.o,$(SDK_FILES))
SDK_LIBS = -lpthread
CFLAGS += $(SDK_INC)
main:$(PROG_OBJS) $(SDK_OBJS)
$(CC) $(CFLAGS) -o $(PROG) $(PROG_OBJS) $(SDK_OBJS) $(SDK_LIBS)
clean:
rm -f *.o $(PROG) $(SDK_OBJS)
上面的改动中需要注意的是:
- SDK_DIR需要包含SDK中设备需要用到功能的目录,如果客户选择了高级功能,那么在
LinkSDK/components
目录下将会出现相应的目录,那么需要将相关目录也加入到SDK_DIR中; - 因为本例中选择使用TLS对数据进行加密,所以编译的时候也编译了SDK中附带的mbedtls,并在
SDK_INC
中指定了对external/mbedtls/include
目录的包含; - 因为编译后的程序在Linux平台上运行,会使用到线程相关的库,所以在SDK_LIBS里面指定了对pthread库的链接。
5、修改hello.c调用SDK,修改hello.c调用文件
mqtt_basic_demo.c
中的函数sdk_test
,初始化SDK并连接硬件云平台:#include <stdio.h>
#include <stdlib.h>
/*声明sdk_test函数*/
extern int sdk_test(int argc, char *argv[]);
int main(int argc, char **argv)
{
printf("Hello World!\n\r");
/*调用SDK Demo中的sdk_test函数,去初始化SDK并连接云物联网平台*/
sdk_test(0,NULL);
return(0);
}
6、编译并运行,在hello.c所在目录运行make命令进行编译,然后运行生成的hello程序,如果一切顺利将输出类似如下内容:
$ ./hello
Hello World!
[61760.936][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
[61760.936][LK-0317] mqtt_basic_demo&a13FN5TplKq
[61760.936][LK-0318] 4780A5F17990D8DC4CCAD392683ED80160C4C2A1FFA649425CD0E2666A8593EB
establish mbedtls connection with server(host='a13FN5TplKq.iot-as-mqtt.cn-shanghai.aliyuncs.com', port=[443])
success to establish tcp, fd=3
success to establish mbedtls connection, fd = 3(cost 43093 bytes in total, max used 45881 bytes)
[61761.107][LK-0313] MQTT connect success in 172 ms
AIOT_MQTTEVT_CONNECT
heartbeat response
二、基于MCU+有方N720模组
本文介绍如何将SDK集成并运行在MCU上, 让SDK通过有方N720通讯模组连接到硬件云平台。
设备说明
- 设备的硬件由一个MCU加上一个通信模组构成, 设备的应用逻辑运行在MCU上。
- 模组上支持了MQTT, MCU通过模组提供的AT指令来控制模组何时连接云端服务以及收发数据。
- MCU:
STM32F103xB
(运行FreeRTOS
操作系统)。 - MQTT模组:
N720V5
(提供MQTT层面的AT指令)。
准备工作
本移植实践中使用了以下工具和材料。
- 硬件:
ST STM32F103xB
开发板, 有方N720V5
MQTT模组。 - 宿主机OS:Windows。
- Keil编译器。
搭建开发环境
在搭建开发环境前, 需要准备好硬件, 即
STM32F103xB
开发板及N720V5
MQTT模组。说明 白色的开发板为STM32开发板, 蓝色的开发板为N720开发板。
1、硬件准备工作
- N720开发板要插入4G的移动sim卡(确保有已激活且能使用)。
- 将两块开发板串口的地线相连接, STM32开发板串口的TX线接到N720开发板的RX口, STM32开发板串口的RX线接到N720开发板的TX口。
- N720开发板通过其电源适配器给其供电。
- 打开N720电源适配器开关, 同时长按PowerKey按钮5秒钟(下图的红色按钮), 直到黄色LED等由浅亮变高亮。
- STM32直接通过USB线连接到Windows电脑上
安装必要的软件
前往Keil 软件下载中心, 选择对应
MDK-Arm
, 进入SDK下载页面。软件安装完成后, 输入license完成激活, 然后连接到STM32开发板, 按照页面的提示完成所有配置文件的下载。
我们已对STM32+N720V5完成了上述工作,对接完成的源码点此下载。
此工程中有STM开发板的配置文件(包括串口, 中断, freertos等), 以及所有代码的完整实现。
如果您的模组不是N720V5, 您可以参考我们针对N720V5模组AT指令的对接代码完成上述工作。
只要修改
main.c
和SDK4.0中的aiot_mqtt_api.c
里AT命令部分, 即可完成对接。编译/烧录/调试STM32的固件:
点击左上角的编译按钮, 完成代码的编译, 然后点击烧录按钮, 进行开发板的烧录。
点击调试按钮, 对程序运行进行控制(比如加断点, 单步调试, 或进入某个函数等)。
由于当前的开发板仅仅将串口1用于发送AT指令, 没有将其他串口配置起来用于日志的输出, 因此主要靠keil的断点和单步调试工具来判断执行的位置。
程序运行。
2、模组初始化
在
main.c
文件的main函数中, mqtt_demo()
被调用之前, 都是在通过AT指令进行模组的初始化的工作, 包括获取IP地址等。设备初始化需要不到1分钟的时间, 如果成功, 则有一盏LED灯会一闪一闪。
如果不成功, 需要通过单步调试的方式, 看在main函数中在
mqtt_demo()
调用前, 哪条AT指令执行错误。3、设备上线测试
设备如果上线成功, 可以通过iot云端的控制台看到设备上线,参考下一节中的第二个截图。
4、
MCU通过MQTT模组接收云端下推消息
- 在
mqtt_basic_demo.c
文件94
行打上断点, 如下图。 - 该断点在用户的代码收到云端推送的mqtt消息后会被触发。
- 在本例中, 我们订阅了
/sys/${PRODUCT_KEY}/${DEVICE_NAME}/thing/service/property/set
因此, 操作控制台使云平台在这个Topic下推消息后, 断点会被触发。
程序运行起来后, 用户登录IoT控制台。
选中要调试的设备, 当观察到设备"在线"后, 选择设备信息的日志可以看到该断点被触发。
同时图中的下方红框里面的
topic
和payload
数组显示了收到的报文。可以点击红框中的加号看到这两个字符串中每个字节的内容。
至此, 说明设备已经成功收到了代表属性设置的
thing/service/property/set
消息。三、移植需要实现的HAL层
上面举例了linux和mcu方式的移植,不论什么操作系统都需要进行HAL层的适配,HAL的适配函数在CSDK的portfiles目录下,因此下面是对这些函数的说明:
core_sysdep_malloc | 申请内存 |
core_sysdep_free | 释放内存 |
core_sysdep_time | 获取当前时间戳,SDK用于计算差值 |
core_sysdep_sleep | 睡眠指定的毫秒数 |
core_sysdep_network_init | 创建一个网络会话 |
core_sysdep_network_setopt | 配置一个网络会话的连接参数 |
core_sysdep_network_establish | 建立一个网络会话,作为MQTT/HTTP等协议的底层承载 |
core_sysdep_network_recv | 在指定的网络会话上接收 |
core_sysdep_network_send | 在指定的网络会话上发送 |
core_sysdep_network_deinit | 销毁一个网络会话 |
core_sysdep_rand | 随机数的生成方法 |
core_sysdep_mutex_init | 创建互斥锁 |
core_sysdep_mutex_lock | 申请互斥锁 |
core_sysdep_mutex_unlock | 释放互斥锁 |
core_sysdep_mutex_deinit | 销毁互斥锁 |
注: •具体的实现参考,可以参考硬件云(公网版)API文档
五、API 参考
云端API列表(共56个)
3.2版本的设备端接口(共54个)
4.x版本的设备端接口(共54个)
详细请见:硬件云(公网版)API文档
产品版本
2020-03-31
V 1.0.0 版本发布
发布时间时间:2020-03-31
云端
一、产品管理 新增产品管理、云端订阅、优模型定义等功能
- 产品管理:提供产品的三元组创建及管理功能,提供产品的增删改查,并且针对产品进行优模型的定义
- 优模型管理:提供优模型的文件上传,在线编辑等能力
二、设备管理 新增设备的创建设备管理、批量创建设备管理
- 设备管理:提供设备的新增功能,实现对设备上下线管理,删除管理等能力
- 批量新增设备管理:实现对设备的批量新增,批量删除等功能
设备端
提供云端直连设备的硬件SDK,可以进行设备连云、设备身份认证、属性上报、设置服务调用、事件上报等功能。 SDK版本包括:
- C SDK
2020-06-01
V 1.00.04.00 版本发布
发布时间时间:2020-06-01
云端
二、设备管理
- 新增批量查询设备属性功能
更新日志
日期 | 修改描述 |
2019-10-13 | 【新增】【产品说明】介绍,介绍产品背景,平台优势等信息 |
2019-10-13 | 【新增】【快速入门】操作指南,提供对初入平台的开发者操作说明,并协助其完成设备云端入网等操作 |
2019-10-14 | 新增开发指南,对云端硬件sdk部分接入能力进行流程指引 |
2020-03-31 | 硬件云 V1.00.00.00 产品发布 |
2020-06-01 | 硬件云 V1.00.04.00 产品发布 |
2021-1-28 | 硬件云新增OTA升级产品模块,提供设备得在线升级,远程升级等功能 |
2021-3-24 | 硬件云 v1.01.00.00 产品发布 |