记录一下自己的开发历程。
照常使用CUBEMX打开ETH,FREERTOS(我给了rtos 30k的内存),LWIP(开启DHCP,DNS),阿里云的MQTT服务器是动态的,需要根据url得到ip。
在阿里云官网下载MQTT的SDK
将SDK导入工程。
其中freertos_port.c是底层依赖
按照LinkSDK/demos/mqtt_basic_demo.c的官方例程进行修改。
/*
* 这个例程适用于`Linux`这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程
*
* + 一个线程用于保活长连接
* + 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调
*
* 需要用户关注或修改的部分, 已经用 TODO 在注释中标明
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "aiot_state_api.h"
#include "aiot_sysdep_api.h"
#include "aiot_mqtt_api.h"
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/*
TODO: 替换为自己实例的接入点
对于企业实例, 或者2021年07月30日之后(含当日)开通的物联网平台服务下公共实例
mqtt_host的格式为"${YourInstanceId}.mqtt.iothub.aliyuncs.com"
其中${YourInstanceId}: 请替换为您企业/公共实例的Id
对于2021年07月30日之前(不含当日)开通的物联网平台服务下公共实例
需要将mqtt_host修改为: mqtt_host = "${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com"
其中, ${YourProductKey}:请替换为设备所属产品的ProductKey。可登录物联网平台控制台,在对应实例的设备详情页获取。
${YourRegionId}:请替换为您的物联网平台设备所在地域代码, 比如cn-shanghai等
该情况下完整mqtt_host举例: a1TTmBPIChA.iot-as-mqtt.cn-shanghai.aliyuncs.com
详情请见: https://help.aliyun.com/document_detail/147356.html
*/
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
/* 位于portfiles/aiot_port文件夹下的系统适配函数集合 */
extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
static pthread_t g_mqtt_process_thread;
static pthread_t g_mqtt_recv_thread;
static uint8_t g_mqtt_process_thread_running = 0;
static uint8_t g_mqtt_recv_thread_running = 0;
/* TODO: 如果要关闭日志, 就把这个函数实现为空, 如果要减少日志, 可根据code选择不打印
*
* 例如: [1577589489.033][LK-0317] mqtt_basic_demo&gb80sFmX7yX
*
* 上面这条日志的code就是0317(十六进制), code值的定义见core/aiot_state_api.h
*
*/
/* 日志回调函数, SDK的日志会从这里输出 */
int32_t demo_state_logcb(int32_t code, char *message)
{
printf("%s", message);
return 0;
}
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type) {
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT: {
printf("AIOT_MQTTEVT_CONNECT\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT: {
printf("AIOT_MQTTEVT_RECONNECT\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT: {
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
default: {
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
printf("heartbeat response\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK: {
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB: {
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK: {
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default: {
}
}
}
/* 执行aiot_mqtt_process的线程, 包含心跳发送和QoS1消息重发 */
void *demo_mqtt_process_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_process_thread_running) {
res = aiot_mqtt_process(args);
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
return NULL;
}
/* 执行aiot_mqtt_recv的线程, 包含网络自动重连和从服务器收取MQTT消息 */
void *demo_mqtt_recv_thread(void *args)
{
int32_t res = STATE_SUCCESS;
while (g_mqtt_recv_thread_running) {
res = aiot_mqtt_recv(args);
if (res < STATE_SUCCESS) {
if (res == STATE_USER_INPUT_EXEC_DISABLED) {
break;
}
sleep(1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int32_t res = STATE_SUCCESS;
void *mqtt_handle = NULL;
uint16_t port = 443; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL) {
printf("aiot_mqtt_init failed\n");
return -1;
}
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 与服务器建立MQTT连接 */
res = aiot_mqtt_connect(mqtt_handle);
if (res < STATE_SUCCESS) {
/* 尝试建立连接失败, 销毁MQTT实例, 回收资源 */
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
return -1;
}
/* MQTT 订阅topic功能示例, 请根据自己的业务需求进行使用 */
/* {
char *sub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/+/post_reply";
res = aiot_mqtt_sub(mqtt_handle, sub_topic, NULL, 1, NULL);
if (res < 0) {
printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
return -1;
}
} */
/* MQTT 发布消息功能示例, 请根据自己的业务需求进行使用 */
/* {
char *pub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/property/post";
char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
res = aiot_mqtt_pub(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)strlen(pub_payload), 0);
if (res < 0) {
printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
return -1;
}
} */
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
g_mqtt_process_thread_running = 1;
res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
return -1;
}
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
g_mqtt_recv_thread_running = 1;
res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
if (res < 0) {
printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
return -1;
}
/* 主循环进入休眠 */
while (1) {
sleep(1);
}
/* 断开MQTT连接, 一般不会运行到这里 */
g_mqtt_process_thread_running = 0;
g_mqtt_recv_thread_running = 0;
sleep(1);
pthread_join(g_mqtt_process_thread, NULL);
pthread_join(g_mqtt_recv_thread, NULL);
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
return -1;
}
/* 销毁MQTT实例, 一般不会运行到这里 */
res = aiot_mqtt_deinit(&mqtt_handle);
if (res < STATE_SUCCESS) {
printf("aiot_mqtt_deinit failed: -0x%04X\n", -res);
return -1;
}
return 0;
}
可以新建一个mqtt_app.c和mqtt_app.h
需要去除例程中的两个头文件。
#include <unistd.h>
#include <pthread.h>
引用自己的头文件
#include "cmsis_os.h"
将设备三元组替换成自己的,host也一样
/* TODO: 替换为自己设备的三元组 */
char *product_key = "${YourProductKey}";
char *device_name = "${YourDeviceName}";
char *device_secret = "${YourDeviceSecret}";
/* 改成自己的host */
char *mqtt_host = "${YourInstanceId}.mqtt.iothub.aliyuncs.com";
这个可以在设备连接信息中找到,需要从$开始替换
修改端口为1883(我的连接信息是1883)
uint16_t port = 1883; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
把mqtt_handle修改为全局变量。
修改创建线程的两端代码(我这里用的CUBEMX CMSIS v1的API,也可以使用LWIP中sys_thread_new())
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
osThreadDef(MQTT_Proc_Task, demo_mqtt_process_thread, osPriorityNormal, 0, 512);
demo_mqtt_process_threadHandler = osThreadCreate(osThread(MQTT_Proc_Task), mqtt_handle);
if (demo_mqtt_process_threadHandler == NULL)
return -1;
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
osThreadDef(MQTT_Recv_Task, demo_mqtt_recv_thread, osPriorityNormal, 0, 512);
demo_mqtt_recv_threadHandler = osThreadCreate(osThread(MQTT_Recv_Task), mqtt_handle);
if (demo_mqtt_recv_threadHandler == NULL)
return -1;
接下来修改freertos_port.c文件。
修改_core_sysdep_network_connect
/* 建立一个连接 */
static int32_t _core_sysdep_network_connect(char *host, uint16_t port, int family, int socktype, int protocol,
uint32_t timeout_ms, int *fd_out)
{
int32_t res = STATE_SUCCESS;
ip4_addr_t dns_ip;
dns_ip.addr = 0;
res = netconn_gethostbyname(host, &dns_ip);
if (res == ERR_OK)
{
int fd = -1;
struct sockaddr_in addr;
//初始化服务器信息
memset(&addr,0,sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = family;
//填写服务器端口号
addr.sin_port = htons(port);
//填写服务器IP地址
addr.sin_addr.s_addr = dns_ip.addr;
fd = socket(family, socktype, protocol);
if (fd < 0)
{
printf("create socket error\n");
res = STATE_PORT_NETWORK_SOCKET_CREATE_FAILED;
}
/* block connect */
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0)
{
*fd_out = fd;
res = STATE_SUCCESS;
}
else
{
close(fd);
printf("connect error, errno: %d\n", errno);
res = STATE_PORT_NETWORK_CONNECT_FAILED;
}
}
else
{
res = STATE_PORT_NETWORK_DNS_FAILED;
printf("fail on DNS\n");
}
if (res < 0)
{
printf("fail to establish tcp\n");
}
else
{
printf("success to establish tcp, fd = %d\n", *fd_out);
struct sockaddr_in loc_addr;
socklen_t len = sizeof(sizeof(loc_addr));
memset(&loc_addr, 0, len);
// 获取socket绑定的本地address信息
if (-1 == getsockname(*fd_out, (struct sockaddr *)&loc_addr, &len))
{
return (-1);
}
// 打印信息
if (loc_addr.sin_family == AF_INET)
{
printf("local port: %u\n",ntohs(loc_addr.sin_port));
}
res = STATE_SUCCESS;
}
return res;
}
修改_core_sysdep_network_send,在495行
#if defined(MSG_NOSIGNAL)
send_res = send(network_handle->fd, buffer + send_bytes, len - send_bytes, MSG_NOSIGNAL);
#elif defined(SO_NOSIGPIPE)
send_res = send(network_handle->fd, buffer + send_bytes, len - send_bytes, SO_NOSIGPIPE);
#endif
改为
send_res = send(network_handle->fd, buffer + send_bytes, len - send_bytes, 0);
至此程序大体没问题,如果还有报错需要更改core等文件的include,把没有的头文件包含去掉
接下来进行测试。
没有路由器可以使用电脑的网口,将WIFI共享到以太网
共享到自己的网卡,此时以太网接口便具备了DHCP的功能,需要使用DHCP获取IP和DNS(生成lwip工程的时候记得将DHCP和DNS启用)
接下来需要在LWIP中进行代码添加,默认是启用LWIP_NETIF_LINK_CALLBACK这个参数的,用于以太网连接状态改变检测。
在ethernetif.c中最下边修改如下
void waittingDHCP()
{
int count = 0;
while(0 == gnetif.ip_addr.addr)
{
osDelay(5);
count++;
if(count > 200)
{
IP4_ADDR(&ipaddr, IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
netif_set_down(&gnetif); //先down
netif_set_addr(&gnetif, &ipaddr, &netmask, &gw);
netif_set_up(&gnetif); //设置完成后再up
break;
}
}
}
__weak void ethernetif_notify_conn_changed(struct netif *netif)
{
/* NOTE : This is function could be implemented in user file
when the callback is needed,
*/
/* Start DHCP negotiation for a network interface (IPv4) */
if(netif_is_link_up(netif))
{
dhcp_start(netif);
waittingDHCP();
}
else
{
netif->ip_addr.addr = 0;
netif->netmask.addr = 0;
netif->gw.addr = 0;
}
}
主要是为了网线热拔插重新获取ip地址,如果超时没获取到就使用静态ip。
一般需要几秒的时间获取DHCP的地址,所以在测试的时候要在连接MQTT直接进行适量的延时,保证在获取到IP的前提下进行连接。同样可以开启一个线程用于MQTT的连接与重连。
void MQTT_ConnectTask(void const *argument)
{
/* MQTT_Process */
int16_t res = 0;
/* 延时5s等待DHCP获取到ip,也可以在此处判断ip是否有效,然后再连接 */
osDelay(5000);
for(;;)
{
res = aiot_mqtt_connect(mqtt_handle);
if(0 == res)
{
printf("MQTT connect successfully!\n");
/* 把自己挂起 */
osThreadSuspend(NULL);
}
printf("MQTT reconnecting...\n");
osDelay(1000);
}
}
连接成功将自己挂起。
当网络出现问题断开的时候需要重连,在demo_mqtt_event_handler增加
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT:
{
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
/*重连*/
osThreadResume(MQTT_ConnectTaskHandle);
}
如果出现卡死在断言的情况,应该是任务堆栈空间太小了,我遇到这个问题以为是程序有问题但是把任务空间改大就好了。
最终的MQTT初始化函数为
int16_t MQTT_Init()
{
uint16_t port = 1883; /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
aiot_sysdep_network_cred_t cred; /* 安全凭据结构体, 如果要用TLS, 这个结构体中配置CA证书等参数 */
/* 配置SDK的底层依赖 */
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
/* 配置SDK的日志输出 */
aiot_state_set_logcb(demo_state_logcb);
/* 创建SDK的安全凭据, 用于建立TLS连接 */
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE; /* 使用RSA证书校验MQTT服务端 */
cred.max_tls_fragment = 16384; /* 最大的分片长度为16K, 其它可选值还有4K, 2K, 1K, 0.5K */
cred.sni_enabled = 1; /* TLS建连时, 支持Server Name Indicator */
/* 创建1个MQTT客户端实例并内部初始化默认参数 */
mqtt_handle = aiot_mqtt_init();
if (mqtt_handle == NULL)
return -1;
/* TODO: 如果以下代码不被注释, 则例程会用TCP而不是TLS连接云平台 */
/*
{
memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t));
cred.option = AIOT_SYSDEP_NETWORK_CRED_NONE;
}
*/
/* 配置MQTT服务器地址 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)mqtt_host);
/* 配置MQTT服务器端口 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
/* 配置设备productKey */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
/* 配置设备deviceName */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
/* 配置设备deviceSecret */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
/* 配置网络连接的安全凭据, 上面已经创建好了 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
/* 配置MQTT默认消息接收回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
/* 配置MQTT事件回调函数 */
aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
/* 创建一个单独的线程, 专用于执行aiot_mqtt_process, 它会自动发送心跳保活, 以及重发QoS1的未应答报文 */
osThreadDef(MQTT_Proc_Task, demo_mqtt_process_thread, osPriorityNormal, 0, 512);
demo_mqtt_process_threadHandler = osThreadCreate(osThread(MQTT_Proc_Task), mqtt_handle);
if (demo_mqtt_process_threadHandler == NULL)
return -1;
/* 创建一个单独的线程用于执行aiot_mqtt_recv, 它会循环收取服务器下发的MQTT消息, 并在断线时自动重连 */
osThreadDef(MQTT_Recv_Task, demo_mqtt_recv_thread, osPriorityNormal, 0, 512);
demo_mqtt_recv_threadHandler = osThreadCreate(osThread(MQTT_Recv_Task), mqtt_handle);
if (demo_mqtt_recv_threadHandler == NULL)
return -1;
/* 创建连接线程 */
osThreadDef(MQTT_Connect, MQTT_ConnectTask, osPriorityNormal, 0, 512);
MQTT_ConnectTaskHandle = osThreadCreate(osThread(MQTT_Connect), NULL);
if(MQTT_ConnectTaskHandle == NULL)
return -1;
return 0;
}
基本流程就是配置SDK的port依赖函数,这些例程都给做好了,需要改自己的三元组和rul,然后配置连接参数,创建两个线程用于mqtt的保活和接受,最后创建一个mqtt的连接线程,用于断线重连。
等连接上以后就可以使用aiot_mqtt_sub进行topic的订阅,该函数的第三个参数为用户自定义的topic消息callback,如果从订阅的topic收到消息则会调用这个函数。
比如
aiot_mqtt_sub(mqtt_handle, (char*)sub_topic, topic_RecvHandler, 0, NULL);
那么回调函数可以写成
/* topic消息回调函数 */
void topic_RecvHandler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
/* 做你自己的处理,数据在 packet->data.pub.payload ,需要使用cJSON解析 */
}
其他函数的功能。
/* MQTT事件回调函数, 当网络连接/重连/断开时被触发, 事件定义见core/aiot_mqtt_api.h */
void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
{
switch (event->type)
{
/* SDK因为用户调用了aiot_mqtt_connect()接口, 与mqtt服务器建立连接已成功 */
case AIOT_MQTTEVT_CONNECT:
{
printf("AIOT_MQTTEVT_CONNECT\n");
/* TODO: 处理SDK建连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络状况被动断连后, 自动发起重连已成功 */
case AIOT_MQTTEVT_RECONNECT:
{
printf("AIOT_MQTTEVT_RECONNECT\n");
/* TODO: 处理SDK重连成功, 不可以在这里调用耗时较长的阻塞函数 */
}
break;
/* SDK因为网络的状况而被动断开了连接, network是底层读写失败, heartbeat是没有按预期得到服务端心跳应答 */
case AIOT_MQTTEVT_DISCONNECT:
{
char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
("heartbeat disconnect");
printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
/* TODO: 处理SDK被动断连, 不可以在这里调用耗时较长的阻塞函数 */
/*重连*/
osThreadResume(MQTT_ConnectTaskHandle);
}
break;
default:
{
break;
}
}
}
/* MQTT默认消息处理回调, 当SDK从服务器收到MQTT消息时, 且无对应用户回调处理时被调用 */
void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
{
switch (packet->type) {
case AIOT_MQTTRECV_HEARTBEAT_RESPONSE:
{
printf("heartbeat response\n");
/* TODO: 处理服务器对心跳的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_SUB_ACK:
{
printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
-packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
/* TODO: 处理服务器对订阅请求的回应, 一般不处理 */
}
break;
case AIOT_MQTTRECV_PUB:
{
printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
/* TODO: 处理服务器下发的业务报文 */
}
break;
case AIOT_MQTTRECV_PUB_ACK:
{
printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
/* TODO: 处理服务器对QoS1上报消息的回应, 一般不处理 */
}
break;
default:
{
break;
}
}
}
这两个主要是mqtt的事件callback和接受callback,如果刚才订阅topic的时候第三个参数给了NULL,那么接受到消息的时候就需要在AIOT_MQTTRECV_PUB处理。
另外,如果生成SDK的时候选择了NTP事件同步功能,那么可以包含头文件
#include "aiot_ntp_api.h"
初始化mqtt的时候初始换NTP
/* 初始化NTP服务 */
ntp_handle = aiot_ntp_init();
if (ntp_handle == NULL)
return -1;
/* 配置NTP的MQTT句柄 */
aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_MQTT_HANDLE, mqtt_handle);
/* 配置时区 */
int8_t time_zone = 8;
aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_TIME_ZONE, (int8_t *)&time_zone);
/* 配置NTP消息回调函数 */
aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_RECV_HANDLER, (void *)demo_ntp_recv_handler);
/* 配置NTP事件回调函数 */
aiot_ntp_setopt(ntp_handle, AIOT_NTPOPT_EVENT_HANDLER, (void *)demo_ntp_event_handler);
然后再mqtt连接上以后使用aiot_ntp_send_request给服务器发送请求,当收到回应后会调用NTP的callback
void demo_ntp_recv_handler(void *handle, const aiot_ntp_recv_t *packet, void *userdata)
{
switch (packet->type)
{
/* TODO: 含当前时区下, 年月日时分秒的数值, 可在这里把它们解析储存起来 */
case AIOT_NTPRECV_LOCAL_TIME:
{
printf("local time: %llu, %02d/%02d/%02d-%02d:%02d:%02d:%d\n",
(long long unsigned int)packet->data.local_time.timestamp,
packet->data.local_time.year,
packet->data.local_time.mon,
packet->data.local_time.day,
packet->data.local_time.hour,
packet->data.local_time.min,
packet->data.local_time.sec,
packet->data.local_time.msec);
}
break;
default:
{
break;
}
}
}
void demo_ntp_event_handler(void *handle, const aiot_ntp_event_t *event, void *userdata)
{
switch (event->type)
{
case AIOT_NTPEVT_INVALID_RESPONSE:
{
printf("AIOT_NTPEVT_INVALID_RESPONSE\n");
}
break;
case AIOT_NTPEVT_INVALID_TIME_FORMAT:
{
printf("AIOT_NTPEVT_INVALID_TIME_FORMAT\n");
}
break;
default:
{
break;
}
}
}
具体过程可以看阿里云官方给出的使用说明 使用示例 (aliyun.com)https://help.aliyun.com/document_detail/256392.html
NTP的使用在高级能力里。