Esp8266 进阶之路26【高级篇】RTOS移植分析 MQTT 实现过程,实现移植 MQTT协议在 esp8266 rtos实时系统,可断线重连。(附带Demo)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://xuhong.blog.csdn.net/article/details/81181707

  • 本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途。如有不对之处,请留言,本人及时更改。

1、 Esp8266之 搭建开发环境,开始一个“hellow world”串口打印。
2、 Esp8266之 利用GPIO开始使用按钮点亮你的“第一盏灯”。
3、 Esp8266之 利用 "软件定时器 " 定时0.5秒闪烁点亮一盏LED。
4 、Esp8266之 了解PWM,更为深入地用PWM控制一盏LED的亮度变化。
5 、Esp8266之 原生乐鑫SDK高级使用之封装Post与Get请求云端,拿到“天气预报信息”。
6 、Esp8266之 了解 SmartConfig与Airkiss一键配网,给8266配网上云端。无需把wifi名字密码写在固件里。
7 、Esp8266之 了解 softAP热点配网模式原理,仿“机智云”定义自己的热点配网模式协议。
8、 Esp8266之 你要找的8266作为UDP、TCP客户端或服务端的角色通讯,都在这了。
9、 Esp8266进阶之路第1篇: [小实战上篇]Windows系统搭建8266的本地Mqtt服务器,局域网点亮一盏LED灯。
10、 Esp8266进阶之路第2篇: [小实战下篇]Windows系统搭建8266的本地Mqtt服务器,局域网点亮一盏LED灯。
11、 Esp8266进阶之路第3篇: 8266接入阿里智能,点亮一盏LED灯,期待天猫精灵语音控制的不约而至!
12、 Esp8266进阶之路第4篇: 图文并茂学习阿里云主机搭建8266MQTT服务器,实现移动网络远程控制一盏LED。
13、 Esp8266进阶之路第5篇: 动手做个8266毕设小案例,smartConfig + MQTT协议轻松实现远程控制一盏LED。
14、 Esp8266进阶之路第6篇: esp8266的 FreeRtos系统学习的正确姿势 ------ 环境搭建、烧录。
15、 Esp8266进阶之路第7篇: esp8266的 物联网又一股清流,8266接入阿里云平台非阿里智能的SDS服务,点亮一盏LED灯。
16、 Esp8266进阶之路第8篇: esp8266的 基于Nonos移植红外线H1838,实现红外遥控器配网,远程控制一盏灯。
17、 Esp8266进阶之路第9篇: esp8266自研的快速上电开关五次 (开-关为一次) ,无需按键触发则8266进去一键配网模式。
18、 Esp8266进阶之路第10篇: esp8266 基于NONOS 实现 OTA 远程升级,实现无线“ 热修复 ”升级固件程序。
19、 Esp8266进阶之路第11篇esp8266驱动 ds18b20、dht11 温湿度传感器,采集温湿度传感器到服务器。
20、 Esp8266进阶之路第12篇深入学习esp8266的esp now模式,仿机智云做一个小网关,实现无需网络下轻松彼此连接通讯交互数据。
21、 Esp8266进阶之路第13篇浅谈 esp8266 如何在本地局域网网络情况下实现最大效率地和前端实现数据交互。
22、 Esp8266进阶之路第14篇esp8266的工程如何添加第三方静态库文件以及如何自定义文件夹,聊聊那些makeFile的事。。
23、 Esp8266进阶之路第15篇再来一波 esp8266 基于 freeRtos系统连接自己私有的服务器实现OTA远程升级,接触下 lwip的基本知识。。
24、 Esp8266进阶之路第16篇渗透学习回顾下esp8266的外置spi芯片25q系列,熟悉8266代码块在其的分布,得心应手放置图片或其他资料。
25、 Esp8266进阶之路第17篇深聊下esp8266的串口 Uart 通讯中断编程,为您准备好了 NONOS 版本 和 RTOS 系统的串口驱动文件。
26、 Esp8266进阶之路第18篇RTOS分析 MQTT 实现过程,实现移植 MQTT协议在 esp8266 rtos实时系统,可断线重连。
27、 Esp8266进阶之路第19篇跟紧脚步,用VisualStudio Code开发 esp8266 rtos SDK v3.0版本,全新的 idf 框架,节省内存模块化开发。
28、 Esp8266进阶之路第20篇教你轻松自如使用cJson在乐鑫 esp8266 如何解析一段json数据以及如何生成一段json数据。
29、 Esp8266进阶之路第21篇百万条消息免费之乐鑫esp8266使用TCP直连模式MQTT协议接入阿里云物联网平台,支持私家服务器对接支持阿里云规则引擎。
30、 Esp8266进阶之路第22篇乐鑫esp8266 SDK编程使用 IIC总线驱动 0.96寸的OLED显示屏,显示天气预报信息。
31、 Esp8266进阶之路第23篇当esp8266遇到 Html,该怎么内置网页控制设备,理清内置网页的实现过程,实现无需路由器手机也可以控制esp8266。
32、 Esp8266进阶之路第24篇细聊HmacMD5的加密方法带来的安全性,并实践在esp8266上,最大保障传输的过程的信息的安全性。
33、 Esp8266进阶之路第25篇如何优雅地像乐鑫原厂封装esp8266底层寄存器的逻辑思维,做成自己的静态库库文件,让第三方人使用?
34、 Esp8266进阶之路第26篇乐鑫esp8266 NONOS SDK 3.0编程使用 SPI 驱动基于Max7219芯片的八位数码管,显示日期信息。
35、 Esp8266进阶之路第27篇乐鑫esp8266芯片借助机智云平台做一个商业化的七彩RGB灯泡可调整体方案项目,炫彩夺目高大尚。
36、 Esp8266学习rtos3.0笔记第1篇认识esp8266 Rtos 3.0 sdk 工程结构,esp8266如何向esp-idf工程靠近的,如何自定义头文件编译?
37、 Esp8266学习rtos3.0笔记第2篇你要找的基本外设功能都在这里了,包括Gpio、Pwm 和 Uart 接口使用。
38、 Esp8266学习rtos3.0笔记第3篇 一篇文章带你搞掂存储技术 NVS 的认识和使用,如何利用NVS保存整型、字符串、数组以及结构体。
39、 Esp8266学习rtos3.0笔记第4篇 带你捋一捋市面上的微信公众号配网智能设备 esp8266 并绑定设备的过程,移植并成功实现在 esp8266 rtos3.1 sdk。
40、 Esp8266学习rtos3.0笔记第5篇 基于乐鑫idf框架,研究出超稳定、掉线重连、解决内存泄露问题的Mqtt框架!支持esp8266和esp32!

一、前言;


  • 由于乐鑫的MQTT代码工程存在些不足,本博文已根据部分修正部分代码。具体的刨坑链接:

https://github.com/espressif/ESP8266_RTOS_SDK/issues/285 ,修订时间:2018/8/27

  • esp8266的实时系统rtos是后面才出来支持的,其最后的调用也是调用乐鑫提供的API接口,所以,如果你已经玩转了NONOS下的编程,那么移植rtos代码是非常迅捷的,因为你已经对其的API接口非常熟悉,当然了,熟透一款芯片开发,当然不是一天半天的事情,需要长时间的积累。

  • 那么本博文是基于rtosMQTT协议的实现,优化了官方的代码示范,而且带你走一走MQTT协议的世界。


二、MQTT的常识;


众所周知,MQTT是一种轻捷快速的协议,基于TCP之上,所以为长连接的一种协议,非常适合那些短小消息发送的数据交互的用途,比如APP的推送新闻用途,最常见的用在我们现在物联网领域;毕竟是小且快;


  • 在进行彼此通讯时候,必须确保底层提供了有序、可靠、双向连接的网络连接。比如可以建立TCP/TLS连接。所以基本的通讯如下:

这里写图片描述


  • 那么设备之间怎么样通讯呢?这就涉及到一些术语;要想指定某一个设备收到此条消息,那么就必须根据topic主题来识别,这个是服务器的事情了;下面列下一些常见的专用名词:

①:ClientID

客户端唯一标识,服务端用于关联一个Session。
只能包含这些 大写字母,小写字母 和 数字(0-9a-zA-Z),23个字符以内,同一时间内 Server 和同一个 ClientID 只能保持一个 TCP 连接,再次连接会踢掉前一个连接的客户端。

②:Keep Alive

顾名思义,目的是保持长连接的可靠性,以及双方对彼此是否在线的确认。
客户端在Connect的时候设置 Keep Alive 时长。如果服务端在 1.5 * KeepAlive 时间内没有收到客户端的报文,它必须断开客户端的网络连接。

③:Will

遗嘱,遗愿;遗嘱消息(Will Message)存储在服务端,当网络连接关闭时,服务端必须发布这个遗嘱消息,所以被形象地称之为遗嘱,可用于通知异常断线。

④:retain

0: 服务端不能存储这个消息,也不能移除或替换任何 现存的保留消息 。
1: 服务端必须存储这个应用消息和它的QoS等级,以便它可以被分发给未来的订阅者,所以如果后面未来有客户端订阅了这个主题,那么这个客户端一上线就会收到此消息。

⑤:qos

0: 【最多一次】 没有回复,不需要存储。有可能丢失(网络异常断开,业务层繁忙或者错误) 。
1: 【至少一次】确保消息到达,但消息重复可能会发生。
2: 【只有一次】确保消息到达一次;

⑥:poyload

用来传输用户的数据,最大允许 256MB ,发布的消息的 Payload允许为空。在很多场合下,代表将持久消息(或者遗嘱消息)清空;格式为UTF-8编码;


三、官方核心代码;

  • 乐鑫已经针对rtos移植了eclipse的标准的paho mqtt,在官方的GitHub已经看到了源码:点我查看,这个库非常出名,很多嵌入式的芯片都是移植这个库。

#define MQTT_CLIENT_THREAD_NAME         "mqtt_client_thread"
#define MQTT_CLIENT_THREAD_STACK_WORDS  2048
#define MQTT_CLIENT_THREAD_PRIO         8

LOCAL xTaskHandle mqttc_client_handle;

static void messageArrived(MessageData* data)
{
    printf("Message arrived: %s\n", data->message->payload);
}

static void mqtt_client_thread(void* pvParameters)
{
    printf("mqtt client thread starts\n");
    MQTTClient client;
    Network network;
    //指定缓存区的大小
    unsigned char sendbuf[80], readbuf[80] = {0};
    int rc = 0, count = 1;
    MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
    pvParameters = 0;
    
    //初始化TCP连接
    NetworkInit(&network);
    //初始化客户端,注意后面都是发送和接收数据的缓存区,一定要加大这个缓存区的大小;否则后面发送不成功!
    MQTTClientInit(&client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf, sizeof(readbuf));

    char* address = MQTT_BROKER;
    //底层的TCP开始连接
    if ((rc = NetworkConnect(&network, address, MQTT_PORT)) != 0) {
        printf("Return code from network connect is %d\n", rc);
    }

//后天任务:如果这个不成功执行,就不会自动进去回调方法:messageArrived;
#if defined(MQTT_TASK)

    if ((rc = MQTTStartTask(&client)) != pdPASS) {
        printf("Return code from start tasks is %d\n", rc);
    } else {
        printf("Use MQTTStartTask\n");
    }

#endif
   
    //定义mqtt版本:  3 = 3.1 , 4 = 3.1.1
    connectData.MQTTVersion = 3;
    //定义客户端ID(必须唯一): 大伙们可以定义mac地址作为ID
	connectData.clientID.cstring = "ESP8266_sample";
	//定义连接的账户名,这个根据服务器的选型来弄;【可有可无】
    connectData.username.cstring= "admin";
    //定义连接的账户名密码,这个根据服务器的选型来弄;【可有可无】
    connectData.password.cstring="admin123456";
    //定义连接心跳;
    connectData.keepAliveInterval = 40;
    //清楚会话
    connectData.cleansession = true;

    //连接MQTT服务器
    if ((rc = MQTTConnect(&client, &connectData)) != 0) {
        printf("Return code from MQTT connect is %d\n", rc);
    } else {
        printf("MQTT Connected\n");
    }

    //订阅主题 MQTTSubscribe --->【ESP8266/sample/pub】
    if ((rc = MQTTSubscribe(&client, "ESP8266/sample/pub", 2, messageArrived)) != 0) {
        printf("Return code from MQTT subscribe is %d\n", rc);
    } else {
        printf("fuck MQTT subscribe to topic \"ESP8266/sample/pub\"\n");
    }

    //死循环,时隔一秒发送一则消息
	while (count++) {
        //初始化一则消息结构体
		MQTTMessage message;
		char payload[80];
		message.qos = QOS2;
		message.retained = 0;
		message.payload = payload;
		sprintf(payload,
				"{\"uuid\":\"dsaasdad22\",\"token\":\"saddsa412\",\"ver\":1.0,\"statusCode\":0,\"skill\":%d}",
				count);
		message.payloadlen = strlen(payload);

		printf("MQTT publish to payloadlen :%s\n", 	message.payload);

		if ((rc = MQTTPublish(&client, "ESP8266/sample/pub", &message)) != 0) {
			printf("Return code from MQTT publish is %d\n", rc);
		} else {
			printf(
					"MQTT publish topic \"ESP8266/sample/pub\", message number is %d\n",
					count);
		}

		vTaskDelay(1000 / portTICK_RATE_MS);  //send every 1 seconds
	}

    printf("mqtt_client_thread going to be deleted\n");
    vTaskDelete(NULL);
    return;
}

四、二次修改完善断开连接;


  • 这个库和乐鑫自己做的那份NONOS代码不一样,这个是不会自动重连服务器的,假如你的路由器突然没了外网,导致这个连接不成功,那么就会永远发布不了消息;所以优化如下,代码略多,主要原理:

1、通过判断是否发布消息成功的标志,是否重新连接服务器和订阅主题;

2、 如果把发布消息的任务独立开来,就相当于开启了新的线程,我看了一些高质量的代码,都是创建一则消息队列,处于阻塞等待,直到有消息要发布,则在此死循环内发布,如果不发布,那么重新连接则发布;

3、连接和订阅主题的代码都是在死循环内的,但是初始化客户端的代码千万别在死循环内,因为这个初始化相当于开辟了内存,会占据内存,多次了连接了 ,就相当于开辟多个内存了!


static void Task_MqttClient_Connect(void* pvParameters) {

	bool isNeedQueue = true;

	Network network;
	unsigned char sendbuf[2048], readbuf[2048] = { 0 };
	int rc = 0, count = 0;
	MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
	pvParameters = 0;
	NetworkInit(&network);
	MQTTClientInit(&client, &network, 30000, sendbuf, sizeof(sendbuf), readbuf,
			sizeof(readbuf));

    //!!!!!不要把初始化放在里面
	for (;;) {

        //判断是否已经获取了路由器分配的IP
		while (wifi_station_get_connect_status() != STATION_GOT_IP) {
			vTaskDelay(1000 / portTICK_RATE_MS);
		}

		char* address = MQTT_SERVICE;
		connectData.MQTTVersion = 3;
		connectData.clientID.cstring = checkTopic;
		connectData.username.cstring = MQTT_USER_NAME;
		connectData.password.cstring = MQTT_USER_PAW;
		connectData.keepAliveInterval = 40;
		connectData.cleansession = true;

		if ((rc = NetworkConnect(&network, address, MQTT_PORT)) != 0) {
			printf("MClouds NetworkConnect connect is %d\n", rc);
		}

		if ((rc = MQTTStartTask(&client)) != pdPASS) {
			printf("Return code from start tasks is %d\n", rc);
		} else {
			printf("Use MQTTStartTask\n");
		}

		if ((rc = MQTTConnect(&client, &connectData)) != 0) {
			printf("[SY] MClouds connect is %d\n", rc);
			network.disconnect(&network);
			vTaskDelay(1000 / portTICK_RATE_MS);
		}

		if ((rc = MQTTSubscribe(&client, subTopic, QOS0, MessageArrived))
				!= 0) {
			printf("[SY] MClouds sub fail is %d\n", rc);
			network.disconnect(&network);
			vTaskDelay(1000 / portTICK_RATE_MS);
		}

		printf("MQTT subscribe to topic -> %s\n", subTopic);
		xQueueReset(MqttMessageQueueHandler);

		while (1) {

			char payload[2048];
            
			struct esp_mqtt_msg_type *pMsg;
			printf("MqttMessageQueueHandler waitting ..\n");
			
			//阻塞等待
			xQueueReceive(MqttMessageQueueHandler, &pMsg, portMAX_DELAY);
			sprintf(payload, "%s", pMsg->allData);
			//printf("MQTT  publish payload: %s\n", payload);
			os_printf(" [SY] 1 MQTT get freeHeap: %d\n",system_get_free_heap_size());
			

			MQTTMessage message;
			message.qos = QOS0;
			message.retained = false;
			message.payload = (void*) payload;
			message.payloadlen = strlen(payload) + 1;

			if ((rc = MQTTPublish(&client, pubTopic, &message)) != 0) {
				printf("Return code from MQTT publish is %d\n", rc);
			} else {
				printf("MQTT publish succeed ..\n");
			}

			if (rc != 0) {
				break;
			}

		}
		network.disconnect(&network);
	}

	printf("mqtt_client_thread going to be deleted\n");
	vTaskDelete(NULL);
	return;

}

(注意要填写服务器地址,还要熟悉rtos的消息队列。)
1.一定要用最新版的SDK包的工程,而且要看博文前面的刨坑的连接里面的库文件是否更新到您的工程。
2. 由于下面的硬件代码链接不可以修改了,大家下载之后,修改下静态库文件和上面的Task_MqttClient_Connect方法即可。之后通过不断轮询服务器是否断开,如果是则发送消息重连即可。
3.目前2018.8.27为止,v2.0.0的SDK的MQTT还是蛮稳定的。断开连接的可能性较低。


展开阅读全文

没有更多推荐了,返回首页