zoukankan      html  css  js  c++  java
  • ESA2GJK1DH1K基础篇: APP使用SmartConfig绑定Wi-Fi 设备并通过MQTT控制设备(V1.0)(AT+TCP透传指令)

    前言

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

        

     

  • 相关阅读:
    Android比较实用的属性
    软件版本命名规则
    Dhroid框架笔记(DhNet、Adapter)
    Activity对话框
    Dhroid框架笔记(IOC、EventBus)
    Eclipse快捷键
    解析Excel_Jxl
    面试题
    java基础——值传递和应用传递
    java基础——子类继承父类程序执行顺序
  • 原文地址:https://www.cnblogs.com/yangfengwu/p/12539421.html
Copyright © 2011-2022 走看看