【IoT】物聯網之 WIFI 一鍵配網 smartConfig 淺析(ESP32)

一、背景

物聯網時代技術開始規模化服務於民衆,方便快捷顯得尤爲重要,WIFI 直連便是一個典型案例。

目前主流的 WIFI 配置模式有以下 2 種:

1、智能硬件處於 AP 模式(類似路由器,組成局域網),手機用於 STA 模式

手機連接到處於 AP 模式的智能硬件後組成局域網,手機發送需要連接路由的 SSID 及密碼至智能硬件,智能硬件主動去連接指定路由後,完成配網

2、一鍵配網(smartConfig)模式

智能硬件處於混雜模式下,監聽網絡中的所有報文;手機 APP 將 SSID 和密碼編碼到 UDP 報文中,通過廣播包或組播報發送,智能硬件接收到 UDP 報文後解碼,得到正確的 SSID 和密碼,然後主動連接指定 SSID 的路由完成連接。

AP 模式:

AP 是 (Wireless) Access Point 的縮寫,即 (無線) 訪問接入點。簡單來講就像是無線路由器一樣,設備打開後進入 AP 模式,在手機的網絡列表裏面,可以搜索到類似 TPLINK_XXX 的名字(SSID)。

連接步驟:

1、智能硬件設備初始化並進入 AP 模式
2、手機掃描 WIFI 列表:掃描到智能硬件設備後(SSID)連接該智能硬件設備,通過 UDP 發送 經過 AES 加密過的 ssid/password/token
3、智能硬件設備通過 UDP 包獲取配置信息,切換網絡模式連接 WIFI 後配網完成
在這裏插入圖片描述
smartConfig 模式:

這種快速連接方式,相對於 AP 模式連接簡化操作,更加貼近於市場

1、手機連上 WiFi,打開智能硬件指定 APP 軟件,進入配置界面,輸入手機所在 WiFi 密碼,請求配網 TOKEN
2、智能硬件開啓混雜模式監聽所有網絡數據包
3、手機通過廣播、組播循環發送 ssid/password/token
4、硬件設備通過 UDP 包(長度)獲取配置信息捕捉到 ssid/password/token,連接路由器(廣播根據 UDP 包長度,組播根據 IP 地址信息)
在這裏插入圖片描述
從原理上講只要芯片驅動支持開啓混雜模式(WiFi Promiscuous),就可以支持一鍵配網功能

手機編碼發送採用有 UDP 組播或廣播,不同的發送方式和編碼,對應的解碼過程也不一樣

1、廣播:

發送方可通過改變其所需要發送數據包的長度進行控制,因此只要指定出一套利用長度編碼的通訊協議,就可利用數據包的 Length 字段進行數據傳遞

2、 組播:

組播地址是保留的 D 類地址從224.0.0.0-239.255.255.255

IP 地址與 MAC 地址映射關係爲:將 MAC 地址的前 25 位設定爲 01.00.5e,而 MAC 地址的後 23 位對應 IP 地址位

故發送端可以將數據編碼在組播 IP 的後 23bit 中,通過組播包發送接收端進行解碼即可
在這裏插入圖片描述
二、smartConfig 原理淺析

在沒有和其他設備(支持 smartConfig 技術)建立任何性質的通訊鏈路的情況下, 配置該設備接入 WIFI 網絡

普通權限的應用程序是沒有能力完全控制和定義傳輸層及下層所有協議數據的, 唯一可以完全控制的就是應用層數據

本質上就是將 UDP 包頭的數據長度作爲 smartConfig 的數據,APP 端和設備端共用一套編碼表即可解析數據

TCP/IP 協議棧中的網絡層和傳輸層的數據結構

常用的網絡層協議是 IPv4, IPv4 的頭部絕大多數情況下都是定長的20字節

傳輸層協議是 UDP, 因爲 UDP 協議頭部爲定長的 8 字節

明文長度 = 20 + 8 + dataLen

密文長度 = 20 + 8 + dataLen + 算法常量

例子:

如果需要發出一個密文長度爲 500 字節的 802.11 幀,只需要在 UDP 中填充任意(500 – 20 – 8 – 算法常亮)個字節數據即可

因此,只需要利用可控的密文長度(dataLen)定義一張編碼表即可將數據告訴任何知道這張編碼表的設備(IoT硬件設備)

自定義一張編碼表,流程如下:

dataLen --> 映射
1234 --> 起始符; 連續的3個起始符, 用於表示數據傳輸開始
1324 --> 結束符; 連續的3個結束符, 用於表示數據傳輸結束
110 --> 間隔符; 連續的2個間隔符, 用於表示數據符之間的間隔
1000 --> 數據符; 表示 ASCII 0x00
1001 --> 數據符; 表示 ASCII 0x01

1127 --> 數據符; 表示 ASCII 0x7F
在這裏插入圖片描述
假設我們要把字符串"Jay"告訴攝像頭, 整個流程大致如下: (假設算法常亮爲 16)

APP 端:

打開手機 APP, 在輸入框中填入要發送的字符串”Jay”, 點擊發送:

1.1、APP 連續發送 3 個 UDP 廣播包, 填充數據爲 1190 個字節 0x00 數據 ( 1234 – 16 – 20 – 8 = 1190 ), 表示傳輸開始
1.2、APP 發送 1 個 UDP 廣播包, 填充數據爲 1030 個字節 0x00 數據 ( 1074 – 16 -20 – 8 = 1030 ), 傳輸字符 J
1.3、APP 連續發送 2 個 UDP 廣播包, 填充數據爲 66 個字節 0x00 數據 ( 110 – 16 – 20 – 8 = 66 ), 表示數據間隔
1.4、APP 發送 1 個 UDP 廣播包, 填充數據爲 1053 個字節 0x00 數據 ( 1097 – 16 -20 – 8 = 1053 ), 傳輸字符 1097 對應 a
1.5、APP 連續發送 2 個 UDP 廣播包, 填充數據爲 66 個字節 0x00 數據 ( 110 – 16 – 20 – 8 = 66 ), 表示數據間隔
1.6、APP 發送 1 個 UDP 廣播包, 填充數據爲 1077 個字節 0x00 數據 ( 1121 – 16 -20 – 8 = 1077 ), 傳輸字符 1121 對應 y
1.7、APP 連續發送 3 個 UDP 廣播包, 填充數據爲 1280 個字節 0x00 數據 ( 1324- 16 – 20 – 8 = 1280 ), 表示傳輸結束

從步驟 1.1 開始循環多次, 直到超時或 IoT 設備成功接入 WIFI

IoT 設備端:

設備上電進入混雜模式,開始監聽信號覆蓋範圍內的所有 WIFI 數據幀

捕獲數據幀, 如果連續收到 3 個密文,其數據長度 dataLen 爲 1234 字節, 且來自於同一個發射源 channel-A 的數據幀, 則進入下一步, 否則該步驟

捕獲發射源 channel-A 的數據幀, 持續捕獲密文數據長度爲 110 或 1000-1127 之間的數據幀, 直到捕獲到連續 3 個密文數據長度爲 1324 的數據幀

將上述數據幀按照編碼表進行映射, 由於手機 APP 並非是獨佔網絡, 所以捕獲到的數據可能有噪音, 比如解碼出來的結果可能是(/表示分隔符): mnJ/o@a/ymmm

如果沒有噪音, 記爲候選數據RC, 重複捕獲X, 進行二次驗證, 通過則表示接收完成, 沒通過也重複捕獲 channel-A,

將這次所得結果同上一次做交集, 循環如此直到得出唯一結果, 即 RC, 之後再重複 5

由於捕獲的數據幀頭部信息中已經包含了 WIFI 的 BSSID 信息, 使用 “Jay” 作爲密碼去嘗試連接相應的 WIFI

三、ESP32 配網實例:

手機 APP:

ESP-TOUCH

燒寫設備端:

#include "WiFi.h"
 
void setup() {
  Serial.begin(115200);
 
  //Init WiFi as Station, start SmartConfig
  WiFi.mode(WIFI_AP_STA);
  WiFi.beginSmartConfig();
 
  //Wait for SmartConfig packet from mobile
  Serial.println("Waiting for SmartConfig.");
  while (!WiFi.smartConfigDone()) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("SmartConfig received.");
 
  //Wait for WiFi to connect to AP
  Serial.println("Waiting for WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("WiFi Connected.");
 
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

四、知識擴展

在當前網絡通信中有三種通信模式:

單播、廣播、組播(又叫多播),其中多播出現的時間最晚,但同時具備單播和廣播的優點,最具有發展前景.

通信方式分類:

1.單播:單臺主機與單臺主機之間的通信;

2.廣播:單臺主機與網絡中所有主機的通信;

3.組播:單臺主機與選定的一組主機的通信;

單播:

單播是網絡通信中最常見的,網絡節點之間的通信 就好像是人們之間的對話一樣,如果一個人對另外一個人說話,那麼用網絡技術的術語來描述就是“單播”,此時信息的接收和傳遞只在兩個節點之間進行。

  1. 單播的優點:

(1)服務器以及響應客戶端的請求;

(2)服務器能針對每個客戶端的不同請求發送不同的響應,容易顯示個性化服務;

  1. 單播的缺點:

服務器針對每個客戶機發送數據流,服務器流量=客戶機數量×客戶機流量;在客戶數量大、每個客戶機流量大的流媒體應用中服務器不堪重負;

  1. 應用場景:

單播在網絡中得到了廣泛的應用,網絡上絕大部分的數據都是以單播的形式傳輸的,例如:收發電子郵件、遊覽網頁時,必須與郵件服務器、服務器建立連接,此時使用的就是單播通信方式;

//UDP 單播:
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
 
// 客戶端
public class ClientTest
{
    private static final int MAXRECEIVED = 255;
 
    public static void main(String[] args) throws IOException
    {
        byte[] msg = new String("connect test successfully!!!").getBytes();
 
        DatagramSocket client = new DatagramSocket();
 
        InetAddress inetAddr = InetAddress.getLocalHost();
        SocketAddress socketAddr = new InetSocketAddress(inetAddr, 8888);
 
        DatagramPacket sendPacket = new DatagramPacket(msg, msg.length,
                socketAddr);
 
        client.send(sendPacket);
 
        client.close();
    }
}
 
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
 
//服務端
public class ServerTest
{
    private static final int MAXREV = 255;
 
    public static void main(String[] args) throws IOException
    {
        DatagramSocket server = new DatagramSocket(8888);
        DatagramPacket recvPacket = new DatagramPacket(new byte[MAXREV], MAXREV);
 
        while (true)
        {
            server.receive(recvPacket);
 
            byte[] receiveMsg = Arrays.copyOfRange(recvPacket.getData(),
                    recvPacket.getOffset(),
                    recvPacket.getOffset() + recvPacket.getLength());
 
            System.out.println("Handing at client "
                    + recvPacket.getAddress().getHostName() + " ip "
                    + recvPacket.getAddress().getHostAddress());
 
            System.out.println("Server Receive Data:" + new String(receiveMsg));
 
            server.send(recvPacket);
 
        }
 
    }
}

廣播:

廣播可以比作爲一個人通過廣播喇叭對在場的全體說話,換句話說: 廣播是一臺主機對某一個網絡上的所有主機發送數據報包。

這個網絡可能是網絡,也可能時子網,還有可能是所有子網。

廣播有兩類:本地廣播和定向廣播:

定向廣播:將數據報包發送到本網絡之外的特定網絡的所有主機,互聯網上的大部分路由器都不轉發定向廣播消息;

本地廣播:將數據報包發送到本地網絡的所有主機,IPv4的本地廣播地址爲“255.255.255.255”,路由器不會轉發此廣播;

  1. 廣播的優點:

(1) 通信的效率高,信息一下子就可以傳遞到某一個網絡上的所有主機。

(2) 由於服務器不用向每個客戶端單獨發送數據,所以服務器流量比較負載低;

  1. 廣播的缺點:

(1) 非常佔用網絡的帶寬;

(2) 缺乏針對性,也不管主機是否真的需要接收該數據, 就強制的接收數據;

  1. 應用場景:

有線電視就是典型的廣播型網絡

UDP 廣播:
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
 
//客戶端
public class BroadcastSender
{
    public static void main(String[] args) throws IOException
    {
        byte[] msg = new String("connection successfully!!!").getBytes();
        /*
         * 在Java UDP中單播與廣播的代碼是相同的,要實現具有廣播功能的程序只需要使用廣播地址即可, 例如:這裏使用了本地的廣播地址
         */
        InetAddress inetAddr = InetAddress.getByName("255.255.255.255");
        DatagramSocket client = new DatagramSocket();
 
        DatagramPacket sendPack = new DatagramPacket(msg, msg.length, inetAddr,
                8888);
 
        client.send(sendPack);
        System.out.println("Client send msg complete");
        client.close();
    }
}
 
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
 
//服務端
public class BroadcastReceive
{
    public static void main(String[] args) throws IOException
    {
 
        DatagramPacket receive = new DatagramPacket(new byte[1024], 1024);
        DatagramSocket server = new DatagramSocket(8888);
 
        System.out.println("---------------------------------");
        System.out.println("Server current start......");
        System.out.println("---------------------------------");
 
        while (true)
        {
            server.receive(receive);
 
            byte[] recvByte = Arrays.copyOfRange(receive.getData(), 0,
                    receive.getLength());
 
            System.out.println("Server receive msg:" + new String(recvByte));
        }
 
    }
}

組播:

組播可以比作爲你對着大街喊:女士免費領優惠券,那麼女士就會過來,男士就不會過來(組播:其中所有的女士就是一個組)

換句話說:

組播是一臺主機向指定的一組主機發送數據報包,因爲如果採用單播方式,逐個節點傳輸,有多少個目標節點就會有多少次傳送過程,這種方式顯然效率極低,是不可取的;

如果採用不區分目標、全部發送的廣播方式,雖然一次可以傳送完數據,但是顯然達不到區分特定數據接收對象的目的,又會佔用網絡帶寬。

採用組播方式,既可以實現一次傳送所有目標節點的數據,也可以達到只對特定對象傳送數據的目的;

IP 網絡的組播一般通過組播 IP 地址來實現,組播 IP 地址就是 D 類 IP 地址,即 224.0.0.0 至 239.255.255.255 之間的IP地址。

  1. 組播的優點:

(1) 具備廣播所具備的所有優點;

(2) 與單播相比,提供了發送數據報包的效率,與廣播相比,減少了網絡流量;

  1. 組播的缺點:

與單播協議相比沒有糾錯機制,發生丟包錯包後難以彌補,但可以通過一定的容錯機制和QOS加以彌補;

UDP 組播:
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
 
//客戶端
public class MulticastSender
{
    public static void main(String[] args) throws IOException
    {
        int port = 8888;
        byte[] msg = "Connection successfully!!!".getBytes();
 
        InetAddress inetRemoteAddr = InetAddress.getByName("224.0.0.5");
 
        /*
         * Java UDP組播應用程序主要通過MulticastSocket實例進行通信,它是DatagramSocket的是一個子類,
         * 其中包含了一些額外的可以控制多播的屬性.
         * 
         * 注意:
         * 
         * 多播數據報包實際上可以通過DatagramSocket發送,只需要簡單地指定一個多播地址。
         * 我們這裏使用MulticastSocket,是因爲它具有DatagramSocket沒有的能力
         */
        MulticastSocket client = new MulticastSocket();
 
        DatagramPacket sendPack = new DatagramPacket(msg, msg.length,
                inetRemoteAddr, port);
 
        client.send(sendPack);
 
        System.out.println("Client send msg complete");
 
        client.close();
 
    }
}
 
 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Arrays;
 
//服務端
public class MulticastReceive
{
    public static void main(String[] args) throws IOException
    {
        InetAddress inetRemoteAddr = InetAddress.getByName("224.0.0.5");
 
        DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024);
 
        MulticastSocket server = new MulticastSocket(8888);
 
        /*
         * 如果是發送數據報包,可以不加入多播組; 如果是接收數據報包,必須加入多播組; 這裏是接收數據報包,所以必須加入多播組;
         */
        server.joinGroup(inetRemoteAddr);
 
        System.out.println("---------------------------------");
        System.out.println("Server current start......");
        System.out.println("---------------------------------");
 
        while (true)
        {
            server.receive(recvPack);
 
            byte[] recvByte = Arrays.copyOfRange(recvPack.getData(), 0,
                    recvPack.getLength());
 
            System.out.println("Server receive msg:" + new String(recvByte));
        }
 
    }
}
發佈了119 篇原創文章 · 獲贊 66 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章