开源 | 智能家居场景,基于 AriKiss 配网的微信小程序

一次偶然的机会,群里某开发者问我为什么他的微信配网出问题了,我跟着回复了些问题,发现并不是那么简单,于是乎帮这位朋友适配了下并成功了,决定在全网首个开源可实现Airkiss配网的微信小程序。

25e301a512c7210233c45e3cc452249a.png

获取源码请见文章底部。

1

揭开微信配网的神秘面纱

AirKiss微信配网虽然是2016年开放的技术,但一直是智能家居配网领域值得深究的话题!因为在实际Wi-Fi产品用到太多了,今天大家依然非常关注这个问题,今天我就用安信可 ESP-C3-12F 模组调试,全面给大家解析我是如何在微信小程序上实现这个协议的。

首先我们理清几个问题,也是我移植前所思考的问题:

1. airkiss实现的原理?协议?配网过程?

2. 对于airkiss源码,有什么途径获取?可以从设备端、前端拿到协议对比吗?

3. 对于一键配网的弊端,你是如何优化的?为什么有些路由器怎么都无法配网?

4. 微信小程序这块支持本地组网?如何调用相关函数方法?

了解AirKiss协议:

AirKiss是微信硬件平台为Wi-Fi设备提供的微信配网、局域网发现和局域网通讯的技术。开发者若要实现通过微信客户端对Wi-Fi设备配网、通过微信客户端在局域网发现Wi-Fi设备,或者把微信客户端内的音乐、图片、文件等消息通过局域网发送至Wi-Fi设备,需要在硬件设备中集成相应的AirKiss静态库。

14de6619eb0555b485a0bcfcea35c5e2.png

AirKiss配网的步骤:

1. 安信可ESP-C3-12F模组开启station模式,并抓取空中的混杂UDP包;

2. 手机微信客户端通过AirKiss发送家里的路由器ssid和密码;

3. 安信可ESP-C3-12F模组通过抓包获取到ssid和密码,然后连接到家里的路由器;

4. 安信可ESP-C3-12F模组局域网发送UDP包给手机客户端表示配网成功;

AirKiss配网的基本原理:

1. 混杂模式

安信可ESP-C3-12F模组刚开始同样是以Station的模式运行,但是还有一个监听模式,我们也称为嗅探探针。是什么意思?它是指正常的wifi设备都有一个MAC地址,其硬件电路会自动过滤目标MAC地址跟其MAC不同的数据包。开启混杂模式就是我们平常时说的抓包,就是空中符合802.11格式的数据包都接收进来,不管MAC是否一样。很明显,手机智能配置APP并不知道该wifi设备的MAC地址,所以手机wifi发送出的数据包,通过家里的路由器转发出去时,wifi设备必须要在混杂模式下才能接收到这些数据包。

2. 信道切换

802.11有多个信道,某一个时候wifi设备和路由器都处于某一个信道。路由器一般都是默认在第六信道,所以要想家里的网信号好一点,可以尝试将路由器的信道改到一个其他道,这样就不会和邻居家的wifi信道重叠了。wifi信号混在同一个频道就会互相干扰。

同理,我们也不能假定wifi设备是处于哪个信道,但是我们可以在app中确定手机wifi的发送信道,这样要求wifi设备在一定的时刻切换信道,以便与接收到数据包。当wifi设备检测到有效的数据包,要锁定在该信道进行后续的通信。

3. 利用数据帧的长度来承载有效信息

我们先来看一看802.2 SNAP(802.11的物理层协议)的数据帧格式

81bdffeb2fe5c4541be24b517dc49b1e.png

DA字段表示目标mac地址,SA字段表示源mac地址,Length字段表示后面数据的长度,LLC字段表示LLC头,SNAP字段包括3bytes的厂商代码和2bytes的协议类型标识,DATA字段为负载,对于加密信道来说是密文的,FCS字段表示帧检验序列。

从无线信号监听方的角度来说,不管无线信道有没有加密DA、SA、Length、LLC、SNAP、FCS字段总是暴露的,因此信号监听方便有了从这6个字段获取信息的可能。但从发送方的角度来说,由于操作系统的限制(比如Android或者IOS),DA、SA、LLC、SNAP、FCS五个字段的控制需要很高的控制权限,发送方一般是很难拿到的。因此只剩下Length这一字段,发送方可以通过改变其所需要发送数据包的长度进行很方便的控制。

所以,只要制定出一套利用长度编码的通信协议,那么就可利用802.2 SNAP 数据包中的Length字段进行信息传递。

在实际应用中,我们采用UDP广播包作为信息的载体。信息发送方向空间中发送一系列的UDP广播包,其中每一包的长度(即Length字段)都按照AirKiss通信协议进行编码,信息接收方利用混杂模式监听空间中的无线信号,并从数据链路层截取802.2 SNAP格式数据包,便可得到已编码的Length字段,随后接收方便可根据AirKiss通信协议解析出需要的信息。

26de9ece85d3a4e48a7601c0d13a3a41.png

2

ESP-C3-12F模组AirKiss源码分析

安信可ESP-C3-12F是基于乐鑫ESP32C3芯片做的一个支持WiFi+蓝牙的模组,具体参数规格请移步到安信可官网,下面只给大家简单分析AirKiss配网源码的每个关键步骤。

开启嗅探模式,抓取空中的 802.11 包,并传给微信静态库解码。

static void wifi_promiscuous_rx(void *buf, wifi_promiscuous_pkt_type_t type)
{
    wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *) buf;
    uint8_t *payload;
    uint16_t len;
    int ret;
    payload = pkt->payload;
    len = pkt->rx_ctrl.sig_len;
    //把空中的全部802.11包传给微信静态库来做解析
    ret = airkiss_recv(ak_ctx, payload, len);
    //符合AirKiss包的规则,开始锁定此信道进行解码
    if (ret == AIRKISS_STATUS_CHANNEL_LOCKED) {
        esp_timer_stop(channel_change_timer);
        esp_timer_delete(channel_change_timer);
        ESP_LOGI(TAG, "AIRKISS_STATUS_CHANNEL_LOCKED");
    //解析完成,停止嗅探   
    } else if (ret == AIRKISS_STATUS_COMPLETE) {
        esp_wifi_set_promiscuous(false);
        airkiss_finish();
        ESP_LOGI(TAG, "AIRKISS_STATUS_COMPLETE");
    }
}

成功解析出来路由器账号密码,并开始连接路由器。

wifi_config_t wifi_config;
    int err;
    err = airkiss_get_result(ak_ctx, &result);
    if (err == 0) {
        //这里的 result 就是一个结构体,里面包含ssid、password等信息
    } else {
        ESP_LOGI(TAG, "airkiss_get_result() failed !");
    }

通过UDP协议发一个ack给手机以通知配网成功,数据是一个长度为7的数组。

struct sockaddr_in server_addr;
    uint8_t buf[7];
    socklen_t sin_size = sizeof(server_addr);


    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_BROADCAST;
    server_addr.sin_port = htons(10000);
    //第1个buff是随机数,这个随机数是手机发来的
    buf[0] = (uint8_t)ak_random_num;
    //第2~6是设备mac地址
    esp_wifi_get_mac(WIFI_IF_STA, &buf[1]);
    //开始发送
    sendlen = sendto(send_socket, buf, 7, 0,(struct sockaddr *) &server_addr, sin_size);

3e9c2c4bbeb88897c44017e404e93a13.png

3

小程序AirKiss源码分析

先写好对应的组包规则,PrefixCode以及Sequence序列包:

function magicCode(ssid, password) {
  let bytes = strToUtf8Bytes(ssid);
  let length = bytes.length + password.length + 1;
  let magicCode = [];
  magicCode[0] = 0x00 | (length >>> 4 & 0xF);
  if (magicCode[0] == 0) {
    magicCode[0] = 0x08;
  }
  magicCode[1] = 0x10 | (length & 0xF);
  let crc8 = CRC8(bytes);
  magicCode[2] = 0x20 | (crc8 >>> 4 & 0xF);
  magicCode[3] = 0x30 | (crc8 & 0xF);
  for (let i = 0; i < 20; ++i) {
    for (let j = 0; j < 4; ++j) {
      appendEncodedData(magicCode[j]);
    }
  }
}


function prefixCode(password) {
  let length = password.length;
  let prefixCode = [];
  prefixCode[0] = 0x40 | (length >>> 4 & 0xF);
  prefixCode[1] = 0x50 | (length & 0xF);
  let crc8 = CRC8([length]);
  prefixCode[2] = 0x60 | (crc8 >>> 4 & 0xF);
  prefixCode[3] = 0x70 | (crc8 & 0xF);
  // console.log(prefixCode.join());
  for (let j = 0; j < 4; ++j) {
    appendEncodedData(prefixCode[j]);
  }
}

发包前,也务必开启UDP监听,等待设备配网成功的ack信息。

wxUdp = wx.createUDPSocket();
  //监听端口是 10000
  let port = wxUdp.bind(10000);
  var replyByteCounter = 0;
  //监听函数
  wxUdp.onMessage(function(res) {
    if (res.remoteInfo.size > 0) {
         //处理下数据,得到设备的IP和bssid
        callback({"ip":res.remoteInfo.address,"bssid":bssid,"result": "success", "code":1});
      }
  });

开始发包,注意地址是 255.255.255.255 表示全网段:

let sendData = new ArrayBuffer(mEncodedData[index]);
  wxUdp.send({
    address: "255.255.255.255",
    port: 10000,
    message: sendData
  });

4

如何集成到我的项目去

考虑到开发者集成方便,我已封装成小程序插件并上架了,大家拿来即可上手集成到自己的项目去。

自行注册一个微信小程序,请下载最新版的微信开发者工具。

新建项目之后,打开 app.js文件添加下面代码:

"plugins": {
    "airkiss": {
      "version": "1.1.0",
      "provider": "wx610ea582556c983e"
    }
  }

然后,会有提示是否添加插件,按照下面提示添加插件使用。

69cf7f7fc60c6090ea1122954dd66302.png

下面举例说明了如何使用,更多使用技巧和方法参考本小程序源码,源码在文章底部。

const airkiss = requirePlugin('wx610ea582556c983e');


//获取版本
console.log( airkiss.version)


//这里最好加微信小程序判断账号密码是否为空,以及其长度和是否为5G频段
airkiss.startAirkiss(this.data.ssid, this.data.password, function (res) {
           switch (res.code) {
               case 0:
                    wx.showModal({
                        title: '初始化失败',
                        content: res.result,
                        showCancel: false,
                        confirmText: '收到',
                    })
                   break;
               case 1:
                   wx.showModal({
                        title: '配网成功',
                        content: '设备IP:' + res.ip + '\r\n 设备Mac:' + res.bssid,
                        showCancel: false,
                        confirmText: '好的',
                    })
                    break;
               case 2:
                   wx.showModal({
                        title: '配网失败',
                        content: '请检查密码是否正确',
                        showCancel: false,
                        confirmText: '收到',
                    })
                   break;


               default:
                   break;
            }


})
//停止配网,建议在页面 unload 等生命周期里面调用,释放线程
airkiss.stopAirkiss()

看到这里,希望你会喜欢。

后台发送“AirKiss”获取微信小程序源代码;

往期推荐

1、云厂商的「物联网平台」不香了吗?

2、2021国内四大IoT平台性能对比

3、国内MCU行业发展研究报告

4、2021年4G通信模组企业排行

5、艾瑞 2021中国 IoT物联网平台研究

6、动图|带你了解PCB板制作过程?

94c208090299c309f073db196fe9c65e.gif

猜你喜欢

转载自blog.csdn.net/klandor2008/article/details/121586512#comments_26377233