Android BLE 操作
来源:互联网 发布:室内设计软件培训 编辑:程序博客网 时间:2024/06/05 05:34
一:基本概念
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:MI1AI/BLE (18109): address:88:0F:10:DA:67:63I/BLE (18109): name:HUAWEI Band 2-86fI/BLE (18109): address:78:62:56:7A:A8:6FI/BLE (18109): name:MI1AI/BLE (18109): address:88:0F:10:DA:67:63I/BLE (18109): name:nullI/BLE (18109): address:49:C3:C8:31:B1:00I/BLE (18109): name:nullI/BLE (18109): address:49:C3:C8:31:B1:00I/BLE (18109): name:MI1AI/BLE (18109): address:88:0F:10:DA:67:63I/BLE (18109): name:HUAWEI Band 2-86fI/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对应的UUIDBluetoothGattCharacteristic 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);}@Overridepublic 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
- Android BLE 操作
- Android BLE基础操作框架使用详解
- android BLE
- android BLE
- android BLE
- Android BLE
- android ble
- Android Ble
- Android BLE操作成功或失败status code对应解释
- CSR8675 BLE操作经验
- 【Android(BLE)】Android(BLE)之启动蓝牙
- BLE简介和Android BLE编程
- BLE简介和Android BLE编程
- Android 蓝牙4.0 Ble 连接Ble模块
- Android BLE开发: BLE Peripheral开发流程
- BLE简介和Android BLE编程
- BLE简介和Android BLE编程
- BLE简介和Android BLE编程
- js延迟加载
- Java8 中的接口
- C#连接sql数据库两种方法
- 字符串的交错组成
- 基础语言要素
- Android BLE 操作
- Java中数值类型之间的转换
- Mac端 查看QQ下载的视频路径
- 2017_12_15 js获取项目路径,js调用问题,jsp获取js传递url中参数
- SpringCloud(第 052 篇)CentOS7 安装 Docker 以及常用操作命令讲解
- 使用react时的ajax传参问题
- this作用域
- 如何把淘抢购做成JSON接口(二)
- 呵护儿童安全——远离红黄蓝,远离七座伪MPV!