Revision | Date | Description |
---|---|---|
1.0 | 2019-1-15 | 文档初版 |
2.0 | 2019-8-13 | 文档 V2.0 |
3.0 | 2020-02-26 | 文档 V3.0 |
腾讯物联网通信(IoT Hub)服务,旨在提供一个安全、稳定、高效的连接平台,帮助开发者低成本、快速地实现“设备 - 设备”、“设备 - 用户应用”、“设备 - 云服务”之间可靠、高并发的数据通信。也就是说,腾讯物联网通信可以实现设备之间的互动、设备的数据上报和配置下发,还可以基于规则引擎和腾讯云产品打通,方便快捷的实现海量设备数据的存储、计算以及智能分析。总之,基于腾讯物联网通信,开发者可以低成本实现“设备 - 数据 - 应用 - 云服务”的连接,快速搭建物联网应用平台。
腾讯云物联网通信产品架构
在上图所示的架构中,本文只讨论"用户设备"和腾讯云的对接。这上图中可以看到,"用户设备"和腾讯云之间是基于MQTT协议或是CoAP协议进行通信的。因此,在"用户设备"上,需要包含连接 SDK 或是实现了 MQTT 或 CoAP 协议的代码。本文讲述 MQTT 连接方法,CoAP 协议类似。
MQTT 简介
物联网 (IoT) 设备必须连接互联网。通过连接到互联网,设备就能相互协作,以及与后端服务协同工作。互联网的基础网络协议是 TCP/IP. MQTT(消息队列遥测传输) 是基于 TCP/IP 协议栈而构建的,已成为 IoT 通信的标准。MQTT 本身是个轻量级的协议,可以在硬件受限设备上实现。同时,MQTT 协议支持在各方之间异步通信的消息协议,异步消息协议在空间和时间上将消息发送者与接收者分离,因此可以在不可靠的网络环境中进行扩展。MQTT 的灵活性使得为 IoT 设备和服务的多样化应用场景提供支持成为可能。
MQTT 协议基于发布 (Publish) 和订阅 (Subscribe) 模型。在网络中定义了两种实体类型:消息代理端 (Broker)和客户端 (Client), 代理是一个服务器,它从客户端接收所有消息,然后将这些消息路由到相关的目标客户端。客户端是能够与代理交互来发送和接收消息的任何事物。客户端可以是现场的 IoT 传感器,或者是数据中心内处理 IoT 数据的应用程序。
Client 和 Broker 之间的发布和订阅是根据主题 (Topic) 来进行的,不同的客户端可以向不同的主题发布消息,设备只有在订阅了某个主题后,才能收到相应主题的消息。
云端设置主要包含了,创建新产品,创建新设备
登录腾讯云,搜索"云产品"下的"物联网通信"产品,或直接访问 https://console.cloud.tencent.com/iotcloud
创建新产品
在"添加新产品"的时候,注意选择。产品类型选择普通产品, 产品名称随意,认证方式选择密钥认证, 数据格式选择自定义.
关于认证方式和数据格式的选择解释
认证方式默认的是证书认证,数据格式默认的是 json 格式。
对于认证方式,指定了设备通过何种方式和云端进行双向认证。默认的证书方式相对于密钥认证安全性高一点,但是问题在于证书方式需要在嵌入式设备端存储证书同时实现证书的相关处理,对设备的 RAM 和 ROM 要求较高,相对而言,密钥认证的方式资源占用量就小点,由于我们主要支持的设备都是小型嵌入式设备,因此选用密钥认证。
数据格式指的是设备和云端进行数据交互时候使用的格式,json 格式为文本字符串,可读性高,并且便于解析,对于功能复杂的设备交互而已比较理想,但是对于小型设备或是定制设备,数据单一,或是有自定义的格式 (二进制或是文本), 这种时候,用自定义的数据格式,一方面节约流量,另一方面比较灵活。
注:这里的数据格式选择会影响之后腾讯云"规则引擎"组件的设置。
新建完产品后,会获得一个平台分配的productID.
创建新设备
设置的时候只需要设置设备名称即可,由于我们在创建产品的时候,认证方式选择了密钥认证,因此在创建设备的时候将会提供设备对应的密钥,这里选择默认的"使用物联通通信提供的密钥"即可。
添加完设备后,会告知设备对应的密钥。该密钥将会用于之后设备与平台通信时的认证。
为了实现设备间的通信,我们还需要创建第二个设备,操作同上,不妨将其命名为"dev2".
此时,在产品"my_product"下面,有 2 个我们添加的设备,分别为"dev1"和"dev2".
Topic 设置
我们知道,设备通过 MQTT 协议进行通信,是基于发布 (publish) 和订阅 (subscribe) 相关的话题 (topic) 来进行的,因此,还需要在云端对话题进行设置。
我们可以在"权限列表"中看到 Topic 对应的操作权限,此处还可以添加新的 Topic.
在这里,我们可以看到,平台默认配置了三类的 Topic, 用于执行发布和订阅。这里之所以是三类而不是三个,是因为 Topic 里使用了变量。这里的25KCIUIR1G
实际上是 productID; ${deviceName}
为平台设置的变量,即设备名; control
和data
以及event
为 Topic 名字。所以,在我们创建了 2 个设备 dev1 和 dev2 的情况下,在 my_product 产品下,即存在 6 个 Topic, 分别为:
- 25KCIUIR1G/dev1/control 订阅权限
- 25KCIUIR1G/dev1/data 发布和订阅权限
- 25KCIUIR1G/dev1/event 发布权限
- 25KCIUIR1G/dev2/control 订阅权限
- 25KCIUIR1G/dev2/data 发布和订阅权限
- 25KCIUIR1G/dev2/event 发布权限
这里默认的 Topic 已经足够我们使用,不需要额外添加 Topic 和权限了。
MQTT 的 Topic 本身是一个普通的字符串,但是可以由多个层级组成,根据
/
来划分。这种分层的 Topic 结构使得主题的过滤和匹配变得相对灵活。多层通配符"#"
必须位于最后一个字符,匹配多层级。例如对于
25KCIUIR1G/#
, 将会匹配25KCIUIR1G/dev1
,25KCIUIR1G/dev2
,25KCIUIR1G/dev1/control
,25KCIUIR1G/dev1/event
,25KCIUIR1G/dev2/control
和25KCIUIR1G/dev2/event
主题。单层通配符"+"
匹配单个层级,在主题的任何层级都可以使用,包括第一个层级和最后一个层级。
例如,对于
25KCIUIR1G/+/event
, 将会匹配25KCIUIR1G/dev1/event
和25KCIUIR1G/dev2/event
.
设置规则引擎
规则引擎本身不属于 MQTT 协议的范畴,但是平台侧出于安全角度考虑添加了规则引擎,实现了 Topic 之间的转发操作,我们需要合理的设置规则引擎才能实现多个设备之间的数据收发,由于理解起来比较复杂,我们这里简要讲解下为什么需要规则引擎,规则引擎的作用,如何设置规则引擎。
-
为什么需要规则引擎
在上节的 Topic 中,我们知道,在平台侧,对于不同的 Topic, 规定了不同的权限,例如,对于
25KCIUIR1G/dev1/event
这个 Topic, 只具有发布权限,而对于25KCIUIR1G/dev1/control
这个 Topic, 只具有订阅权限。对于设备 dev1, 很自然的,会朝25KCIUIR1G/dev1/event
这个 Topic 发送数据,并且订阅25KCIUIR1G/dev1/control
这个 Topic 的消息。但是这里就会涉及到,event 的数据最后到哪去,control 的数据从哪里来的问题。在本文的例子中,我们希望 dev1 和 dev2 发生交互,即相互收发消息。由于 MQTT 是基于 Topic 的发布订阅机制,因此,dev1 想要获得 dev2 的数据,直觉上,需要订阅 dev2 发布消息的那个 Topic. 假定 dev2 朝
25KCIUIR1G/dev2/event
Topic 上发送数据,那么 dev1 想要获得 dev2 发布的消息,最直接的办法是订阅同样的 Topic, 即25KCIUIR1G/dev2/event
, 但是这里存在几个问题,首先,event Topic 只具有发布权限,没有订阅权限,其次,在平台侧,规定了,不允许跨设备发布或是订阅 Topic, 也就是说,对于 dev1, 只能看到或只允许访问25KCIUIR1G/dev1
这个 Topic 以及其下属的 Topic, 不能访问25KCIUIR1G/dev2
及其下属 Topic.平台侧添加不允许跨设备访问 Topic 的规则虽然不直观,但却是合理的。如果不添加这条限制,那么一个设备可以不加限制的订阅同一个产品下所有其他设备的 Topic, 获取其上报的消息,这存在潜在的安全漏洞。
-
规则引擎的作用
因为不允许直接跨设备访问 Topic, 所以需要依靠"规则引擎"来手动添加规则,将指定的 Topic 消息转发到另一个 Topic 上,实现不同设备之间的通信。
上图介绍了规则引擎的主要作用"republish", 即将一个 Topic 下的消息 republish 到另一个 Topic 下。从图中我们可以看到,(产品 ID 以您实际的 ID 为准)规则引擎将
25KCIUIR1G/dev2/event
的消息 republish 到了25KCIUIR1G/dev1/control
下。将25KCIUIR1G/dev1/event
的消息 republish 到了25KCIUIR1G/dev2/control
下。这样,对于 dev1 而言,只需要订阅
25KCIUIR1G/dev1/control
就可以接收来自25KCIUIR1G/dev2/event
的消息了。dev2 同理。 -
设置规则引擎
在物联网通信界面选择"规则引擎"--"新建规则", 随意指定一个规则名称,我们这里不妨设置为"1to2".
这里,我们看到规则的详细设置信息,主要包括"筛选数据"和"行为操作". "筛选数据"针对指定 Topic 接收到的消息内容进行进一步的筛选,比如匹配消息中的字段来决定是否执行之后的设置的"行为操作". 而"行为操作"则是指定对通过匹配的消息进行何种操作,主要的操作有"数据转发到另一个 Topic(Republish)", "转发到第三方服务 (Forward)"以及转发到腾讯云各个对应组件中。
上图是设置好的规则,这里,我们将"筛选数据"部分的筛选字段设置为
*
, 筛选的 Topic 为25KCIUIR1G/dev1/event
, 条件设置为空,即不筛选数据,全部匹配。然后,执行的操作是将数据转发到25KCIUIR1G/dev2/control
, 设置完这条规则,就实现了 dev2 通过订阅 control 就能收到 dev1 发送到 event 的数据。关于"筛选数据"的设定
由于我们在新建产品,设置数据格式的时候选择了自定义数据格式,在自定义数据格式的情况下,当前平台将其当做二进制流来处理,也就无法通过匹配字段进行数据筛选。
如果在进行产品的时候,使用数据格式是 json, 那么此处就可以根据 json 中的字段进行 SQL 的匹配和筛选。
同理,我们再设置新的一个规则"2to1", 实现
25KCIUIR1G/dev2/event
到25KCIUIR1G/dev1/control
的转发。规则引擎都设置好后,记得点启用按钮,这样,在平台侧 dev1 到 dev2 的双向数据通路就打通了。
云日志和消息队列 CMQ
在平台侧都设置好后,我们在之后的测试过程或是通信过程中,往往还需要查看平台是否收到了设备发送上来的消息,对消息执行了哪些操作,消息的具体内容 (payload) 是什么。腾讯云提供了物联网通信产品的"云日志"功能和腾讯云组件"消息队列 CMQ".
云日志
可以在产品列表下找到"云日志", 点击搜索即可显示对应的行为日志
参考日志如下,可以看到日志记录了设备的连接,连接断开,发布,订阅等行为,也记录了规则引擎的操作,还有 CMQ 队列的一些行为日志。但是关于设备发布的消息内容,在云日志中无法查看,需要借助消息队列 CMQ.
消息队列 CMQ
可以在产品列表中找到"消息队列"选项,设置队列所想要接收的消息类型后保存配置,即可将平台侧收到的设备消息额外发送到腾讯云消息队列 CMQ 组件中。
设置完消息队列后可以在"云产品"中搜索 CMQ, 即可找到对应的消息队列,点击"开始接收消息" 接收消息队列中的内容,参考如下
其中可以看到有些消息带有"PayloadLen"和"Playload"字段,即为具体的消息内容。
在密钥认证下,消息的内容 (payload) 是经过 base64 编码的,所以在平台侧看到的数据类似乱码实际上是经过编码后的结果,想要查看具体的内容,可以在 linux 下,
echo <payload> | base64 --decode
.
在前文中我们提到,设备终端跟腾讯云之间的通信采用的是 MQTT 协议,而 MQTT 协议需要消息代理端和客户端相互配合,因此,我们在终端设备上,需要实现一个 MQTT Client. 腾讯云上支持的 MQTT 协议和标准协议区别不大,可以在MQTT 协议说明查看具体区别。因此,技术上来说,设备终端只要实现了标准的 MQTT 客户端,均可以跟腾讯云正常通信。
在本节中,我们介绍如何使用腾讯 TencentOS tiny 与腾讯云通过 MQTT 进行通信。
TencentOS Tiny 集成了对 MQTT 和腾讯云的支持,开发者只需要通过简单的设置即可实现与腾讯云的通信,整体步骤主要包含:
- 项目工程中添加 MQTT 组件相关文件
- 适配 AT HAL 层串口及网络接口函数
- 执行脚本生成 MQTT 配置头文件
- 调用 OS 提供的接口完成和腾讯云的对接
项目工程中添加 MQTT 组件相关文件
在工程添加 hal、at、devices、和 mqtt client 源码,可以参考 TencentOS-tiny\board\TencentOS_tiny_EVB_MX_Plus\KEIL\mqttclient 工程
适配 AT HAL 层串口及网络接口函数
上层 MQTT 的操作最终都会转为为对底层通信设备的 socket 操作,OS 集成了 sal_module_wrapper 层,但是由于各个开发板硬件不同,因此需要对 sal_module_wrapper 进行适配。适配过程的核心数据结构为sal_module_t
, 在TencentOS_tiny\net\sal_module_wrapper\sal_module_wrapper.h
文件中定义:
typedef struct sal_module_st{
int (*init)(void);
int (*get_local_mac)(char *mac);
int (*get_local_ip)(char *ip, char *gw, char *mask);
int (*parse_domain)(const char *host_name, char *host_ip, size_t host_ip_len);
int (*connect)(const char *ip, const char *port, sal_proto_t proto);
int (*send)(int sock, const void *buf, size_t len);
int (*recv_timeout)(int sock, void *buf, size_t len, uint32_t timeout);
int (*recv)(int sock, void *buf, size_t len);
int (*sendto)(int sock, char *ip, char *port, const void *buf, size_t len);
int (*recvfrom)(int sock, char *ip, char *port, void *buf, size_t len);
int (*recvfrom_timeout)(int sock, char *ip, char *port, void *buf, size_t len, uint32_t timeout);
int (*close)(int sock);
}sal_module_t;
在使用的时候,并不需要全部实现,只需实现下列几个核心的接口即可:
sal_module_t sal_module_esp8266 = {
.init = esp8266_init,
.connect = esp8266_connect,
.send = esp8266_send,
.recv_timeout = esp8266_recv_timeout,
.recv = esp8266_recv,
.sendto = esp8266_sendto,
.recvfrom = esp8266_recvfrom,
.recvfrom_timeout = esp8266_recvfrom_timeout,
.close = esp8266_close,
.parse_domain = esp8266_parse_domain,
};
这里接口对应的esp8266_xxx
函数需要开发者根据硬件上具有的通信模组进行相应的适配。
该结构填充完后,需要在使用 MQTT 之前,调用一下初始化接口注册串口和网络 API 函数:
esp8266_sal_init(hal_uart_port_t uart_port);
修改 MQTT 配置头文件
从之前介绍腾讯云端配置的章节中可以知道,在 MQTT 客户端,所需要的核心信息只包含:
- Product ID
- Device Name
- Device Password
- Client id
- mqtt username
- mqtt password
开发者在终端节点开发过程中,可以自行修改 MQTT 配置参数
#define IOTHUB_MQTT_IP "111.230.189.156"
#define IOTHUB_MQTT_PORT "1883"
#define IOTHUB_MQTT_CLIENT_ID "KHHIXM0JJ5dev001"
#define IOTHUB_MQTT_USERNAME "KHHIXM0JJ5dev001;12010126;9K3WO;1647628324"
#define IOTHUB_MQTT_PASSWD "53696f22a061ed2524b1de185815ad2fcae7739afd3f395cb6131521da7f0bb7;hmacsha256"
#define IOTHUB_MQTT_PUB_TOPIC "KHHIXM0JJ5/dev001/data"
#define IOTHUB_MQTT_SUB_TOPIC "KHHIXM0JJ5/dev001/control"
如上所示,需要腾讯云设备信息自行修改Product ID
, Device Name
和Password
,MQTT_USR_NAME
,MQTT_PASSWORD
,MQTT_SUBSCRIBE_TOPIC
,MQTT_PUBLISH_TOPIC
,订阅/发布的 topic, 如果采用默认的 control 和 event 如图即可,也可自行设置。开发者将其放置到自己的工程目录下任意位置并在工程中添加头文件引用即可。
调用 TencentOS tiny 提供的接口完成和腾讯云的对接
以下是与腾讯云对接并收发信息的例程:
/* Tencent IoThub Device Info */
#define IOTHUB_MQTT_IP "111.230.189.156"
#define IOTHUB_MQTT_PORT "1883"
#define IOTHUB_MQTT_CLIENT_ID "KHHIXM0JJ5dev001"
#define IOTHUB_MQTT_USERNAME "KHHIXM0JJ5dev001;12010126;9K3WO;1647628324"
#define IOTHUB_MQTT_PASSWD "53696f22a061ed2524b1de185815ad2fcae7739afd3f395cb6131521da7f0bb7;hmacsha256"
#define IOTHUB_MQTT_PUB_TOPIC "KHHIXM0JJ5/dev001/data"
#define IOTHUB_MQTT_SUB_TOPIC "KHHIXM0JJ5/dev001/control"
static void tos_topic_handler(void* client, message_data_t* msg)
{
(void) client;
MQTT_LOG_I("-----------------------------------------------------------------------------------");
MQTT_LOG_I("%s:%d %s()...\ntopic: %s, qos: %d. \nmessage:\n\t%s\n", __FILE__, __LINE__, __FUNCTION__,
msg->topic_name, msg->message->qos, (char*)msg->message->payload);
MQTT_LOG_I("-----------------------------------------------------------------------------------\n");
}
void mqttclient_task(void)
{
int error;
char buf[100] = { 0 };
mqtt_client_t *client = NULL;
mqtt_message_t msg;
memset(&msg, 0, sizeof(msg));
#ifdef USE_ESP8266
esp8266_sal_init(HAL_UART_PORT_0);
esp8266_join_ap("Mculover666", "mculover666");
#endif
#ifdef USE_NB_BC35
int bc35_28_95_sal_init(hal_uart_port_t uart_port);
bc35_28_95_sal_init(HAL_UART_PORT_0);
#endif
#ifdef USE_EC600S
ec600s_sal_init(HAL_UART_PORT_0);
#endif
mqtt_log_init();
client = mqtt_lease();
mqtt_set_port(client, IOTHUB_MQTT_PORT);
mqtt_set_host(client, IOTHUB_MQTT_IP);
mqtt_set_client_id(client, IOTHUB_MQTT_CLIENT_ID);
mqtt_set_user_name(client, IOTHUB_MQTT_USERNAME);
mqtt_set_password(client, IOTHUB_MQTT_PASSWD);
mqtt_set_clean_session(client, 1);
error = mqtt_connect(client);
MQTT_LOG_D("mqtt connect error is %#x", error);
mqtt_subscribe(client, IOTHUB_MQTT_SUB_TOPIC, QOS0, tos_topic_handler);
MQTT_LOG_D("mqtt subscribe error is %#x", error);
memset(&msg, 0, sizeof(msg));
for (;;) {
sprintf(buf, "welcome to mqttclient, this is a publish test, a rand number: %d ...", random_number());
msg.qos = QOS0;
msg.payload = (void *) buf;
error = mqtt_publish(client, IOTHUB_MQTT_PUB_TOPIC, &msg);
tos_task_delay(4000);
}
}
void application_entry(void *arg)
{
mqttclient_task();
while (1) {
printf("This is a mqtt demo!\r\n");
tos_task_delay(1000);
}
}