Android 與AP模式下的硬體連線(嵌入式基礎需求)
Android客戶端開發可以大致分為兩種,一是純客戶端APP開發,像某寶,某東,二是與硬體進行互動的APP,比如家庭WiFi、掃碼乘地鐵等。我之前是做純客戶端開發,並未與硬體進行互動開發過,也是懷著諸多猜測進入目前的公司,也是僅僅用了十天左右的時間由0到1的快速開發了一款他們需要的產品…之前自己也是查閱了相關部落格什麼的,希望能夠讓自己做APP時更有底氣和省時間點,但是發現找的貌似都比較深奧,不懂在說啥,所以也通過這段時間的學習整理一份簡單易懂的部落格,一起學習一起參考。
tip:1.本文用到了retrofit+okhttp+rxjava,不管你會不會,除了動態變化url的地方其他都無關緊要。2.本公司產品程式碼肯定沒這麼粗糙,我這邊只是給需要學習的小夥伴做下例項介紹。
一、AP模式:
官方解釋:AP模式其實是Access Point的簡稱,意思是:訪問接入點。而無線網橋的AP模式,也就是利用無線網橋做無線訊號的接入點了。那麼,究竟是什麼東西接入無線訊號呢?其實就是我們平日常用到的手機、膝上型電腦、平板電腦,又或者是無線CPE(WIFI訊號無線接入端),因此,我們可以理解為,AP模式,就是當做無線WIFI共享點。
簡單的理解:AP模式就是一個不能上網的WiFi(用手機在WLAN
二、業務需求
要求:手機控制機器人比如燈亮,枕頭高度(機器人控制枕高),機器人給手機發送使用者資料,手機端展示資料給使用者看。
功能分析(該分析基於機器人程式,不同程式操作不同):
- WiFi 搜尋
- wifi連線(切換)
- udp廣播
- udp點對點通訊
- baseUrl切換
(以下操作,根據自己的產品會把硬體叫做機器人,以下相關程式碼主要展示關鍵API,文末有GitHub連線)
1.WiFi 搜尋
與硬體互動首先發現AP模式下的機器人(你可以通過手機WLAN,自主切換,但是為了減少使用者的操作,我們可以這樣),提供一位大牛寫的工程所打包的apk,直接裝上可以使用
mainWifi = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); receiverWifi = new WifiReceiver(); //註冊廣播 registerReceiver(receiverWifi, new IntentFilter( WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
以註冊廣播的形式去接收附近的路由通道並展示到螢幕上(手機在區域網中):
點選要連結的wifi,進行網路切換
2.wifi連線(網路切換)
//連結AP路由器 AP模式下密碼為空
connectWithWpa(result.SSID, "");
//切換網路總方法
private void connectWithWpa(String ssid, String pw) {
WifiUtils.withContext(getApplicationContext())
.connectWith(ssid, pw)
.setTimeout(40000)
.onConnectionResult(this::checkResult)
.start();
}
//該方法為切換wifi回撥,(裡面的程式碼下面見分曉)
private void checkResult(boolean isSuccess) {
if (isSuccess) {
if (isFirst) {
//第一次如果連結成功,向AP路由傳送WiFissid以及password
showDialog();
} else {
//切換成功後,啟動廣播,以及接收
socket = new UDPSocket(MainActivity.this);
socket.startUDPSocket();
socket.startHeartbeatTimer("傳送的廣播內容,隨便定義");
}
} else {
Toast.makeText(this, "fail", Toast.LENGTH_SHORT).show();
}
}
這裡面網路切換會進行兩次,1是連線AP模式的機器人,連線成功後,向機器人傳送機器人需要的引數:
// 傳送WiFi ssid以及pw以及硬體需要的資料
WifiApi.getService().lineLogin(wifiNmae, pw, "ACESPillow_neck20180906000000001").compose(MainActivity.this.<WifiBean>getDefaultTransformer())
.subscribe(new Observer<WifiBean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(WifiBean r) {
//成功返回後,進行網路切換,切換到家庭WiFi
isFirst = false;
//在這裡可以用sp把之前的家庭WiFi ssid以及pw儲存起來
connectWithWpa("ssid", "pw");//第二次切換網路
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
2是成功連線機器人後,切換到家庭WiFi,
//成功返回後,進行網路切換,切換到家庭WiFi
isFirst = false;
//在這裡可以用sp把之前的家庭WiFi ssid以及pw儲存起來
connectWithWpa("ssid", "pw");//第二次切換網路
並開始傳送廣播。
3.udp廣播
udp通訊不需要建立通道,做簡單的資料傳輸還是很快捷很方便的;廣播分為單播、廣播、多播,其中多播還是相當牛逼的,可以跨區域網,搞事情的可以研究下。
udp廣播我這邊封裝在了UDPSocket中,主要運用了DatagramSocket ,不管是廣播還是udp點對點通訊用的都是它:
try {
byte[] data = new byte[BUFFER_LENGTH];
InetAddress bcIP = Inet4Address.getByAddress(Utils.getWifiBroadcastIP(context));
DatagramSocket udp = new DatagramSocket();
udp.setBroadcast(true);
DatagramPacket dp = new DatagramPacket(data, data.length, bcIP, CLIENT_PORT);
// String s = new String("0x00" + "ACESPillow_neck20180906000000001");
String s = new String("0x00" + pId);//這裡的廣播內容隨便定義
Log.i(LOG_TAG, "Packet send from "+ dp.getAddress()+" with address: " + dp.getSocketAddress());
// 設定傳送廣播內容
byte[] buff =s.getBytes();
dp.setData(buff,0,buff.length);
//一次性廣播,用完即關閉
udp.send(dp);
udp.disconnect();
udp.close();
} catch (IOException e) {
e.printStackTrace();
}
**dp.getAddress()**可以獲取傳送端的IP,這樣就可以進行點對點通訊,傳送廣播的同時,緊接著開啟接收廣播。
4.udp點對點通訊
在這裡其實就是廣播接收,開啟接收:
try {
Log.i(LOG_TAG, "Listener started!");
// DatagramSocket socket = new DatagramSocket(3333);
client.setSoTimeout(1500);
byte[] buffer = new byte[BUFFER_LENGTH];
DatagramPacket packet = new DatagramPacket(buffer, BUFFER_LENGTH);
while(LISTEN) {
try {
Log.i(LOG_TAG, "Listening for packets");
client.receive(packet);
//返回值
String data = new String(buffer, 0, packet.getLength());
Log.i(LOG_TAG, "Packet received from "+ packet.getAddress() +" with contents: " + data);
String socketAddress = packet.getSocketAddress() + "";
// String s = socketAddress.split(":")[0];
String address = packet.getAddress() +"";
int port = packet.getPort();
Log.i(LOG_TAG, "Packet received from "+ socketAddress+" with port: " + port);
if (port == 3333) {//判斷是不是硬體方發過來的udp資訊,介面篩選
// do something
//儲存硬體傳送過來的IP,就是address,拼接成"http:/"+ address
stopUDPSocket();//主動關閉廣播
}
}
catch(IOException e) {
Log.e(LOG_TAG, "IOException in Listener " + e);
}
}
機器人在接收到廣播後,會發送udp通訊,客戶端只需要注意兩點,1是new DatagramSocket(3333)
介面3333是之前擬定協商好的不能變,2是if (port == 3333) //判斷是不是硬體方發過來的udp資訊,介面篩選
排除自己傳送的廣播,並在其中的邏輯裡關閉接收,獲取packet.getAddress()
udp通訊攜帶的IP。因為此IP是所在區域網重新分配給機器人的新IP,手機和機器人的通訊就全靠它了。
到此,機器人由AP模式進入了局域網中
5.URl動態切換
完成1-4,當你直接與機器人進行連線時會發現404或者請求斷開,檢視日誌你會發現,請求url還是AP模式時的Url…
網路請求框架在app初始化時就被初始化了,一個retrofit對應一個baseURl…於是乎,動態改變Url,陷入了沉思。
首先感謝:https://www.jianshu.com/p/2919bdb8d09a
大家想更清楚地瞭解的話先去看看他的部落格吧,我只是幫忙宣揚一下我怎麼用的,解釋權歸他
首先在Application進行對動態變化的retrofit進行初始化:
//@parm 1 需要url動態變化所對應的ke'y
//@parm2 AP_WIFI是需要動態變化的url本身,在本文中是AP模式下的URL
@Override
public void onCreate() {
super.onCreate();
//初始化要動態變化的wifi
RetrofitUrlManager.getInstance().putDomain("wifi_name", AP_WIFI);
}
構建retrofit
public WifiApi() {
OkHttpClient builder = RetrofitUrlManager.getInstance().with(new OkHttpClient.Builder()) //RetrofitUrlManager 初始化
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5, TimeUnit.SECONDS)
.retryOnConnectionFailure(true) // 失敗重發
// .addInterceptor(new HeaderInterceptor2())
// .addInterceptor(new LoggingInterceptor.Builder()
// .setLevel(Level.BASIC)
// .log(Platform.INFO)
// .build())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("192.168.0.100")//這裡就是上面的AP_WIFI
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 新增Rx介面卡
.addConverterFactory(GsonConverterFactory.create()) // 新增Gson轉換器
.client(builder)
.build();
wifiService = retrofit.create(WifiService.class);
}
在需要轉換url的地方:
//連線AP模式的IP可以寫入到工程裡,但是硬體連線wifi成功後,會重新被當地路由重新分配一個IP,這時候就需要動態換個新生成的IP
HttpUrl httpUrl = RetrofitUrlManager.getInstance().fetchDomain("wifi_name");//application 處設定的同名
if (httpUrl == null || !httpUrl.toString().equals("192.168.0.100")) { //可以在 App 執行時隨意切換某個介面的 BaseUrl
// RetrofitUrlManager.getInstance().putDomain(Constant.WIFI, Constant.WIFI_BASE_URL);
RetrofitUrlManager.getInstance().setGlobalDomain("udp收到的address");
//如果您已經確定了最終的 BaseUrl ,不需要再動態切換 BaseUrl, 請
//RetrofitUrlManager.getInstance().setRun(false);
// FIXME: 2018/12/12 再進行下面的請求
WifiApi.getService().test().compose(MainActivity.this.<WifiBean>getDefaultTransformer())
.subscribe(new Observer<WifiBean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(WifiBean r) {
//成功連線WiFi的硬體,就像個小伺服器,可以根據需求進行POST 和 GET請求
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
});
不需要轉換,就像給AP模式下的機器人傳送資訊時就不需要變化url,這個時候不要
HttpUrl httpUrl = RetrofitUrlManager.getInstance().fetchDomain("wifi_name");//application 處設定的同名
if (httpUrl == null || !httpUrl.toString().equals("192.168.0.100")) { /
RetrofitUrlManager.getInstance().setGlobalDomain("udp收到的address");
即可。
三、其他
<uses-permission-sdk-23 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_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
所具備的許可權
程式碼通道:
示例程式碼——不要吝嗇你的小星星