一、參考的demo:
1、http://download.csdn.net/detail/kjunchen/9363233
2、http://download.csdn.net/detail/lqw770737185/8116019
3、https://github.com/lidong1665/Android-ble
4、https://github.com/litesuits/android-lite-bluetoothLE
5、https://github.com/alt236/Bluetooth-LE-Library---Android
二、基本知識
研究藍牙,還是要把那些基本知識了解好了,在去研究,我是覺得我還是朦朦的,都是因為趕項目,匆匆了解的
1、(英文精通的可以看看這個)https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
2、http://www.blogjava.net/zh-weir/archive/2013/12/09/407373.html
3、http://blog.csdn.net/shulianghan/article/details/50515359
三、對于通過監聽裝置接收資料的簡單的流程:
a、掃描
b、發現裝置,比對建立連結
c、掃描服務,比對服務與特征的uuid
d、監聽比對到的uuid。(如果是讀寫的話,就讀寫該uuid,回調函數裡的onCharacteristicChanged()方法會得到藍牙那邊傳回來的值)
四、實際中遇到的問題:(開發的是藍牙鎖,對時間要求很高)
android的BLE實際中的遇到的問題很多,裝置與IOS通信不會出問題,與android通信容易出問題。
1、app與裝置連接配接問題,點選連接配接後,傳回的SEVICE(服務)和Characteristic(特征值)有的手機(例:小米3C、華為C199s)收到很快,有的手機傳回特别慢(MX4),魅族有的快有的慢。連接配接後開鎖後,老是會斷開,藍牙那邊一直說不會斷開。資料接收不穩定時快時慢。有的手機(例:小米3)連接配接後好像是直接斷開了,也就找不到SEVICE(服務)和Characteristic(特征值),也就無法接受發送資料了。咨詢别人,有人說是手機系統不同的問題,安卓手機都很不穩定。
2、給藍牙下發資料(寫資料)的時候,回調函數中的onCharacteristicChanged()方法有時候會調用,有時候不會調用,以至于有時候有傳回值,有時候沒有傳回值。咨詢别人,有的人說有可能是丢包的問題,或者接受發送資料的效率。
3、我們需要發送64個位元組的數組,如果一次性發送過去,藍牙裝置那裡可能無法及時處理以緻沒有任何回應。要想暢通的與藍牙子產品通信,考慮資料接收的延時時間非常重要。調整位元組的發送速率,就成為非常關鍵的一步。值得注意的是,資料的發送是非常快的,就是因為這樣才會導緻藍牙裝置那裡無法及時處理,是以,每次發送後的延時(Thread.sleep(200);)是非常重要的。每次發送資料後要延遲才能收到藍牙傳回的值。
4、android的藍牙開發遇到最常見的問題就是發現連接配接藍牙裝置連接配接不上,仔細一看竟然是BluetoothGatt status 133或者129,我是通過重新開機手機或者重新開機藍牙後才能在連接配接的。
(别人的方法)最後終于找到緩解這種現象的辦法android ble 133,解決辦法就是要重新連接配接同一個藍牙裝置的時候,記得調用BluetoothGatt的.close()方法來關閉目前的藍牙連接配接并清掉已使用過的藍牙連接配接。
五、别人遇到的問題:
1、某些函數調用之間存在先後關系。例如首先需要connect上才能discoverServices。
2、一些函數調用是異步的,需要得到的值不會立即傳回,而會在BluetoothGattCallback的回調函數中傳回。
3、http://www.race604.com/android-ble-tips/
六、開發藍牙可能會卡住的點:
1、根據接收到的藍牙裝置端的無線信号強度(RSSI)來估算距離。其計算公式是:
d=10^ (( abs ( RSSI ) - A ) / ( 10*n ) )
d是計算距離,RSSI是信号強度,A為發射端和接收端相隔1米時的信号強度,n是環境衰減因子。對于不同的藍牙裝置該值是不一樣的,同樣的裝置在不同的發射功率的情況下其信号強度也是不一樣的,而且對于同是1米的情況下,環境對于信号強度也是有影響的。n是環境衰減因子,自然跟環境有關。是以在确切發射功率的情況下,A和n對于同一款裝置來說,也是一個經驗值。
2、一秒擷取rssi(信号強度)(在開發中因為要一直擷取rssi來判斷要不要自動開鎖)
(1)掃描藍牙的時候,可以擷取到rssi
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback;
{
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.e("url","rssi=="+rssi);
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
}
(2)連接配接藍牙後一秒擷取一次rssi
/**
* 讀取藍牙RSSI線程
*/
Thread readRSSI = new Thread() {
int Rssi = 0;
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while (isReadRssi) {
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (MainActivity.is_upload == false) {
// 如果讀取藍牙RSSi回調成功
if (mBleService.getRssiVal()) {
// 擷取已經讀到的RSSI值
Rssi = mBleService.getBLERSSI();
sendRSSI(Rssi);//當連接配接成功下發RSSI值
Log.e("url", "藍牙連接配接後的rssi==" + Rssi);
}
}
}
}
}; /**
* Implements callback methods for GATT events that the app cares about. For example,
* connection change and services discovered.
*/
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
}
// New services discovered
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
}
// Result of a characteristic read operation
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
//将回調的RSSI值指派
BLERSSI = rssi;
}
};
//擷取已經得到的RSSI值
public static int getBLERSSI() {
return BLERSSI;
}
//是都能讀取到已連接配接裝置的RSSI值
//執行該方法一次,獲得藍牙回調onReadRemoteRssi()一次
/**
* Read the RSSI for a connected remote device.
*/
public boolean getRssiVal() {
if (mBluetoothGatt == null)
return false;
return mBluetoothGatt.readRemoteRssi();
}
3、寫藍牙資料用byte,資料轉換
/**
* 十六進制串轉化為byte數組
*
* @return the array of byte
*/
public static final byte[] hex2byte(String hex)
throws IllegalArgumentException {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException();
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = "" + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}
/** Convert byte[] to hex string.這裡我們可以将byte轉換成int,然後利用Integer.toHexString(int)來轉換成16進制字元串。
* @param src byte[] data
* @return hex string
*/
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* java 合并兩個byte數組
*/
public static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
byte[] byte_3 = new byte[byte_1.length + byte_2.length];
System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
return byte_3;
}
4、寫藍牙資料:要調用writeCharacteristic()方法和setCharacteristicNotification()方法。寫資料後,藍牙的傳回值在回調函數中的onCharacteristicChanged()方法中擷取
/**
* Write data to characteristic, and send to remote bluetooth le device.
*
* @param serviceUUID remote device service uuid
* @param characteristicUUID remote device characteristic uuid
* @param value Send to remote ble device data.
*/
public boolean writeCharacteristic(String serviceUUID, String characteristicUUID, byte[] value) {
if (mBluetoothGatt != null && characteristicUUID != null) {
BluetoothGattService service =
mBluetoothGatt.getService(UUID.fromString(serviceUUID));
if (service != null) {
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(UUID.fromString(characteristicUUID));
if (characteristic != null) {
characteristic.setValue(value);
Log.e("url", "Write Success, DATA1: " + Arrays.toString(characteristic.getValue()));
return mBluetoothGatt.writeCharacteristic(characteristic);
}
}
}
return false;
} /**
* Enables or disables notification on a give characteristic.
*
* @param characteristic Characteristic to act on.
* @param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(String serviceUUID, String characteristicUUID,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Toast.makeText(this, "藍牙斷開了,請重新連接配接!", Toast.LENGTH_SHORT).show();
Log.w(TAG, "BluetoothAdapter not initialized1111");
isconnect = false;
ble_connect = "disconnect";
return;
}
ble_connect = "connect";
BluetoothGattService service =
mBluetoothGatt.getService(UUID.fromString(serviceUUID));
if (service != null) {
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(UUID.fromString(characteristicUUID));
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
} /**
* Implements callback methods for GATT events that the app cares about. For example,
* connection change and services discovered.
*/
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
}
// New services discovered
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
}
// Result of a characteristic read operation
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.e("tag", "onCharacteristicWrite" );
super.onCharacteristicWrite(gatt, characteristic, status);
String address = gatt.getDevice().getAddress();
for (int i = 0; i < characteristic.getValue().length; i++) {
Log.i(TAG, "address: " + address + ",Write: " + characteristic.getValue()[i]);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.e("tag", "傳回資訊--> " + DigitalTrans.bytesToHexString(characteristic.getValue()));
if (mOnDataAvailableListener != null) {
mOnDataAvailableListener.onCharacteristicChanged(gatt, characteristic);
}
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (mOnDataAvailableListener != null) {
mOnDataAvailableListener.onDescriptorRead(gatt, descriptor, status);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
//将回調的RSSI值指派
BLERSSI = rssi;
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
if (mOnMtuChangedListener != null) {
mOnMtuChangedListener.onMtuChanged(gatt, mtu, status);
}
}
};
5、因為我搜尋到藍牙分類是通過藍牙的名稱,是以在搜尋的時候會遇到搜尋到藍牙名稱為空的其他藍牙,比如手機上的藍牙,是以需要對空名稱的藍牙信号進行處理。
過濾掉空指針的異常(過濾空名字的藍牙)
解決方案:
try {
//實作方法;
}
} catch (NullPointerException e) {
e.printStackTrace();
}
6、如果有設定提示音,藍牙連接配接或者開鎖的時候,要有提示音。
提示音實作方法一:
private SoundPool sp;//聲明一個SoundPool
private int music;//定義一個整型用load();來設定suondID
sp= new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);//第一個參數為同時播放資料流的最大個數,第二資料流類型,第三為聲音品質
music = sp.load(this, R.raw.connect_dingdong, 1); //把你的聲音素材放到res/raw裡,第2個參數即為資源檔案,第3個為音樂的優先級
button.onclick(){
sp.play(music,7, 7, 0, 0, 1);
}
提示音實作方法二:
private MediaPlayer music = null;// 播放器引用
button.onclick(){
music = MediaPlayer.create(MainActivity.this,R.raw.lock_open);
music.start();
}
7、CRC檢驗
要求下發資料是:0x02 0x53 0x4A CRC (根據{0x02, 0x53, 0x4A}求得crc)然後在下發
public void crc() {
short[] upload = new short[]{0x02, 0x53, 0x4A};
short crc1 = 0;
crc1 =appData_Crc(upload, crc1, upload.length);
String str_crc = Integer.toHexString(crc1);
if (str_crc.length() == 1) {//如果長度為1,那麼DigitalTrans.hex2byte(str_crc)的時候會報錯
str_crc = 0 + str_crc;
}
Log.e("url", "02_53_4a的str_CRC==" + str_crc);//55
byte[] crc = com.yundiankj.ble_lock.Resource.DigitalTrans.hex2byte(str_crc);//十六進制串轉化為byte數組
Log.e("url", "02_53_4a的byte_crc==" + crc[0]); // crc[0]就是根據0x02, 0x53, 0x4A求得的
}
/**
* crc的求法
*
* @return the array of byte
*/
public static short appData_Crc(short[] src, short crc, int len) {
int i;
short bb;
for (int j = 0; j < len; j++) {
bb = src[j];
for (i = 8; i > 0; --i) { //Boolean.parseBoolean(Integer.toBinaryString((bb & 0x01)^(crc &0x01)))
if ((((bb ^ crc) & 0x01)) == 1) { //判斷與x7異或的結果(x8)((bb ^ crc) & 0x01)
crc ^= 0x18; //回報到x5 x4
crc >>= 1; //移位
crc |= 0x80; //x7異或的結果送x0
} else {
crc >>= 1;
}
bb >>= 1;
}
}
return (crc);
}
/**
* 十六進制串轉化為byte數組
*
* @return the array of byte
*/
public static final byte[] hex2byte(String hex)
throws IllegalArgumentException {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException();
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = "" + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}
這是我們項目要求這樣弄的,可能每個項目的做法不一樣,僅供參考哈
有問題歡迎指正!(後面繼續補充)