1 /**
2  * @file aiot_mqtt_api.h
3  * @brief MQTT模块头文件, 提供用MQTT协议连接阿里云物联网平台的能力
4  * @date 2019-12-27
5  *
6  * @copyright Copyright (C) 2015-2018 Alibaba Group Holding Limited
7  *
8  * @details
9  *
10  * MQTT模块用于建立与阿里云物联网平台的连接, API使用流程如下:
11  *
12  * 1. 调用 @ref aiot_mqtt_init 初始化MQTT会话, 获取会话句柄
13  *
14  * 2. 调用 @ref aiot_mqtt_setopt 配置MQTT会话的参数, 常用配置项见 @ref aiot_mqtt_setopt 的说明
15  *
16  * 3. 调用 @ref aiot_mqtt_connect 建立与阿里云物联网平台的连接
17  *
18  * 4. 启动一个线程, 线程中间歇性调用 @ref aiot_mqtt_process 处理心跳和QoS1的消息
19  *
20  * 5. 启动一个线程, 线程中持续调用 @ref aiot_mqtt_recv 接收网络上的MQTT报文
21  *
22  *    + 当接收到一条报文时, 按以下顺序检查当前MQTT会话的参数, 当满足某条的描述时, 会通过对应的回调函数进行通知, 并停止检查
23  *
24  *      + 检查收到的报文topic是否已经通过 @ref aiot_mqtt_setopt 的 @ref AIOT_MQTTOPT_APPEND_TOPIC_MAP 参数配置回调函数
25  *
26  *      + 检查收到的报文topic是否已经通过 @ref aiot_mqtt_sub API配置回调函数
27  *
28  *      + 检查是否通过 @ref aiot_mqtt_setopt 的 @ref AIOT_MQTTOPT_RECV_HANDLER 参数配置默认回调函数
29  *
30  * 6. 经过以上步骤后, MQTT连接已建立并能保持与物联网平台的连接, 接下来按自己的场景用 @ref aiot_mqtt_sub 和 @ref aiot_mqtt_pub 等API实现业务逻辑即可
31  *
32  */
33 
34 #ifndef _AIOT_MQTT_API_H_
35 #define _AIOT_MQTT_API_H_
36 
37 #if defined(__cplusplus)
38 extern "C" {
39 #endif
40 
41 #include <stdint.h>
42 
43 /**
44  * @brief MQTT报文类型
45  *
46  * @details
47  *
48  * 传入@ref aiot_mqtt_recv_handler_t 的MQTT报文类型
49  */
50 typedef enum {
51     /**
52      * @brief MQTT PUBLISH报文
53      */
54     AIOT_MQTTRECV_PUB,
55 
56     /**
57      * @brief MQTT PINGRESP报文
58      */
59     AIOT_MQTTRECV_HEARTBEAT_RESPONSE,
60 
61     /**
62      * @brief MQTT SUBACK报文
63      */
64     AIOT_MQTTRECV_SUB_ACK,
65 
66     /**
67      * @brief MQTT UNSUB报文
68      */
69     AIOT_MQTTRECV_UNSUB_ACK,
70 
71     /**
72      * @brief MQTT PUBACK报文
73      */
74     AIOT_MQTTRECV_PUB_ACK,
75 
76     /**
77      * @brief MQTT CONACK报文
78      */
79     AIOT_MQTTRECV_CON_ACK,
80 
81     /**
82      * @brief MQTT SERVER DISCONNECT报文
83      */
84     AIOT_MQTTRECV_DISCONNECT,
85 } aiot_mqtt_recv_type_t;
86 
87 
88 /**
89  * @brief MQTT协议版本
90  *
91  * @details
92  *
93  * 传入@ref aiot_mqtt_setopt 的MQTT协议版本号
94  */
95 
96 typedef enum {
97     AIOT_MQTT_VERSION_3_1,
98     AIOT_MQTT_VERSION_5_0
99 } aiot_mqtt_protocol_version;
100 
101 /**
102  * @brief value-length 结构体.
103  *
104  * @details
105  *
106  * 用于MQTT 5.0协议中作为表示response topic/corelation data/disconnect reason等属性的通用的数据结构, 同时也是@ref user_property_t 的构成元素
107  */
108 typedef struct {
109     uint16_t len;
110     uint8_t  *value;
111 } len_value_t;
112 
113 /**
114  * @brief MQTT 5.0协议中用户属性
115  *
116 * @details
117  *
118  * 作为@ref conn_property_t ,@ref pub_property_t ,@ref disconn_property_t 的结构体的成员
119  *
120  */
121 typedef struct {
122     len_value_t key;
123     len_value_t value;
124 } user_property_t;
125 
126 /* MQTT服务端支持的最大的用户属性的数目. */
127 #define USER_PROPERTY_MAX    (20)
128 
129 /**
130  * @brief MQTT 5.0协议中, 下行的conack报文conack中的属性
131  */
132 typedef struct {
133     uint8_t max_qos;
134     uint16_t topic_alias_max;
135     uint8_t *assigned_clientid;
136     uint32_t max_packet_size;
137     uint16_t server_receive_max;
138     uint8_t wildcard_subscription_available;
139     uint8_t subscription_identifier_available;
140     uint8_t shared_subscription_available;
141     user_property_t *user_property[USER_PROPERTY_MAX];
142 } connack_property_t;
143 
144 /**
145  * @brief MQTT 5.0协议中, 上行和下行pub报文中的属性.
146  */
147 typedef struct {
148     uint32_t message_expire_interval;
149     uint16_t topic_alias;
150     len_value_t response_topic;
151     len_value_t correlation_data;
152     uint32_t subscription_identifier;
153     user_property_t *user_property[USER_PROPERTY_MAX];
154 } pub_property_t;
155 
156 /**
157  * @brief MQTT 5.0协议中, 上行的connect报文中的属性.
158  *
159  * @details
160  *
161  * 传入@ref aiot_mqtt_connect_with_prop 的MQTT报文类型
162  */
163 typedef struct {
164     uint16_t topic_alias_max;                               /* topic 别名最大数量 */
165     uint16_t client_receive_max;
166     user_property_t *user_property[USER_PROPERTY_MAX];      /* 用户属性 */
167 } conn_property_t;
168 
169 /**
170  * @brief MQTT 5.0协议中, 上行的disconnect报文中的属性.
171  *
172  * @details
173  *
174  * 传入@ref aiot_mqtt_disconnect_with_prop 的MQTT报文类型
175  *
176  */
177 typedef struct {
178     len_value_t *reason_string;
179     user_property_t *user_property[USER_PROPERTY_MAX];      /* 用户属性 */
180 } disconn_property_t;
181 
182 /**
183  * @brief MQTT 5.0协议中, 上行的subscribe报文中的属性.
184  *
185  * @details
186  *
187  * 传入@ref aiot_mqtt_sub_with_prop 的MQTT报文类型
188  *
189  */
190 typedef struct {
191     user_property_t *user_property[USER_PROPERTY_MAX];
192 } sub_property_t;
193 
194 /**
195  * @brief MQTT 5.0协议中, 上行的subscribe报文中的属性.
196  *
197  * @details
198  *
199  * 传入@ref aiot_mqtt_unsub_with_prop 的MQTT报文类型
200  *
201  */
202 typedef struct {
203     user_property_t *user_property[USER_PROPERTY_MAX];
204 } unsub_property_t;
205 
206 
207 typedef struct {
208     /**
209      * @brief MQTT报文类型, 更多信息请参考@ref aiot_mqtt_recv_type_t
210      */
211     aiot_mqtt_recv_type_t type;
212     /**
213      * @brief MQTT报文联合体, 内容根据type进行选择
214      */
215     union {
216         /**
217          * @brief MQTT PUBLISH报文
218          */
219         struct {
220             uint8_t qos;
221             char *topic;
222             uint16_t topic_len;
223             uint8_t *payload;
224             uint32_t payload_len;
225             pub_property_t *pub_prop;  /* pub报文中的属性. MQTT 5.0 特性*/
226         } pub;
227         /**
228          * @brief AIOT_MQTTRECV_SUB_ACK
229          */
230         struct {
231             int32_t res;
232             uint8_t max_qos;
233             uint16_t packet_id;
234         } sub_ack;
235         /**
236          * @brief AIOT_MQTTRECV_UNSUB_ACK
237          */
238         struct {
239             uint16_t packet_id;
240         } unsub_ack;
241         /**
242          * @brief AIOT_MQTTRECV_PUB_ACK
243          */
244         struct {
245             uint16_t packet_id;
246         } pub_ack;
247 
248         /**
249          * @brief AIOT_MQTTRECV_CON_ACK
250          */
251         struct {
252             uint8_t reason_code;
253             connack_property_t prop;   /* 建连回复报文中的属性. MQTT 5.0 特性 */
254         } con_ack;
255 
256         /**
257          * @brief AIOT_MQTTRECV_DISCONNECT. MQTT 5.0特性.
258          */
259         struct {
260             uint8_t reason_code;
261         } server_disconnect;
262 
263     } data;
264 } aiot_mqtt_recv_t;
265 
266 /**
267  * @brief MQTT报文接收回调函数原型
268  *
269  * @param[in] handle MQTT实例句柄
270  * @param[in] packet MQTT报文结构体, 存放收到的MQTT报文
271  * @param[in] userdata 用户上下文
272  *
273  * @return void
274  */
275 typedef void (*aiot_mqtt_recv_handler_t)(void *handle, const aiot_mqtt_recv_t *packet, void *userdata);
276 
277 /**
278  * @brief MQTT内部事件类型
279  */
280 typedef enum {
281     /**
282      * @brief 当MQTT实例第一次连接网络成功时, 触发此事件
283      */
284     AIOT_MQTTEVT_CONNECT,
285     /**
286      * @brief 当MQTT实例断开网络连接后重连成功时, 触发此事件
287      */
288     AIOT_MQTTEVT_RECONNECT,
289     /**
290      * @brief 当MQTT实例断开网络连接时, 触发此事件
291      */
292     AIOT_MQTTEVT_DISCONNECT
293 } aiot_mqtt_event_type_t;
294 
295 typedef enum {
296     /**
297      * @brief MQTT实例网络连接由于网络故障而断开
298      */
299     AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT,
300     /**
301      * @brief MQTT实例网络连接由于心跳丢失超过指定次数(@ref AIOT_MQTTOPT_HEARTBEAT_MAX_LOST )而断开
302      */
303     AIOT_MQTTDISCONNEVT_HEARTBEAT_DISCONNECT
304 } aiot_mqtt_disconnect_event_type_t;
305 
306 /**
307  * @brief MQTT内部事件
308  */
309 typedef struct {
310     /**
311      * @brief MQTT内部事件类型. 更多信息请参考@ref aiot_mqtt_event_type_t
312      *
313      */
314     aiot_mqtt_event_type_t type;
315     /**
316      * @brief MQTT事件数据联合体
317      */
318     union {
319         /**
320          * @brief MQTT连接断开时, 具体的断开原因
321          */
322         aiot_mqtt_disconnect_event_type_t disconnect;
323     } data;
324 } aiot_mqtt_event_t;
325 
326 /**
327  * @brief MQTT事件回调函数
328  *
329  * @details
330  *
331  * 当MQTT内部事件被触发时, 调用此函数. 如连接成功/断开连接/重连成功
332  *
333  */
334 typedef void (*aiot_mqtt_event_handler_t)(void *handle, const aiot_mqtt_event_t *event, void *userdata);
335 
336 /**
337  * @brief 使用 @ref aiot_mqtt_setopt 配置 @ref AIOT_MQTTOPT_APPEND_TOPIC_MAP 时的数据
338  *
339  * @details
340  *
341  * 用于在建立MQTT连接前配置topic与相应的回调函数
342  *
343  */
344 typedef struct {
345     char *topic;
346     aiot_mqtt_recv_handler_t handler;
347     void *userdata;
348 } aiot_mqtt_topic_map_t;
349 
350 /**
351  * @brief @ref aiot_mqtt_setopt 函数的option参数. 对于下文每一个选项中的数据类型, 指的是@ref aiot_mqtt_setopt 中的data参数的数据类型
352  *
353  * @details
354  *
355  * 1. data的数据类型是char *时, 以配置@ref AIOT_MQTTOPT_HOST 为例:
356  *
357  *    char *host = "xxx";
358  *
359  *    aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, host);
360  *
361  * 2. data的数据类型是其他数据类型时, 以配置@ref AIOT_MQTTOPT_PORT 为例:
362  *
363  *    uint16_t port = 443;
364  *
365  *    aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
366  */
367 typedef enum {
368     /**
369      * @brief MQTT 服务器的域名地址或者ip地址
370      *
371      * @details
372      *
373      * 阿里云物联网平台域名地址列表(必须使用自己的product key替换${pk}):
374      *
375      * 使用tcp或tls证书方式建联:
376      *
377      * | 域名地址                                        | 区域    | 端口号
378      * |-------------------------------------------------|---------|---------
379      * | ${pk}.iot-as-mqtt.cn-shanghai.aliyuncs.com      | 上海    | 443
380      * | ${pk}.iot-as-mqtt.ap-southeast-1.aliyuncs.com   | 新加坡  | 443
381      * | ${pk}.iot-as-mqtt.ap-northeast-1.aliyuncs.com   | 日本    | 443
382      * | ${pk}.iot-as-mqtt.us-west-1.aliyuncs.com        | 美西    | 443
383      * | ${pk}.iot-as-mqtt.eu-central-1.aliyuncs.com     | 德国    | 443
384      *
385      * 使用tls psk方式建联:
386      *
387      * | 域名地址                                | 区域    | 端口号
388      * |-----------------------------------------|---------|---------
389      * | ${pk}.itls.cn-shanghai.aliyuncs.com     | 上海    | 1883
390      *
391      * 使用tls x509客户端证书方式建联:
392      *
393      * | 域名地址                            | 区域    | 端口号
394      * |-------------------------------------|---------|---------
395      * | x509.itls.cn-shanghai.aliyuncs.com  | 上海    | 1883
396      *
397      * 数据类型: (char *)
398      */
399     AIOT_MQTTOPT_HOST,
400 
401     /**
402      * @brief MQTT 服务器的端口号
403      *
404      * @details
405      *
406      * 连接阿里云物联网平台时:
407      *
408      * 1. 如果使用的是tcp或者tls证书方式, 端口号设置为443
409      *
410      * 2. 如果使用的是tls psk和tls x509客户端证书方式, 端口号设置为1883
411      *
412      * 数据类型: (uint16_t *)
413      */
414     AIOT_MQTTOPT_PORT,
415 
416     /**
417      * @brief 设备的product key, 可从<a href="http://iot.console.aliyun.com/">阿里云物联网平台控制台</a>获取
418      *
419      * @details
420      *
421      * 数据类型: (char *)
422      */
423     AIOT_MQTTOPT_PRODUCT_KEY,
424 
425     /**
426      * @brief 设备的device name, 可从<a href="http://iot.console.aliyun.com/">阿里云物联网平台控制台</a>获取
427      *
428      * @details
429      *
430      * 数据类型: (char *)
431      */
432     AIOT_MQTTOPT_DEVICE_NAME,
433 
434     /**
435      * @brief 设备的device secret, 可从<a href="http://iot.console.aliyun.com/">阿里云物联网平台控制台</a>获取
436      *
437      * @details
438      *
439      * 数据类型: (char *)
440      */
441     AIOT_MQTTOPT_DEVICE_SECRET,
442 
443     /**
444      * @brief 设备连接阿里云物联网平台时的扩展clientid
445      *
446      * @details
447      *
448      * 若需要上报模组商id和模组id以及os信息, 按以下格式填写:
449      *
450      * "mid=<模组ID>,pid=<模组商ID>,os=<操作系统>"
451      *
452      * 数据类型: (char *)
453      */
454     AIOT_MQTTOPT_EXTEND_CLIENTID,
455 
456     /**
457      * @brief 设备连接阿里云物联网平台时的安全模式, 使用标准的tcp或tls时无需配置
458      *
459      * @details
460      *
461      * 数据类型: (char *)
462      */
463     AIOT_MQTTOPT_SECURITY_MODE,
464 
465     /**
466      * @brief 使用自定义连接凭据连接mqtt服务器时, 凭据的username
467      *
468      * @details
469      *
470      * 数据类型: (char *)
471      */
472     AIOT_MQTTOPT_USERNAME,
473 
474     /**
475      * @brief 使用自定义连接凭据连接mqtt服务器时, 凭据的password
476      *
477      * @brief
478      *
479      * 数据类型: (char *)
480      */
481     AIOT_MQTTOPT_PASSWORD,
482 
483     /**
484      * @brief 使用自定义连接凭据连接mqtt服务器时, 凭据的clientid
485      *
486      * @details
487      *
488      * 数据类型: (char *)
489      */
490     AIOT_MQTTOPT_CLIENTID,
491 
492     /**
493      * @brief MQTT建联时, CONNECT报文中的心跳间隔参数
494      *
495      * @details
496      *
497      * 受阿里云物联网平台限制, 取值范围为30 ~ 1200s
498      *
499      * 1. 如果设置的值小于30, mqtt建联会被云端拒绝,  @ref aiot_mqtt_connect 函数会返回@ref STATE_MQTT_CONNACK_RCODE_SERVER_UNAVAILABLE 错误
500      *
501      * 2. 如果设置的值大于1200, mqtt连接仍然可以建立, 但此参数会被服务器覆盖为1200
502      *
503      * 数据类型: (uint16_t *) 取值范围: 30 ~ 1200s 默认值: 1200s
504      */
505     AIOT_MQTTOPT_KEEPALIVE_SEC,
506 
507     /**
508      * @brief MQTT建联时, CONNECT报文中的clean session参数
509      *
510      * @details
511      *
512      * 1. 设备上线时如果clean session为0, 那么上线前服务器推送QoS1的消息会在此时推送给设备
513      *
514      * 2. 设备上线时如果clean session为1, 那么上线前服务器推送的QoS1的消息会被丢弃
515      *
516      * 数据类型: (uint8_t *) 取值范围: 0, 1 默认值: 1
517      */
518     AIOT_MQTTOPT_CLEAN_SESSION,
519 
520     /**
521      * @brief MQTT建联时, 网络使用的安全凭据
522      *
523      * @details
524      *
525      * 该配置项用于为底层网络配置@ref aiot_sysdep_network_cred_t 安全凭据数据
526      *
527      * 1. 若该选项不配置, 那么MQTT将以tcp方式直接建联
528      *
529      * 2. 若@ref aiot_sysdep_network_cred_t 中option配置为@ref AIOT_SYSDEP_NETWORK_CRED_NONE , MQTT将以tcp方式直接建联
530      *
531      * 3. 若@ref aiot_sysdep_network_cred_t 中option配置为@ref AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA , MQTT将以tls方式建联
532      *
533      * 4. 若@ref aiot_sysdep_network_cred_t 中option配置为@ref AIOT_SYSDEP_NETWORK_CRED_SVRCERT_PSK , MQTT将以tls psk方式建联
534      *
535      * 数据类型: (aiot_sysdep_network_cred_t *)
536      */
537     AIOT_MQTTOPT_NETWORK_CRED,
538 
539     /**
540      * @brief MQTT建联时, 建立网络连接的超时时间
541      *
542      * @details
543      *
544      * 指建立socket连接的超时时间
545      *
546      * 数据类型: (uint32_t *) 默认值: (5 *1000) ms
547      *
548      */
549     AIOT_MQTTOPT_CONNECT_TIMEOUT_MS,
550 
551     /**
552      * @brief 配置MQTT PINGREQ报文发送时间间隔. (心跳发送间隔)
553      *
554      * @details
555      *
556      * 数据类型: (uint32_t *) 默认值: (25 * 1000) ms
557      */
558     AIOT_MQTTOPT_HEARTBEAT_INTERVAL_MS,
559 
560     /**
561      * @brief 配置MQTT PINGRESP报文允许连续丢失的最大次数, 当超过这个次数时, 触发重连机制
562      *
563      * @details
564      *
565      * 数据类型: (uint8_t *) 默认值: (2)
566      */
567     AIOT_MQTTOPT_HEARTBEAT_MAX_LOST,
568 
569     /**
570      * @brief 打开/关闭MQTT重连机制
571      *
572      * @details
573      *
574      * 数据类型: (uint8_t *) 取值范围: 0, 1 默认值: 1
575      */
576     AIOT_MQTTOPT_RECONN_ENABLED,
577 
578     /**
579      * @brief 当由于心跳丢失或者网络断开触发重连机制时, 尝试重连的时间间隔
580      *
581      * @details
582      *
583      * 数据类型: (uint32_t *) 默认值: (2 * 1000) ms
584      */
585     AIOT_MQTTOPT_RECONN_INTERVAL_MS,
586 
587     /**
588      * @brief MQTT发送数据时, 在协议栈花费的最长时间
589      *
590      * @details
591      *
592      * 数据类型: (uint32_t *) 默认值: (5 * 1000) ms
593      */
594     AIOT_MQTTOPT_SEND_TIMEOUT_MS,
595 
596     /**
597      * @brief MQTT接收数据时, 在协议栈花费的最长时间
598      *
599      * @details
600      *
601      * 数据类型: (uint32_t *) 默认值: (5 * 1000) ms
602      */
603     AIOT_MQTTOPT_RECV_TIMEOUT_MS,
604 
605     /**
606      * @brief QoS1消息重发间隔
607      *
608      * @details
609      *
610      * 当发送qos1 MQTT PUBLISH报文后, 如果在@ref AIOT_MQTTOPT_REPUB_TIMEOUT_MS 时间内未收到mqtt PUBACK报文,
611      * @ref aiot_mqtt_process 会重新发送此qo1 MQTT PUBLISH报文, 直到收到PUBACK报文为止
612      *
613      * 数据类型: (uint32_t *) 默认值: (3 * 1000) ms
614      */
615     AIOT_MQTTOPT_REPUB_TIMEOUT_MS,
616 
617     /**
618      * @brief 销毁MQTT实例时, 等待其他api执行完毕的时间
619      *
620      * @details
621      *
622      * 当调用@ref aiot_mqtt_deinit 销毁MQTT实例时, 若继续调用其他aiot_mqtt_xxx API, API会返回@ref STATE_USER_INPUT_EXEC_DISABLED 错误
623      *
624      * 此时, 用户应该停止调用其他aiot_mqtt_xxx API
625      *
626      * 数据类型: (uint32_t *) 默认值: (2 * 1000) ms
627      */
628     AIOT_MQTTOPT_DEINIT_TIMEOUT_MS,
629 
630     /**
631      * @brief 从MQTT服务器收取的数据从此默认回调函数进行通知
632      *
633      * @details
634      *
635      * 1. 若没有配置该回调函数, 当有消息到达但找不到对应的已注册topic时, 消息会被丢弃
636      *
637      * 2. 若已配置该回调函数, 当有消息到达但找不到对应的已注册topic时, 消息从此默认回调函数进行通知
638      *
639      * 数据类型: ( @ref aiot_mqtt_recv_handler_t )
640      */
641     AIOT_MQTTOPT_RECV_HANDLER,
642 
643     /**
644      * @brief MQTT客户端内部发生的事件会从此回调函数进行通知, 如上线/断线/重新上线
645      *
646      * @details
647      *
648      * 数据类型: ( @ref aiot_mqtt_event_handler_t )
649      */
650     AIOT_MQTTOPT_EVENT_HANDLER,
651 
652     /**
653      * @brief 可在MQTT建立连接之前配置MQTT topic与其对应的回调函数
654      *
655      * @details
656      *
657      * 数据类型: ( @ref aiot_mqtt_topic_map_t )
658      */
659     AIOT_MQTTOPT_APPEND_TOPIC_MAP,
660 
661     /**
662      * @brief 取消之前建立的MQTT topic与其回调函数的对应关系
663      *
664      * @details
665      *
666      * 数据类型: ( @ref aiot_mqtt_topic_map_t )
667      */
668     AIOT_MQTTOPT_REMOVE_TOPIC_MAP,
669 
670     /**
671      * @brief 在publish消息的topic上附加请求ID字符串, 用于全链路日志追踪
672      *
673      * @details
674      *
675      * 数据类型: (uint8_t *) 默认值: 0
676      *
677      * 配置为0则不附加请求ID字符串, 配置为1将附加请求ID字符串
678      */
679     AIOT_MQTTOPT_APPEND_REQUESTID,
680 
681     /**
682      * @brief 用户需要SDK暂存的上下文
683      *
684      * @details
685      *
686      * 1. 该上下文会在@ref AIOT_MQTTOPT_RECV_HANDLER 和@ref AIOT_MQTTOPT_EVENT_HANDLER 中传回给用户
687      *
688      * 2. 当使用@ref AIOT_MQTTOPT_APPEND_TOPIC_MAP 或者@ref aiot_mqtt_sub 时未指定userdata, 该上下文也会传给这些回调函数
689      *
690      * 数据类型: (void *)
691      */
692     AIOT_MQTTOPT_USERDATA,
693 
694     /**
695      * @brief 设置MQTT 协议的版本号
696      *
697      * @details
698      *
699      * 1. 默认协议版本号是3.1.1
700      *
701      * 2. 用户可以使用该选项将版本号设置为AIOT_MQTT_VERSION_5_0
702      *
703      * 数据类型: (void *)
704      */
705     AIOT_MQTTOPT_VERSION,
706 
707     /**
708      * @brief MQTT 5.0特性. 设置是否要使能assigned clentid功能
709      *
710      * @details
711      *
712      * 1. 默认不使能
713      *
714      * 2. 用户可以设置值1将该功能使能
715      *
716      * 数据类型: (void *)
717      */
718     AIOT_MQTTOPT_ASSIGNED_CLIENTID,
719 
720     /**
721      * @brief MQTT 5.0特性. 设置是否要使能设备端流控功能
722      *
723      * @details
724      *
725      * 1. 默认不使能
726      *
727      * 2. 用户可以设置值1将该功能使能
728      *
729      * 数据类型: (void *)
730      */
731     AIOT_MQTTOPT_FLOW_CONTROL_ENABLED,
732 
733     AIOT_MQTTOPT_MAX
734 } aiot_mqtt_option_t;
735 
736 /**
737  * @brief 初始化mqtt实例并设置默认参数
738  *
739  * @return void*
740  * @retval 非NULL MQTT实例句柄
741  * @retval NULL 初始化失败, 一般是内存分配失败导致
742  *
743  */
744 void   *aiot_mqtt_init(void);
745 
746 /**
747  * @brief 设置mqtt参数
748  *
749  * @details
750  *
751  * 下面列出常用的配置选项, 至少需要配置以下选项才可使用MQTT的基本功能
752  *
753  * 其余配置选项均设有默认值, 可按业务需要进行调整
754  *
755  * + `AIOT_MQTTOPT_HOST`: 配置连接的阿里云MQTT站点地址
756  *
757  * + `AIOT_MQTTOPT_PORT`: 配置连接的阿里云MQTT站点端口号
758  *
759  * + `AIOT_MQTTOPT_PRODUCT_KEY`: 配置设备的 productKey
760  *
761  * + `AIOT_MQTTOPT_DEVICE_NAME`: 配置设备的 deviceName
762  *
763  * + `AIOT_MQTTOPT_DEVICE_SECRET`: 配置设备的 deviceSecret
764  *
765  * + `AIOT_MQTTOPT_NETWORK_CRED`: 配置建立MQTT连接时的安全凭据
766  *
767  * + `AIOT_MQTTOPT_RECV_HANDLER`: 配置默认的数据接收回调函数
768  *
769  * + `AIOT_MQTTOPT_EVENT_HANDLER`: 配置MQTT事件通知回调函数
770  *
771  * @param[in] handle mqtt句柄
772  * @param[in] option 配置选项, 更多信息请参考@ref aiot_mqtt_option_t
773  * @param[in] data   配置选项数据, 更多信息请参考@ref aiot_mqtt_option_t
774  *
775  * @return int32_t
776  * @retval <STATE_SUCCESS 参数设置失败, 更多信息请参考@ref aiot_state_api.h
777  * @retval >=STATE_SUCCESS 参数设置成功
778  *
779  */
780 int32_t aiot_mqtt_setopt(void *handle, aiot_mqtt_option_t option, void *data);
781 
782 /**
783  * @brief 释放mqtt实例句柄的资源
784  *
785  * @param[in] handle 指向mqtt实例句柄的指针
786  *
787  * @return int32_t
788  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
789  * @retval >=STATE_SUCCESS 执行成功
790  *
791  */
792 int32_t aiot_mqtt_deinit(void **handle);
793 
794 /**
795  * @brief 与MQTT服务器建立连接
796  *
797  * @details
798  *
799  * 使用@ref aiot_mqtt_setopt 配置的mqtt连接参数连接mqtt服务器, 使用的建联参数按如下顺序选择
800  *
801  * 1. 若配置了以下选项, 直接用配置的连接参数连接 @ref AIOT_MQTTOPT_HOST 选项指定的任意MQTT服务器
802  *
803  *    + @ref AIOT_MQTTOPT_USERNAME
804  *    + @ref AIOT_MQTTOPT_PASSWORD
805  *    + @ref AIOT_MQTTOPT_CLIENTID
806  *
807  * 2. 若配置了以下选项, 则强制以阿里云平台的签名算法计算连接参数作为MQTT的用户名/密码, 连接阿里云平台
808  *
809  *    + @ref AIOT_MQTTOPT_PRODUCT_KEY
810  *    + @ref AIOT_MQTTOPT_DEVICE_NAME
811  *    + @ref AIOT_MQTTOPT_DEVICE_SECRET
812  *
813  * @param[in] handle MQTT实例句柄
814  *
815  * @return int32_t
816  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
817  * @retval >=STATE_SUCCESS 执行成功
818  *
819  * @note
820  *
821  * 当配置@ref AIOT_MQTTOPT_USERNAME , @ref AIOT_MQTTOPT_PASSWORD 和@ref AIOT_MQTTOPT_CLIENTID 配置自定义连接凭据时,
822  *
823  * 此函数会忽略@ref AIOT_MQTTOPT_PRODUCT_KEY , @ref AIOT_MQTTOPT_DEVICE_NAME 和@ref AIOT_MQTTOPT_DEVICE_SECRET,
824  *
825  * 直接使用自定义凭据连接指定的MQTT服务器
826  */
827 int32_t aiot_mqtt_connect(void *handle);
828 
829 /**
830  * @brief 与MQTT服务器断开连接
831  *
832  * @details
833  *
834  * 向MQTT服务器发送MQTT DISCONNECT报文, 然后断开网络连接
835  *
836  * 如果需要再次与MQTT服务器建立连接, 调用@ref aiot_mqtt_connect 即可
837  *
838  * @param[in] handle MQTT实例句柄
839  *
840  * @return int32_t
841  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
842  * @retval >=STATE_SUCCESS 执行成功
843  */
844 int32_t aiot_mqtt_disconnect(void *handle);
845 
846 /**
847  * @brief 发送MQTT PINGREQ报文, 用于维持心跳
848  *
849  * @details
850  *
851  * @ref aiot_mqtt_process 包含了定时发送心跳的机制, 如果有特殊需要的话, 可以使用此函数直接发送心跳报文
852  *
853  * @param[in] handle MQTT实例句柄
854  *
855  * @return int32_t
856  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
857  * @retval >=STATE_SUCCESS 执行成功
858  */
859 int32_t aiot_mqtt_heartbeat(void *handle);
860 
861 /**
862  * @brief 此函数用于处理定时心跳发送和qos1消息的重传逻辑
863  *
864  * @details
865  *
866  * 1. 发送心跳至mqtt broker以维护mqtt连接, 心跳发送间隔由@ref AIOT_MQTTOPT_HEARTBEAT_INTERVAL_MS 配置项控制
867  *
868  * 2. 如果一条qos1的mqtt PUBLISH报文在@ref AIOT_MQTTOPT_REPUB_TIMEOUT_MS 时间内没有收到mqtt PUBACK应答报文, 该函数会重发此消息, 直到成功为止
869  *
870  * @param[in] handle MQTT实例句柄
871  *
872  * @return int32_t
873  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
874  * @retval >=STATE_SUCCESS 执行成功
875  *
876  * @note
877  *
878  * 该函数为非阻塞, 需要间歇性被调用, 调用间隔应当小于@ref AIOT_MQTTOPT_HEARTBEAT_INTERVAL_MS 和@ref AIOT_MQTTOPT_REPUB_TIMEOUT_MS 时间内没有收到mqtt的最小值,
879  *
880  * 以确保心跳发送和QoS1消息的重传逻辑正常工作
881  */
882 int32_t aiot_mqtt_process(void *handle);
883 
884 /**
885  * @brief 发送一条PUBLISH报文到MQTT服务器, QoS为0, 用于发布指定的消息
886  *
887  * @param[in] handle MQTT实例句柄
888  * @param[in] topic 指定MQTT PUBLISH报文的topic
889  * @param[in] payload 指定MQTT PUBLISH报文的payload
890  * @param[in] payload_len 指定MQTT PUBLISH报文的payload_len
891  * @param[in] qos 指定mqtt的qos值, 仅支持qos0和qos1
892  *
893  * @return int32_t
894  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
895  * @retval >=STATE_SUCCESS 执行成功
896  */
897 int32_t aiot_mqtt_pub(void *handle, char *topic, uint8_t *payload, uint32_t payload_len, uint8_t qos);
898 
899 /**
900  * @brief 发送一条mqtt SUBSCRIBE报文到MQTT服务器, 用于订阅指定的topic
901  *
902  * @param[in] handle MQTT实例句柄
903  * @param[in] topic 指定MQTT SUBSCRIBE报文的topic
904  * @param[in] handler 与topic对应的MQTT PUBLISH报文回调函数, 当有消息发布到topic时, 该回调函数被调用
905                     若handler为NULL传入, 则SDK调用@ref AIOT_MQTTOPT_RECV_HANDLER 配置的回调函数
906                     若多次调用aiot_mqtt_sub()并对同一topic指定不同的handler, 有消息到达时不同handler都会被调用到
907  * @param[in] qos 指定topic期望mqtt服务器支持的最大qos值, 仅支持qos0和qos1
908  * @param[in] userdata 可让SDK代为保存的用户上下文, 当回调函数被调用时, 此上下文会通过handler传回给用户
909  *                  若未指定该上下文, 那么通过@ref AIOT_MQTTOPT_USERDATA 配置的上下文会通过handler传回给用户
910  *
911  * @return int32_t
912  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
913  * @retval >=STATE_SUCCESS 执行成功
914  */
915 int32_t aiot_mqtt_sub(void *handle, char *topic, aiot_mqtt_recv_handler_t handler, uint8_t qos, void *userdata);
916 
917 /**
918  * @brief 发送一条mqtt UNSUBSCRIBE报文到MQTT服务器, 用于取消订阅指定的topic
919  *
920  * @param[in] handle MQTT实例句柄
921  * @param[in] topic 指定MQTT UNSUBSCRIBE报文的topic
922  *
923  * @return int32_t
924  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
925  * @retval >=STATE_SUCCESS 执行成功
926  */
927 int32_t aiot_mqtt_unsub(void *handle, char *topic);
928 
929 /**
930  * @brief 尝试从网络上接收MQTT报文
931  *
932  * @details
933  *
934  * 除了从网络上接收MQTT报文之外, 本函数也包含了重连机制
935  *
936  * 1. 当MQTT心跳丢失超过@ref AIOT_MQTTOPT_HEARTBEAT_MAX_LOST 配置的次数时, 触发重连机制
937  *
938  *    + 重连间隔由 @ref AIOT_MQTTOPT_RECONN_INTERVAL_MS 指定
939  *
940  * 2. 当SDK检测到网络断开时, 触发重连机制
941  *
942  *    + 重连间隔由 @ref AIOT_MQTTOPT_RECONN_INTERVAL_MS 指定
943  *
944  * @param[in] handle
945  *
946  * @retval STATE_SYS_DEPEND_NWK_READ_LESSDATA 执行成功, 此时网络上暂无可以收取的MQTT报文
947  * @retval >=STATE_SUCCESS 执行成功
948  * @retval 其他返回值 执行失败, 更多信息请参考@ref aiot_state_api.h
949  *
950  * @note
951  *
952  * 当网络连接正常并且@ref aiot_mqtt_deinit 未被调用时, 该函数为阻塞, 需要持续被调用
953  *
954  * 1. 当网络连接断开时, 该函数会立即返回, 此时返回值为@ref STATE_SYS_DEPEND_NWK_CLOSED
955  *
956  * 2. 当@ref aiot_mqtt_deinit 被调用时, 该函数会立即返回, 此时返回值为@ref STATE_USER_INPUT_EXEC_DISABLED
957  */
958 int32_t aiot_mqtt_recv(void *handle);
959 
960 /**
961  * @brief 与MQTT服务器建立连接. 以MQTT 5.0协议的方式接入, 支持5.0的特性.
962  * 在调用这个接口前, 需要确保已经通过AIOT_MQTTOPT_VERSION的方式, 设置过版本号为AIOT_MQTT_VERSION_5_0
963  * @details
964  *
965  * 使用@ref aiot_mqtt_setopt 配置的mqtt连接参数连接mqtt服务器, 使用的建联参数按如下顺序选择
966  *
967  * 1. 若配置了以下选项, 直接用配置的连接参数连接 @ref AIOT_MQTTOPT_HOST 选项指定的任意MQTT服务器
968  *
969  *    + @ref AIOT_MQTTOPT_USERNAME
970  *    + @ref AIOT_MQTTOPT_PASSWORD
971  *    + @ref AIOT_MQTTOPT_CLIENTID
972  *
973  * 2. 若配置了以下选项, 则强制以阿里云平台的签名算法计算连接参数作为MQTT的用户名/密码, 连接阿里云平台
974  *
975  *    + @ref AIOT_MQTTOPT_PRODUCT_KEY
976  *    + @ref AIOT_MQTTOPT_DEVICE_NAME
977  *    + @ref AIOT_MQTTOPT_DEVICE_SECRET
978  *
979  * @param[in] handle MQTT实例句柄
980  * @param[in] conn_prop 指定MQTT CONNECT报文的属性
981  *
982  * @return int32_t
983  * @retval STATE_MQTT_INVALID_PROTOCOL_VERSION mqtt协议的版本号不对, 没有通过AIOT_MQTTOPT_VERSION将版本号将设置为5.0
984  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
985  * @retval >=STATE_SUCCESS 执行成功
986  *
987  * @note
988  *
989  * 当配置@ref AIOT_MQTTOPT_USERNAME , @ref AIOT_MQTTOPT_PASSWORD 和@ref AIOT_MQTTOPT_CLIENTID 配置自定义连接凭据时,
990  *
991  * 此函数会忽略@ref AIOT_MQTTOPT_PRODUCT_KEY , @ref AIOT_MQTTOPT_DEVICE_NAME 和@ref AIOT_MQTTOPT_DEVICE_SECRET,
992  *
993  * 直接使用自定义凭据连接指定的MQTT服务器
994  */
995 int32_t aiot_mqtt_connect_with_prop(void *handle, conn_property_t *conn_prop);
996 
997 /**
998  * @brief 发送一条PUBLISH报文到MQTT服务器, QoS为0, 用于发布指定的消息
999  * 以MQTT 5.0协议的方式接入, 支持5.0的特性.
1000  * 在调用这个接口前, 需要确保已经通过AIOT_MQTTOPT_VERSION的方式, 设置过版本号为AIOT_MQTT_VERSION_5_0
1001  *
1002  * @param[in] handle MQTT实例句柄
1003  * @param[in] topic 指定MQTT PUBLISH报文的topic
1004  * @param[in] payload 指定MQTT PUBLISH报文的payload
1005  * @param[in] payload_len 指定MQTT PUBLISH报文的payload_len
1006  * @param[in] qos 指定mqtt的qos值, 仅支持qos0和qos1
1007  * @param[in] pub_prop 指定MQTT PUB报文的属性
1008  *
1009  * @return int32_t
1010  * @retval STATE_MQTT_INVALID_PROTOCOL_VERSION mqtt协议的版本号不对, 没有通过AIOT_MQTTOPT_VERSION将版本号将设置为5.0
1011  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
1012  * @retval >=STATE_SUCCESS 执行成功
1013  */
1014 int32_t aiot_mqtt_pub_with_prop(void *handle, char *topic, uint8_t *payload, uint32_t payload_len, uint8_t qos,
1015                                 pub_property_t *pub_prop);
1016 
1017 /**
1018  * @brief 与MQTT服务器断开连接
1019  * 以MQTT 5.0协议的方式接入, 支持5.0的特性.
1020  * 在调用这个接口前, 需要确保已经通过AIOT_MQTTOPT_VERSION的方式, 设置过版本号为AIOT_MQTT_VERSION_5_0
1021 
1022  * @details
1023  *
1024  * 向MQTT服务器发送MQTT DISCONNECT报文, 然后断开网络连接
1025  *
1026  * 如果需要再次与MQTT服务器建立连接, 调用@ref aiot_mqtt_connect 即可
1027  *
1028  * @param[in] handle MQTT实例句柄
1029  * @param[in] reason_code 指定MQTT DISCONNECT的原因
1030  * @param[in] disconn_property 指定MQTT DISCONNECT报文的属性
1031  *
1032  * @return int32_t
1033  * @retval STATE_MQTT_INVALID_PROTOCOL_VERSION mqtt协议的版本号不对, 没有通过AIOT_MQTTOPT_VERSION将版本号将设置为5.0
1034  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
1035  * @retval >=STATE_SUCCESS 执行成功
1036  */
1037 int32_t aiot_mqtt_disconnect_with_prop(void *handle, uint8_t reason_code, disconn_property_t *disconn_property);
1038 
1039 /**
1040  * @brief 发送一条mqtt SUBSCRIBE报文到MQTT服务器, 用于订阅指定的topic
1041  * 以MQTT 5.0协议的方式接入, 支持5.0的特性.
1042  * 在调用这个接口前, 需要确保已经通过AIOT_MQTTOPT_VERSION的方式, 设置过版本号为AIOT_MQTT_VERSION_5_0
1043 
1044  *
1045  * @param[in] handle MQTT实例句柄
1046  * @param[in] topic 指定MQTT SUBSCRIBE报文的topic
1047  * @param[in] handler 与topic对应的MQTT PUBLISH报文回调函数, 当有消息发布到topic时, 该回调函数被调用
1048                     若handler为NULL传入, 则SDK调用@ref AIOT_MQTTOPT_RECV_HANDLER 配置的回调函数
1049                     若多次调用aiot_mqtt_sub()并对同一topic指定不同的handler, 有消息到达时不同handler都会被调用到
1050  * @param[in] qos 指定topic期望mqtt服务器支持的最大qos值, 仅支持qos0和qos1
1051  * @param[in] userdata 可让SDK代为保存的用户上下文, 当回调函数被调用时, 此上下文会通过handler传回给用户
1052  *                  若未指定该上下文, 那么通过@ref AIOT_MQTTOPT_USERDATA 配置的上下文会通过handler传回给用户
1053  * @param[in] sub_prop 指定MQTT SUBSCRIBE报文的属性
1054  *
1055  * @return int32_t
1056  * @retval STATE_MQTT_INVALID_PROTOCOL_VERSION mqtt协议的版本号不对, 没有通过AIOT_MQTTOPT_VERSION将版本号将设置为5.0
1057  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
1058  * @retval >=STATE_SUCCESS 执行成功
1059  */
1060 int32_t aiot_mqtt_sub_with_prop(void *handle, char *topic, aiot_mqtt_recv_handler_t handler, uint8_t qos,
1061                                 void *userdata, sub_property_t *sub_prop);
1062 
1063 /**
1064  * @brief 发送一条mqtt UNSUBSCRIBE报文到MQTT服务器, 用于取消订阅指定的topic
1065  * 以MQTT 5.0协议的方式接入, 支持5.0的特性.
1066  * 在调用这个接口前, 需要确保已经通过AIOT_MQTTOPT_VERSION的方式, 设置过版本号为AIOT_MQTT_VERSION_5_0
1067  *
1068  * @param[in] handle MQTT实例句柄
1069  * @param[in] topic 指定MQTT UNSUBSCRIBE报文的topic
1070  * @param[in] unsub_prop 指定MQTT UNSUBSCRIBE报文的属性
1071  *
1072  * @return int32_t
1073  * @retval STATE_MQTT_INVALID_PROTOCOL_VERSION mqtt协议的版本号不对, 没有通过AIOT_MQTTOPT_VERSION将版本号将设置为5.0
1074  * @retval <STATE_SUCCESS 执行失败, 更多信息请参考@ref aiot_state_api.h
1075  * @retval >=STATE_SUCCESS 执行成功
1076  */
1077 int32_t aiot_mqtt_unsub_with_prop(void *handle, char *topic, unsub_property_t *unsub_prop);
1078 
1079 #if defined(__cplusplus)
1080 }
1081 #endif
1082 
1083 #endif
1084 
1085