BLE有Center(简单理解为客户端)和Peripheral(服务端)2个角色。我们android开发常用的就是客户端角色,使用API连接硬件外设(服务端),然后进行相关的数据交互,如读取手环上的心率电量等数据,控制蓝牙灯的开关。
Center主要可以分为 扫描->连接->通讯 三块内容. 通讯主要包含read,write,notify/indicate。read就是读取设备上的数据(如读取外设电量),write就是发送数据(如发送关闭命令关闭蓝牙灯),notify/indicate 字面意思就是通知/指示, 是用来接收设备主动上报的数据的(如手环可以每隔1秒就告诉APP心率值, 然后APP展现一个心率谱图),这样就达成了双向通讯。
Peripheral主要理解为硬件外设,提供数据用的。在开发APP时很少关心这个,因为大家都是直接拿着硬件来调试的。在android5.0时,增加了Peripheral相关的API, 意味着可以让android设备模拟成外设,作为Peripheral来提供数据。这样,当我们没有硬件设备的时候,可以拿2台手机进行BLE开发,很方便。还可以扩展很多其他功能,比如使用BLE实现蓝牙聊天(google sample里面有经典蓝牙的聊天demo),还可以把手机模拟成iBeacon等等。
- 简单明了,一个OKBLEDevice即可完成所有通讯操作
- 顺序执行通讯操作,可以在任何地方任何时机调用read/write/notify/indicate等通讯方法,不用担心命令丢失、命令不执行。BLE内部是不允许同时执行多次通讯的,okble使用队列让操作排队,先进先执行,保证所有的操作都能执行,可自定义操作超时时间
- 动态权限集成,target sdk在23(android6.0)及以上时, 扫描是需要定位权限的,okble已集成,会有回调告知是否已授权
- 向下兼容到API 18(android4.3),在android5.0中google变更了扫描api,okble依然使用的是4.3的api,只为了兼容更多的手机
- 双回调,处理灵活:定义了统一的listener来监听设备的状态,也可以为每一个蓝牙通讯操作设置单独的监听,方便页面多的情况下使用
- 自动重连:断开后可以实现自动连接,直到连接成功
- 自动识别write type,自动识别notify or indicate
- 多设备连接管理简单
- 支持APP模拟成外设,支持APP模拟成iBeacon
- 支持扫描识别iBeacon,支持iBeacon区域的进入和退出检测
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
dependencies {
implementation 'com.github.a1anwang:okble:1.1.0'
}
OKBLEScanManager scanManager=new OKBLEScanManager(this);
scanManager.setScanCallBack(scanCallBack);
DeviceScanCallBack scanCallBack=new DeviceScanCallBack() {
@Override
public void onBLEDeviceScan(BLEScanResult device, int rssi) {
LogUtils.e(" scan:"+device.toString());
}
@Override
public void onFailed(int code) {
switch (code){
case DeviceScanCallBack.SCAN_FAILED_BLE_NOT_SUPPORT:
Toast.makeText(mContext,"该设备不支持BLE",Toast.LENGTH_SHORT).show();
break;
case DeviceScanCallBack.SCAN_FAILED_BLUETOOTH_DISABLE:
Toast.makeText(mContext,"请打开手机蓝牙",Toast.LENGTH_SHORT).show();
break;
case DeviceScanCallBack.SCAN_FAILED_LOCATION_PERMISSION_DISABLE:
Toast.makeText(mContext,"请授予位置权限以扫描周围的蓝牙设备",Toast.LENGTH_SHORT).show();
break;
case DeviceScanCallBack.SCAN_FAILED_LOCATION_PERMISSION_DISABLE_FOREVER:
Toast.makeText(mContext,"位置权限被您永久拒绝,请在设置里授予位置权限以扫描周围的蓝牙设备",Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public void onStartSuccess() {
}
};
OKBLEDevice okbleDevice=new OKBLEDeviceImp(mContext,bleScanResult);
//okbleDevice=new OKBLEDeviceImp(mContext);
//okbleDevice.setBluetoothDevice(mBluetoothDevice);
okbleDevice.addDeviceListener(this);
okbleDevice.connect(true);//true表示连接断开后OKBLE的会自动重连
okbleDevice.disConnect(false); //false表示断开后不需要OKBLE的自动重连; disConnect断开后,可以使用okbleDevice.connect()重新连接回来
okbleDevice.remove(); //remove会清除连接的外设信息; 重新连接前需要重新调用setBleScanResult/setBluetoothDevice 来设置外设信息
okbleDevice.addReadOperation("feea", new BLEOperation.ReadOperationListener() {
@Override
public void onReadValue(final byte[] value) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("onReadValue:"+ OKBLEDataUtils.BytesToHexString(value)+" ("+new String(value)+")");
}
});
}
@Override
public void onFail(int code, final String errMsg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("read onFail:"+ errMsg);
}
});
}
@Override
public void onExecuteSuccess(BLEOperation.OperationType type) {
}
});
okbleDevice.addWriteOperation("feea",value,new BLEOperation.WriteOperationListener() {
@Override
public void onWriteValue(final byte[] byteValue) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog(" onWriteValue:"+value);
}
});
}
@Override
public void onFail(int code, final String errMsg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("write onFail:"+errMsg);
}
});
}
@Override
public void onExecuteSuccess(BLEOperation.OperationType type) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("write value execute success");
}
});
}
});
okbleDevice.addNotifyOrIndicateOperation("feea", true, new BLEOperation.NotifyOrIndicateOperationListener() {
@Override
public void onNotifyOrIndicateComplete() {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("onNotifyOrIndicateComplete");
}
});
}
@Override
public void onFail(int code, final String errMsg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addLog("NotifyOrIndicate onFail:"+ errMsg);
}
});
}
@Override
public void onExecuteSuccess(BLEOperation.OperationType type) {
}
});
OAD原理其实就是把固件(如.bin文件)加载成byte[]数组,然后把byte[]数据分段发送给设备,以下代码演示了主要分段部分,OAD细节不展示(如每个包的整合),根据需求会有变化
final int sendInterval=50;//每个包之间的发送间隔,有些手机会因为发送太快而导致蓝牙奔溃,OAD失败率很高,可以适当增大,如80-100;
okbleDevice.setOperationInterval(sendInterval);
byte[] oadValues=loadBytesFromFile(filePath);
final int blockSize=20;//表示一个包发送20个字节
final int blockCount= (int) Math.ceil(oadValues.length*1.0f/blockSize);//发送的总包数
percent=0;
for (int i=0;i<blockCount;i++){
byte[] value=new byte[blockSize];
System.arraycopy(oadValues,i*blockSize,value,0,blockSize);
okbleDevice.addWriteOperation(OAD_WRITE_UUID, value, new OKBLEOperation.WriteOperationListener() {
@Override
public void onWriteValue(byte[] value) {
percent++;
float progress=percent*1.0f/blockCount;
int leftSeconds= (int) ((sendInterval*blockCount)/1000*(1-progress));
Log.e("TAG"," OAD 进度:"+progress+" 剩余时间:"+leftSeconds +"秒");
}
@Override
public void onFail(int code, String errMsg) {
Log.e("TAG"," OAD failed");
okbleDevice.clearOperations();
break;
}
@Override
public void onExecuteSuccess(OKBLEOperation.OperationType type) {
}
});
}
OKBLEAdvertiseManager okbleAdvertiseManager;
OKBLEServerDevice serverDevice;
okbleAdvertiseManager=new OKBLEAdvertiseManager(this);
serverDevice=new OKBLEServerDeviceImp(this);
OKBLEAdvertiseSettings settings= new OKBLEAdvertiseSettings.Builder().setConnectable(true).build();
OKBLEAdvertiseData data=new OKBLEAdvertiseData.Builder().setIncludeDeviceName(true).build();
//开启广播
okbleAdvertiseManager.startAdvertising(settings, data, new OKBLEAdvertiseCallback() {
@Override
public void onStartSuccess() {
LogUtils.e("---onStartSuccess ---");
Toast.makeText(mContext,"Advertising Success",Toast.LENGTH_SHORT).show();
configServer();//配置service 和characteristic
}
@Override
public void onStartFailure(int errorCode, String errMsg) {
LogUtils.e("---onStartFailure errMsg:"+errMsg);
Toast.makeText(mContext,"Advertising Failed:"+errMsg,Toast.LENGTH_LONG).show();
}
});
private void configServer(){
OKBLEServiceModel serviceModel=new OKBLEServiceModel(CommonUUIDUtils.createCompleteUUIDByShortUUID("fff0"));
OKBLECharacteristicModel characteristicModel=new OKBLECharacteristicModel(CommonUUIDUtils.createCompleteUUIDByShortUUID("fff1"));
characteristicModel.setCanWrite(true);
characteristicModel.setCanNotify(true);
characteristicModel.setCanRead(true);
OKBLECharacteristicModel characteristicModel_2=new OKBLECharacteristicModel(CommonUUIDUtils.createCompleteUUIDByShortUUID("fff2"));
characteristicModel_2.setCanWriteNoResponse(true);
List<OKBLECharacteristicModel> characteristicModels=new ArrayList<>();
characteristicModels.add(characteristicModel);
characteristicModels.add(characteristicModel_2);
serverDevice.addCharacteristic(characteristicModels, serviceModel, new OKBLEServerOperation.BLEServerOperationListener() {
@Override
public void onAddCharacteristicFailed(int errorCode, String errorMsg) {
LogUtils.e("onAddCharacteristicFailed:"+errorMsg );
}
@Override
public void onAddCharacteristicSuccess() {
LogUtils.e("onAddCharacteristicSuccess");
}
});
}
OKBLEBeaconScanManager scanManager;
scanManager=new OKBLEBeaconScanManager(this);
scanManager.setBeaconScanCallback(scanCallBack);
OKBLEBeaconScanManager.OKBLEBeaconScanCallback scanCallBack=new OKBLEBeaconScanManager.OKBLEBeaconScanCallback() {
@Override
public void onScanBeacon(OKBLEBeacon beacon) {
Log.e("TAG"," scan beacon:"+beacon.toString());
}
};
OKBLEBeBeaconManager beBeaconManager;
beBeaconManager=new OKBLEBeBeaconManager(this);
beBeaconManager.setOKBLEBeBeaconListener(startBeaconListener);
String uuid ="12345678-1234-1234-1234-1234567890ab";
int major=1;
int minor=2;
beBeaconManager.startIBeacon(uuid,major,minor);
OKBLEBeBeaconManager.OKBLEStartBeaconListener startBeaconListener=new OKBLEBeBeaconManager.OKBLEStartBeaconListener() {
@Override
public void onStartSuccess() {
Toast.makeText(mContext,"start success",Toast.LENGTH_SHORT).show();
}
@Override
public void onStartFailure(String errMsg) {
Toast.makeText(mContext,"start failed:"+errMsg,Toast.LENGTH_SHORT).show();
}
};
OKBLEBeaconManager beaconManager=new OKBLEBeaconManager(this);
beaconManager.setRegionListener(this);
String uuid ="12345678-1234-1234-1234-1234567890ab";
int major=1;
int minor=2;
OKBLEBeaconRegion okbleBeaconRegion=OKBLEBeaconRegion.getInstance(uuid,major,minor);
//OKBLEBeaconRegion okbleBeaconRegion=OKBLEBeaconRegion.getInstance(uuid,major);
//OKBLEBeaconRegion okbleBeaconRegion=OKBLEBeaconRegion.getInstance(uuid);
beaconManager.startMonitoringForRegion(okbleBeaconRegion);
@Override
public void onEnterBeaconRegion(OKBLEBeaconRegion beaconRegion) {
}
@Override
public void onExitBeaconRegion(OKBLEBeaconRegion beaconRegion) {
}
Copyright 2018 a1anwang
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.