一、参考的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;
}
这是我们项目要求这样弄的,可能每个项目的做法不一样,仅供参考哈
有问题欢迎指正!(后面继续补充)