在前面網(wǎng)絡(luò)篇TCP通信中,我們用到了Wi-Fi功能,但是沒(méi)有細(xì)講,今天我們?cè)谶@篇文章中仔細(xì)探討一下ESP8266的Wi-Fi聯(lián)網(wǎng)過(guò)程,以及各種方式的配網(wǎng)過(guò)程是如何實(shí)現(xiàn)的。文章中難免有疏漏或不正確的地方,如遇不正確的表述還請(qǐng)指出,本系列文章現(xiàn)在在知乎和博客園同步更新。
開(kāi)始寫(xiě)之前,我們先來(lái)看一下為什么設(shè)備需要配網(wǎng)呢?
我們來(lái)先看個(gè)圖片:

假如你買(mǎi)了一個(gè)智能插座,想讓它連接到家里的Wi-Fi,但是智能插座又沒(méi)有鍵盤(pán)和觸摸屏此時(shí)你該怎么操作呢?總不能拆開(kāi)然后重新給它寫(xiě)個(gè)程序吧!此時(shí)就需要我們的智能配網(wǎng)模式出馬了, SmartConfig最先是TI開(kāi)始研究的,其實(shí)類似的協(xié)議有很多,各家也都有各家的稱呼,樂(lè)鑫這里也還是叫SmartConfig,但是配網(wǎng)模式也不僅僅局限于SmartConfig,還是有很多方式可以做到的,比如看下邊~
下面分析一下幾種配網(wǎng)方式和優(yōu)缺點(diǎn):
- 直接配網(wǎng) -> SSID(Wi-Fi名稱)和PWD(Wi-Fi密碼)保存在設(shè)備中(每次修改都要重新燒錄代碼)
- ap配網(wǎng) -> 設(shè)備處于路由模式下等待客戶端發(fā)送來(lái)SSID和PWD(可以隨時(shí)修改,不易于操作)
- web配置 -> 設(shè)備內(nèi)做了個(gè)小web服務(wù)器通過(guò)網(wǎng)頁(yè)交換SSID和PWD(界面化操作,流程繁瑣)
- SmartConfig -> 手機(jī)通過(guò)軟件發(fā)送UDP廣播包(包含SSID和PWD,界面化操作,操作簡(jiǎn)單)
- Airkiss -> 類似與SmartConfig,可以使用微信公眾號(hào)直接配置(界面化操作,不需要裝APP)
- 零配 -> 以配網(wǎng)設(shè)備為未配網(wǎng)設(shè)備配網(wǎng),兩個(gè)設(shè)備間數(shù)據(jù)交互(AliOS-Things中有涉及)
- 藍(lán)牙配網(wǎng) -> 利用藍(lán)牙設(shè)備配網(wǎng),藍(lán)牙模塊跟ESP8266串口數(shù)據(jù)交互(未使用過(guò),不做評(píng)價(jià))
目前應(yīng)該就這幾種吧,據(jù)我了解應(yīng)該就這幾種了,每種方式都有一定的優(yōu)缺點(diǎn),本篇文章先給大家講解一下前五種方式,零配方式目前我還沒(méi)有嘗試過(guò),后面實(shí)際開(kāi)發(fā)過(guò),再給大家講解如何使用,至于藍(lán)牙配網(wǎng)手頭沒(méi)有藍(lán)牙模塊,這里也不給大家講了,不過(guò)思路是很簡(jiǎn)單的,大家如果有藍(lán)牙模塊,或者有BLE開(kāi)發(fā)經(jīng)驗(yàn),相信自己摸索一下就可以做出來(lái)了。
- 直接配網(wǎng)
這里叫直接配網(wǎng)應(yīng)該不是很妥當(dāng),其實(shí)就是將SSID和PWD直接寫(xiě)在了固件中,設(shè)備上電后會(huì)去搜索保存的SSID,如果搜索到指定的SSID后就用保存的PWD去連接Wi-Fi,TCP通信那片文章中我們就是用的這種方式,我們先來(lái)回顧一下,在user_config.c文件中有如下定義:

這就是我們保存的Wi-Fi名稱和密碼,然后我們?cè)趗ser_init函數(shù)中直接調(diào)用的是Wi-Fi連接函數(shù):

可以看出,我們是直接使用保存的信息去進(jìn)行Wi-Fi操作的,這種方式比較簡(jiǎn)單,這里就不再做具體的講解了。
- ap配網(wǎng)
AP(Access Point)模式,就是我們常說(shuō)的路由模式,這里的思路是ESP8266上電進(jìn)入AP模式開(kāi)啟TCP Server,然后手機(jī)或者PC連接ESP8266的熱點(diǎn),然后作為T(mén)CP Client去連接Server進(jìn)行數(shù)據(jù)交互,TCP數(shù)據(jù)交互我們?cè)谇懊娴奈恼轮薪o大家講過(guò),這里我們主要講一下ESP8266怎么開(kāi)啟AP模式,還有為什么我們讓ESP8266作為T(mén)CP Server呢?其實(shí)主要是ESP8266做Server去監(jiān)聽(tīng)固定端口更方便Client去連接,加入我們手機(jī)或者PC做Server,萬(wàn)一遇到端口不可用,那豈不是沒(méi)辦法去交互數(shù)據(jù)了?所以我們讓ESP8266做Server很顯然是更妥當(dāng)一點(diǎn)。
還有比較重要的一點(diǎn)就是數(shù)據(jù)格式,數(shù)據(jù)格式就像我們交流的語(yǔ)言一樣,是兩者都能夠"聽(tīng)懂","讀懂"的,我們這里使用一種非常常見(jiàn)的數(shù)據(jù)格式 -> JSON,相信很多人都有聽(tīng)說(shuō)過(guò),或者使用過(guò),我們選擇JSON,是因?yàn)樗母袷焦潭ǎ⑶乙子诮馕?,這里我們使用cJSON庫(kù)去解析,官方SDK中雖然也有JSON API,但是由于我一直都是使用cJSON,對(duì)cJSON庫(kù)還是比較喜歡的,而且是用標(biāo)準(zhǔn)C寫(xiě)的,跨平臺(tái)so easy,項(xiàng)目地址戳卡片:
使用cJSON非常簡(jiǎn)單,只需要添加一個(gè)頭文件和源文件就好了,但是在ESP8266上使用還是要修改很多東西的,這里就先不細(xì)講了,大家可以先下載我的工程,直接使用,后面我們?cè)賳为?dú)講一下JSON。
我們先規(guī)定一下數(shù)據(jù)格式吧,還不了解JSON的話可以先去了解一下,很簡(jiǎn)單的其實(shí),就是Key-Value型的鍵值對(duì),就是一個(gè)名字對(duì)應(yīng)一個(gè)值,這里我們就簡(jiǎn)單定一個(gè)格式吧!

其中XXXXXXXX和XXXXXXXX都是需要替換成你的實(shí)際Wi-Fi名稱和密碼,剩下兩項(xiàng)沒(méi)有實(shí)際意義,只是防止有人不署名轉(zhuǎn)載,然后幫助讀者回到正確的車上~啊哈哈哈!
我們來(lái)分析一下代碼吧,跟之前的TCP通信差不多,這是這里對(duì)收到的數(shù)據(jù)做了進(jìn)一步處理,我們只看關(guān)鍵代碼好了:
/**
* TCP Server數(shù)據(jù)接收回調(diào)函數(shù),可以在這處理收到Client發(fā)來(lái)的數(shù)據(jù)
*/
static void ICACHE_FLASH_ATTR
tcp_server_recv_cb(void *arg,char *pdata,unsigned short len){
os_printf("tcp server receive tcp client data\r\n");
os_printf("length: %d \r\ndata: %s\r\n",len,pdata);
//TO DO
/**
*process the receive data
*/
AP_recv_data_process(pdata);//在TCP Server接收回調(diào)函數(shù)中,新增了數(shù)據(jù)處理函數(shù)
}
然后我們?cè)倏纯碅P_recv_data_process函數(shù)是如何處理的:
/**
* AP配網(wǎng)模式處理TCP client發(fā)送來(lái)的數(shù)據(jù)
*/
void ICACHE_FLASH_ATTR
AP_recv_data_process(uint8 *pdata){
cJSON *root = cJSON_Parse(pdata);//將一個(gè)JSON數(shù)據(jù)包,按照cJSON結(jié)構(gòu)體的結(jié)構(gòu)序列化整個(gè)數(shù)據(jù)包,并在堆中開(kāi)辟一塊內(nèi)存存儲(chǔ)cJSON結(jié)構(gòu)體
//成功返回一個(gè)指向內(nèi)存塊中的cJSON指針,失敗返回NULL,表示JSON格式不正確
if(root != NULL){
uint8 *SSID = cJSON_GetObjectItem(root,"SSID")->valuestring;//解析SSID key對(duì)應(yīng)的Value值,也就是我們發(fā)送的Wi-Fi名稱
uint8 *PWD = cJSON_GetObjectItem(root,"PWD")->valuestring;//解析PWD key對(duì)應(yīng)的Value值,也就是我們發(fā)送的Wi-Fi密碼
uint8 *Author = cJSON_GetObjectItem(root,"Author")->valuestring;//
uint8 *zhuanlan = cJSON_GetObjectItem(root,"zhuanlan")->valuestring;//
os_printf("\r\nAuthor: %s\r\nzhuluan: %s",Author,zhuanlan);
os_printf("SSID: %s PWD: %s\r\n",SSID,PWD);//打印我們收到的密碼
wifi_set_opmode(STATION_MODE);//設(shè)置WiFi模式為STATION模式
WIFI_Connect(SSID, PWD, wifiConnectCb);//連接目標(biāo)WiFi
}else{
os_printf("json data format error!\r\n");//收到的數(shù)據(jù)不正確就打印
}
cJSON_Delete(root);//解析完JSON后一定要記得釋放!
}
在此函數(shù)中,我們只需對(duì)收到的數(shù)據(jù)簡(jiǎn)單一處理,就得到了我們需要的Wi-Fi名稱和密碼,然后我們就可以快快樂(lè)樂(lè)的去連接Wi-Fi了,這里AP配網(wǎng)模式是按鍵觸發(fā)的,我們?cè)赪IFI_Connect函數(shù)中調(diào)用了wifi_station_set_config函數(shù),下次上電是會(huì)直接連接這次我們配置的Wi-Fi的
-
SmartConfig配網(wǎng)
下面我們?cè)賮?lái)分析一下SmartConfig配網(wǎng)模式,其實(shí)SmartConfig模式與Airkiss模式基本是一致的,主要不同就是發(fā)數(shù)據(jù)包方式還是有點(diǎn)不同的:
SmartConfig 組播,通過(guò)長(zhǎng)度編碼
Airkiss 全網(wǎng)廣播,通過(guò)長(zhǎng)度編碼
別的區(qū)別的話就是代碼有些不同了,下面我們來(lái)實(shí)際分析一下:
/**
* SmartConfig 測(cè)試代碼
*/
void ICACHE_FLASH_ATTR
SmartConfig_test(void){
smartconfig_set_type(SC_TYPE_ESPTOUCH);//設(shè)置快連模式類型,必須在smartconfig_start之前調(diào)用
wifi_set_opmode(STATION_MODE);
smartconfig_start(smartconfig_done);
}
/**
* Airkiss 測(cè)試代碼
*/
void ICACHE_FLASH_ATTR
Airkiss_test(void){
smartconfig_set_type(SC_TYPE_AIRKISS);//設(shè)置快連模式類型,必須在smartconfig_start之前調(diào)用
wifi_set_opmode(STATION_MODE);
smartconfig_start(smartconfig_done);
}
/**
* SmartConfig Airkiss 測(cè)試代碼
*/
void ICACHE_FLASH_ATTR
SmartConfig_Airkiss_test(void){
smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS);//設(shè)置快連模式類型,必須在smartconfig_start之前調(diào)用
wifi_set_opmode(STATION_MODE);
smartconfig_start(smartconfig_done);
}
/*===================================
* Smartconfig Airkiss FUNCTIONS *
====================================*/
/**
*
*/
const airkiss_config_t akconf =
{
(airkiss_memset_fn)&memset,
(airkiss_memcpy_fn)&memcpy,
(airkiss_memcmp_fn)&memcmp,
0,
};
/**
*
*/
LOCAL void ICACHE_FLASH_ATTR
airkiss_wifilan_time_callback(void)
{
uint16 i;
airkiss_lan_ret_t ret;
if ((udp_sent_cnt++) >30) {
udp_sent_cnt = 0;
os_timer_disarm(&ssdp_time_serv);//airkiss_wifilan_time_callback運(yùn)行三十次后停止運(yùn)行
//return;
}
//設(shè)置本地udp發(fā)送端口號(hào),IP地址設(shè)置成255.255.255.255是進(jìn)行廣播,UDP有單播,多播和廣播三種模式
ssdp_udp.remote_port = DEFAULT_LAN_PORT;
ssdp_udp.remote_ip[0] = 255;
ssdp_udp.remote_ip[1] = 255;
ssdp_udp.remote_ip[2] = 255;
ssdp_udp.remote_ip[3] = 255;
lan_buf_len = sizeof(lan_buf);
ret = airkiss_lan_pack(AIRKISS_LAN_SSDP_NOTIFY_CMD,
DEVICE_TYPE, DEVICE_ID, 0, 0, lan_buf, &lan_buf_len, &akconf);
if (ret != AIRKISS_LAN_PAKE_READY) {
os_printf("Pack lan packet error!");
return;
}
ret = espconn_sendto(&pssdpudpconn, lan_buf, lan_buf_len);
if (ret != 0) {
os_printf("UDP send error!");
}
os_printf("Finish send notify!\n");//UDP發(fā)送完成
}
/**
*
*/
LOCAL void ICACHE_FLASH_ATTR
airkiss_wifilan_recv_callbk(void *arg, char *pdata, unsigned short len)
{
uint16 i;
remot_info* pcon_info = NULL;
airkiss_lan_ret_t ret = airkiss_lan_recv(pdata, len, &akconf);
airkiss_lan_ret_t packret;
switch (ret){
case AIRKISS_LAN_SSDP_REQ:
espconn_get_connection_info(&pssdpudpconn, &pcon_info, 0);
os_printf("remote ip: %d.%d.%d.%d \r\n",pcon_info->remote_ip[0],pcon_info->remote_ip[1],
pcon_info->remote_ip[2],pcon_info->remote_ip[3]);
os_printf("remote port: %d \r\n",pcon_info->remote_port);
pssdpudpconn.proto.udp->remote_port = pcon_info->remote_port;
os_memcpy(pssdpudpconn.proto.udp->remote_ip,pcon_info->remote_ip,4);
ssdp_udp.remote_port = DEFAULT_LAN_PORT;
lan_buf_len = sizeof(lan_buf);
packret = airkiss_lan_pack(AIRKISS_LAN_SSDP_RESP_CMD,
DEVICE_TYPE, DEVICE_ID, 0, 0, lan_buf, &lan_buf_len, &akconf);
if (packret != AIRKISS_LAN_PAKE_READY) {
os_printf("Pack lan packet error!");
return;
}
os_printf("\r\n\r\n");
for (i=0; i<lan_buf_len; i++)
os_printf("%c",lan_buf[i]);
os_printf("\r\n\r\n");
packret = espconn_sendto(&pssdpudpconn, lan_buf, lan_buf_len);
if (packret != 0) {
os_printf("LAN UDP Send err!");
}
break;
default:
os_printf("Pack is not ssdq req!%d\r\n",ret);
break;
}
}
/**
*
*/
void ICACHE_FLASH_ATTR
airkiss_start_discover(void)
{
ssdp_udp.local_port = DEFAULT_LAN_PORT;//設(shè)置本地端口
pssdpudpconn.type = ESPCONN_UDP;//設(shè)置通信方式為UDP
pssdpudpconn.proto.udp = &(ssdp_udp);//
espconn_regist_recvcb(&pssdpudpconn, airkiss_wifilan_recv_callbk);//注冊(cè)收到數(shù)據(jù)回調(diào)函數(shù)
espconn_create(&pssdpudpconn);//創(chuàng)建一個(gè)UDP傳輸
os_timer_disarm(&ssdp_time_serv);
os_timer_setfn(&ssdp_time_serv, (os_timer_func_t *)airkiss_wifilan_time_callback, NULL);//注冊(cè)airkiss定時(shí)回調(diào)函數(shù)
os_timer_arm(&ssdp_time_serv, 1000, 1);//1s
}
/**
*
*/
static void ICACHE_FLASH_ATTR
smartconfig_done(sc_status status, void *pdata)
{
switch(status) {
case SC_STATUS_WAIT://連接未開(kāi)始,請(qǐng)勿在此階段開(kāi)始連接
os_printf("SC_STATUS_WAIT\n");
break;
case SC_STATUS_FIND_CHANNEL://請(qǐng)?jiān)诖穗A段開(kāi)啟APP進(jìn)行配對(duì)
os_printf("SC_STATUS_FIND_CHANNEL\n");
break;
case SC_STATUS_GETTING_SSID_PSWD://獲取到Wi-Fi名稱和密碼
os_printf("SC_STATUS_GETTING_SSID_PSWD\n");
sc_type *type = pdata;
if (*type == SC_TYPE_ESPTOUCH) {//判斷類型,在這一步發(fā)來(lái)的數(shù)據(jù)pdata中應(yīng)該有配置類型
os_printf("SC_TYPE:SC_TYPE_ESPTOUCH\n");
} else {
os_printf("SC_TYPE:SC_TYPE_AIRKISS\n");
}
break;
case SC_STATUS_LINK://開(kāi)始連接Wi-Fi
os_printf("SC_STATUS_LINK\n");
struct station_config *sta_conf = pdata;
wifi_station_set_config(sta_conf);
wifi_station_disconnect();
wifi_station_connect();
break;
case SC_STATUS_LINK_OVER://獲取到IP,連接路由完成
os_printf("SC_STATUS_LINK_OVER\n");
if (pdata != NULL) {//連接完成,如果使用的是SmartConfig,此時(shí)手機(jī)會(huì)將自己的IP地址發(fā)給ESP8266
//SC_TYPE_ESPTOUCH
uint8 phone_ip[4] = {0};
os_memcpy(phone_ip, (uint8*)pdata, 4);
os_printf("Phone ip: %d.%d.%d.%d\n",phone_ip[0],phone_ip[1],phone_ip[2],phone_ip[3]);
} else {//練成完成,如果是使用的Airkiss方式,到這一步還沒(méi)有完成,還會(huì)跟微信進(jìn)行數(shù)據(jù)交互,應(yīng)該告知微信配網(wǎng)完成之類的
//SC_TYPE_AIRKISS - support airkiss v2.0
airkiss_start_discover();//開(kāi)始Airkiss內(nèi)網(wǎng)發(fā)現(xiàn)
}
smartconfig_stop();//SmartConfig 完成
break;
}
}
這里的代碼稍微有點(diǎn)多,不過(guò)其實(shí)是三種模式,SmartConfig模式、Airkiss模式、SmartConfigAirkiss混合模式,這是樂(lè)鑫官方給出的例子,某些步驟我按我的理解給注釋了一下,大家可以仔細(xì)閱讀以下看看,其中需要注意的是在調(diào)用smartconfig_start函數(shù)之前必須先調(diào)用smartconfig_set_type函數(shù)聲明配網(wǎng)模式類型。
Airkiss配網(wǎng)
與SmartConfig類似,代碼在上邊,大家可以看一下。Web頁(yè)面配置
Web頁(yè)面配置也相對(duì)不是很難,主要是對(duì)表單的處理,但是要把網(wǎng)頁(yè)寫(xiě)在ESP8266的flash中,最近沒(méi)什么時(shí)間,等改天有時(shí)間了再給大家分析一下,不過(guò)ESP8266內(nèi)置網(wǎng)頁(yè)的Demo在我的Github倉(cāng)庫(kù)中有一個(gè),大家感興趣的可以先去看一下,然后看一下自己是否能實(shí)現(xiàn)。
最后上一段小視頻,手機(jī)錄制的,不是很好,大家將就著看,還有些小瑕疵,這里就不剪了。也怪麻煩的,主要看一下流程,可能講的也不是很詳細(xì),有什么建議可以私信我哦~
視頻有點(diǎn)大,處理后再上傳~(后期可以在B站觀看)
附上我的倉(cāng)庫(kù)地址:
makingfunxyz-esp8266
歡迎大家Star,您的鼓勵(lì)是我最大的動(dòng)力,有問(wèn)題可以私信我,或者提交issues~
本系列文章在知乎,博客園同步更新,知乎搜索專欄:IAMLIUBO的神奇物聯(lián)網(wǎng)之旅
視頻可以去知乎上的文章查看~
QQ交流群:592587184