Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

softAP配网:用Android手机为linux无屏设备输入wifi密码

$
0
0

softAP配网,即利用设备的无线芯片,将设备进入到softAP模式,开启一个无线局域网,手机(或其它移动设备)通过连入设备开启的无线局域网后,向设备发送路由器的ssidpassword等信息,让设备在无屏幕的情况下,获取到路由器的ssid信息,达到联网的目的。

配网的流程其实还是比较繁杂的,手机要先和原来的路由器断开连接,然后设备开softap,然后手机连入softap,然后发送ssid和密码,然后关闭softap模式,退回到正常的station模式,然后设备连接路由器,手机也视情况重新连接路由器。但是这个过程是可以在软件端整合起来的,以达到一键联网的结果,断开和连接的操作都整合起来。在这方面小米的体验就做得很好。我本来是比较推崇无线数据帧配网的方式的,因为在软件端只要不断地往空气中发无线数据包就可以了,设备端也只需要捕捉到这些包就可以了。但是无线数据帧配网的方式极不稳定,配网成功率低,受环境、手机型号、路由器型号、设备laytout和无线芯片的选型影响较大。而softAp配网,本身稳定性较高,通过开发者的整合,可以达到和无线数据帧配网一样的用户体验。

softAp配网流程图如下:





我这里实现配网的两端设备,一边是一个跑linux系统的开发板,另一边是我们日常用的普通Android手机。如图:



设备端的linux系统,需要移植好wifi芯片对应的驱动,并且可以配置softAp模式(p2p模式),这个一般是有wifi芯片厂商在fw里实现好的,我们只要切换节点,加载驱动,push fw就可以了。不同的厂商实现的方法可能有异,我在此就不介绍了。


基于已经移植好wifi驱动的设备,我这里介绍如何在应用层实现softAP配网的demo。


设备端实现

设备端需要实现的流程有如下:

 

1)将设备进入到softAP模式,需要所使用的wifi芯片支持spftAP模式;


2)创建一个socket用于接收手机端发来的ssid信息,demo中使用广播的方式收发信息;

 

3)接收手机发来的ssid信息;

 

4)收到手机发来的ssid信息后返回确认信息,让手机暂时停止发送ssid信息并断开和设备端的连接(即使不主动断开在设备端退出softAP模式之后,手机也会因为找不到softAP而被动断开连接);

 

5)退出sopAP模式;


6)解析从手机端得到的ssidpassword等信息;

 

7)连接路由器;


8)结束。如因ssid信息错误等原因,未能成功连上路由器,可根据用户体验需求重新开始配网流程,该循环的触发流程,请根据实际功能定位自行设计。


#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <net/if.h>
#include <errno.h>
#include <sys/ioctl.h>

#include "include/smartlink.h"

#define IP_SIZE 16

#define MSG_LEN_MX 256
#define CMD_LEN_MX 128

#define PORT_DEFAULT 8066

//server
int main(int argc, char **argv){
	
	int port = PORT_DEFAULT;
	if(argc >=2){
	 	port = atoi(argv[1]);
	}
	printf("*****softAP demo*****\n");

	//先把设备设置为softAP模式
	if(set_softAP_up()==0){
		printf("start softAP success.\n");
	}
	

	/*创建socket接收手机发来的ssid信息,并返回确认信息*/

	//创建socket
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	printf("port:%d \n", port);
	
	

	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	int sock;
	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
		printf("socket error!\n");
		printf("%s \n", strerror(errno));
		exit(1);
	}else{
		printf("socket success.\n");
	}
	//printf("addr:%s \n",addr.sin_addr.s_addr);
	if(bind(sock, (struct sockaddr *)&addr, sizeof(addr))< 0){
		printf("bind error!\n");
		printf("%s \n", strerror(errno));
		exit(1);
	}else{
		printf("bind success.\n");
	}

	//接收手机发来的ssid信息
	char buff[256];
	struct sockaddr_in clientAddr;
	int len = sizeof(clientAddr);
	int n;
	while(1){
		printf("recvfrom...\n");
		n = recvfrom(sock, buff, 511, 0, (struct sockaddr*)&clientAddr, &len);
		printf("addr: %s -port: %u says: %s \n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), buff);
		if(n>0){
			printf("recvfrom success.\n");
			break;
		}
	}

	//往手机返回确认信息
	char buff_confirm[3];
	strcpy(buff_confirm,"OK");
	while(1){
		n = sendto(sock, buff_confirm, n, 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));
		printf("addr: %s -port: %u says: %s \n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), buff_confirm);
		if(n>0){
			printf("sendto success.\n");
			//usleep(10*1000000);
			break;
		}
	}

	usleep(5*1000000);
	close(sock);

	//设备退出softAP模式,退出后将自动恢复到station模式
	if(set_softAP_down()==0){
		printf("down softAP success.\n");
	}
	usleep(5*1000000);

	//解析设备端发过来的ssid信息
	char* smartlink_softAP_ssid;// = "12345678";
	char* smartlink_softAP_password;// = "12345678";

	char *p =NULL;

	int ssid_len = strstr(buff, "::Password::")-buff-8;
 	printf("ssid_len  %d\n",ssid_len);
 	smartlink_softAP_ssid = (char *)malloc(ssid_len+1);

	if((p = strstr(buff, "::SSID::")) != NULL){
		p += strlen("::SSID::");//跳过前缀
		
			if(*p){
				if(strstr(p, "::Password::") != NULL){
					memset((void*)smartlink_softAP_ssid,'\0',ssid_len+1);
					strncpy(smartlink_softAP_ssid, p, ssid_len);
				}
			}
		
	}

	printf("%s\n",smartlink_softAP_ssid);

	int password_len = strstr(buff, "::End::") - strstr(buff, "::Password::") - 12;
	printf("password_len  %d\n",password_len);

	smartlink_softAP_password = (char *)malloc(password_len+1);

	if((p = strstr(buff, "::Password::")) != NULL){
		p += strlen("::Password::");//跳过前缀
			
			if(*p){
				if(strstr(p, "::End::") != NULL){
					memset((void*)smartlink_softAP_password,'\0',password_len+1);
					strncpy(smartlink_softAP_password, p, password_len);
				}
			}
		
	}
	printf("%s\n",smartlink_softAP_password);


	//使用解析到的ssid信息连接wifi
	smartlink_softAP_connect_ap(smartlink_softAP_ssid, smartlink_softAP_password);
	
	free(buff);
	free(smartlink_softAP_ssid);
	free(smartlink_softAP_password);
	return 0;
}

手机端实现

手机端需要实现的流程如下:

 

1)检查手机当前wifi状态,如未开启需先开启wifi,如已连接某路由器需与该路由器断开连接;

 

2)搜索设备开启的wifi,直到找到或超时为止;

 

3)连入设备开启wifi

 

4)创建socket,与设备进行通信,demo中选用广播的方式进行通信;

 

5)开启接收线程,等待接收设备端发来的确认信息。接收线程建议在发送线程开启前先开启,以免收不到确认信息;

 

6)开启发送线程,向设备发送ssid信息;

 

7)在收到设备发送的确认信息后,断开与设备的连接(即使不主动断开,也会在设备退出softAP模式后,手机也会因为找不到AP而被动断开连接);

 

8)重新连入路由器。可在重新连入路由器后询问设备是否也成功连入路由器,如没有可重新进行配网,设备端也要做相应的操作,具体可根据场景需求自行设计。


Android手机需要实现的权限:


<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


代码示例


package com.example.chenkunyao.ckysoftapdemo;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;

import com.example.chenkunyao.ckysoftapdemo.tools.SmartlinkInfo;
import com.example.chenkunyao.ckysoftapdemo.tools.WifiAdmin;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class MainActivity extends AppCompatActivity {
    private Button btnSend;
    private Button btnStop;
    private int sendNum=0;
    int recvNum=0;

    private EditText etTargetIP;
    private EditText etTargetPort;
    private EditText etLocalIP;
    private EditText etLocalPort;

    private EditText etSSID;
    private EditText etPassword;

    private Spinner spEncrypt;

    private String strTargetIP;
    private String strTargetPort;
    private String strLocalIP;
    private String strLocalPort;

    private String strSSID;
    private String strPassword;
    private String strEncrypt;

    private Button btnConnect;

    DatagramSocket socket;
    InetAddress addr;

    public int STOPNUM = 1024;
    public String SOTFAPSSID = "Smart-AW-HOSTAPD";

    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context=this.getBaseContext();
        init();

    }

    private void init(){
        btnSend = (Button) this.findViewById(R.id.btnSend);
        btnSend.setOnClickListener(click);
        btnStop = (Button) this.findViewById(R.id.btnStop);
        btnStop.setOnClickListener(click);

        etTargetIP= (EditText) findViewById(R.id.etTargetIP);
        etTargetPort= (EditText) findViewById(R.id.etTargetPort);
        etLocalIP= (EditText) findViewById(R.id.etTargetPort);
        etLocalPort= (EditText) findViewById(R.id.etLocalPort);

        etSSID= (EditText) findViewById(R.id.etSSID);
        etPassword= (EditText) findViewById(R.id.etPassword);

        spEncrypt= (Spinner) findViewById(R.id.spEncrypt);
        spEncrypt.setOnItemSelectedListener(select);
        spEncrypt.setSelection(1,true);
        strEncrypt= String.valueOf(spEncrypt.getSelectedItemId());

        btnConnect= (Button) findViewById(R.id.btnConnect);
        btnConnect.setOnClickListener(click);

    }

    private Spinner.OnItemSelectedListener select= new Spinner.OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            String[] languages = getResources().getStringArray(R.array.encrypt);
            Log.e("Encrypt:",languages[position]);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    };

    private Button.OnClickListener click =new Button.OnClickListener(){
        public void onClick(View v){
            switch (v.getId()){
              case R.id.btnStop:{
                    sendNum = STOPNUM;
                    recvNum = STOPNUM;
                    break;
                }
                case R.id.btnConnect:{

                    SmartlinkInfo info = new SmartlinkInfo();
                    info.ssid=SOTFAPSSID;
                    info.password="wifi1111";
                    info.wlansecurity=3;


                    //获取屏幕上输入的IP\port\ssid等信息
                    strTargetIP= String.valueOf(etTargetIP.getText());
                    strTargetPort= String.valueOf(etTargetPort.getText());
                    strLocalIP= String.valueOf(etLocalIP.getText());
                    strLocalPort= String.valueOf(etLocalPort.getText());
                    strSSID= String.valueOf(etSSID.getText());
                    strPassword = String.valueOf(etPassword.getText());
                    Log.e("Info",strTargetIP+" "+strTargetPort);
                    strSSID= String.valueOf(etSSID.getText());
                    strPassword = String.valueOf(etPassword.getText());

                    try {
                        socket = new DatagramSocket(Integer.parseInt(strTargetPort));
                        socket.setBroadcast(true);
                        addr = InetAddress.getByName(strTargetIP);
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    } catch (SocketException e) {
                        e.printStackTrace();
                    }

                    connectWifi(info);


                    break;
                }
                default:break;
            }
        }
    };

    private int connectWifi(SmartlinkInfo info) {

        WifiAdmin mWifiAdmin = new WifiAdmin(this);
        mWifiAdmin.openWifi();
        boolean FOUNDSOFTAP=false;
        for(int j=0;j<20;j++) {
            mWifiAdmin.startScan();
            for (int i = 0; i < mWifiAdmin.getWifiList().size(); i++) {
                //Log.e("WIFIINFO", i+": " + mWifiAdmin.getWifiList().get(i).SSID);
                if (mWifiAdmin.getWifiList().get(i).SSID.equals(SOTFAPSSID)) {
                    FOUNDSOFTAP=true;
                    Log.e("WIFIINFO", i+": " + mWifiAdmin.getWifiList().get(i).SSID);
                    Log.e("WIFIINFO", "FOUND SOFTAP.");
                    break;
                }
            }
            if(FOUNDSOFTAP){
                mWifiAdmin.creatWifiLock();
                WifiConfiguration config = mWifiAdmin.CreateWifiInfo(info.ssid,info.password,info.wlansecurity);
                mWifiAdmin.addNetwork(config);
                mWifiAdmin.releaseWifiLock();
                break;
            }
        }

        if (context != null) {
            Log.e("SLEEP","if (context != null)");
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            Log.e("SLEEP"," ConnectivityManager mConnectivityManager = (ConnectivityManager) context");
            //NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            //Log.e("SLEEP"," NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();");

            while (mConnectivityManager.getActiveNetworkInfo()==null) {//WIFI_STATE_ENABLING = 2;
                Log.e("SLEEP", "mNetworkInfo==null sleep 2 s~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                while (!mConnectivityManager.getActiveNetworkInfo().isConnected()) {//WIFI_STATE_ENABLING = 2;
                    Log.e("SLEEP", "!mNetworkInfo.isConnected() sleep 2 s~");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            Log.e("mNetworkInfo","mNetworkInfo == null");
        }

                //打开接收线程
                Thread recvThread = new Thread(recvRunnable);
                recvThread.start();

                //打开发送线程
                Thread sendThread = new Thread(sendRunnable);
                sendThread.start();
        /*
        mWifiAdmin.creatWifiLock();
        WifiConfiguration config = mWifiAdmin.CreateWifiInfo(info.ssid,info.password,info.wlansecurity);
        mWifiAdmin.addNetwork(config);
        mWifiAdmin.releaseWifiLock();
        */
        return 0;
    }

    private Runnable sendRunnable = new Runnable() {
        @Override
        public void run() {
                String strInfo="::SSID::"+strSSID+"::Password::"+ strPassword+"::End::";

                try {

                    byte[] buffer = strInfo.getBytes();
                    DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
                    packet.setAddress(addr);
                    packet.setPort(Integer.parseInt(strTargetPort));

                    for(sendNum=0;sendNum<60;sendNum++) {
                        socket.send(packet);
                        Thread.sleep(1000);
                        Log.e("Send","Num:"+sendNum+"  "+strInfo);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("发送线程",e+"");
                }

                /*
                SmartlinkInfo info = new SmartlinkInfo();
                info.ssid=strSSID;
                info.password=strPassword;
                info.wlansecurity=3;
                connectWifi(info);
                */
        }
    };

    private Runnable recvRunnable = new Runnable() {
        @Override
        public void run() {

                try {
                    byte[] buffer ="XX".getBytes();
                    DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
                    packet.setAddress(addr);
                    packet.setPort(Integer.parseInt(strTargetPort));
                    for(recvNum=0;recvNum<65;recvNum++) {
                        Log.e("recv", "正在接收..."+recvNum );
                        socket.receive(packet);
                        if(new String(packet.getData()).equals("OK")){
                            sendNum = STOPNUM;
                            Log.e("recv", "收到消息:" + new String(packet.getData()));
                            break;
                        }
                        Thread.sleep(1000);
                    }

                /*
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //mTextView.setText("收到消息" + new String(packet.getData()) + ":" + String.valueOf(++index));
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                */
                    //socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("接收线程",e+"");
                }

        }

    };
}


手机端界面:




设备端收到手机端发来的广播,解析出wifi密码并返回wifi信息:




作者:qq_15062527 发表于2017/5/31 9:21:53 原文链接
阅读:122 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>