STM32F107 LWIP 接入阿里云MQTT

记录一下自己的开发历程。

照常使用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的使用在高级能力里。 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值