admin管理员组

文章数量:1531472

写在前面

站在巨人的肩膀上,可以看得更远

一、什么是Air Kiss

可以说AirKiss 是微信硬件平台提供的一种WIFI硬件设备快速配置连接网络的技术,是一种创新性的信息传递技术。通过该技术可以便捷的向一台具有WIFI功能的设备传递配网信息,比如wifi的ssid,密码。

在嵌入式设备或没有交互功能的智能设备,需要连接网络时,使用手机将设备需要配网的ap信息发送到空中,待配网设备捕获所发送的信息,通过解析获取需要的ssid和pwd. 示意图如下所示

 二、应用场景

随着移动互联网与物联网技术的发展,越来越多的设备具有了无线网络的接入能力,促使物联网进入大爆发阶段,这些设备的特点是小型化,低功耗,大多设备没有配置屏幕以及键盘,因此将设备连接网络就变成比较困难的问题,怎样将无线网络的SSID与密码传输到设备上,AirKiss技术正是为解决这种难题产生。通过该技术,不借助任何外设的情况下,通过已经接入ap的设备向没有连接ap的设备传递ssid和密码。类似设备有智能手环,无线监控,智能相册等。

AirKiss配网基本流程:

1,wifi设备能够以混杂模式sniffer运行

2,手机上安装微信客户端或含有该协议的其他应用

3,wifi设备通过抓包获取手机上发送的ssid 和密码,连接无线WiFi

三、技术原理

802.11是IEEE制定的无线局域网协议,802.11以802.2的逻辑链路控制封装来携带IP包因此能够以802.2SNAP格式接收无线网络数据。开启WIFI芯片的混杂模式监听空中的无线信号,并以802.2SNAP格式从数据链路层截取数据,得到SNAP数据包

--DA       目标MAC 地址

--SA        源MAC地址

--Length  数据的长度

--LLC       LLC头

--SNA      SNAP包括3bytes的厂商代码和2bytes的协议类型标识

--DATA    密文

--FCS      帧检验序列

怎样利用这个数据包发送我们想要送出去的数据呢?从无线信号监听角度讲, 各个字段都是暴露在外的,因此,Monitor模式监听可以获取相应的信息。但是从发送方来说,因为操作系统的限制,想拿到DA、SA、LLC、SNAP、FCS五个字段的控制需要很高的权限,一般很难拿到,所以只有Length一个字段给我们使用。发送方可以通过改变其所需要发送数据包的长度进行很方便的控制。所以,只要制定出一套利用长度编码的通信协议,就可利用802.2SNAP数据包中的Length字段进行信息传递。

实现原理:在实际应用中,采用UDP广播包的形式作为信息的载体。信息发送方向空间中发送一连串的UDP广播包,每一包的长度都按照Air Kiss协议进行编码,信息接收方利用混杂模式监听空间中的无线信号,并从数据链路层截取SNAP格式数据包,得到已经编码的Length字段,然后接收方按照约定的AirKiss协议解析出需要的信息。

四、通信协议

在信号载体方面,采用wifi无线信号进行信息传递,1-14信道支持。

在信号编码方面,802.2SNAP数据包中的Length字段为数据发送方唯一可控字段。所以AirKiss通信协议就是利用发送数据包中的长度编码,在本协议中,把Length字段编码位数限制在9bit,udp广播的最大长度为512字节。

在实际应用中,很有可能在一个空间中存在多个AP,AP可能分布在相同或不相同的信道上,配网设备处在混杂模式,接收信息不知道是在哪一个信道上,而且在同一个信道上也有可能很多设备在发送UDP广播包,就造成了监听接收到的信息是海量的,设备在接收到的大量信息中定位出发送方所在的信道和发送方的mac地址。第二个就是UDP广播包经过IP层,数据链路层的封装,加密,导致发送方发送UDP广播包的长度以接收方SNAP包中的Length字段存在差异,而且,由于加密方式的不确定,使得差异值也具有不确定性。

所以,在发送链路层数据之前,需要先发送400ms的前导域,前导域由4个字节组成,固定为1,2,3,4,接收方在一定时间内收到差值为1,2,3,4的连续数值,接收方接收到这些前导域数据包后, 利用SNAP包的Length字段与之相减,得到差异值,例如收到53,54,55,56,差异值就是53-1=52,固定在这个信道,以后收到的数据SNAP报的Length减去52,就获得实际信息数据。

链路层协议 

链路层数据结构分为两类,control字段和data字段,control字段包括magic code、prefix code 、sequence header field,data段只有一个data field,control字段和data字段怎么区别呢?他们以第8bit位区别,该位为1表示data field字段,为0表示control字段。

control字段中, magic code 和 prefix code 字段相同,magic code 字段与sequence header 字段通过第7bit位加以区分,改位为1表示sequence header, 0 表示magic code, prefix code;

(一) magic code 字段

magic code 由4个9bits组成,高5位位magic code, 低4位为information字段,前两个9bits的 information字段装载的是要发送数据长度,后面两个9bits的information装载的是要发送的crc8值。我们知道路由器的SSID是一直被路由以广播的形式发送到四周,使wifi设备能搜到该路由,这样我们在开始时获得搜到的路由的ssid,计算出对应ssid的CRC存储起来,与我们接收到的magic code 的CRC值一样,就可以匹配出ssid,减少传输数据,加快配网速度,所以magic code 可以适当多发送。

(二)prefix code 字段

 表格中描述的比较清楚了,前两个information字段装载要发送密码长度,后面两个information装载密码长度的crc8的值,它的作用是告诉接收端接下来要接收密码的长度,以便接收完数据后,对数据进行分割解密。

(三)sequence header 字段

 我们把待发送的数据以4字节划分,每4个数据组成一个sequence, 以sequence为单位进行数据发送,每个sequence都有sequence header 字段和data字段组成,最后一个sequence如果不够4个数据也不用补全。sequence header字段由两个9bits组成,第一个的低7位装载从本sequence index开始到本sequence结束发送的所有数据的crc8的低7位,在接收完一个sequence的数据后,进行crc8值校验,不同的话就说明接收错误,抛掉。data字段就是4个数据帧,包含要发送的数据。

(四)data 字段

data字段的数据结构可以看出,data字段由4个9bits组成,第8位为控制位,固定值为1,0-7位为需要传输的数据。

五、应用层协议

应用要发送的数据有三部分:密码,随机数,SSID。随机数的作用是当设备连上路由后,发送以随机数为内容的UDP广播包,当应用收到含该随机数广播包后就能确认设备已经准确接收到所有数据了,传输的都已字符串形式发送,以'\0'结尾,AES加密,发送的逻辑顺序是先发送密码,在发送随机数,最后发送ssid. 

 

六、案例

 设备端的主程序main.c,采用先搜索周围所有ap的ssid,保存下来,解决解析ssid中文错误问题,采用多线程处理,提高配网速度

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

#include "capture/common.h"
#include "capture/osdep.h"
#include "utils/wifi_scan.h"
#include "airkiss.h"
#include "utils/utils.h"

#define CHANNELS_LENGTH 16
#define CHANNELS_TEMP_LENGTH 13

typedef struct{
    char essid[MAX_ESSID_SIZE];
    char bssid[MAX_BSSID_SIZE];
    unsigned char essid_crc;
    unsigned int freq;
    int power;
    int channel;
} wifi_info;

static airkiss_context_t *akcontex = NULL;
const airkiss_config_t akconfig = {
    (airkiss_memset_fn)&memset,
    (airkiss_memcpy_fn)&memcpy,
    (airkiss_memcmp_fn)&memcmp,
    (airkiss_printf_fn)&printf
};

airkiss_result_t ak_result;
struct wif *wi = NULL;

int g_channels[CHANNELS_LENGTH] = {1,6,11};
int g_channels_index = 0;
char * iface;

int switch_flag = 1;
int thread_run = 1;
struct timeval start;
pthread_mutex_t lock;

wifi_info g_wifi_info[50]; 

int udp_broadcast(unsigned char random, int port);
int wi_reset_manager(const char * iface);
/**
 * 信道排序
 */
void sort_channels(int * channels, int length){
    int i, j, temp, m_length=0;
    for(i=3;i<length; i++){
        if(channels[i] != 0){
            m_length++;
        }
    }
    printf("%s: %d",__func__, m_length);
    for(i=0; i < m_length-1; i++){
        for(j=3; j < m_length-1-i+3; j++){
            if(channels[j] > channels[j+1]){
                temp = channels[j+1];
                channels[j+1] = channels[j];
                channels[j] = temp;
            }
        }
    }
}

void switch_channel_func(){
    int ret = 0;
    pthread_mutex_lock(&lock);
    if(g_channels[g_channels_index] == 0 && g_channels[g_channels_index] < 20){
        g_channels_index = 0;
    }
    printf("%s: switch channel %d\n", __func__, g_channels[g_channels_index]);
    ret = wi->wi_set_channel(wi, g_channels[g_channels_index]);
    if (ret)
    {
        LOG_TRACE("Can't set channel to %d", g_channels[g_channels_index]);
    }
    airkiss_change_channel(akcontex);
    g_channels_index++;
    gettimeofday(&start, NULL); 
    pthread_mutex_unlock(&lock);
}


int process_airkiss(const unsigned char *frame, int size){
    int ret = 0;
    int i=0;

    ret = airkiss_recv(akcontex, (void *)frame, size);
    switch (ret)
    {
    case AIRKISS_STATUS_CONTINUE:

        break;
    case AIRKISS_STATUS_CHANNEL_LOCKED:
        //锁定信道, 停止切换操作
        LOG_TRACE("Lock channel %d", wi->wi_get_channel(wi));
        switch_flag = 0;
        break;
    case AIRKISS_STATUS_COMPLETE:
        //解析完成,获取数据
        airkiss_get_result(akcontex, &ak_result);
        for(i=0;i<50;i++){
            //printf("%s: channel = %d, gssid_crc = %x, crc = %x \n",__func__, g_wifi_info[i].channel, g_wifi_info[i].essid_crc, ak_result.ssid_crc);
            if(g_wifi_info[i].channel != 0 && ak_result.ssid_crc == g_wifi_info[i].essid_crc){
                    printf("%s: gessid = %s, ssid = %s\n", __func__,g_wifi_info[i].essid, ak_result.ssid);
                   if(strcmp(g_wifi_info[i].essid, ak_result.ssid)){
                       printf("%s: err ssid = %s \n", __func__, ak_result.ssid);
                       ak_result.ssid = g_wifi_info[i].essid;
                       break;
                   }else{
                       //一样的
                       break;
                   }
            }
        }
        LOG_TRACE("\nResult:  ssid_crc: %x, random = 0x%02x, pwd:%s, ssid:%s",
            ak_result.ssid_crc,ak_result.random, ak_result.pwd, ak_result.ssid);

		wi_reset_manager(iface);
        //连接网络操作

        //ap 连接网络后发送random
        udp_broadcast(ak_result.random, 24333);
        break;
    case AIRKISS_STATUS_DATAFIELD_TIMEOUT:
        LOG_TRACE(" Airkiss parse data field timeout");
        switch_flag = 1;
        break;
    default:
        break;
    }


    return ret;
}

void * switch_channel_thread_func(void *arg){
    struct timeval end;
    long time_value;

    printf("%s: start \n", __func__);
    while (thread_run)
    {
        usleep(200);
        if (switch_flag)
        {
            gettimeofday(&end, NULL);

            time_value = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;

            if (time_value > 0.5* 1000 * 1000 && switch_flag)
            {
                switch_channel_func();
            }
        }
    }
    return 0;
}

void * wi_thread_func(void *arg){
    int read_size;
    unsigned char buf[RECV_BUFSIZE] = {0};

    while(true){
        read_size = wi->wi_read(wi, buf, RECV_BUFSIZE, NULL);
        if(read_size < 0){
            LOG_ERROR("recv failed, ret = %d", read_size);
            break;
        }
        if(AIRKISS_STATUS_COMPLETE == process_airkiss(buf, read_size)) break;
    }

    thread_run = 0;
    return 0;
}

int udp_broadcast(unsigned char random, int port){
    int fd;
    int enabled = 1;
    int err;
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_BROADCAST;
    addr.sin_port = htons(port);

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0){
        LOG_ERROR("Create socket failed");
        return -1;
    }

    err = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *)&enabled, sizeof(enabled));
    if(err == -1){
        close(fd);
        return -1;
    }

    LOG_TRACE("Sending random to broadcast:");
    int i;
    useconds_t usecs = 20 * 1000;
    for(i=0; i<50; i++){
        sendto(fd, (unsigned char *)&random, 1, 0,(struct sockaddr *)&addr, sizeof(addr));
        usleep(usecs);
    }
    close(fd);

    return 0;
}

int wi_reset_manager(const char * iface){
    int fd;
    fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if(fd < 0){
        LOG_ERROR(" socket failed");
        return -1;
    }
    if(wi_reset_monitor_osdep(fd, iface) < 0){
        LOG_ERROR(" reset monitor failed");
        close(fd);
        return -1;
    }
    close(fd);

    return 0;
}

/**
 *  从第三个开始加入, 保证前3个始终是初始的 1,6,11 
 */
void add_channel(int *array_channel,int channel, int index, int lenght){
    int i=0, j=0;
    for(i=index;i < lenght; i++){
        if (array_channel[i] == 0) //先找一个未被设置的位置,判断此channel是否在g_channels[3,end]存在,存在舍弃,否则加入
        {
            for(j=index; j < lenght; j++){
                if(array_channel[j] == channel) return;
            }
            array_channel[i] = channel;
        }
    }
}

void add_wifi_info(wifi_info info){
    int i,j,index;

    for(i=0; i< 50; i++){
        index = i;
        if(g_wifi_info[i].channel == 0){break;}        
    }

    for(j=0;j<=index;j++){
        if(!strcmp(g_wifi_info[j].bssid, info.bssid)){
            return;
        }
    }
    if(index >= 50) return;
    g_wifi_info[index] = info;

}

int main(int argc, char *argv[])
{
    int i = 0, j, ret;
    int init;
    wireless_scan_head head;
    wireless_scan *presult = NULL;
    int temp_channels[CHANNELS_TEMP_LENGTH] = {0};
    pthread_t ak_pthread, switch_pthread;

    if (argc != 2)
    {
        LOG_ERROR("Please input wifi module, ex: %s wlan0", argv[0]);
        return -1;
    }

    iface = argv[1];

    //设置wifi mode 为managed
    wi_reset_manager(iface);
    switch_flag = 1;
    thread_run = 1;

    //扫描环境中现有的路由信道,做两次操作,比较合并
    for (i = 0; i < 2; i++)
    {
        ret = wifi_scan(iface, &head);
        if (ret < 0)
            continue;

        presult = head.result;
        wifi_info info;
        while (presult != NULL)
        {
            get_essid(presult, info.essid, MAX_ESSID_SIZE);
            get_bssid(presult, info.bssid, MAX_BSSID_SIZE);
            info.freq = get_freq_mhz(presult);
            info.power = get_strength_dbm(presult);
            info.channel = getChannelFromFrequency(info.freq);
            info.essid_crc = airkiss_crc8((unsigned char *)info.essid, strlen(info.essid));

            LOG_TRACE("bssid:[%s], channel:[%2d], power:[%d dBm], essid_crc:[%02x], essid:[%s]",
                      info.bssid, info.channel, info.power, info.essid_crc, info.essid);
            add_channel(temp_channels, info.channel, 0, CHANNELS_TEMP_LENGTH);
            add_wifi_info(info);
            presult = presult->next;
        }
        for (j = 0; j < CHANNELS_TEMP_LENGTH; j++)
        {
            add_channel(g_channels, temp_channels[j], 3, CHANNELS_LENGTH);
        }
        usleep(1000);
    }

    sort_channels(g_channels, CHANNELS_LENGTH);
    printf("g_channels: ");
    for (i = 0; i < CHANNELS_LENGTH; i++)
    {
        printf("%d ", g_channels[i]);
    }
    printf("\n");

    wi = wi_open(iface);
    if (wi == NULL)
    {
        LOG_ERROR("Can't init interface %s", iface);
        return -1;
    }

    akcontex = (airkiss_context_t *)malloc(sizeof(airkiss_context_t));
    init = airkiss_init(akcontex, &akconfig);
    if (init != 0)
    {
        LOG_ERROR("Airkiss init failed");
        return -1;
    }

    LOG_TRACE("Airkiss version:%s", airkiss_version());

    pthread_mutex_init(&lock, NULL);
    gettimeofday(&start, NULL); 

    ret = pthread_create(&ak_pthread, NULL, wi_thread_func, NULL);
    if(ret == 0){
        LOG_TRACE("phtread create success");
    }else{
        LOG_TRACE("phtread create failed");
    }

    ret = pthread_create(&switch_pthread, NULL, switch_channel_thread_func, NULL);
    if(ret == 0){
        LOG_TRACE("phtread create success");
    }else{
        LOG_TRACE("phtread create failed");
    }

    pthread_join(ak_pthread,NULL);
    pthread_join(switch_pthread, NULL);
    free(akcontex);

    return 0;
}

协议端代码可以去github上找,有大神贡献的,在此不方便贴出来。

应用端app示例

MainActivity.java

package com.android.airkisswechat;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.ConnectivityManager;
import android.NetworkInfo;
import android.wifi.WifiInfo;
import android.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.DatagramPacket;
import java.DatagramSocket;
import java.InetAddress;
import java.SocketException;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

public class MainActivity extends AppCompatActivity {

    private static String TAG="Airkiss";

    @BindView(R.id.bt_confirm)
    Button mSendButton;

    @BindView(R.id.tv_ssid)
    TextView mSSIDTV;

    @BindView(R.id.et_password)
    EditText mPasswordEt;


    private boolean isSendPackage=false;

    private Subscription sendSubscribe;
    private Subscription receiveSubscribe;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        ActionBar bar = getSupportActionBar();
        bar.setLogo(getResources().getDrawable(R.mipmap.ic_launcher));
        bar.setDisplayUseLogoEnabled(true);
        bar.setDisplayShowHomeEnabled(true);
        bar.setTitle("  Scics-SmartConfig");

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (checkPermission(this)) {
            Context context = getApplicationContext();
            ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (networkInfo.isConnected()) {
                final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                final WifiInfo connectionInfo = wifiManager.getConnectionInfo();
                if (connectionInfo != null) {
                    String ssid = connectionInfo.getSSID();
                    if (Build.VERSION.SDK_INT >= 17 && ssid.startsWith("\"") && ssid.endsWith("\""))
                        ssid = ssid.replaceAll("^\"|\"$", "");
                    mSSIDTV.setText(ssid);
                    //mSSIDTV.setEnabled(false);
                }
            }
        }
    }

    //检查位置权限
    public boolean checkPermission(Activity context) {
        //9.0以前版本获取wifi ssid不用申请此权限
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return true;
        }
        int result = context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
        if (result == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
        String[] permission = {Manifest.permission.ACCESS_COARSE_LOCATION};
        context.requestPermissions(permission, 1001);
        return false;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode,  String[] permissions, int[] grantResults) {
        if (requestCode == 1001) {
            if (grantResults.length > 0 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {

            } else {
                AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
                alertDialog.setTitle("权限拒绝");
                alertDialog.setMessage("请在设置中打开此应用的位置权限后重试");
                alertDialog.setCancelable(false);
                alertDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                });
                alertDialog.show();
            }
        }
    }



    @OnClick(R.id.bt_confirm)
    public void onViewClicked(View view) {
        switch (view.getId()){
            case R.id.bt_confirm:
                if (!isSendPackage) {
                    isSendPackage = true;
                    onConnectBtnClick(view);
                    mSendButton.setText(R.string.tx_stop);
                }else{
                    isSendPackage = false;
                    mSendButton.setText(R.string.tx_confirm);
                }
                break;
        }
    }




    public void onConnectBtnClick(View view) {
        if (sendSubscribe != null && sendSubscribe.isUnsubscribed()) {
            sendSubscribe.unsubscribe();
        }
        if (receiveSubscribe != null && receiveSubscribe.isUnsubscribed()) {
            receiveSubscribe.unsubscribe();
        }
        final String ssid = mSSIDTV.getText().toString();
        final String password = mPasswordEt.getText().toString();
        if (ssid.isEmpty() || password.isEmpty()) {
            Context context = getApplicationContext();
            CharSequence text = "请输入wifi密码";
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
            return;
        }

        //发送AirKiss
        sendSubscribe = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext("start");
                byte DUMMY_DATA[] = new byte[1500];
                Log.i(TAG," send subscribe start  ");
                AirKissEncoder airKissEncoder = new AirKissEncoder(ssid, password);
                DatagramSocket sendSocket = null;
                try {
                    sendSocket = new DatagramSocket();
                    sendSocket.setBroadcast(true);
                    int encoded_data[] = airKissEncoder.getEncodedData();
                    for (int i = 0; i < encoded_data.length; ++i) {
                        DatagramPacket pkg = new DatagramPacket(DUMMY_DATA,
                                encoded_data[i],
                                InetAddress.getByName("255.255.255.255"),
                                10000);
                        sendSocket.send(pkg);
                        Thread.sleep(4);
                    }
                    Log.i(TAG," send subscribe oncmpleted  ");
                    subscriber.onCompleted();
                } catch (Exception e) {
                    subscriber.onError(e);
                    e.printStackTrace();
                } finally {
                    sendSocket.close();
                    sendSocket.disconnect();
                }
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<String>() {
                    ProgressDialog mDialog = new ProgressDialog(MainActivity.this);
                    @Override
                    public void onCompleted() {
                        Log.i(TAG,"send onCompleted");

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i(TAG,"send onError ");
                        Toast.makeText(MainActivity.this, "连接失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onNext(String string) {
                        Log.i(TAG,"send onNext ="+string);

                        mDialog.setTitle("配网");
                        mDialog.setMessage("发送配网信息...");
                        mDialog.setCancelable(false);
                        mDialog.show();
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                //发送配网ssid pwd,因为字节发送需要时间比较久,此处只是设置比较短的提示,但实际上后台线程还在发送中。
                                mDialog.dismiss();
                                receiveUDPPackage();
                            }
                        }, 5000);

                    }



                });




    }



    public void receiveUDPPackage(){
        //接收udp包
        receiveSubscribe = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                byte[] buffer = new byte[15000];
                DatagramSocket udpServerSocket = null;
                try {
                    udpServerSocket = new DatagramSocket(24333);
                    udpServerSocket.setSoTimeout(1000 * 60); //设置超时时间, 超过时间没有收到停止接收
                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                    while (true) {
                        Log.d(TAG, "running");
                        udpServerSocket.receive(packet);
                        buffer = packet.getData();
                        String hexString = Str_Hex.byte2hex(buffer);
                        Log.d("received:", hexString);
                        //对收到的UDP包进行解码
                        //各个设备返回的UDP包格式不一样  将解码的UDP包通过RxJava发送到主线程 进行UI处理
                        if (!TextUtils.isEmpty(hexString)) {
                            Log.d("received:", hexString);
                            subscriber.onNext(hexString);
                            break;
                        }
                    }

                    subscriber.onCompleted();
                } catch (SocketException e) {
                    Log.i(TAG,"receive socket exception");
                    subscriber.onError(e);
                    e.printStackTrace();
                } catch (IOException e) {
                    Log.i(TAG,"receive io exception");
                    subscriber.onError(e);
                    e.printStackTrace();
                } finally {
                    Log.i(TAG,"receive finally");
                    udpServerSocket.close();
                    udpServerSocket.disconnect();
                }
            }
        }).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<String>() {
                    ProgressDialog mDialog = new ProgressDialog(MainActivity.this);

                    @Override
                    public void onStart() {
                        super.onStart();
                        mDialog.setTitle("配网");
                        mDialog.setMessage("正在连接...");
                        mDialog.setCancelable(false);
                        mDialog.show();
                    }

                    @Override
                    public void onCompleted() {
                        mDialog.dismiss();
                        mSendButton.performClick();
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i(TAG,"receive onError");
                        Toast.makeText(MainActivity.this, "连接失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                        mDialog.dismiss();
                        mSendButton.performClick();
                    }

                    @Override
                    public void onNext(String s) {
                        Log.i(TAG,"receive onNext");
                        Toast.makeText(MainActivity.this, "收到的UDP包:" + s, Toast.LENGTH_SHORT).show();
                    }

                });
    }


    @Override
    protected void onStop() {
        Log.i(TAG, "onStop ");
        super.onStop();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy");
        if (sendSubscribe != null && sendSubscribe.isUnsubscribed()) {
            sendSubscribe.unsubscribe();
        }
        if (receiveSubscribe != null && receiveSubscribe.isUnsubscribed()) {
            receiveSubscribe.unsubscribe();
        }
        mSendButton.performClick();
    }

}

AirKissEncoder.java

package com.android.airkisswechat;

import java.util.Arrays;
import java.util.Random;

public class AirKissEncoder {
    private int mEncodedData[] = new int[2 << 15];
    private int mLength = 0;

    // Random char should be in range [0, 127).
    private char mRandomChar = (char)(new Random().nextInt(0x7F));

    public AirKissEncoder(String ssid, String password) {
        int times = 10;     //设置发送的数据组数,此处设为10,说明要发送10个这个的一串数据
        while (times-- > 0) {
            leadingPart();
            magicCode(ssid, password);

            for (int i = 0; i < 15; ++i) {
                prefixCode(password);
                String data = password + mRandomChar + ssid;
                int index;
                byte content[] = new byte[4];
                for (index = 0; index < data.length() / 4; ++index) {
                    System.arraycopy(data.getBytes(), index * 4, content, 0, content.length);
                    sequence(index, content);
                }

                if (data.length() % 4 != 0) {
                    content = new byte[data.length() % 4];
                    System.arraycopy(data.getBytes(), index * 4, content, 0, content.length);
                    sequence(index, content);
                }
            }
        }
    }

    public int[] getEncodedData() {
        return Arrays.copyOf(mEncodedData, mLength);
    }

    public char getRandomChar() {
        return mRandomChar;
    }

    private void appendEncodedData(int length) {
        mEncodedData[mLength++] = length;
    }

    private int CRC8(byte data[]) {
        int len = data.length;
        int i = 0;
        byte crc = 0x00;
        while (len-- > 0) {
            byte extract = data[i++];
            for (byte tempI = 8; tempI != 0; tempI--) {
                byte sum = (byte) ((crc & 0xFF) ^ (extract & 0xFF));
                sum = (byte) ((sum & 0xFF) & 0x01);
                crc = (byte) ((crc & 0xFF) >>> 1);
                if (sum != 0) {
                    crc = (byte)((crc & 0xFF) ^ 0x8C);
                }
                extract = (byte) ((extract & 0xFF) >>> 1);
            }
        }
        return (crc & 0xFF);
    }

    private int CRC8(String stringData) {
        return CRC8(stringData.getBytes());
    }

    private void leadingPart() {
        for (int i = 0; i < 50; ++i) {
            for (int j = 1; j <= 4; ++j)
                appendEncodedData(j);
        }
    }

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

    private void prefixCode(String password) {
        int length = password.length();
        int prefixCode[] = new int[4];
        prefixCode[0] = 0x40 | (length >>> 4 & 0xF);
        prefixCode[1] = 0x50 | (length & 0xF);
        int crc8 = CRC8(new byte[] {(byte)length});
        prefixCode[2] = 0x60 | (crc8 >>> 4 & 0xF);
        prefixCode[3] = 0x70 | (crc8 & 0xF);
        for (int j = 0; j < 4; ++j)
            appendEncodedData(prefixCode[j]);
    }

    private void sequence(int index, byte data[]) {
        byte content[] = new byte[data.length + 1];
        content[0] = (byte)(index & 0xFF);
        System.arraycopy(data, 0, content, 1, data.length);
        int crc8 = CRC8(content);
        appendEncodedData(0x80 | crc8);
        appendEncodedData(0x80 | index);
        for (byte aData : data)
            appendEncodedData(aData | 0x100);
    }
}

测试时需要先启动设备端程序,然后在手机上发送配网信息,根据周围环境条件,配网速度可能或快或慢,本文章旨在记录工作中学习,长时间不用忘记,方便回忆。

 

 

本文标签: 原理智能方案技术AIRKISS