[智能硬件] 1、三分鍾看懂智能硬件原理——藍牙防丟器制作教程(包括手機應用)


 

 

1 什么是智能藍牙防丟器

  所謂智能藍牙(Smart Bluetooth)防丟器,是采用藍牙技術專門為智能手機設計的防丟器。其工作原理主要是通過距離變化來判斷物品是否還控制在你的安全范圍。主要適用於手機、錢包、鑰匙、行李等貴重物品的防丟,也可用於防止兒童或寵物的走失 。[請看正版請百度:beautifulzzzz(看樓主博客園官方博客,享高質量生活)嘻嘻!!!]

圖 1-1 藍牙防丟器應用領域

 

2 藍牙防丟器的主要構造

  目前比較成熟的產品一般是采用藍牙4.0技術,具有低功耗、雙向防丟、自動報警等優點。雖然市場上該類產品種類繁多、層出不窮,但其核心構成一般包括:藍牙4.0芯片、藍牙芯片輔助電路、藍牙天線、蜂鳴器、開關、電源等。

圖 2-1 藍牙防丟器構成

 

3 藍牙模塊的選擇

  由於這是第一個智能硬件DIY篇,樓主可不想一下子把大家給嚇倒了。所以這里我們先用一個相對簡單但常用的藍牙模塊HC-05/HC-06進行DIY 。如下圖該模塊把天線、濾波電路、Soc、晶振都集成在了一起,當我們用的時候只要關注1、2、12、13、24、26這幾個引腳就能實現比較完整的藍牙通信功能,這樣就為我們制作藍牙防丟器節省了很多挑選元件、設計電路、焊接制板的功夫,是不是超贊呀?

 

圖 3-1 藍牙模塊1

  其實還有更贊的呢!由於考慮到很多讀者在硬件方面還都是新手,初次拿到郵票邊緣式引腳的模塊會焊接不好,於是樓主又找到了一款封裝更好的藍牙模塊(其實就是把上面的模塊加一個托,然后將VCC\GND\TXD\RXD四個關鍵的引腳引出)。當我們只是想把藍牙模塊作為標簽時,只要在VCC和GND之間給它加上相應的電壓就行了;當想用它進行無線數據傳輸時,這時TXD和RXD兩個引腳就起作用了。

  

圖 3-2 藍牙模塊2

 

4 開始制作一個簡易的藍牙防丟器

  上面說了這么多了,那么我們的藍牙防丟器的設計方案到底是什么樣的呢?簡單起見,咱么僅僅實現通過距離變化來判斷物品是否還控制在你的安全范圍內的具有核心功能的防丟器,對於節能功能、雙向報警功能甚至是自拍功能咱們就先不考慮了。哈哈,可能說到這里大家還是對咱們要做的防丟器一點想法都沒有,其實通過上面的鋪墊樓主有信心大家可以在一分鍾之內知道怎么完成它!

          圖 4-1 簡易藍牙防丟器

  相信很多看完上面圖片同學會恍然大悟——不是嘛,只要將藍牙模塊接上相應的電源模塊就能夠做成一個簡單的可以發出藍牙信號的防丟器了!對的,由於我們沒有加入復雜的通信功能,所以我們僅僅把藍牙模塊通上電做成一個藍牙標簽就可以了。但是大家不要高興太早,雖然是第一個DIY,樓主也不會這么水吧~(沒發現上面圖片中手機屏幕里的應用還是一片空白嗎?哈哈)。

 

5 如何找到並學習要用到的API 

  上面制作藍牙防丟器的硬件部分讓大家覺得沒什么挑戰性,那么接下來的東西可就有一定難度了!記得樓主當時學安卓藍牙開發的時候費了好大力氣的。這里樓主強烈建議大家着手了解安卓某個功能的應用時最好去安卓開發者社區,但是隨着Google被屏Android Developer也不能被訪問了。雖然樓主在MIT網站上又找到了一個類似的網頁,但是訪問速度不是那么流暢,為了方便,LZ挑出了和本節相關的知識幫助大家理解和運用。

          圖 5-1 Android Developer頁面

 

6 安卓藍牙編程小知識

  安卓平台支持藍牙協議棧,允許一台設備和其他設備進行無線數據交換,當大家想使用藍牙時可以通過調用相應的API實現功能。這些API提供的主要功能有:

△ 掃描搜索其他藍牙設備(Scan for other Bluetooth devices)
△ 查詢本地藍牙適配器配對的藍牙設備(Query the local Bluetooth adapter for paired Bluetooth devices)
△ 建立RFCOMM通道(Establish RFCOMM channels)
△ 連接其他設備(Connect to other devices through service discovery)
△ 與其他設備進行數據交換(Transfer data to and from other devices)
△ 管理多組連接(Manage multiple connections) 

  當准備在應用程序中使用藍牙時,首先需要在manifest文件中包含BLUETOOTH和BLUETOOTH_ADMIN權限:

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

  接着涉及藍牙的操作主要有:1、開啟藍牙 2、關閉藍牙 3、能被搜到 4、獲取配對設備 5、傳輸數據。由於本節只涉及到搜索周邊設備,所以配對並傳輸數據暫時不介紹。

 

7 安卓藍牙編程主要操作

  要想通過編程操作藍牙設備,首先我們得了解一下有可供我們使用的相關類及其成員函數有哪些,如下圖:藍牙設備包括本地設備和遠程設備,本地設備對應的類為BluetoothAdapter,遠程設備對應的類為BluetoothDevice,兩者的成員函數基本相同,樓主將在接下來詳細講解。

圖 7-1 藍牙設備相關函數

 

7_1 打開本地藍牙設備

  我們要做帶有藍牙的應用,首先要保證用戶的本地藍牙設備是打開的,否則什么都不管直接使用搜索、連接、通信等函數肯定會得到和預期不一樣的效果。但是有時候用戶為了節省電池電量會把藍牙關閉,那我們該怎么辦呢?

  其實,遇到這種情況大家也不用擔心!請看下面的解決方案:其中第1行調用BluetoothAdapter的getDefaultAdapter()方法獲得本地藍牙設備,接着調用isEnabled()判斷本地藍牙設備是否被打開,如果被打開了就執行接下來的操作,否則可以發送Intent消息,請求打開本地藍牙設備,接着待用戶打開藍牙后再進入接下來的操作。

1 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
2 if (!mBluetoothAdapter.isEnabled()) {
3     Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
4     startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
5 } else {
6     nextOperation();
7 }

  這時候有些同學可能要吐槽樓主了“你上面發送、請求、接着、然后說的挺輕巧,我怎么知道我發送完Intent消息后系統到底干了什么?用戶又如何打開藍牙的?應用程序又在哪里等待用戶完成打開藍牙事件,然后在哪里執行nextOperation()函數的?”哈哈,樓主知道大家動手心切啦!下面將給大家詳細介紹這一過程。

  想解答大家的這些問題還得看上面代碼:其中第4行startActIvityForResult會啟動一個系統Preference Activity並將ACTION_REQUEST_ENABLE靜態常量作為其動作字符串,得到的Preference Activity如下圖:

       

  該Activity提醒用戶是否授予權限打開本地藍牙設備,當用戶點擊“是”或者“否”的時候,該Activity將會關閉。我們可以使用onActivityResult處理程序中返回的結果代碼參數來確定是否操作成功。

      正如上面介紹,當用戶點擊“是”授予藍牙權限請求后,確認請求的Activity會關閉,在下面的函數中將會收到此操作的消息,這樣我們就能很瀟灑的在第4行安全的進入接下來的操作了。

1 protected void onActivityResult(int requestCode,int resultCode,Intent data){
2     if(requestCode==ENABLE_BLUETOOTH){
3         if(resultCode==RESULT_OK){
4             nextOperation();
5         }
6     }
7 }

 

7_2 搜索周邊藍牙設備

  上面解決了藍牙設備打開問題,接下來我們就要嘗試調用相關函數搜索周邊的藍牙設備,這樣我們就能知道我們制作的藍牙防丟器是否在我們周邊了(是不是很興奮呢?)。

  這里我們要用到BluetoothAdapter的成員函數startDiscovery,該方法可以執行一個異步方式獲得周邊藍牙設備,因為是一個異步的方法所以我們不需要考慮線程被阻塞問題,整個過程大約需要12秒時間。這時我們可以注冊一個BroadcastReceiver 對象來接收查找到的藍牙設備信息,我們通過Filter來過濾ACTION_FOUND這個Intent動作以獲取每個遠程設備的詳細信息,過濾ACTION_DISCOVERY_FINISHED這個Intent動作以獲取整個藍牙搜索過程結束的信息。

1 // Register for broadcasts when a device is discovered
2 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
3 this.registerReceiver(mReceiver, filter);
4 // Register for broadcasts when discovery has finished
5 filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
6 this.registerReceiver(mReceiver, filter);

  上面講的可能有點專業,下面俺用一種簡單非專業的方式向大家介紹一下:每次我們想編程搜索周邊的藍牙設備時,只要簡單調用BluetoothAdapter的成員函數startDiscovery就可以了。所謂的異步方法大家可以這樣理解——start方法就好像一個總司令告訴情報搜查員去搜查情報,然后自己繼續做自己的事,情報員去收集各種情報;這里的filter就好像總司令告訴情報搜查員,我只要ACTION_FOUND和ACTION_DISCOVERY_FINISHED信息;那么這里的mReceiver就是總司令指定的情報搜查員了。

  接下來就讓咱們神奇的情報搜查員登場!如下,相信大家一看就明白了,咱們的情報搜查員會一絲不苟地將總司令下達的任務執行。這樣當他收到FOUND動作信息時就用第6~8行的方法將每個發現的藍牙設備的名字和地址存儲進一個Vector中,這樣等自己完成任務時,就能告訴總司令任務完成,所有周邊藍牙情報都在xxx向量中(嘻嘻,多么讓領導喜歡的員工呀!)。

 1 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 2   @Override
 3   public void onReceive(Context context, Intent intent) {
 4         String action = intent.getAction();
 5       if (BluetoothDevice.ACTION_FOUND.equals(action)) {
 6           BluetoothDevice device =                                                               
 7                         intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 8           mDevicesVector.add(device.getName() + "\n" + device.getAddress());           
 9       } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
10           //...
11       }
12   }
13 };

 

7_3 獲取藍牙設備信號強度

  到目前想必很多人已經能夠一氣呵成自己的簡單藍牙防丟器了,因為大家已經掌握了硬件部分的設計方法、擁有軟件藍牙開發打開和搜索周邊藍牙的編程技巧。但是如果心急的同學只用前面介紹的一點小知識的話,肯定設計的小東西很不盡人意(自己都不好意思給自己及格)。是不是出現了藍牙防丟器放到很遠很遠時應用程序才會報告東西已丟?如果是這樣請耐心地看完下面的介紹(也許能給你更多的啟發(⊙o⊙)哦)。

  想必大家也想到了問題所在——距離沒控制好!如果我們能夠知道藍牙防丟器距離我們手機的距離那就好了,這樣我們就能設定一個范圍值,當它超出這個范圍時手機就發出丟失警告,這樣就不會出現防丟器要放很遠才能被手機察覺已丟失的問題了。要解決這個問題就要向大家介紹一下無線傳感器網絡中的RSSI了!

  RSSI全名Received Signal Strength Indication,翻譯過來是接收的信號強度指示。在我們的經驗中一般離得越近信號越強,所以通過這個RSSI能夠大致估計接收點與發射點之間的距離。而在無線傳感器網絡中經常會設置多個固定的發射源(也是所謂的標簽),然后根據一定的算法來確定移動目標的空間位置信息。例如下圖左分別在A、B、C三點放置三個發射功率相同的信號源(標簽),這樣移動點D通過收集A、B、C三點的RSSI並估計距離rA、rB、rC,由於這個距離並不是完全精准,所以三個圓並不一定交於一點,大多數情況是下面交於一個公共區域的情況,而移動點D的當前位置很有可能在該區域。

 

            圖 7-2 藍牙標簽定位

  我們這里只要簡單地用到通過RSSI估計手機和防丟器之間的距離就行了,上面的定位技術相信大家也能舉一反三輕松搞定(\(^o^)/~其實沒那么簡單,哈哈)。那么在安卓編程時如何獲得RSSI呢?其實並不難,我們可以利用和獲取周邊藍牙設備的名稱和地址類似的方法獲取RSSI:

1 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
2 mDevicesVector.add(device.getName() + "\n" + device.getAddress());
3 short rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
4 mRSSIVector.add(rssi);

 

8 着手開發我們的APP

1)   使用Eclipse創建一個安卓項目,命名為first_test,並將Activity Name命名為UI_Main,Layout Name命名為ui_main(如圖8-1所示)。

圖8-1 新建安卓項目

2)   展開res文件夾下的layout文件夾,雙擊ui_main.xml文件,選擇xml編輯模式,並將其代碼改為:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6 
 7     <SurfaceView
 8         android:id="@+id/surfaceView"
 9         android:layout_width="match_parent"
10         android:layout_height="wrap_content"
11         android:layout_weight="0.75" />
12 
13     <Button
14         android:id="@+id/button_add"
15         android:layout_width="match_parent"
16         android:layout_height="wrap_content"
17         android:text="添加藍牙防丟器" />
18 
19     <Button
20         android:id="@+id/button_start"
21         android:layout_width="match_parent"
22         android:layout_height="wrap_content"
23         android:text="開始防丟" />
24     
25 </LinearLayout>

操作說明:這里修改的ui_main.xml為應用的主界面(如圖8-2所示)。該頁面采用LinearLayout布局模式,從上到下依次為用於動態顯示藍牙信號強弱的SurfaceView控件、用於添加防丟設備的按鈕和用於開始防丟控制的按鈕。

     

          圖 8-2 ui_main.xml效果

3)   右擊layout,依次選擇New|Android XML File新建安卓XML文件。

4)   將新建的Android XML File命名為ui_list,點擊Finish按鈕。接着同第二步將新建的ui_list.xml修改為:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6 
 7     <ListView
 8         android:id="@+id/listView1"
 9         android:layout_width="match_parent"
10         android:layout_height="wrap_content"
11         android:layout_weight="1" >
12     </ListView>
13 
14     <Button
15         android:id="@+id/button_search"
16         android:layout_width="match_parent"
17         android:layout_height="wrap_content"
18         android:text="search" />
19 
20     <Button
21         android:id="@+id/button_ok"
22         android:layout_width="match_parent"
23         android:layout_height="wrap_content"
24         android:text="OK" />
25 
26 </LinearLayout>

 

操作說明:這里新建的ui_list.xml為搜索並添加藍牙防丟器界面。該界面同樣采用LinearLayout布局模式,包含一個用於開始搜索的按鈕、一個用於列出搜索結果的list和一個用於確認返回的OK按鈕。

5)   右擊src文件夾下的包名,依次選擇New|Class命令(如圖8-3所示)

                    圖 8-3 新建類

6)   新建類文件命名為My_BTS,點擊Finish按鈕。

7)   將My_BTS.java文件修改為:

 1 package com.example.first_test;
 2 
 3 import java.util.Vector;
 4 
 5 public class My_BTS {
 6         public String mName;
 7         public String mAddr;
 8         public Vector<Short> mRSSIVector;
 9 
10         public My_BTS() {
11             mName = new String();
12             mAddr = new String();
13             mRSSIVector = new Vector<Short>();
14         }
15 
16         public My_BTS(String name, String addr) {
17             mName = name;
18             mAddr = addr;
19             mRSSIVector = new Vector<Short>();
20         }
21 }

操作說明:該類表示藍牙防丟器。其中mName和mAddr分別表示藍牙防丟器的名字和地址;mSSIVector用來存放一段時間檢測到該藍牙防丟器的RSSI值(之所以保留多組數據,是方便今后大家擴展)。

8)   采用同樣的方法新建一個Func_Draw.java文件,並將文件修改為:

 1 package com.example.first_test;
 2 
 3 import java.util.Vector;
 4 
 5 import android.graphics.Canvas;
 6 import android.graphics.Color;
 7 import android.graphics.Paint;
 8 import android.graphics.Paint.Style;
 9 import android.view.SurfaceHolder;
10 
11 public class Func_Draw {
12         private static Vector<Paint> mPaint = new Vector<Paint>();
13         public static Integer times = 0;// 防丟搜索次數
14         public static float Bei = 200;// 繪制圖形時放大倍數
15     
16         public static void initPaint() {
17             Paint paint0 = new Paint();
18             paint0.setAntiAlias(true);
19             paint0.setStyle(Style.STROKE);
20             paint0.setColor(Color.RED);
21             mPaint.add(paint0);
22             Paint paint1 = new Paint();
23             paint1.setAntiAlias(true);
24             paint1.setStyle(Style.STROKE);
25             paint1.setColor(Color.GREEN);
26             mPaint.add(paint1);
27             Paint paint2 = new Paint();
28             paint2.setAntiAlias(true);
29             paint2.setStyle(Style.STROKE);
30             paint2.setColor(Color.BLUE);
31             mPaint.add(paint2);
32             Paint paint3 = new Paint();
33             paint3.setAntiAlias(true);
34             paint3.setStyle(Style.STROKE);
35             paint3.setColor(Color.YELLOW);
36             mPaint.add(paint3);
37             Paint paint4 = new Paint();
38             paint4.setAntiAlias(true);
39             paint4.setStyle(Style.STROKE);
40             paint4.setColor(Color.WHITE);
41             mPaint.add(paint4);
42             Paint paint5 = new Paint();
43             paint5.setAntiAlias(true);
44             paint5.setStyle(Style.STROKE);
45             paint5.setColor(Color.LTGRAY);
46             mPaint.add(paint5);
47             Paint paint6 = new Paint();
48             paint6.setAntiAlias(true);
49             paint6.setStyle(Style.STROKE);
50             paint6.setColor(Color.CYAN);
51             mPaint.add(paint6);
52         }
53     
54         public static void draw(SurfaceHolder mHolder) {
55             Canvas canvas = mHolder.lockCanvas();
56             canvas.drawRGB(0, 0, 0);
57             for (int i = 0; i < UI_Main.mBTSArrayList.size(); i++) {
58                 boolean find = false;
59                 short rssi = 0;
60                 for (int j = 0; j < UI_Main.mFuncBT.mAddrVector.size(); j++) {
61                     if (UI_Main.mBTSArrayList.get(i).mAddr
62                             .equals(UI_Main.mFuncBT.mAddrVector.get(j))) {
63                         find = true;
64                         rssi = UI_Main.mFuncBT.mRSSIVector.get(j);
65                     }
66                 }
67                 if (find == false) {
68                     canvas.drawText(
69                             times + ": NOT_FIND "
70                                     + UI_Main.mBTSArrayList.get(i).mName, 5,
71                             i * 10 + 12, mPaint.get(i));
72                 } else {
73                     float power = (float) ((Math.abs(rssi) - 59) / (10 * 2.0));
74                     float dis = (float) Math.pow(10, power);
75     
76                     canvas.drawText(
77                             times + ": FIND " + UI_Main.mBTSArrayList.get(i).mName
78                                     + " dis: " + new Float(dis).toString()
79                                     + " rssi: " + rssi, 5, i * 10 + 12,
80                             mPaint.get(i));
81                     canvas.drawCircle(canvas.getWidth() / 2,
82                             canvas.getHeight() / 2, Bei * dis, mPaint.get(i));//畫圓圈
83                 }
84             }
85             times++;
86             mHolder.unlockCanvasAndPost(canvas);// 更新屏幕顯示內容
87             UI_Main.mFuncBT.mRSSIVector.clear();
88             UI_Main.mFuncBT.mNameVector.clear();
89             UI_Main.mFuncBT.mAddrVector.clear();
90         }
91 }

操作說明:該類提供在SurfaceView上繪制功能。其中靜態方法initPaint對畫筆進行初始化,draw函數負責繪制。

draw函數的核心在於canvas繪圖,canvas繪圖的過程和我們在白紙上繪繪圖的過程很像,如:

l  第55行鎖定canvas相當於得到一張紙;
l  第56行用RGB為0的顏色來刷新canvas相當於用橡皮擦把紙上原來的東西擦掉;
l  第68和76行drawText相當於在紙的相應位置寫文字;
l  第81行drawCircle相當於在紙的相應位置繪制一個指定的圓;
l  第86行的nlockCanvasAndPost相當於你把繪制好的作品拿出來展示給別人看;

正是因為canvas的加鎖和解鎖這一機制,才保證了繪制過程中屏幕正確地顯示。

  接着再來理解這里draw函數的功能:mBTSArrayList是一個My_BTS類型的數組,保存我們想要防丟的藍牙防丟器設備的名稱、地址等信息;mFuncBT是一個可以實時搜索周邊藍牙設備的一個對象,其靜態變量mNameVector、mAddrVector、mRSSIVector保存着實時搜索結果;這樣核心部分的功能便是通過兩層循環遍歷待防丟設備是否在本次搜索中,如果不在則顯示“NOT_FIND”,如果在則由RSSI計算距離。(效果如圖8-4)

圖 8-4 找到藍牙設備圖

9)   新建一個Func_BT.java文件,並修改為:

  1 package com.example.first_test;
  2 
  3 import java.util.Vector;
  4 
  5 import android.app.Activity;
  6 import android.bluetooth.BluetoothAdapter;
  7 import android.bluetooth.BluetoothDevice;
  8 import android.content.BroadcastReceiver;
  9 import android.content.Context;
 10 import android.content.Intent;
 11 import android.content.IntentFilter;
 12 import android.os.Bundle;
 13 import android.os.Handler;
 14 import android.os.Message;
 15 
 16 public class Func_BT {
 17         private BluetoothAdapter mBtAdapter;// 藍牙適配器
 18         private static final int ENABLE_BLUETOOTH = 1;
 19         // 分別用於存儲設備名地址名稱和RSSI的向量
 20         public Vector<String> mNameVector;
 21         public Vector<String> mAddrVector;
 22         public Vector<Short> mRSSIVector;
 23     
 24         private Handler myHandler;
 25         private Activity activity;
 26         
 27         public Func_BT(Activity activity, Handler myHandler) {
 28             this.myHandler = myHandler;
 29             this.activity = activity;
 30     
 31             mNameVector = new Vector<String>();// 向量
 32             mAddrVector = new Vector<String>();
 33             mRSSIVector = new Vector<Short>();
 34     
 35             IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
 36             activity.registerReceiver(mReceiver, filter);
 37             filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
 38             activity.registerReceiver(mReceiver, filter);
 39             activity.registerReceiver(mReceiver, filter);
 40     
 41             mBtAdapter = BluetoothAdapter.getDefaultAdapter();
 42         }
 43     
 44         private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 45             @Override
 46             public void onReceive(Context context, Intent intent) {
 47                 String action = intent.getAction();
 48                 if (BluetoothDevice.ACTION_FOUND.equals(action)) {
 49                     BluetoothDevice device = intent
 50                             .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 51                     short rssi = intent.getExtras().getShort(
 52                             BluetoothDevice.EXTRA_RSSI);
 53                     mNameVector.add(device.getName());
 54                     mAddrVector.add(device.getAddress());
 55                     mRSSIVector.add(rssi);
 56                 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
 57                         .equals(action)) {
 58                     /*if (mNameVector.size() != 0) {
 59                         Message msg = new Message();// 消息
 60                         msg.what = 0x01;// 消息類別
 61                         myHandler.sendMessage(msg);
 62                     }*/
 63                 }
 64             }
 65         };
 66     
 67         public void doDiscovery() {
 68             if (mBtAdapter.isDiscovering()) {
 69                 mBtAdapter.cancelDiscovery();
 70             }
 71             mBtAdapter.startDiscovery();
 72             new TimeLimitThread().start();
 73         }
 74     
 75         public void openBT() {
 76             // 如果沒有打開則打開
 77             if (!mBtAdapter.isEnabled()) {
 78                 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
 79                 activity.startActivityForResult(intent, ENABLE_BLUETOOTH);
 80             } else {
 81                 doDiscovery();
 82             }
 83         }
 84     
 85         protected void onActivityResult(int requestCode, int resultCode, Intent data){
 86             if (requestCode == ENABLE_BLUETOOTH) {
 87                 if (resultCode == Activity.RESULT_OK) {
 88                     doDiscovery();
 89                 }
 90             }
 91         }
 92     
 93         public void setHandler(Handler myHandler) {
 94             this.myHandler = myHandler;
 95         }
 96     
 97         public void setFunc_BT(Activity activity, Handler myHandler) {
 98             this.myHandler = myHandler;
 99             this.activity = activity;
100         }
101         
102         class TimeLimitThread extends Thread{
103             public void run() {
104                 try {
105                     sleep(3000);
106                     if (mBtAdapter.isDiscovering()) {
107                         mBtAdapter.cancelDiscovery();
108                     }
109                     Message msg = new Message();// 消息
110                     msg.what = 0x01;// 消息類別
111                     myHandler.sendMessage(msg);
112                 } catch (InterruptedException e) {
113                     e.printStackTrace();
114                 }
115             } 
116         }
117 }

操作說明:該類用來實時搜索周邊藍牙設備,並把搜索結果保存在其靜態成員變量mNameVector、mAddrVector、mRSSIVector中。此外這里還使用Handler用於向調用其搜索方法的Activity發送本次搜索完畢的消息。具體介紹如下:

l  第27到42行為構造函數,主要負責成員變量初始化、注冊filter、獲取本地藍牙設備;
l  第44到65行為BroadcastReceiver,主要負責異步搜索周邊藍牙設備的廣播的接收;
l  第67到73行為開始搜索周邊藍牙設備函數;
l  第75到83行為帶有檢查本地藍牙是否開啟並能夠發出開啟請求的搜索周邊藍牙設備函數;
l  第85到91行為接收用戶是否授權打開藍牙設備的Activity的結果函數;
l  第102到116行是一個線程類主要用於限定搜索周邊藍牙設備的最長時間(默認為12s左右);

  正是因為加了獨立用於限定搜索時長的線程,才讓搜索過程的時間長短便於我們控制,但是sleep的時間也不要設置的過小,否則會出現一個藍牙設備也搜索不到的情況。(建議最好參照第七部分——“安卓藍牙編程主要操作”來理解本部分的代碼)

10)   修改UI_Main.java為:

 1 package com.example.first_test;
 2 
 3 import java.util.ArrayList;
 4 import android.app.Activity;
 5 import android.content.Intent;
 6 import android.os.Bundle;
 7 import android.os.Handler;
 8 import android.os.Message;
 9 import android.view.SurfaceHolder;
10 import android.view.SurfaceHolder.Callback;
11 import android.view.SurfaceView;
12 import android.view.View;
13 import android.view.View.OnClickListener;
14 import android.widget.Button;
15 
16 public class UI_Main extends Activity implements Callback {
17 
18         public Func_Draw mFuncDraw;
19         public SurfaceHolder mHolder;
20         public static Func_BT mFuncBT;
21         // 防丟設備
22         public static ArrayList<My_BTS> mBTSArrayList = new ArrayList<My_BTS>();
23     
24         // 消息句柄(線程里無法進行界面更新,
25      // 所以要把消息從線程里發送出來在消息句柄里進行處理)
26         public Handler myHandler = new Handler() {
27             @Override
28             public void handleMessage(Message msg) {
29                 if (msg.what == 0x01) {
30                     Func_Draw.draw(mHolder);
31                 }
32                 mFuncBT.doDiscovery();
33             }
34         };
35     
36         @Override
37         protected void onCreate(Bundle savedInstanceState) {
38             super.onCreate(savedInstanceState);
39             setContentView(R.layout.ui_main);
40     
41             Func_Draw.initPaint();
42     
43             SurfaceView mSurface = (SurfaceView) findViewById(R.id.surfaceView);
44             mHolder = mSurface.getHolder();
45             mHolder.addCallback(this);
46     
47             mFuncBT = new Func_BT(this, myHandler);
48     
49             Button mButton1 = (Button) findViewById(R.id.button_start);
50             mButton1.setOnClickListener(new OnClickListener() {
51                 @Override
52                 public void onClick(View v) {
53                     Func_Draw.times = 0;
54                     mFuncBT.openBT();
55                 }
56             });
57     
58             Button mButton2 = (Button) findViewById(R.id.button_add);
59             mButton2.setOnClickListener(new OnClickListener() {
60                 @Override
61                 public void onClick(View v) {
62                     startActivity(new Intent(UI_Main.this, UI_List.class));
63                 }
64             });
65         }
66     
67         @Override
68         public void surfaceCreated(SurfaceHolder holder) {
69             // TODO Auto-generated method stub
70     
71         }
72     
73         @Override
74         public void surfaceChanged(SurfaceHolder holder, int format, int width,
75                 int height) {
76             // TODO Auto-generated method stub
77     
78         }
79     
80         @Override
81         public void surfaceDestroyed(SurfaceHolder holder) {
82             // TODO Auto-generated method stub
83     
84         }
85 }

操作說明:上面第2步時我們修改了ui_main.xml並做出應用程序主頁面的效果,而該類則是其對應的邏輯實現。

nn  首先看第37到65行的onCreate函數:

l  第39行設置要顯示的頁面為ui_main.xml所展示的效果;
l  第41行調用Func_Draw的靜態initPaint()方法對畫筆進行初始化;
l  第43到45行負責獲取並設置SurfaceView;
l  第47行實例化一個Func_BT;
l  第49到56行是給開始防丟按鈕綁定一個監聽器,一旦點擊該按鈕則執行onClick內代碼;
l  第58到65行是給添加藍牙防丟器綁定一個監聽器,一旦點擊則啟動另一個Activity;

nn  第24到34行實例化一個Handler用於接收Func_BT一次搜索結束時發回結束的消息。在這里一旦收到本次周邊藍牙設備搜索結束的消息就調用Func_Draw.draw(mHolder)進行繪制,然后繼續調用mFuncBT.doDiscovery()實現周期性搜索/防丟。

nn  第67到84行是自動生成的,暫且不用管(也不要刪除!)。

11)   新建一個UI_List.java文件,並修改代碼為:

  1 package com.example.first_test;
  2 
  3 import java.util.ArrayList;
  4 import android.annotation.SuppressLint;
  5 import android.app.Activity;
  6 import android.app.ProgressDialog;
  7 import android.content.Intent;
  8 import android.os.Bundle;
  9 import android.os.Handler;
 10 import android.os.Message;
 11 import android.util.Log;
 12 import android.view.View;
 13 import android.view.View.OnClickListener;
 14 import android.widget.ArrayAdapter;
 15 import android.widget.Button;
 16 import android.widget.ListView;
 17 
 18 public class UI_List extends Activity {
 19 
 20         private ArrayList<String> Items;
 21         private ListView myListView;
 22         private ArrayAdapter<String> aa;
 23         private boolean getDIV;
 24     
 25         @SuppressLint("InlinedApi")
 26         protected void onCreate(Bundle savedInstanceState) {
 27             super.onCreate(savedInstanceState);
 28             setContentView(R.layout.ui_list);
 29     
 30             UI_Main.mFuncBT.setFunc_BT(this, myHandler);
 31     
 32             // 獲取listview並設置為多選
 33             myListView = (ListView) findViewById(R.id.listView1);
 34             myListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
 35             myListView.setTextFilterEnabled(true);
 36             // 設置listview數組並綁定
 37             Items = new ArrayList<String>();
 38             aa = new ArrayAdapter<String>(this,
 39                     android.R.layout.simple_list_item_checked, Items);
 40             myListView.setAdapter(aa);
 41     
 42             // 獲取OK按鈕,並遍歷選擇的設備,返回主Activity
 43             Button myButton1 = (Button) findViewById(R.id.button_ok);
 44             myButton1.setOnClickListener(new OnClickListener() {
 45                 @Override
 46                 public void onClick(View v) {
 47                     int num = 0;
 48                     for (int i = 0; i < myListView.getCount(); i++) {
 49                         if (myListView.isItemChecked(i)) {
 50                             String item = myListView.getItemAtPosition(i)
 51                                     .toString();
 52                             String name = item.substring(0, item.indexOf("\n"));
 53                             String addr = item.substring(item.indexOf("\n") + 1);
 54                             Log.i("UI_LIST", name + " " + addr);
 55                             UI_Main.mBTSArrayList
 56                                     .add(num++, new My_BTS(name, addr));
 57                         }
 58                     }
 59                     Intent mIntent = new Intent(UI_List.this, UI_Main.class);
 60                     mIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 61                     startActivity(mIntent);
 62                 }
 63             });
 64             // 獲取Search按鈕,設置監聽事件
 65             Button myButton2 = (Button) findViewById(R.id.button_search);
 66             myButton2.setOnClickListener(new OnClickListener() {
 67                 @Override
 68                 public void onClick(View v) {
 69                     getDIV = false;
 70                     UI_Main.mFuncBT.openBT();
 71                     final ProgressDialog dialog = ProgressDialog.show(UI_List.this,
 72                             "搜索藍牙設備", "稍等一下~", true);
 73                     new Thread(new Runnable() {
 74                         public void run() {
 75                             while (getDIV == false);
 76                             dialog.dismiss();
 77                         }
 78                     }).start();
 79                 }
 80             });
 81         }
 82     
 83         // 消息句柄(線程里無法進行界面更新,
 84      // 所以要把消息從線程里發送出來在消息句柄里進行處理)
 85         public Handler myHandler = new Handler() {
 86             @Override
 87             public void handleMessage(Message msg) {
 88                 if (msg.what == 0x01) {
 89                     Items.clear();
 90                     for (int i = 0; i < UI_Main.mFuncBT.mNameVector.size(); i++) {
 91                         Items.add(i, UI_Main.mFuncBT.mNameVector.get(i) + '\n'
 92                                 + UI_Main.mFuncBT.mAddrVector.get(i));
 93                         aa.notifyDataSetChanged();// 通知數據變化
 94                     }
 95                     getDIV = true;
 96                     UI_Main.mFuncBT.mRSSIVector.clear();
 97                     UI_Main.mFuncBT.mNameVector.clear();
 98                     UI_Main.mFuncBT.mAddrVector.clear();
 99                 }
100             }
101         };
102 }

操作說明:這個類和ui_list.xml也是配套的。因此:

nn  在onCreate中:

l  第28行設置要顯示的頁面為ui_list.xml所展示的效果;
l  第30行負責將周邊藍牙搜索對象的Handler設置為本Activity內的Handler;
l  第32到40行是list相關;
l  第42到80行是兩個按鈕點擊事件監聽相關;

nn  第85到101行的Handler則同UI_Main.java中的Handler類似負責接收周期性藍牙搜索結束消息。在這里當接到藍牙搜索結束的消息后是將搜索到的設備信息放入list中待用戶選擇。(沒有再次調用藍牙搜索)

nn  此外兩個按鈕監聽中執行部分要特別說明下:

l  當點擊OK按鈕時程序將用戶在list中選擇的設備的信息存放在UI_Main.mBTSArrayList中,然后結束當前Activity並啟動UI_Main所對應的Activity。
l  當點擊search按鈕時會啟動周邊藍牙設備搜索並打開一個等待對話框,其中的Thread負責等待直到搜索完畢。

12)   最后(如圖8-5)找到AndroidMainFest.xml文件修改為: 

            圖 8-5 AnDroidManifest.xml文件

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.example.first_test"
 4     android:versionCode="1"
 5     android:versionName="1.0" >
 6 
 7     <uses-sdk
 8         android:minSdkVersion="8"
 9         android:targetSdkVersion="19" />
10     
11     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
12     <uses-permission android:name="android.permission.BLUETOOTH" />
13     
14     <application
15         android:allowBackup="true"
16         android:icon="@drawable/ic_launcher"
17         android:label="@string/app_name"
18         android:theme="@style/AppTheme" >
19         <activity
20             android:name=".UI_Main"
21             android:label="@string/app_name" >
22             <intent-filter>
23                 <action android:name="android.intent.action.MAIN" />
24                 <category android:name="android.intent.category.LAUNCHER" />
25             </intent-filter>
26         </activity>
27         
28         <activity
29             android:name=".UI_List"
30             android:label="@string/app_name" >
31             <intent-filter>
32                 <action android:name="android.intent.action.UI_List" />
33                 <category android:name="android.intent.category.DEFAULT" />
34             </intent-filter>
35         </activity>
36         
37     </application>
38     
39 
40 </manifest>

操作說明:主要是在11、12行添加藍牙權限以及在28到35行添加一個activity。

 

9 最終成果檢查

到目前為止我們已經全部完工,下面是成果自評的時候啦(請參照下列條目給自己打分):

  • 你做的藍牙防丟器可以被搜到(+ 20分)
  • 大致讀懂了第7部分的內容(+ 10分)
  • 親自按照第8部分實現了防丟器的安卓應用(+ 40分)
  • 對軟件或者硬件有加入自己想法改進的(+ 20分)
  • 將自己制作的防丟器推廣給別人用的(+ 20分)
  • 搜索資料做出藍牙室內導航的(+ 80分)
  • ……

及格分70分,不夠的要注意打好基礎哦!

[請看正版請百度:beautifulzzzz(看樓主博客園官方博客,享高質量生活)嘻嘻!!!]

[如果有需要制作藍牙防丟器或藍牙室內定位的可以聯系我哦~]

如果您覺得不錯,別忘點個贊讓更多的小伙伴看到\(^o^)/~ 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM