Android开发之蓝牙(二)——基于BLE协议蓝牙模块通信

时间:2021-07-02 17:44:09

app是ble与spp选择连接蓝牙模块,关于spp的可以看
http://blog.csdn.net/wzhworld/article/details/76223301

介绍

摘要:蓝牙4.0——BLE是近年来应用比较广泛的profile,与传统蓝牙相比,其低功耗的特征最为显著,在蓝牙模块中(CC2540),一块纽扣电池可供电半年到一年之间。Android 4.3才开始支持BLE API,所以请使用蓝牙4.0和Android 4.3及其以上的系统,本文所用的BLE终端是一个蓝牙4.0的串口蓝牙模块FSC——BT826,是一块双模蓝牙模块。

基本概念

1、在蓝牙BLE中有四个角色:
广播者(Braodcaster):广播发送者,是不可连接的设备。
观察者(Observer):扫描广播,不能够启动连接。
外围(periphery):广播发送者,可连接的设备,在单一链路层作为从机。
*(central):扫描广播,启动连接,在单一或多链路层作为主机。

一般我们开发的话是使用*(BluetoothGatt)或者外围(BluetoothGattServer)来进行开发的,正常我们是把手机当作是主机来接收信息,而蓝牙模块当作是从机发送数据。下面所讲的就是按照这个方式进行开发的。

2、UUID
BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。
蓝牙就是通过这几个UUID识别交换数据的,这几个很重要。当时因为蓝牙模块是直接网上买的,所以具体的情况都不清楚,捣鼓一天之后不知道那个是读哪个是写。

网上也有一些直接方法获取UUID是读的还是写的,但是对于现成的蓝牙模块好像不行。因为我的程序是用来类似串口调试助手那样可以用安卓跟PC端调试助手通信,试了一天之后才发现我的蓝牙模块只能通过notify通知把数据传给手机,还有一个就是有些Characteristic是可读可写,但那些是基于有进行蓝牙模块的开发才可以自定义去选择不同的特征值,我买的蓝牙某块FSC-BT826已经写死了某些通道才可以,所以这一点要考虑清楚自己的情况进行选择。

Android开发流程

(1)获取本手机的Manager
(2)获取本手机适配器adapter
(3)开启扫描设备scanLeDevice,扫描到的结果会在ScanCallback回调中出现
(4)对目标设备进行Gatt连接,连接结果会在GattCallback的各个回调结果中,有连接状态、扫描服务、读写通知的回调

Android开发之蓝牙(二)——基于BLE协议蓝牙模块通信

1、设置AndroidManifest.xml
要使用蓝牙肯定要有所需要的权限,与蓝牙spp不同的是多了一个BLE的设置

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

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

<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

如果你做的app是基于BLE的,required=”false”这个可以设置为true,因为我做的app是可以选择spp和BLE连接,有所不同,在活动中去开启BLE。

2、判断是否开启蓝牙,是否支持蓝牙ble

  //检测是否支持蓝牙ble
if(!getPackageManager().
hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(getApplicationContext(),
"ble_not_supported_in_this_phone",
Toast.LENGTH_SHORT).show();

stopSelf();
}

//如果没有打开蓝牙,则打开,两种情况,一种是强制打开,一种是对话框打开
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent openBlEIntent = new Intent(BluetoothTools.ACTION_OPEN_BLE);
sendBroadcast(openBlEIntent);
} else {
Log.e(TAG, "蓝牙已经打开");
return false;
}

3、获取本机蓝牙适配器

final BluetoothManager bluetoothManager 
= (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);

mBluetoothAdapter = bluetoothManager.getAdapter();

4、扫描设备scanDevice

private void scanLeDevice(final boolean enable) {
Log.e(TAG, "START_TO_SCAN");
Handler mHandler = new Handler();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothLeScanner.stopScan(mScanCallback);
}
}, SCAN_PERIOD);//SCAN_PERIOD=10000,即十秒后关闭扫描
Toast.makeText(getApplicationContext(), "扫描10s", Toast.LENGTH_SHORT).show();
mScanning = true;
mBluetoothLeScanner.startScan(mScanCallback);
} else {
Toast.makeText(getApplicationContext(), "停止扫描", Toast.LENGTH_SHORT).show();
mScanning = false;
//mScanning是一个按钮的判断,本来想做一个开启关闭的按钮,但是逻辑上有点错误,
mBluetoothLeScanner.stopScan(mScanCallback);
}
}

做之前参考别人的文章,发现好像开始扫描跟关闭扫描的api已经不建议使用了,所以后面差了下,改成一个Scanner。startScan(回调函数)和stop(回调函数)

5、扫描回调结果

private ScanCallback mScanCallback = new ScanCallback() {
boolean flag = true;

@Override
public void onScanResult(int callbackType, ScanResult result) {
// super.onScanResult(callbackType, result);
if (result != null) {
BluetoothDevice device = result.getDevice();
if ((device != null) && (devList != null)) {
for (BluetoothDevice de : devList) {
if (device.getName().equals(de.getName())) {
flag = false;
break;
} else {
flag = true;
}
}
if (true == flag) {
devList.add(device);
Intent dataListIntent = new Intent(BluetoothTools.ACTION_LEDATA_SEND);
Bundle mBundle = new Bundle();
mBundle.putParcelable("Device_Data", device);
dataListIntent.putExtras(mBundle);
sendBroadcast(dataListIntent);
}
}
}
}
};

扫描结果添加到一个List里面,因为不会用set,所以我就直接用一个判断List是否有相同的蓝牙设备名字来确定是否添加,如果添加的话我就把蓝牙设备的独享通过广播传到主活动中更新列表,因为BluetoothDevice已经序列化过,所以可以直接传送。

6、连接设备,主活动那边listview获取你点击的对象后,把选择的蓝牙设备通过广播发送到服务中连接。
mLastBluetoothDevice.connectGatt(getApplicationContext(), true, mGattCallback);

if (BluetoothTools.ACTION_DEVICE_SEND.equals(action)) {
mLastBluetoothDevice = intent.getParcelableExtra("BluetoothDevice");
// Toast.makeText(getApplicationContext(), mLastBluetoothDevice.getName(), Toast.LENGTH_SHORT).show();

mGatt = mLastBluetoothDevice.connectGatt(getApplicationContext(), true, mGattCallback);
if (mGatt != null) {
if (mGatt.connect()) {
Log.e(TAG, "-------------------------Connect succeed.");
} else {
Log.e(TAG, "-------------------------Connect fail.");
}
} else {
Log.e(TAG, "----------------------------BluetoothGatt null.");
}
}

7、连接设备之后自然是开始进行读写的配置,这个时候就跟你的UUID有关了,如果你有iphone的话可以下载LightBlue这个软件,他里面能很方便的标出相关的Charactistic的通道,不过我的程序里面把找出服务的UUID那一段程序删除了,所以你可以网上收一下,LightBlue可以看到你使用的是那一个通道进行通信。

/**
* BluetoothGatt回调
*/

private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

@Override
public void onConnectionStateChange(BluetoothGatt gatt,
int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {

mGatt.discoverServices(); //执行到这里其实蓝牙已经连接成功
Log.e(TAG, "Connected to GATT server.");

} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
if (mBluetoothDevice != null) {
Log.e(TAG, "重新连接");
mGatt.connect();
} else {
Log.e(TAG, "Disconnected from GATT server.");
}
}
}


/**
* 发现服务的回调
*
* @param gatt
* @param status
*/

public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//成功发现服务后可以调用相应方法得到该BLE设备的所有服务,并且打印每一个服务的UUID和每个服务下各个特征的UUID
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "GATT_connect_succeed");
service = mGatt.getService(UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb"));
if (service != null) {
BluetoothGattCharacteristic characteristicRead
= service.getCharacteristic(
UUID.fromString("0000fff1-0000-1000-8000-
00805f9b34fb"
));
if (characteristicRead != null) {
gatt.setCharacteristicNotification(characteristicRead, true);
BluetoothGattDescriptor descriptor
= characteristicRead.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-
00805f9b34fb"
));

descriptor.setValue(
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
Log.e("TAG","CharacteristicRead has saved");
}
characteristicWrite = service.getCharacteristic(
UUID.fromString("0000fff2-0000-1000-8000-
00805f9b34fb"
));
if (characteristicWrite != null ) { mGatt.setCharacteristicNotification(
characteristicWrite, true); characteristicWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
Log.e("TAG","CharacteristicWrite has saved");
}
}
createWriteDataThread();
}
}


/**
* 读操作的回调
*
* @param gatt
* @param characteristic
* @param status
*/

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
/**
/注意!!!!!!!!你如果是通知的话使用的是下面的那个回调函数
/**
}
}

/**
* 通知回调
*
* @param gatt
* @param characteristic
*/

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
try {
receiveData = new String(characteristic.getValue(),"gbk");

Intent receiveIntent = new Intent(BluetoothTools.ACTION_RECEIVELE_DATA);
receiveIntent.putExtra("receiveData",receiveData);
sendBroadcast(receiveIntent);
Log.e(TAG, "读取成功" + new String(characteristic.getValue(), "gbk"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}

/**
* 写操作回调
*
* @param gatt
* @param characteristic
* @param status
*/

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
try {
boolean isBoolean = false;
// isBoolean = mGatt.writeCharacteristic(characteristic);

//建议如果复制粘贴别人的也要先看懂再说,因为我就是这样被坑的,开始调试的时候在手机端写数据到pc,一直循环在发送,我知道应该是回调结果里面那一句出问题了,因为回调结果是一直在监视,才会不停地读。我一开始以为上面isBoolean只是一个判断变量而已,没有影响,结果查到最后才发现这货一直在写发送到pc端,因为没看清楚、大意了造成找了一天的时间,白白浪费了。

Log.e(TAG, "BluetoothAdapter_writeCharacteristic = " + isBoolean); //如果isBoolean返回的是true则写入成功
} catch (Exception e) {
e.printStackTrace();
}
}
};

8、最后将收到的数据开启一个线程去处理

public void createWriteDataThread() {
Log.e(TAG,"-------> createWriteDataThread()");
// setCharacteristicNotification(characteristicWrite, true);
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if (isSend == true) {
characteristicWrite.setValue(bytes);
mGatt.writeCharacteristic(characteristicWrite);
Log.e(TAG, "------->write");
isSend = false;
}
}
}
}).start();
}

程序到这里就结束了,想看的话我后面把程序发一下,因为初次写,所以有点乱。这是一个spp和ble结合的类似串口调试助手的app,另一篇关于spp的文章有兴趣可以看下,方法差不多,但还是有点小小不同。

程序是三个主活动,第一个是用来开启第二或者第三个活动的,第二个主活动是用来spp,第三个用来ble,然后两个活动都是开启服务去进行一些操作,有不懂的可以留言问我。

spp:

Android开发之蓝牙(一)——基于spp协议蓝牙模块通信

项目下载:

github
csdn