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

* 1、 Esp8266之 搭建开发环境,开始一个“hellow world”串口打印。
<http://blog.csdn.net/xh870189248/article/details/77985541>
* 2、 Esp8266之 利用GPIO开始使用按钮点亮你的“第一盏灯”。
<http://blog.csdn.net/xh870189248/article/details/78126689>
* 3、 Esp8266之 利用 “软件定时器 ” 定时0.5秒闪烁点亮一盏LED。
<http://blog.csdn.net/xh870189248/article/details/78155357>
* 4 、Esp8266之 了解PWM,更为深入地用PWM控制一盏LED的亮度变化。
<http://blog.csdn.net/xh870189248/article/details/78202224>
* 5 、Esp8266之 原生乐鑫SDK高级使用之封装Post与Get请求云端,拿到“天气预报信息”。
<http://blog.csdn.net/xh870189248/article/details/78656563>
* 6 、Esp8266之 了解 SmartConfig与Airkiss一键配网,给8266配网上云端。无需把wifi名字密码写在固件里。
<http://blog.csdn.net/xh870189248/article/details/78677952>
* 7 、Esp8266之 了解 softAP热点配网模式原理,仿“机智云”定义自己的热点配网模式协议。
<http://blog.csdn.net/xh870189248/article/details/78703905>
* 8、 Esp8266之 你要找的8266作为UDP、TCP客户端或服务端的角色通讯,都在这了。
<http://blog.csdn.net/xh870189248/article/details/78739311>
* 9、 Esp8266进阶之路第一篇: [小实战上篇]Windows系统搭建8266的本地Mqtt服务器,局域网点亮一盏LED灯。
<http://blog.csdn.net/xh870189248/article/details/78761948>
* 10、 Esp8266进阶之路第二篇: [小实战下篇]Windows系统搭建8266的本地Mqtt服务器,局域网点亮一盏LED灯。
<http://blog.csdn.net/xh870189248/article/details/78777371>
* 11、 Esp8266进阶之路第三篇: 8266接入阿里智能,点亮一盏LED灯,期待天猫精灵语音控制的不约而至!
<http://blog.csdn.net/xh870189248/article/details/78807018>
* 12、 Esp8266进阶之路第四篇: 图文并茂学习阿里云主机搭建8266MQTT服务器,实现移动网络远程控制一盏LED。
<http://blog.csdn.net/xh870189248/article/details/78867173>
* 13、 Esp8266进阶之路第五篇: 动手做个8266毕设小案例,smartConfig + MQTT协议轻松实现远程控制一盏LED。
<http://blog.csdn.net/xh870189248/article/details/79052347>
* 14、 Esp8266进阶之路第六篇: esp8266的 FreeRtos系统学习的正确姿势 —— 环境搭建、烧录。
<http://blog.csdn.net/xh870189248/article/details/79103373>
* 15、 Esp8266进阶之路第七篇: esp8266的 物联网又一股清流,8266接入阿里云平台非阿里智能的SDS服务,点亮一盏LED灯。
<http://blog.csdn.net/xh870189248/article/details/79197459>
* 16、 Esp8266进阶之路第八篇: esp8266的 基于Nonos移植红外线H1838,实现红外遥控器配网,远程控制一盏灯。
<http://blog.csdn.net/xh870189248/article/details/79486075>
* 17、 Esp8266进阶之路第九篇: esp8266自研的快速上电开关五次 (开-关为一次) ,无需按键触发则8266进去一键配网模式。
<https://blog.csdn.net/xh870189248/article/details/80027961>
* 18、 Esp8266进阶之路第十篇: esp8266 基于NONOS 实现 OTA 远程升级,实现无线“ 热修复 ”升级固件程序。
<https://blog.csdn.net/xh870189248/article/details/80095139>
* 19、 Esp8266进阶之路第十一篇【外设篇】: esp8266驱动 ds18b20、dht11 温湿度传感器,采集温湿度传感器到服务器。
<https://blog.csdn.net/xh870189248/article/details/80284827>
* 20、 Esp8266进阶之路第十一篇【高级篇】: 深入学习esp8266的esp
now模式,仿机智云做一个小网关,实现无需网络下轻松彼此连接通讯交互数据。
<https://blog.csdn.net/xh870189248/article/details/80631739>
* 21、 Esp8266进阶之路第十二篇【高级篇】: 浅谈 esp8266 如何在本地局域网网络情况下实现最大效率地和前端实现数据交互。
<https://blog.csdn.net/xh870189248/article/details/80859347>
* 22、 Esp8266进阶之路第十三篇【混杂篇】: esp8266的工程如何添加第三方静态库文件以及如何自定义文件夹,聊聊那些makeFile的事。。
<https://blog.csdn.net/xh870189248/article/details/80909216>
* 23、 Esp8266进阶之路第十四篇【高级篇】: 再来一波 esp8266 基于 freeRtos系统连接自己私有的服务器实现OTA远程升级,接触下
lwip的基本知识。。 <https://blog.csdn.net/xh870189248/article/details/80924538>
* 24、 Esp8266进阶之路第十五篇【高级篇】:
渗透学习回顾下esp8266的外置spi芯片25q系列,熟悉8266代码块在其的分布,得心应手放置图片或其他资料。
<https://blog.csdn.net/xh870189248/article/details/81017735>
* 25、 Esp8266进阶之路第十六篇【高级篇】: 深聊下esp8266的串口 Uart 通讯中断编程,为您准备好了 NONOS 版本 和 RTOS
系统的串口驱动文件。 <https://blog.csdn.net/xh870189248/article/details/81017735>
* 26、 Esp8266进阶之路第十七篇【高级篇】: RTOS分析 MQTT 实现过程,实现移植 MQTT协议在 esp8266
rtos实时系统,可断线重连。 <https://blog.csdn.net/xh870189248/article/details/81181707>


*
*
* 一、前言; <https://blog.csdn.net/xh870189248/article/details/81181707#一前言>
* 二、MQTT的常识;
<https://blog.csdn.net/xh870189248/article/details/81181707#二mqtt的常识>
* 三、官方核心代码;
<https://blog.csdn.net/xh870189248/article/details/81181707#三官方核心代码>
* 四、二次修改完善断开连接;
<https://blog.csdn.net/xh870189248/article/details/81181707#四二次修改完善断开连接>


一、前言;

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

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

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

*
那么本博文是基于rtos的MQTT协议的实现,优化了官方的代码示范,而且带你走一走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已经看到了源码:点我查看
<https://github.com/eclipse/paho.mqtt.c>,这个库非常出名,很多嵌入式的芯片都是移植这个库。 #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还是蛮稳定的。断开连接的可能性较低。

* 本硬件代码下载:https://download.csdn.net/download/xh870189248/10565032
<https://download.csdn.net/download/xh870189248/10565032>
* esp8266源代码学习汇总:https://github.com/xuhongv/StudyInEsp8266
<https://github.com/xuhongv/StudyInEsp8266>
* esp32源代码学习汇总:https://github.com/xuhongv/StudyInEsp32
<https://github.com/xuhongv/StudyInEsp32>
* QQ付费交流群,众多大神带您飞: 434878850

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信