前言
近期刚刚封装好了比较完善的MQTT库
后期的文章将对最新封装的库做一下补充
如果是初学者可以先学习51单片机实现MQTT实现通信控制的文章
https://www.cnblogs.com/yangfengwu/p/12536382.html
之所以又封装了一套是因为前面用的官方的库太大,小容量的单片机无法承受
当然主要还是为了大家可以方便理解MQTT协议
用51单片机跑了MQTT,这样便可以方便让更多的人入门学习.
实现功能概要
STM32控制WI-Fi模块以AT指令TCP透传方式连接MQTT服务器, 实现MQTT通信控制.
测试准备工作(详细下载步骤请参考 硬件使用说明 )
一,下载单片机程序
工程目录: STM32F10xTemplateProgect
hex文件目录: STM32F10xTemplateProgectProgect
二,安装APP软件
三,调整波动开关位置,STM32和Wi-Fi通信
四,短接STM32的PB2和Wi-Fi模块的RST引脚(为了做项目稳定可靠,请使用单片机硬件复位Wi-Fi)
注意:版本号2.5及其以上的版本不需要跳线
注意:版本号2.5及其以上的版本不需要跳线
注意:版本号2.5及其以上的版本不需要跳线
V2.5.1版本内部默认PB2连接了Wi-Fi模块的RST引脚,不需要短接
V2.5.1版本内部默认PB2连接了Wi-Fi模块的RST引脚,不需要短接
V2.5.1版本内部默认PB2连接了Wi-Fi模块的RST引脚,不需要短接
开始测试
一.打开手机APP,点击右上角菜单 "添加设备" ,手动输入自家路由器密码.(路由器名称为自动获取,不需要用户填写)
二.长按PB5大约4S,等待指示灯快闪,松开PB5,Wi-Fi模块进入配网状态
三.点击APP的搜索设备按钮,开始搜索设备,搜索成功,将自动跳转到主页面,并显示设备
四.单片机控制Wi-Fi连接上MQTT服务器以后,指示灯1S闪耀
五.点击设备进入,设备控制页面,页面显示当前温湿度数据,显示当前设备的状态
六.远程控制继电器吸合
七.远程控制继电器断开
八.请自行控制家电(最大支持10A,注意安全!)
关于程序
整个程序是STM32使用AT指令控制Wi-Fi模块实现SmartConfig配网和MQTT通信控制
程序的整体结构: https://www.cnblogs.com/yangfengwu/p/11669323.html
程序的按键处理: https://www.cnblogs.com/yangfengwu/p/11669354.html
串口接收数据 : https://www.cnblogs.com/yangfengwu/p/11669373.html
配置AT指令模板(阻塞版): https://www.cnblogs.com/yangfengwu/p/11673439.html
配置AT指令模板(非阻塞版): https://www.cnblogs.com/yangfengwu/p/11674814.html
提醒:
提醒:
提醒:
看完此节介绍以后如果已经对MQTT相当熟悉,
可看以下链接学习如何把MQTT移植到自己的工程
https://www.cnblogs.com/yangfengwu/p/12540710.html
如果是新手,请接着按部就班学习!
SmartConfig实现部分
一,AT指令配置模块启动SmartConfig的程序处理模板是:配置AT指令模板(阻塞版)
二,按键按下3S以后 变量 SmartConfigFlage = 1;
定时器里面开始控制 指示灯100Ms闪耀
三,AT指令控制Wi-Fi模块执行SmartConfig 配网程序部分
三,SmartConfig执行流程
实际上启用SmartConfig指令是 AT+CWSTARTSMART=3
最后的参数 1-SmartConfig配网 2-微信Airkiss配网 3-SmartConfig配网+微信Airkiss配网
下面进入了 while(1) 循环 我设置的30S超时
实际上此时Wi-Fi模块正在监听APP在空气中发出的无线信号
下图只要执行了搜索设备,APP就在不停的发出无线信号
Wi-Fi模块接收到APP发出的路由器信息以后,就会根据信息去连接路由器
Wi-Fi模块连接上了路由器以后便会返回 WIFI CONNECTED 和 WIFI GOT IP
注:只要配网一次,以后Wi-Fi模块便会自动连接此路由器,不需要重复配网!
四,SmartConfig执行流程-等待路由器把自己的MAC信息返回给APP
为了让APP确定Wi-Fi模块确实连接上了路由器,Wi-Fi连接上路由器以后
需要返回给APP自己的MAC地址和自己连接路由器后分得的IP地址
所以延时了5S时间,让Wi-Fi模块把信息发给APP
下图中,显示的就是所配网的Wi-Fi模块的MAC地址信息
当然MAC地址很有用(全球唯一),通信的时候可以用来区分设备.
MQTT实现部分
一,前言
Wi-Fi模块发布的主题: device/设备MAC
Wi-Fi模块订阅的主题: user/设备MAC
APP通过SmartConfig获取Wi-Fi的MAC,然后设置
订阅的主题:device/设备MAC
发布的主题:user/设备MAC
二,连接TCP服务器(MQTT服务器)
AT指令配置模块连接TCP的程序处理模板是:配置AT指令模板(非阻塞版)
配置Wi-Fi模块连接TCP服务器是使用的 "AT+SAVETRANSLINK=1,"%s",%s,"TCP" ",IP,Port
这个指令配置好以后,Wi-Fi模块便是透传模式,而且是自动连接
(串口接收的数据,自动发给TCP服务器)
(从TCP服务器接收的数据自动发给串口)
三,连接MQTT
四,判断是够连接成功
五,连接成功以后订阅主题
提示:该底层库大部分都是使用了注册回调函数的形式
六,发布消息
七,接收处理消息
八,处理心跳包
处理心跳包已经封装在了内部,只需要在连接上MQTT的地方写上处理函数即可!
结语
这版的MQTT底层包采用了数据缓存处理的方式
更加详细的移植使用,请继续阅读后续文章
支持处理消息等级0,1,2
主要代码如下
/** * @brief 发送MQTT数据 * 把发送数据给网络模块的函数放在此处 * @param buffer 发送的数据 * @param length 数据长度 * @retval None * @warning None * @example **/ void mqtt_send_function(mqtt_t *mqtt) { /*有缓存的数据需要发送;同时未启动超时检测*/ if(mqtt->buff_manage_struct_t.SendLen !=0 && mqtt->timer_out_cnt<=0)//需要发送数据 { if(mqtt_get_type(mqtt->send_buff) == MQTT_MSG_TYPE_SUBSCRIBE){//发送的消息是订阅 mqtt->mqtt_message_id = mqtt_get_id(mqtt->send_buff, mqtt->buff_manage_struct_t.SendLen);//获取消息ID mqtt->mqtt_message_type = MQTT_MSG_TYPE_SUBSCRIBE; mqtt->timer_out_cnt = mqtt_timerout_default;//设置超时时间 } else if(mqtt_get_type(mqtt->send_buff) == MQTT_MSG_TYPE_PUBLISH){//发送的消息是发布 if(mqtt_get_qos(mqtt->send_buff) == 1 || mqtt_get_qos(mqtt->send_buff) == 2)//消息等级是1或者2 { mqtt->mqtt_message_id = mqtt_get_id(mqtt->send_buff, mqtt->buff_manage_struct_t.SendLen);//获取消息ID,以便应答 mqtt->mqtt_message_type = MQTT_MSG_TYPE_PUBLISH; mqtt->timer_out_cnt = mqtt_timerout_default;//设置超时时间 } } //使用串口发送数据(发给网络模块):请替换自己的发送函数 //mqtt->send_buff:发送的数据 //mqtt->buff_manage_struct_t.SendLen:发送的数据长度 //Send function UsartOutStrIT(mqtt->send_buff,mqtt->buff_manage_struct_t.SendLen);//请替换自己的发送函数 mqtt->buff_manage_struct_t.SendLen=0; } } /** * @brief 接收处理MQTT数据 *用户需要把网络模块接收到的MQTT数据传给此函数处理 * @param buffer 接收的MQTT数据 * @param length 数据长度 * @retval None * @warning None * @example **/ void mqtt_read_function(mqtt_t *mqtt,unsigned char* buffer, uint16_t length) { uint8_t msg_type; uint8_t msg_qos; uint16_t msg_id; msg_type = mqtt_get_type(buffer); msg_qos = mqtt_get_qos(buffer); msg_id = mqtt_get_id(buffer, length); if(mqtt->mqtt_message_type == MQTT_MSG_TYPE_SUBSCRIBE){//上次发送的是订阅主题 mqtt_keep_alive_init(mqtt);//初始化心跳包变量 //上次发送的消息是订阅 if(msg_type == MQTT_MSG_TYPE_SUBACK){//获取应答 mqtt->timer_out_cnt = 0;//停止超时定时器 if(msg_id == mqtt->mqtt_message_id && ( buffer[length-1]&0xff) != 0x80 ) { mqtt->mqtt_message_type = MQTT_MSG_TYPE_SUBACK; if(mqtt->subscribedCb!=NULL){ mqtt->subscribedCb(mqtt->mqtt_message_id); } } else { mqtt->mqtt_message_type = 0; if(mqtt->failsubscribedCb!=NULL)mqtt->failsubscribedCb(mqtt->mqtt_message_id); } } else{ mqtt->mqtt_message_type = 0; if(mqtt->failsubscribedCb!=NULL)mqtt->failsubscribedCb(mqtt->mqtt_message_id); } } switch (msg_type) { case MQTT_MSG_TYPE_PUBLISH://接收到消息 mqtt_keep_alive_init(mqtt);//初始化心跳包变量 if (msg_qos == 1){//消息等级是1,打包需要返回的PUBACK数据 mqtt->mqtt_send_data_len = mqtt_msg_puback(msg_id,&mqtt->ptr,mqtt->mqtt_data_buff,mqtt_send_buff_len); } else if (msg_qos == 2){//消息等级是2,打包需要返回的PUBREC mqtt->mqtt_send_data_len = mqtt_msg_pubrec(msg_id,&mqtt->ptr,mqtt->mqtt_data_buff,mqtt_send_buff_len); } if (msg_qos == 1 || msg_qos == 2) { if(mqtt->mqtt_send_data_len >0 ){ BufferManageWrite(&mqtt->buff_manage_struct_t,mqtt->ptr,mqtt->mqtt_send_data_len);/*把协议存入存入缓存*/ } } //调用接收回调函数 if(mqtt->recCb){ mqtt->topic_length = length; mqtt->topic = mqtt_get_publish_topic(buffer, &mqtt->topic_length); mqtt->data_length = length; mqtt->data = mqtt_get_publish_data(buffer, &mqtt->data_length); mqtt->recCb(mqtt->topic,mqtt->topic_length,mqtt->data,mqtt->data_length); } break; case MQTT_MSG_TYPE_PUBACK://客户端上次发送了消息等级是1的消息,服务器返回PUBACK,说明消息已经送达 if(mqtt->mqtt_message_type == MQTT_MSG_TYPE_PUBLISH && mqtt->mqtt_message_id == msg_id){ mqtt->mqtt_message_type = 0; mqtt->timer_out_cnt = 0;//停止超时定时器 if(mqtt->PublishedCb){ mqtt->PublishedCb(); } } break; case MQTT_MSG_TYPE_PUBREC://客户端上次发送了消息等级是2的消息,服务器返回PUBREC,客户端需要返回PUBREL mqtt->mqtt_send_data_len = mqtt_msg_pubrel(msg_id,&mqtt->ptr,mqtt->mqtt_data_buff,mqtt_send_buff_len); //使用串口发送数据(发给网络模块):请替换自己的发送函数 //mqtt->ptr:发送的数据 //mqtt->mqtt_send_data_len:发送的数据长度 //Send function UsartOutStrIT(mqtt->ptr,mqtt->mqtt_send_data_len);//请替换自己的发送函数 break; case MQTT_MSG_TYPE_PUBCOMP://客户端上次发送了消息等级是2的消息,服务器返回PUBREC,客户端返回了PUBREL,服务器最后返回PUBCOMP,说明消息已经送达 if (mqtt->mqtt_message_type == MQTT_MSG_TYPE_PUBLISH && mqtt->mqtt_message_id == msg_id) { mqtt->mqtt_message_type = 0; mqtt->timer_out_cnt = 0;//停止超时定时器 if(mqtt->PublishedCb){ mqtt->PublishedCb(); } } break; case MQTT_MSG_TYPE_PUBREL://客户端收到了消息等级为2的消息,同时回复了PUBREC,服务器返回PUBREL,客户端最后需要返回PUBCOMP mqtt->mqtt_send_data_len = mqtt_msg_pubcomp(msg_id,&mqtt->ptr,mqtt->mqtt_data_buff,mqtt_send_buff_len); BufferManageWrite(&mqtt->buff_manage_struct_t,mqtt->ptr,mqtt->mqtt_send_data_len);/*把协议存入存入缓存*/ break; case MQTT_MSG_TYPE_PINGRESP://接收到心跳包数据 mqtt_keep_alive_init(mqtt);//初始化心跳包变量 break; default: break; } }