學習筆記-ESP32_Wifi_Camera:SmartConfig編碼規則及解碼(以及crc踩坑;附python3代碼)

購買了一個帶有wifi的攝像頭, 沒有usb, 沒有以太網, 沒有藍牙......如何配置這個攝像頭接入家中的wifi?

一、代碼

1.SmartConfig

from socket import *
import time
import struct
from app.libs.crc8 import Crc8


def creat_content(ssid, password_, ip, bssid):
    password = password_ + '1'
    guide_code = [515, 514, 513, 512]
    password_len = len(password)
    ssid_len = len(ssid)
    total_len = 5 + 4 + password_len + ssid_len
    ssid_crc = get_crc(ssid, 1)
    bssid_list = get_bssid_list(bssid)

    bssid_crc = get_crc(bssid_list, 0)
    datum_data = [total_len, password_len, ssid_crc, bssid_crc]
    ip_address = get_ip_list(ip)
    ap_password = [ord(str) for str in password]
    ap_ssid = [ord(str) for str in ssid]
    data = ip_address + ap_password + ap_ssid
    total_data = datum_data + data
    total_data_xor = 0
    for num in total_data:
        total_data_xor ^= num
    datum_data.append(total_data_xor)
    return guide_code, datum_data, data


def get_crc(list, str):
    crc = Crc8()
    list_num = list
    if str:
        list_str = list
        list_num = [ord(str) for str in list_str]
    [crc.update(num) for num in list_num]
    return crc.value & 0xFF


def get_ip_list(ip):
    return [int(num) for num in ip.split('.')]


def get_bssid_list(bssid):
    return [int(num, 16) for num in bssid.split(':')]


def get_upper_lower(num):
    high_ = num & 0xF0
    high = high_ >> 4
    low = num & 0x0F
    word = [high, low]
    return word[0], word[1]


def get_content(guide_code, datum_data, data):
    messages = []
    content_data = datum_data + data
    length = len(content_data)
    for k in range(20):
        messages.extend(guide_code)
    for j in range(25):
        for k, num in enumerate(content_data):
            crc = get_crc([num, k], 0)
            crc_high, crc_low = get_upper_lower(crc)
            data_high, data_low = get_upper_lower(num)
            message_1 = (crc_high*16 + data_high + 40) & 0x1FF
            message_2 = 296 + k % length
            message_3 = (crc_low*16 + data_low + 40) & 0x1FF
            message = [message_1, message_2, message_3]
            messages.extend(message)
    return messages


def add_contents(num_list):
    all_msg = []
    for num in num_list:
        words = []
        for i in range(num):
            words.append('a')
        one_msg = ''.join(words)
        all_msg.append(one_msg)
    return all_msg


MYGROUP = '224.1.1.1'
MYPORT = 7001


class Camera_UDP:

    def __init__(self, port):
        from app.libs.robot import Robot
        robot = Robot()
        self.ip = robot.ip
        self.bssid = robot.bssid
        self.addr = (robot.ip, port)

        self.s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
        self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.s.bind(self.addr)

        ttl_bin = struct.pack('@i', 255)
        self.s.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, ttl_bin)
        self.s.setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, inet_aton(MYGROUP) + inet_aton(self.ip))

    def send_message(self, ssid, passphrase):
        guide_code, datum_data, data = creat_content(ssid, passphrase, self.ip, self.bssid)
        messages = get_content(guide_code, datum_data, data)
        k = 0
        while True:
            for i, msg in enumerate(add_contents(messages)):
                if i % 5 == 0:
                    k += 1
                MYGROUP = '224' + '.' + str(k % 100) + '.' + str(k % 100) + '.' + str(k % 100)
                self.s.sendto(bytes(msg, 'utf-8'), (MYGROUP, MYPORT))


class Camera_TCP:

    def __init__(self, port):
        from app.libs.robot import Robot
        robot = Robot()
        self.ip = robot.ip
        self.connected = False
        self.addr = (robot.ip, port)
        self.client_addr = ''
        self.s = socket(AF_INET, SOCK_STREAM)
        self.s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.s.bind(self.addr)
        self.s.listen(2)

    def receive_message(self):
        data_temp = ['', '', '']
        data_time = [0.0, 0.0, 0.0]
        count = 0
        while True:
            connetion, client_address = self.s.accept()
            try:
                print("Connection from: ", client_address, 'count: ', count)
                data = connetion.recv(1024)
                data_temp[count] = data
                data_time[count] = time.time()
                if count > 0:
                    if data == data_temp[count-1] and time.time() - data_time[count-1] < 3:
                        count += 1
                    else:
                        count = 0
                elif count == 0:
                    count += 1
                if count == 2:
                    self.connected = True
                    count = 0
                    [connetion.send(bytes('OK', 'utf-8')) for i in range(10)]
                    print('Connected! IP: ', data)
            finally:
                connetion.close()

2.crc8

crc8採用標準多項式x8+x5+x4+1,依次對byte和seq做crc8校驗,低位先傳

class Crc8:
    CRC_POLYNOM = 0x8c  # 低位傳輸時標準多項式
    CRC_INITIAL = 0x00

    value = CRC_INITIAL  # 全局變量

    # 計算crcTable,即0~255對應的crc8值
    dividend = 0
    remainder = 0
    crcTable = []
    for i in range(1000):
        crcTable.append(0)
    while dividend < 256:
        remainder = dividend
        bit = 0
        while bit < 8:
            bit = bit + 1
            # 與標準多項式異或操作
            if (remainder & 0x01) != 0:
                remainder = (remainder >> 1) ^ CRC_POLYNOM
            # 如果最低位是0則向右移一位
            else:
                remainder = remainder >> 1
        crcTable[dividend] = remainder
        dividend = dividend + 1

    # 用於計算一串數據的crc8,例如:DatumData中的ssid_crc8
    # self.value是之前所有數據得到的crc8值
    # tep是此次輸入的需要計算crc8的數字
    def update(self, tep):
        i = 0
        value = self.value
        while i < 1:
            i = i + 1
            # 當前數據與此次輸入的數據取異的值作爲本次用來計算crc8的數據:data
            data = tep ^ value
            # data與0xff和操作取到數據低8位的值,通過crcTable得到對應的crc8值:data_crc8
            # 當前數據左移8位然後與data_crc8異或得到新的數據,低8位爲剛剛計算得到的data_crc8
            value1 = (self.crcTable[data & 0xff] ^ (value << 8))
            self.value = value1

二、SmartConfig編碼原理

1. 介紹
攝像頭一開始並未連接wifi網絡,於是電腦向當前接入的AP發送一系列UDP包,其中每一包的長度(Length字段)都按照SmartConfig通訊協議編碼,當前網絡的SSID和密碼就包含在Length字段中。此時攝像頭應工作在在混雜模式下,在混雜模式下,攝像頭將收到當前環境下所有wifi設備(AP/STATION)數據,通過一定的算法,識別數據包,解析出數據包發送方IP地址等信息,以及其中的SSID和密碼,並接入網絡,然後返回自己的IP地址。然後電腦和攝像頭即可建立TCP連接。

2.發送規則

1.GuideCode發送規則:

GuideCode由515/514/513/512組成,4包爲一組,需要連續發送10組以上。通過Wireshark抓包發現不同設備發送的前導碼數量並不完全一致。

2.DatumData和Data發送規則:

一組由3包組成,每組傳送1字節有效數據,每包Length爲9bit,每一組由如下格式數據組成:

① sequence header: 從某個數開始,每一組數據加1

② 每一包數據內的3個數據在組包完成後都須 + 40

3. 編碼內容

攝像頭髮送的數據由三部分組成:“GuideCode”、“DatumData”、“Data”

1) GuideCode:

GuideCode由515/514/513/512幾個數字組成

例如:

2) DatumData:

DatumData由5部分組成,分別爲:”totaldata_len” 、”password_len” 、”ssid_crc8” 、“bssid_crc8” 、“total_data_xor”

① totaldata_len(總數據長度;1字節)= 固定5字節(DatumData) + IP地址長度+ 密碼長度 + SSID長度

② password_len(密碼長度;1字節)

③ ssid_crc8(ssid的crc8結果;1字節)

④ bssid_crc8(bssid的crc8結果;1字節)

⑤ total_data xor(全部數據異或結果;1字節)

注意:SSID長度需要根據當前網絡是否隱藏判斷是否需要加入,如當前是隱藏網絡,totaldata_len須加ssid_len;,時Data區也須加ssid,否則不加此2項內容;但total_data_xor必須加入SSID計算xor。

例如:

解碼結果:

① totaldata_len: 0x1a ((5+4+10)+7)

② password_len: 0x0a

③ ssid_crc8: 0x84

④ bssid_crc8: 0xae

⑤ total_data_xor: 0xe8

3) Data:

Data由3部分組成,分別爲: “ip_address”、 “ap_password” 、“ap_ssid”

① ip_address(IP地址;4字節)

② ap_password

③ ap_ssid

例如:

解碼結果:

① ip_address: ”172.22.79.2”

② ap_password: ”1234567890”

三、crc8原理

CRC校驗算法,就是把需要校驗的數據與多項式進行循環異或(XOR),但進行XOR的方式與實際中數據傳輸時,是高位先傳、還是低位先傳有關。對於數據高位先傳的方式,XOR從數據的高位開始;對於數據低位先傳的方式,XOR從數據的低位開始。兩種不同的異或方式,即使對應相同的多項式,計算出來的結果也是不一樣的。 

例如:數據高位先傳
使用多項式:x8+x5+x4+1(二進制爲:100110001) 
計算一個字節:0x11(二進制爲:00010001) 
計算步驟: 
1)因爲採用順序異或,所以需要計算的數據左移8位,移位後數據爲:0001 0001 0000 000 
2)先進行高9bit異或(多項式爲9bit),0001 0001 0000 0000,因爲高9bit的最高bit爲0,不需要進行異或,同理,接下來的兩bit也是0,也不需要進行進行異或。這樣處理後數據爲:1 0001 0000 0000
3)接下來最高位爲1,需要進行異或操作了 

從上面的計算過程可以看到,多項式最高位爲1,遇到需要異或數據最高位爲1時,才進行異或計算,並且異或後,最高位就爲0了,最高位爲0,下次也不需要異或了,這樣需要採用代碼計算的方式,就可以把最高位去掉,不需要異或,最後結果也是一樣的。

四、參考

https://blog.csdn.net/flyingcys/article/details/54670688

https://github.com/1zlab/1ZLAB_ESP32_Wifi_Camera/blob/master/%E6%96%87%E6%A1%A3/ESP-CAM%E7%9A%84%E7%AE%80%E6%98%93%E4%BD%BF%E7%94%A8.md

http://depletionregion.blogspot.com/2013/10/cc3000-smart-config-transmitting-ssid.html

https://blog.csdn.net/zjli321/article/details/52998468

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章