ESA2GJK1DH1K基础篇: APP使用SmartConfig绑定Wi-Fi 设备并通过MQTT控制设备(SimplePackage)

2020-03-23 11:45:03 浏览数 (1)

前言

  近期刚刚封装好了比较完善的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;
    }
}

0 人点赞