藍牙( Bluetooth ):是一種無線技術標準,可實現固定設備、移動設備和樓宇個人域網之間的短距離數據交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍牙技術初由電信巨頭愛立信公司于1994年創制,當時是作為RS232數據線的替代方案。藍牙可連接多個設備,克服了數據同步的難題。
藍牙4.0是2012年新藍牙版本,是3.0的升級版本;較3.0版本更省電、成本低、3毫秒低延遲、超長有效連接距離、AES-128加密等;通常用在藍牙耳機、藍牙音箱等設備上。
本項目是基于藍牙4.0的串口調試助手。主要應用在攜帶藍牙4.0的設備上,功能是主動掃描周邊的藍牙設備,連接成功后,模塊所發送的數據會顯示在對應的區域。同時,還能夠向模塊發送信息。接受和發送的信息都可以選擇為十六進制。
第 1 章 使用說明
軟件共分為三個部分:數據接收區,數據發送區,設備掃描區。
點擊搜索,軟件會開始進行周邊藍牙設備的掃描,需要等待五分鐘。
掃描完成后,設備會在右側顯示出來,點擊設備就可以連接。
這是結婚搜到的數據就會顯示在左側,當不需要的時候,點擊右下角的斷開,就可以中斷和設備的連接。
第 2 章 環境搭建
2.1 Android 開發環境的安裝與配置
Android應用軟件開發需要的開發環境在路徑“光盤\Android應用開發環境\”下:
JDK: JDK\JDK8\jdk-8u5-windows-i586.exe(32bit)或者jdk-8u5-windows-x64.exe(64bit)
(從JDK 8.0開始不支持Windows XP操作系統,使用Windows XP的用戶可以使用JDK7目錄下的內容)
ADT: adt-bundle-windows-x86.7z(32bit)或者adt-bundle-windows-x86_64.7z(64bit)
以下主要介紹在Windows環境下搭建Android開發環境的步驟和注意事項。
2.2 安裝JDK和配置Java開發環境
雙擊JDK\JDK8\jdk-8u5-windows-i586.exe(32bit操作系統)或者jdk-8u5-windows-x64.exe(64bit操作系統)進行安裝(從JDK 8.0開始不支持Windows XP操作系統,使用Windows XP的用戶可以使用JDK7目錄下的內容選擇代替JDK8目錄下的內容)。接受許可證,選擇需要安裝的組件和安裝路徑后,單擊“下一步”按鈕,完成安裝過程。
安裝完成后,利用以下步驟檢查安裝是否成功:打開Windows CMD窗口,在CMD窗口中輸入java –version命令,如果屏幕出現下圖所示的代碼信息,說明JDK安裝成功。
XP下安裝JDK7如下:
非XP下安裝JDK8如下:
2.3 解壓adt-bundle-windows
JDK安裝成功后,使用軟件解壓ADT目錄下的adt-bundle-windows-x86.7z(32bit)或者adt-bundle-windows-x86_64.7z(64bit)。
注意:解壓路徑不包含中文;
2.4 運行Eclipse
解壓完畢后,直接執行其中的eclipse\eclipse.exe文件,Eclipse可以自動找到用戶前期安裝的JDK路徑。
2.5 配置Eclipse
運行解壓目錄下的eclipse\eclipse.exe,為自己選擇一個工作目錄Workspace,不要有中文路徑,不選擇默認也可以。
需要為Eclipse關聯SDK的安裝路徑,即解壓路徑下的sdk目錄。在Eclipse中,點擊Window->Preferences,會看到其中添加了Android的配置,按圖所示的操作,然后點擊Apply,后點擊OK即可。
完成以上步驟后,設置Eclipse環境
勾選Android相關的工具,點擊OK(如果已經勾選,則不理會)。
第 3 章 源碼編譯
3.1 導入源碼
打開Eclipse環境,選擇File->Import。
然后,導入光盤資料中的“BlueHelper”工程,勾選下圖中的選項。
點擊finish完成工程的導入
3.2 運行程序
注意:如果在調試開發板的時候,出現ADB連接不上的問題(已知華清遠見FSPAD723開源平板),可以試著替換Android SDK的ADB工具(把光盤\Android應用開發環境\ADB\ADB1.0.26\下的4個文件拷貝到用戶ADT解壓目錄下的sdk\platform-tools中)
開發期間,在實際的設備上運行Android程序與在模擬器上運行該程序的效果幾乎相同,需要做的就是用USB電纜連接手機與計算機,并安裝一個對應的設備驅動程序。如果模擬器窗口已打開,請將其關閉。只要將開發平臺通過USB下載線與計算機相連,應用程序就會在開發平臺上加載并運行。
在Eclipse中選擇“Run” →“Run”(或Debug)命令(或者在工程上點擊右鍵),這時會彈出一個窗口,讓你選擇用模擬器還是手機來顯示,如果選擇手機,即可在手機上運行該程序。
第 4 章 詳細設計
4.1 BlueTool藍牙工具
藍牙4.0采用了新的協議,與2.0并不通用,所以這里封裝了工具類來進行數據的溝通。
BluetoothGatt類是連接遠程設備返回的代理類,我們需要用這個類來進行服務和特征值得獲取。
NormalText Code
private BluetoothGatt bluetoothGatt = null;
private BluetoothAdapter bluetoothAdapter = null;
private BluetoothGattService bluetoothService = null;
private BluetoothGattCharacteristic characteristic = null;
這里定義的UUID是連接特定的服務和特征值,如果有需要的話,可以在這里進行更改來實現別的需求。
NormalText Code
public static String serviceUUID = "00001234-0000-1000-8000-00805f9b34fb";
public static String characteristicUUID = "0000fff6-0000-1000-8000-00805f9b34fb";
連接藍牙的第一步,需要先掃描設備,這里給出掃描的方法,需要調用協議中已經實現的方法startLeScan()來獲得設備,之后,再通過stopLeScan()函數來停止掃描。
NormalText Code
// 搜索設備并添加到列表中
public Boolean SearchToList() {// 打開藍牙,搜索設備
if (bluetoothAdapter != null) {
if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
// 打開藍牙
bluetoothAdapter.enable();
log.E("打開藍牙!");
// 搜索設備
bluetoothAdapter.startLeScan(scanCallback);
log.E("開始搜索!");
return true;
} else {
// 搜索設備
bluetoothAdapter.startLeScan(scanCallback);
log.E("開始搜索!");
return true;
}
} else {
log.E("bluetoothAdapter == null");
return false;
}
}
當startLeScan()掃描到設備的時候,會回調下面的這個函數,我們要做的就是在這個函數中將掃描到的設備添加進設備列表就行。
NormalText Code
private BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
if ((device != null) && (deviceList != null)) {
if ((Isrepeat(device, deviceList) == false)
&& (device.getName() != null)) {
deviceList.add(device);
deviceName.add(device.getName());
deviceAddr.add(device.getAddress());
}
}
}
};
連接完成后,需要獲得特征值進行讀寫操作。
NormalText Code
// 連接設備并獲得特征值
public synchronized boolean BLEConnect(BluetoothDevice remoteDev) {
if (remoteDev == null) {
return false;
}
bluetoothGatt = remoteDev.connectGatt(context, false, gattCallback);
return true;
}
當connectGatt函數返回之后,會回調BluetoothGattCallback 變量當中的onConnectionStateChange函數,然后在這個函數中,我們要繼續調用discoverServices這個函數來發現設備所提供的服務。當discoverServices被調用的時候,也會進行onServicesDiscovered這個函數的回調。在這個函數中,如果我們想要讀取設備的數據,需要通過setCharacteristicNotification來設定接受通知,這樣,當設備有數據傳過來的時候,會自動通知程序,我們只需要在onCharacteristicChanged中將返回的數據取出來就行。
NormalText Code
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (bluetoothGatt != null) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
log.E("連接成功!");
handler.sendEmptyMessage(3);
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
log.E("連接斷開!");
handler.sendEmptyMessage(4);
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (gatt != null) {
bluetoothService = gatt
.getService(UUID.fromString(serviceUUID));
if (bluetoothService != null) {
characteristic = bluetoothService.getCharacteristic(UUID
.fromString(characteristicUUID));
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(
characteristic, true);
// bluetoothGatt.readCharacteristic(characteristic);
} else {
log.E("characteristic == null");
}
} else {
log.E("bluetoothService == null");
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
// log.E("onCharacteristicChanged");
readDate = characteristic.getValue();
if ((readDate != null) && (readDate.length > 0)) {
// log.E("接收數據成功!" + printHex(readDate) + "-" +
// readDate.length);
handler.sendEmptyMessage(1);
handler.sendEmptyMessage(2);
} else {
log.E("接收數據失敗!");
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
log.E("onCharacteristicWrite" + "-" + status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
log.E("onCharacteristicRead" + "-" + status);
};
};
4.2 Bluehelper調試助手
主界面的程序,是通過Handler機制進行消息的傳遞的。
NormalText Code
@SuppressLint("HandlerLeak")
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:// 刷新搜索列表
devices = blue.GetdeviceName();
adapter.clear();
for (int i = 0; i < devices.size(); i++) {
adapter.add(blue.GetdeviceName().get(i) + " : "
+ blue.GetdeviceAddr().get(i));
}
adapter.notifyDataSetChanged();
break;
case 1:// 更新接收界面
if (hexrece.isChecked()) {
txtrece.append(printHex(BlueTool.readDate) + " \n");
} else {
txtrece.append(BlueTool.readDate.toString() + " \n");
}
scroll.fullScroll(ScrollView.FOCUS_DOWN);// 滾動到底
break;
case 2:// 持續讀取數據
if (threadon == false) {
threadon = true;
// new readThread().start();
}
break;
case 3:// 改變按鈕
close.setVisibility(0);
Toast.makeText(Bluehelper.this, "連接成功".toString(),
Toast.LENGTH_SHORT).show();
break;
case 4:// 改變按鈕
close.setVisibility(4);
Toast.makeText(Bluehelper.this, "連接斷開".toString(),
Toast.LENGTH_SHORT).show();
break;
case 5:// 改變按鈕
Toast.makeText(Bluehelper.this, "沒有搜索到設備".toString(),
Toast.LENGTH_SHORT).show();
break;
case 10:// 改變按鈕
Toast.makeText(Bluehelper.this, msg.obj.toString(),
Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
};
在onCreate函數中,我們需要設定界面中各個按鍵的回調函數。
NormalText Code
adapter = new ArrayAdapter
android.R.layout.simple_list_item_1, new ArrayList
list.setAdapter(adapter);
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterViewparent, View view,
int position, long id) {
if (close.getVisibility() == 4) {
// log.E("點擊位置:" + position);
new connectThread(position).start();
}
}
});
search.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new searchThread().start();
}
});
close.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (blue != null) {
blue.Quit();
}
close.setVisibility(4);
}
});
clear.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
txtrece.setText("");
}
});
send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (close.getVisibility() == 0) {
if (txtsend.getText().toString().length() > 0) {
if (hexsend.isChecked()) {
blue.write(BlueTool.StrToByte(txtsend.getText()
.toString()));
} else {
blue.write(txtsend.getText().toString().getBytes());
}
}
} else {
Toast.makeText(Bluehelper.this, "請先連接設備".toString(),
Toast.LENGTH_SHORT).show();
}
}
});
searchThread 連接線程,主要工作就是調用藍牙工具中的SearchToList,等待掃描5s后,再停止掃描。
NormalText Code
class searchThread extends Thread {
@Override
public void run() {
// log.E("開始藍牙搜索。。。。。");
Message msg = new Message();
msg.what = 10;
msg.obj = "開始搜索。。。";
handler.sendMessage(msg);
blue.Clear();
handler.sendEmptyMessage(0);
Boolean on = blue.SearchToList();
try {
sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
blue.Disserach();
if (blue.GetdeviceName().size() > 0) {
log.E("搜索到的設備:" + blue.GetdeviceName());
if (true == on) {
handler.sendEmptyMessage(0);
} else {
log.E("搜索異常!");
}
} else {
log.E("沒有搜索到設備!");
handler.sendEmptyMessage(5);
}
}
}