前言
近期刚刚封装好了比较完善的MQTT库
后期的文章将对最新封装的库做一下补充
如果是初学者可以先学习51单片机实现MQTT实现通信控制的文章
https://cloud.tencent.com/developer/article/1602665
之所以又封装了一套是因为前面用的官方的库太大,小容量的单片机无法承受
当然主要还是为了大家可以方便理解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及其以上的版本不需要跳线
开始测试
一.打开手机APP,点击右上角菜单 "添加设备" ,手动输入自家路由器密码.(路由器名称为自动获取,不需要用户填写)
二.长按PB5大约4S,等待指示灯快闪,松开PB5,Wi-Fi模块进入配网状态
三.点击APP的搜索设备按钮,开始搜索设备,搜索成功,将自动跳转到主页面,并显示设备
四.单片机控制Wi-Fi连接上MQTT服务器以后,指示灯1S闪耀
五.点击设备进入,设备控制页面,页面显示当前温湿度数据,显示当前设备的状态
六.远程控制继电器吸合
七.远程控制继电器断开
八.请自行控制家电(最大支持10A,注意安全!)
关于程序
整个程序是STM32使用AT指令控制Wi-Fi模块实现SmartConfig配网和MQTT通信控制
程序的整体结构: https://cloud.tencent.com/developer/article/1520558
程序的按键处理: https://cloud.tencent.com/developer/article/1520555
串口接收数据 : https://cloud.tencent.com/developer/article/1520554
配置AT指令模板(阻塞版): https://cloud.tencent.com/developer/article/1521443
配置AT指令模板(非阻塞版): https://cloud.tencent.com/developer/article/1521442
SmartConfig实现部分
一,AT指令配置模块启动SmartConfig的程序处理模板是:配置AT指令模板(阻塞版)
二,按键按下3S以后 变量 SmartConfigFlage = 1;
定时器里面开始控制 指示灯100Ms闪耀
三,AT指令控制Wi-Fi模块执行SmartConfig 配网程序部分
三,SmartConfig执行流程
实际上启用SmartConfig指令是 AT CWSTARTSMART=3rn
最后的参数 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"rn",IP,Port
这个指令配置好以后,Wi-Fi模块便是透传模式,而且是自动连接
(串口接收的数据,自动发给TCP服务器)
(从TCP服务器接收的数据自动发给串口)
三,连接MQTT
四,判断是够连接成功
五,连接成功以后订阅主题
提示:该底层库大部分都是使用了注册回调函数的形式
六,发布消息
七,接收处理消息
八,处理心跳包
处理心跳包已经封装在了内部,只需要在连接上MQTT的地方写上处理函数即可!
结语
这版的MQTT底层包采用了数据缓存处理的方式
更加详细的移植使用,请继续阅读后续文章
支持处理消息等级0,1,2
主要代码如下
代码语言:javascript复制/**
* @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;
}
}