一:基本概念
BLE:BluetoothLow Energy蓝牙低功耗技术,是蓝牙4.0引入的新技术,Android4.3中引进了对BLE的支持
蓝牙:Bluetooth是一种无线技术标准,短距离数据交换(使用2.4-2.485GHz)无线电波
蓝牙典型距离是10m以内,传输速度可达24Mbps(蓝牙3.0高速蓝牙)
蓝牙4.0/4.1 即低功耗蓝牙
蓝牙:5.0针对物联网方向的改进
BLE最大的特点就是低功耗,有些BLE设备一个纽扣电池可以使用一两年.
BLE功耗低那么能传输的速率就慢了,它被设计来传输少量的数据,适合物联网应用
GAP:定义了整个通信过程中的流程,如广播,扫描,连接流程
GATT:Generic Attributes,它定义了一套数据结构和BLE设备进行交互,蓝牙4.0特有
这个结构包含了Service,Characteristic,可以用下图示意
1)Profile它是一个被定义的Service集合
例如心率Profile包含了HeartRate Service和DeviceInformation Service
2)Service
把数据分成一个个单独的逻辑项,包含一个或者多个Characteristic
每个Service有一个UUID唯一标识
如:HeartRate Service:
0000180d-0000-1000-8000-00805f9b34fb[0x180d]
DeviceInformation Service:
0000180a-0000-1000-8000-00805f9b34fb[0x180a]
16bit的UUID:是通过官方证证的.Afee of $2,500 per UUID,需要购买
128bit的UUID:自定义的UUID
例如:心率服务的UUID为0x180d,它包含了三个Characteristic:
HeartRate Measurement:
00002a37-0000-1000-8000-00805f9b34fb
BodySensor Location
00002a38-0000-1000-8000-00805f9b34fb
HeartRate Control Point
0000fe86-0000-1000-8000-00805f9b34fb
且只有第一个是必须的,其它是可选的
蓝牙串口UUID
SerialPortServiceClass_UUID= ‘{00001101-0000-1000-8000-00805F9B34FB}’
LANAccessUsingPPPServiceClass_UUID= ‘{00001102-0000-1000-8000-00805F9B34FB}
UUID:唯一标识号,Service,Characteristic通过UUID来通信
BluetoothBase UUID:蓝牙基础UUID形如:
0000xxxx-0000-1000-8000-00805f9b34fb
其中xxxx是厂家的16bitUUID,其它的是固定不变的,16bit的UUID对应128bit的UUID8-4-4-4-12这个形式如
123ef678-1264-1008-126457894561
所以16位的UUID只有65536个
3)Characteristic(特征):
是最小的逻辑数据单元,和Service一样,每个Characteristic用16bit或者128bit的UUID唯一标识
Characteristic包含属性(Properties)、值(Value)、值的描述(Descriptpr,对Characteristic的描述,例如范围,计量单位)
如HeartRate Measurmement的UUID为[0x2a37]
HeartRateMeasurement | org.bluetooth.characteristic.heart_rate_measurement | 0x2A37 |
可以在官网查找
https://www.bluetooth.com/specifications/gatt/characteristics
外围设备:小和低功耗的设备,用来提供数据,如小米手环
中心设备:用来连接其它外围设备,如手机,平板
中心设备可以连接多个外围设备,一般不超过7个,外围设备和一个中心设备连接
外围设备通过AdvertisingData Payload(广播数据)和ScanResponse Data Payload(扫描回复)来向外广播,只有不停的向外广播中心设备才知道它的存在.
GATT通信双方是C/S关系,外设作为GATT服务端,它维持了ATT的查找表以及Service和Characteristic的定义,中心设备是GATT客户端Client,它向Server发起请求,并且接收服务端的响应
BLE协议栈从下到上可以分为三层,Controller控制器,Host(主机),Applications应用
1)BLEController
它是协议栈的底层实现,直接与硬件相关,直接集成到SOC中,由芯片厂商实现,包括物理层和链路层
物理层:BLE是无线通信,物理层是一定频率范围下的频带资源2.4-2.4835GHz
为了支持多个设备,将整个频带分为40份,每份的带宽为2MHzRF Channel
3个广播通道,37个数据通道,按照一定规律跳频通信(高斯频移键控 GFSK)
链路层:LinkLayer,让PhysicalChannel上可以收发数据
HCI:定义Host和Controller(通常是两颗IC)之间的通信协议,可基于Uart、USB等物理介质
2)BLEHost主机:这是协议栈的上层实现,是硬件的抽象,与具体的硬件和厂家无关
包括逻辑链路和适配层,安全管理模块等
GAP:定义了整个通信过程中的流程,如广播,扫描,连接流程
GATT:是一个Profile,包含Service和Characteristic
SM:Security Manager安全相关,包括配对pairing认证authentication加密encryption
3)应用层,使用Host层提供的API开发应用
android4.2是基于BlueZ实现的,4.2后换成了BlueDroid,4.3后支持BLE,5.0后才支持外设模式,6.0后需要申请定位权限
BLE应用可以分为两大类,基于非连接的和连接的
非连接的应用依赖于BLE广播,也叫作Beacon,发送广播的叫Broadcaster,监听广播的叫Observer
基于连接的应用是通过GATT连接,收发数据,发起连接的一方叫中心设备(手机),被连接的叫外设(手环)
BLE的连接速度可以达到3.75ms
二.Android操作BLE
测试的Android系统为Android5.1
从Android5.0开始,Android设备就可以像外设一样发送BLE广播了,Android设备之间可以通过BLE来交互数据
1.增加蓝牙的权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
2.获取BluetoothAdapter
private BluetoothAdapter mBluetoothAdapter;
BluetoothAdapter类代表了本设备(手机,平板)的蓝牙适配器
通过它可以进行,蓝牙开关,扫描,获取蓝牙状态name,mac
BbluetoothManager =
(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if(mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()){
//1)请求打开蓝牙
// startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
//2)隐式打开蓝牙
mBluetoothAdapter.enable();
}
BluetoothManager:管理蓝牙服务的一个类,主要用于得到一个蓝牙适配器BluetoothAdapter
3.扫描设备BluetoothLeScanner
private BluetoothLeScanner scanner;
scanner = mBluetoothAdapter.getBluetoothLeScanner();
//开机自动扫描
scanner.startScan(leScanCallback);
startScan()方法扫描周围的BLE设备
stopScan()方法停止扫描
public void startScan(ScanCallback callback)
public void stopScan(ScanCallback callback)
leScanCallback回调函数,通过onScanResult()把每次扫描到的设备添加到本地
BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态
private ScanCallback leScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, final ScanResult result) {
super.onScanResult(callbackType, result);
//result:包含BLE的信息,信号强度,和播数据
//result.getDevice(),result.getDeviceName();
//更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
BluetoothDevice device = result.getDevice();
Log.i("BLE","name:"+device.getName()+"\n"+
"address:"+device.getAddress());
mLeDeviceScanAdapter.addDevice(device);
mLeDeviceScanAdapter.notifyDataSetChanged();
}
});
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.i(TAG,"描扫失败"+errorCode);
}
};
I/BLE (18109): name:MI1A
I/BLE (18109): address:88:0F:10:DA:67:63
I/BLE (18109): name:HUAWEI Band 2-86f
I/BLE (18109): address:78:62:56:7A:A8:6F
I/BLE (18109): name:MI1A
I/BLE (18109): address:88:0F:10:DA:67:63
I/BLE (18109): name:null
I/BLE (18109): address:49:C3:C8:31:B1:00
I/BLE (18109): name:null
I/BLE (18109): address:49:C3:C8:31:B1:00
I/BLE (18109): name:MI1A
I/BLE (18109): address:88:0F:10:DA:67:63
I/BLE (18109): name:HUAWEI Band 2-86f
I/BLE (18109): address:78:62:56:7A:A8:6F
4.连接设备
BLE连接的建立是通过GAP来协商的,中心设备发起连接,外设接收连接请求
GATT的核心内容是Service,Characteristic以及Descriptor,最重要的是获取Service中的Characteristic,Characteristic可以被读,写,有变化的时候有通知,实现双向通信
通过 BluetoothDevice的connectGatt()方法得到BluetoothGatt,表示一个连接,用完以后,记得close()来释放资源
public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)
参数1:上下文
参数2:是否自动连接
参数3:回调
public BluetoothDevice getRemoteDevice(String address)
以给定MAC地址address去创建一个BluetoothDevice类实例(代表远程蓝牙实例)
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt;
private BluetoothDevice mBluetoothDevice;
mBluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
mBluetoothGatt = mBluetoothDevice.connectGatt(this,
false,bleGattCallback);
连接过程是,首先使用connectGatt发起连接,收到onConnectionStateChange()通知连接是否成功,若成功,则进行下一步的discoverService(),这一步就是发现设备所有的GATTService,若发现成功,通过onServiceDiscovered()回调,这时才算真正的连接成功。然后可以通过BluetoothGatt的getService()来获得BluetoothGattService,进而获得BluetoothGattCharacteristic等,然后对Characteristic进行读写。
这些过程主要是在回调函数bleGattCallback里实现
BLE 连接回调函数
BluetoothGattCallback bleGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.i("BLE","onConnectionStateChange");
if(newState== BluetoothProfile.STATE_CONNECTED){
//发现设备的所有 GATT server
gatt.discoverServices();
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("BLE","已连接");
tvState.setText("已连接");
}
});
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("BLE","已断开");
tvState.setText("已断开");
}
});
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
Log.i("BLE","onServiceDiscovered");
String uuid = null;
if(status==BluetoothGatt.GATT_SUCCESS){
List<BluetoothGattService> gattServices = gatt.getServices();
//获取所有Service的UUID
for(BluetoothGattService gattService : gattServices){
uuid = gattService.getUuid().toString();
// Log.i("BLE","Services UUID:"+uuid);
//获取Service的所有Char
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){
uuid = gattCharacteristic.getUuid().toString();
// Log.i("BLE","Characteristic UUID:"+uuid);
}
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
Log.i("BLE","onCharacteristicRead");
String uuid = null;
if(status == BluetoothGatt.GATT_SUCCESS){
uuid = characteristic.getUuid().toString();
if(uuid.equals("0000ff0c-0000-1000-8000-00805f9b34fb")){
final byte[] data = characteristic.getValue();
//data[0]为手环的电量
runOnUiThread(new Runnable() {
@Override
public void run() {
tvBattery.setText(data[0]+"%");
}
});
}
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
String uuid = null;
Log.i("BLE","onCharacteristicWrite");
if(status == BluetoothGatt.GATT_SUCCESS){
uuid = characteristic.getUuid().toString();
if(uuid.equals("0000ff05-0000-1000-8000-00805f9b34fb")){
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.i("BLE","onCharacteristicChanged");
}
};
调用流程,APP发起请求,ble设备回调
5.和BLE手环通信,读写BLE
public List<BluetoothGattService> getServices()
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic)
//读数据
当调用gatt.readCharacteristic(gattCharacteristic)时会触发读回调函数
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
从 onCharacteristicRead获取读取的数据
final byte[] data = characteristic.getValue();
//电量读取是在UUID0xfee0里的特征UUID0xff0c
//1)获取Service对象
BluetoothGattService service =
mBluetoothGatt.getService(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
//2)获取characteristic对应的UUID
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(UUID.fromString("0000ff0c-0000-1000-8000-00805f9b34fb"));
//3)读数据
mBluetoothGatt.readCharacteristic(characteristic);
//写数据
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic)
当调用gatt.writeCharacteristic(gattCharacteristic);
写数据时会触发回调函数
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
btReadBattery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//电量读取是在UUID0xfee0里的特征UUID0xff0c
//1)获取Service对象
BluetoothGattService service =
mBluetoothGatt.getService(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
//2)获取characteristic对应的UUID
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(UUID.fromString("0000ff0c-0000-1000-8000-00805f9b34fb"));
//3)读数据
mBluetoothGatt.readCharacteristic(characteristic);
}
});
btFind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//找回手环,是发指令让手环发光+振动
//服务UUID:0x1802 特征UUID:0x2a06
//1)获取Service对象
BluetoothGattService service =
mBluetoothGatt.getService(UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"));
//2)获取characteristic对应的UUID
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"));
//3)发送指令0x02
byte[] cmd = {0x02};
characteristic.setValue(cmd);
boolean ret = mBluetoothGatt.writeCharacteristic(characteristic);
}
});
以上的测试是基于Android5.1的系统
6.Android7.1上测试
在扫描设备的时候,报安全错误,导至扫描不到设备
java.lang.SecurityException:Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to getscan results
fix:加上权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
除些之外还要在app里添加运行时权限检查
从AndroidM开始,Google就正式推出了官方的权限管理机制AndroidRuntime Permission
在扫描代码之前添加
//运行时权限检查
if(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){
mNumPermissionsToRequest++;
mShouldRequestLocationPermission = true;
}else{
mFlagHasLocationPermission = true;
}
String[] permissionToRequest = new String[mNumPermissionsToRequest];
int permissionRequestIndex = 0;
if(mShouldRequestLocationPermission){
permissionToRequest[permissionRequestIndex] =
Manifest.permission.ACCESS_COARSE_LOCATION;
mIndexPermissionRequestLocation = permissionRequestIndex;
permissionRequestIndex++;
}
if(permissionToRequest.length > 0){
requestPermissions(permissionToRequest, 0);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[],
int[] grantResults){
switch (requestCode){
case 0:
if(grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED){
Log.i("BLE","Grant permission successfully");
}else{
Log.i("BLE","Grant permission unsuccessfully");
}
break;
default:
break;
}
}
三:把手机当作BLE 从设备
从Android5.0后开始就支持把一个中心设备的BLE当成外围设备的BLE
这里把手机当成一个外设BLE设备,用另外一台手机和它通信
1.查查设备是否支持ble外设功能
如果支持则构建广播,只有当广播发出去后,才能被中心设备所发现
//检查设备是否支持ble外围设备通信
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if(mBluetoothLeAdvertiser == null){
Log.i("BLE","不支持 Peripheral 模式");
}else{
Log.i("BLE","支持 Peripheral 模式");
//1)创建广播设置
AdvertiseSettings.Builder settingBuilder = new AdvertiseSettings.Builder();
//设置广播为可连接广播
//设置广播模式:ADVERTISE_MODE_LOW_POWER/BALANCED/LOW_LATENCY
//低功耗/平衡/低延迟
settingBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
settingBuilder.setConnectable(true);
settingBuilder.setTimeout(0);//设置最长超时时间0表示一直广播
//设置广播信号强度
settingBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
AdvertiseSettings settings = settingBuilder.build();
//2)创建广播参数
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
mBluetoothAdapter.setName("ViVO Xplay6");
dataBuilder.setIncludeDeviceName(true);
dataBuilder.setIncludeTxPowerLevel(true);
dataBuilder.addServiceUuid(ParcelUuid.
fromString("0000180d-0000-1000-8000-00805f9b34fb"));
AdvertiseData data = dataBuilder.build();
//3)开始广播
mBluetoothLeAdvertiser.startAdvertising(settings,
data,
advertiseCallback);
}
2.开始广播后的回调
//发送广播回调
private AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
if(settingsInEffect != null){
Log.i("BLE","onStartSuccess txpower="+
settingsInEffect.getTxPowerLevel());
BluetoothManager bluetoothManager =
(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothGattServer = bluetoothManager.openGattServer(getApplicationContext(),
bluetoothGattServerCallback);
BluetoothGattService service = new BluetoothGattService(
UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
//特征值读写设置
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(
UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb"),
BluetoothGattCharacteristic.PROPERTY_WRITE |
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE
);
service.addCharacteristic(characteristicWrite);
mBluetoothGattServer.addService(service);
}else{
Log.i("BLE","onStartSuccess, settingInEffect is null");
}
}
//回调
private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.i("BLE","onServiceAdded:"+service.getUuid().toString());
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
Log.i("BLE","onConnectionStateChange"+"" +
"status:"+status+" ->newState:"+newState);
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.i("BLE","onCharacteristicWriteRequest");
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_SUCCESS,
offset,
value);
String info = "Request:"+requestId+"|Offset:"+offset+
"|characteristic:"+characteristic.getUuid()+
"|Values:"+bytesToHexString(value,value.length);
Log.i("BLE","数据信息:"+info);
}
};
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.i("BLE","onStartFailure errorCode="+errorCode);
}
};
public String bytesToHexString(byte[] src, int len){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0 || src.length < len) {
return null;
}
for (int i = 0; i < len; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
3.测试
手机运行软件后,打印
I/BLE (32289): 支持 Peripheral 模式
I/BLE (32289): onStartSuccess txpower=3 //发送广播成功
I/BLE (32289): onServiceAdded:0000180d-0000-1000-8000-00805f9b34fb //添加了server
在另一台手机运行nRF Connect测试软件
可以扫描到打开广播的手机
点击Vivo Xplay6 开始连接
连接成功打印I/BLE ( 5695): onConnectionStateChangestatus:0 ->newState:2
对特征值进行写值0x18
打印:
I/BLE ( 5695): onCharacteristicWriteRequest
I/BLE ( 5695): 数据信息:Request:1|Offset:0|characteristic:0000ff010-1000-8000-00805f9b34fb|Values:18
说明值已写入,读类似,实现了两台手机通信
参考:
https://www.bluetooth.com/
http://www.jianshu.com/p/93f795c210b6
http://www.blogjava.net/baicker/archive/2015/09/05/427125.html
https://www.bluetooth.com/specifications/gatt/generic-attributes-overview
https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all
http://www.wowotech.net/bluetooth/bt_overview.html