• [网络篇]ESP8266-SDK教程(五)之SmartConfig、Airkiss等多种配网方式


    在前面网络篇TCP通信中,我们用到了Wi-Fi功能,但是没有细讲,今天我们在这篇文章中仔细探讨一下ESP8266的Wi-Fi联网过程,以及各种方式的配网过程是如何实现的。文章中难免有疏漏或不正确的地方,如遇不正确的表述还请指出,本系列文章现在在知乎和简书同步更新。

    IAMLIUBO-知乎专栏

    IAMLIUBO-简书

    开始写之前,我们先来看一下为什么设备需要配网呢?

    我们来先看个图片:

    假如你买了一个智能插座,想让它连接到家里的Wi-Fi,但是智能插座又没有键盘和触摸屏此时你该怎么操作呢?总不能拆开然后重新给它写个程序吧!此时就需要我们的智能配网模式出马了, SmartConfig最先是TI开始研究的,其实类似的协议有很多,各家也都有各家的称呼,乐鑫这里也还是叫SmartConfig,但是配网模式也不仅仅局限于SmartConfig,还是有很多方式可以做到的,比如看下边~

    下面分析一下几种配网方式和优缺点:

    • 直接配网 -> SSID(Wi-Fi名称)和PWD(Wi-Fi密码)保存在设备中(每次修改都要重新烧录代码)
    • ap配网 -> 设备处于路由模式下等待客户端发送来SSID和PWD(可以随时修改,不易于操作)
    • web配置 -> 设备内做了个小web服务器通过网页交换SSID和PWD(界面化操作,流程繁琐)
    • SmartConfig -> 手机通过软件发送UDP广播包(包含SSID和PWD,界面化操作,操作简单)
    • Airkiss -> 类似与SmartConfig,可以使用微信公众号直接配置(界面化操作,不需要装APP)
    • 零配 -> 以配网设备为未配网设备配网,两个设备间数据交互(AliOS-Things中有涉及)
    • 蓝牙配网 -> 利用蓝牙设备配网,蓝牙模块跟ESP8266串口数据交互(未使用过,不做评价)

    目前应该就这几种吧,据我了解应该就这几种了,每种方式都有一定的优缺点,本篇文章先给大家讲解一下前五种方式,零配方式目前我还没有尝试过,后面实际开发过,再给大家讲解如何使用,至于蓝牙配网手头没有蓝牙模块,这里也不给大家讲了,不过思路是很简单的,大家如果有蓝牙模块,或者有BLE开发经验,相信自己摸索一下就可以做出来了。

    • 直接配网

    这里叫直接配网应该不是很妥当,其实就是将SSID和PWD直接写在了固件中,设备上电后会去搜索保存的SSID,如果搜索到指定的SSID后就用保存的PWD去连接Wi-Fi,TCP通信那片文章中我们就是用的这种方式,我们先来回顾一下,在user_config.c文件中有如下定义:

    这就是我们保存的Wi-Fi名称和密码,然后我们在user_init函数中直接调用的是Wi-Fi连接函数:

    可以看出,我们是直接使用保存的信息去进行Wi-Fi操作的,这种方式比较简单,这里就不再做具体的讲解了。

    • ap配网

    AP(Access Point)模式,就是我们常说的路由模式,这里的思路是ESP8266上电进入AP模式开启TCP Server,然后手机或者PC连接ESP8266的热点,然后作为TCP Client去连接Server进行数据交互,TCP数据交互我们在前面的文章中给大家讲过,这里我们主要讲一下ESP8266怎么开启AP模式,还有为什么我们让ESP8266作为TCP Server呢?其实主要是ESP8266做Server去监听固定端口更方便Client去连接,加入我们手机或者PC做Server,万一遇到端口不可用,那岂不是没办法去交互数据了?所以我们让ESP8266做Server很显然是更妥当一点。

    还有比较重要的一点就是数据格式,数据格式就像我们交流的语言一样,是两者都能够"听懂","读懂"的,我们这里使用一种非常常见的数据格式 -> JSON,相信很多人都有听说过,或者使用过,我们选择JSON,是因为它的格式固定,并且易于解析,这里我们使用cJSON库去解析,官方SDK中虽然也有JSON API,但是由于我一直都是使用cJSON,对cJSON库还是比较喜欢的,而且是用标准C写的,跨平台so easy,项目地址戳卡片:

    cJSON项目地址

    使用cJSON非常简单,只需要添加一个头文件和源文件就好了,但是在ESP8266上使用还是要修改很多东西的,这里就先不细讲了,大家可以先下载我的工程,直接使用,后面我们再单独讲一下JSON。

    我们先规定一下数据格式吧,还不了解JSON的话可以先去了解一下,很简单的其实,就是Key-Value型的键值对,就是一个名字对应一个值,这里我们就简单定一个格式吧!

    其中XXXXXXXX和XXXXXXXX都是需要替换成你的实际Wi-Fi名称和密码,剩下两项没有实际意义,只是防止有人不署名转载,然后帮助读者回到正确的车上~啊哈哈哈!

    我们来分析一下代码吧,跟之前的TCP通信差不多,这是这里对收到的数据做了进一步处理,我们只看关键代码好了:

     1 /**
     2  * TCP Server数据接收回调函数,可以在这处理收到Client发来的数据
     3  */
     4 static void ICACHE_FLASH_ATTR
     5 tcp_server_recv_cb(void *arg,char *pdata,unsigned short len){
     6     os_printf("tcp server receive tcp client data
    ");
     7     os_printf("length: %d 
    data: %s
    ",len,pdata);
     8 
     9     //TO DO
    10 
    11     /**
    12      *process the receive data
    13      */
    14     AP_recv_data_process(pdata);//在TCP Server接收回调函数中,新增了数据处理函数
    15 }
    然后我们再看看AP_recv_data_process函数是如何处理的:

     1 /**
     2  * AP配网模式处理TCP client发送来的数据
     3  */
     4 void ICACHE_FLASH_ATTR
     5 AP_recv_data_process(uint8 *pdata){
     6 
     7     cJSON *root = cJSON_Parse(pdata);//将一个JSON数据包,按照cJSON结构体的结构序列化整个数据包,并在堆中开辟一块内存存储cJSON结构体
     8                                         //成功返回一个指向内存块中的cJSON指针,失败返回NULL,表示JSON格式不正确
     9     if(root != NULL){
    10         uint8 *SSID = cJSON_GetObjectItem(root,"SSID")->valuestring;//解析SSID key对应的Value值,也就是我们发送的Wi-Fi名称
    11         uint8 *PWD = cJSON_GetObjectItem(root,"PWD")->valuestring;//解析PWD key对应的Value值,也就是我们发送的Wi-Fi密码
    12         uint8 *Author = cJSON_GetObjectItem(root,"Author")->valuestring;//
    13         uint8 *zhuanlan = cJSON_GetObjectItem(root,"zhuanlan")->valuestring;//
    14         os_printf("
    Author: %s
    zhuluan: %s",Author,zhuanlan);
    15 
    16         os_printf("SSID: %s   PWD: %s
    ",SSID,PWD);//打印我们收到的密码
    17 
    18         wifi_set_opmode(STATION_MODE);//设置WiFi模式为STATION模式
    19         WIFI_Connect(SSID, PWD, wifiConnectCb);//连接目标WiFi
    20     }else{
    21         os_printf("json data format error!
    ");//收到的数据不正确就打印
    22     }
    23 
    24     cJSON_Delete(root);//解析完JSON后一定要记得释放!
    25 }

    在此函数中,我们只需对收到的数据简单一处理,就得到了我们需要的Wi-Fi名称和密码,然后我们就可以快快乐乐的去连接Wi-Fi了~,这里AP配网模式是按键触发的,我们在WIFI_Connect函数中调用了wifi_station_set_config函数,下次上电是会直接连接这次我们配置的Wi-Fi的~

    • SmartConfig配网

    下面我们再来分析一下SmartConfig配网模式,其实SmartConfig模式与Airkiss模式基本是一致的,主要不同就是发数据包方式还是有点不同的:

    • SmartConfig 组播,通过长度编码
    • Airkiss 全网广播,通过长度编码

    别的区别的话就是代码有些不同了,下面我们来实际分析一下:

      1 /**
      2  * SmartConfig 测试代码
      3  */
      4 void ICACHE_FLASH_ATTR
      5 SmartConfig_test(void){
      6     smartconfig_set_type(SC_TYPE_ESPTOUCH);//设置快连模式类型,必须在smartconfig_start之前调用
      7     wifi_set_opmode(STATION_MODE);
      8     smartconfig_start(smartconfig_done);
      9 }
     10 
     11 /**
     12  * Airkiss 测试代码
     13  */
     14 void ICACHE_FLASH_ATTR
     15 Airkiss_test(void){
     16     smartconfig_set_type(SC_TYPE_AIRKISS);//设置快连模式类型,必须在smartconfig_start之前调用
     17     wifi_set_opmode(STATION_MODE);
     18     smartconfig_start(smartconfig_done);
     19 }
     20 
     21 /**
     22  * SmartConfig Airkiss 测试代码
     23  */
     24 void ICACHE_FLASH_ATTR
     25 SmartConfig_Airkiss_test(void){
     26 
     27     smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS);//设置快连模式类型,必须在smartconfig_start之前调用
     28     wifi_set_opmode(STATION_MODE);
     29     smartconfig_start(smartconfig_done);
     30 }
     31 
     32 
     33 /*===================================
     34  *  Smartconfig Airkiss FUNCTIONS   *
     35  ====================================*/
     36 
     37 /**
     38  *
     39  */
     40 const airkiss_config_t akconf =
     41 {
     42     (airkiss_memset_fn)&memset,
     43     (airkiss_memcpy_fn)&memcpy,
     44     (airkiss_memcmp_fn)&memcmp,
     45     0,
     46 };
     47 
     48 /**
     49  *
     50  */
     51 LOCAL void ICACHE_FLASH_ATTR
     52 airkiss_wifilan_time_callback(void)
     53 {
     54     uint16 i;
     55     airkiss_lan_ret_t ret;
     56 
     57     if ((udp_sent_cnt++) >30) {
     58         udp_sent_cnt = 0;
     59         os_timer_disarm(&ssdp_time_serv);//airkiss_wifilan_time_callback运行三十次后停止运行
     60         //return;
     61     }
     62     //设置本地udp发送端口号,IP地址设置成255.255.255.255是进行广播,UDP有单播,多播和广播三种模式
     63     ssdp_udp.remote_port = DEFAULT_LAN_PORT;
     64     ssdp_udp.remote_ip[0] = 255;
     65     ssdp_udp.remote_ip[1] = 255;
     66     ssdp_udp.remote_ip[2] = 255;
     67     ssdp_udp.remote_ip[3] = 255;
     68     lan_buf_len = sizeof(lan_buf);
     69 
     70     ret = airkiss_lan_pack(AIRKISS_LAN_SSDP_NOTIFY_CMD,
     71         DEVICE_TYPE, DEVICE_ID, 0, 0, lan_buf, &lan_buf_len, &akconf);
     72     if (ret != AIRKISS_LAN_PAKE_READY) {
     73         os_printf("Pack lan packet error!");
     74         return;
     75     }
     76 
     77     ret = espconn_sendto(&pssdpudpconn, lan_buf, lan_buf_len);
     78     if (ret != 0) {
     79         os_printf("UDP send error!");
     80     }
     81     os_printf("Finish send notify!
    ");//UDP发送完成
     82 }
     83 
     84 /**
     85  *
     86  */
     87 LOCAL void ICACHE_FLASH_ATTR
     88 airkiss_wifilan_recv_callbk(void *arg, char *pdata, unsigned short len)
     89 {
     90     uint16 i;
     91     remot_info* pcon_info = NULL;
     92 
     93     airkiss_lan_ret_t ret = airkiss_lan_recv(pdata, len, &akconf);
     94     airkiss_lan_ret_t packret;
     95 
     96     switch (ret){
     97     case AIRKISS_LAN_SSDP_REQ:
     98         espconn_get_connection_info(&pssdpudpconn, &pcon_info, 0);
     99         os_printf("remote ip: %d.%d.%d.%d 
    ",pcon_info->remote_ip[0],pcon_info->remote_ip[1],
    100                                                 pcon_info->remote_ip[2],pcon_info->remote_ip[3]);
    101         os_printf("remote port: %d 
    ",pcon_info->remote_port);
    102 
    103         pssdpudpconn.proto.udp->remote_port = pcon_info->remote_port;
    104         os_memcpy(pssdpudpconn.proto.udp->remote_ip,pcon_info->remote_ip,4);
    105         ssdp_udp.remote_port = DEFAULT_LAN_PORT;
    106 
    107         lan_buf_len = sizeof(lan_buf);
    108         packret = airkiss_lan_pack(AIRKISS_LAN_SSDP_RESP_CMD,
    109             DEVICE_TYPE, DEVICE_ID, 0, 0, lan_buf, &lan_buf_len, &akconf);
    110 
    111         if (packret != AIRKISS_LAN_PAKE_READY) {
    112             os_printf("Pack lan packet error!");
    113             return;
    114         }
    115 
    116         os_printf("
    
    ");
    117         for (i=0; i<lan_buf_len; i++)
    118             os_printf("%c",lan_buf[i]);
    119         os_printf("
    
    ");
    120 
    121         packret = espconn_sendto(&pssdpudpconn, lan_buf, lan_buf_len);
    122         if (packret != 0) {
    123             os_printf("LAN UDP Send err!");
    124         }
    125 
    126         break;
    127     default:
    128         os_printf("Pack is not ssdq req!%d
    ",ret);
    129         break;
    130     }
    131 }
    132 
    133 /**
    134  *
    135  */
    136 void ICACHE_FLASH_ATTR
    137 airkiss_start_discover(void)
    138 {
    139     ssdp_udp.local_port = DEFAULT_LAN_PORT;//设置本地端口
    140     pssdpudpconn.type = ESPCONN_UDP;//设置通信方式为UDP
    141     pssdpudpconn.proto.udp = &(ssdp_udp);//
    142     espconn_regist_recvcb(&pssdpudpconn, airkiss_wifilan_recv_callbk);//注册收到数据回调函数
    143     espconn_create(&pssdpudpconn);//创建一个UDP传输
    144 
    145     os_timer_disarm(&ssdp_time_serv);
    146     os_timer_setfn(&ssdp_time_serv, (os_timer_func_t *)airkiss_wifilan_time_callback, NULL);//注册airkiss定时回调函数
    147     os_timer_arm(&ssdp_time_serv, 1000, 1);//1s
    148 }
    149 
    150 /**
    151  *
    152  */
    153 static void ICACHE_FLASH_ATTR
    154 smartconfig_done(sc_status status, void *pdata)
    155 {
    156     switch(status) {
    157         case SC_STATUS_WAIT://连接未开始,请勿在此阶段开始连接
    158             os_printf("SC_STATUS_WAIT
    ");
    159             break;
    160         case SC_STATUS_FIND_CHANNEL://请在此阶段开启APP进行配对
    161             os_printf("SC_STATUS_FIND_CHANNEL
    ");
    162             break;
    163         case SC_STATUS_GETTING_SSID_PSWD://获取到Wi-Fi名称和密码
    164             os_printf("SC_STATUS_GETTING_SSID_PSWD
    ");
    165             sc_type *type = pdata;
    166             if (*type == SC_TYPE_ESPTOUCH) {//判断类型,在这一步发来的数据pdata中应该有配置类型
    167                 os_printf("SC_TYPE:SC_TYPE_ESPTOUCH
    ");
    168             } else {
    169                 os_printf("SC_TYPE:SC_TYPE_AIRKISS
    ");
    170             }
    171             break;
    172         case SC_STATUS_LINK://开始连接Wi-Fi
    173             os_printf("SC_STATUS_LINK
    ");
    174             struct station_config *sta_conf = pdata;
    175 
    176             wifi_station_set_config(sta_conf);
    177             wifi_station_disconnect();
    178             wifi_station_connect();
    179             break;
    180         case SC_STATUS_LINK_OVER://获取到IP,连接路由完成
    181             os_printf("SC_STATUS_LINK_OVER
    ");
    182             if (pdata != NULL) {//连接完成,如果使用的是SmartConfig,此时手机会将自己的IP地址发给ESP8266
    183                 //SC_TYPE_ESPTOUCH
    184                 uint8 phone_ip[4] = {0};
    185 
    186                 os_memcpy(phone_ip, (uint8*)pdata, 4);
    187                 os_printf("Phone ip: %d.%d.%d.%d
    ",phone_ip[0],phone_ip[1],phone_ip[2],phone_ip[3]);
    188             } else {//练成完成,如果是使用的Airkiss方式,到这一步还没有完成,还会跟微信进行数据交互,应该告知微信配网完成之类的
    189                 //SC_TYPE_AIRKISS - support airkiss v2.0
    190                 airkiss_start_discover();//开始Airkiss内网发现
    191             }
    192             smartconfig_stop();//SmartConfig 完成
    193             break;
    194     }
    195 
    196 }

    这里的代码稍微有点多,不过其实是三种模式,SmartConfig模式、Airkiss模式、SmartConfigAirkiss混合模式,这是乐鑫官方给出的例子,某些步骤我按我的理解给注释了一下,大家可以仔细阅读以下看看,其中需要注意的是在调用smartconfig_start函数之前必须先调用smartconfig_set_type函数声明配网模式类型。

    • Airkiss配网

    与SmartConfig类似,代码在上边,大家可以看一下。

    • Web页面配置

    Web页面配置也相对不是很难,主要是对表单的处理,但是要把网页写在ESP8266的flash中,最近没什么时间,等改天有时间了再给大家分析一下,不过ESP8266内置网页的Demo在我的Github仓库中有一个,大家感兴趣的可以先去看一下,然后看一下自己是否能实现。

    最后上一段小视频,手机录制的,不是很好,大家将就着看,还有些小瑕疵,这里就不剪了。也怪麻烦的,主要看一下流程,可能讲的也不是很详细,有什么建议可以私信我哦~

    演示视频-B站

    最后欢迎大家去我的仓库点个Star,您的鼓励是我最大的动力~有问题可以私信我,或者提交issues。

    makingfunxyz-esp8266

    欢迎大家Star,您的鼓励是我最大的动力,有问题可以私信我,或者提交issues~

    本系列文章在知乎,博客园同步更新,知乎搜索专栏:IAMLIUBO的神奇物联网之旅

    QQ交流群:592587184

    唯有爱与科技不可辜负。
  • 相关阅读:
    jQuery插件开发——全屏切换插件
    原生JS实现全屏切换以及导航栏滑动隐藏及显示——修改
    CSS 3学习——animation动画
    CSS 3学习——transition 过渡
    CSS 3 学习——transform 3D转换渲染
    CSS 3学习——transform 2D转换
    消息过滤
    jms消息组成
    vim常用命令整理
    知识碎片
  • 原文地址:https://www.cnblogs.com/imliubo/p/10101528.html
Copyright © 2020-2023  润新知