红联Linux门户
Linux帮助

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

发布时间:2017-05-31 11:02:53来源:linux网站作者:遥同学
softAP配网,即利用设备的无线芯片,将设备进入到softAP模式,开启一个无线局域网,手机(或其它移动设备)通过连入设备开启的无线局域网后,向设备发送路由器的ssid及password等信息,让设备在无屏幕的情况下,获取到路由器的ssid信息,达到联网的目的。
配网的流程其实还是比较繁杂的,手机要先和原来的路由器断开连接,然后设备开softap,然后手机连入softap,然后发送ssid和密码,然后关闭softap模式,退回到正常的station模式,然后设备连接路由器,手机也视情况重新连接路由器。但是这个过程是可以在软件端整合起来的,以达到一键联网的结果,断开和连接的操作都整合起来。在这方面小米的体验就做得很好。我本来是比较推崇无线数据帧配网的方式的,因为在软件端只要不断地往空气中发无线数据包就可以了,设备端也只需要捕捉到这些包就可以了。但是无线数据帧配网的方式极不稳定,配网成功率低,受环境、手机型号、路由器型号、设备laytout和无线芯片的选型影响较大。而softAp配网,本身稳定性较高,通过开发者的整合,可以达到和无线数据帧配网一样的用户体验。
 
softAp配网流程图如下:
softAP配网:用Android手机为linux无屏设备输入wifi密码
 
我这里实现配网的两端设备,一边是一个跑Linux系统的开发板,另一边是我们日常用的普通Android手机。如图:
softAP配网:用Android手机为linux无屏设备输入wifi密码
 
设备端的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)解析从手机端得到的ssid、password等信息;
(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+"");  
}  
}  
};  
}
 
手机端界面:
softAP配网:用Android手机为linux无屏设备输入wifi密码
 
设备端收到手机端发来的广播,解析出wifi密码并返回wifi信息:
softAP配网:用Android手机为linux无屏设备输入wifi密码
 
本文永久更新地址:http://www.linuxdiyf.com/linux/31181.html