[網絡篇]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\r\n");
 7     os_printf("length: %d \r\ndata: %s\r\n",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("\r\nAuthor: %s\r\nzhuluan: %s",Author,zhuanlan);
15 
16         os_printf("SSID: %s   PWD: %s\r\n",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!\r\n");//收到的數據不正確就打印
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!\n");//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 \r\n",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 \r\n",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("\r\n\r\n");
117         for (i=0; i<lan_buf_len; i++)
118             os_printf("%c",lan_buf[i]);
119         os_printf("\r\n\r\n");
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\r\n",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\n");
159             break;
160         case SC_STATUS_FIND_CHANNEL://請在此階段開啟APP進行配對
161             os_printf("SC_STATUS_FIND_CHANNEL\n");
162             break;
163         case SC_STATUS_GETTING_SSID_PSWD://獲取到Wi-Fi名稱和密碼
164             os_printf("SC_STATUS_GETTING_SSID_PSWD\n");
165             sc_type *type = pdata;
166             if (*type == SC_TYPE_ESPTOUCH) {//判斷類型,在這一步發來的數據pdata中應該有配置類型
167                 os_printf("SC_TYPE:SC_TYPE_ESPTOUCH\n");
168             } else {
169                 os_printf("SC_TYPE:SC_TYPE_AIRKISS\n");
170             }
171             break;
172         case SC_STATUS_LINK://開始連接Wi-Fi
173             os_printf("SC_STATUS_LINK\n");
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\n");
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\n",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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM