一、蓝牙BLE(Bluetooth Low Energy)介绍概要
蓝牙低能耗(BLE)技术是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM射频频段。它从一开始就设计为超低功耗(ULP)无线技术。
蓝牙低能耗技术的三大特性成就了ULP性能,这三大特性分别是最大化的待机时间、快速连接和低峰值的发送/接收功耗。
无线“开启”的时间只要不是很短就会令电池寿命急剧降低,因此任何必需的发送或接收任务需要很快完成。被蓝牙低能耗技术用来最小化无线开启时间的第一个技巧是仅用3个“广告”信道搜索其它设备,或向寻求建立连接的设备宣告自身存在。相比之下,标准蓝牙技术使用了32个信道。
蓝牙低能耗技术“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms。而标准蓝牙技术完成相同的连接周期需要数百毫秒。再次提醒,无线开启时间越长,消耗的电池能量就越多。
具有低功耗蓝牙模块的设备可以扮演2个角色,中心,周边。周边是数据提供者,中心是数据接收/处理者。IOS设备可以很好的扮演这2个角色,利用现成的API就能开发出具有周边和中心功能的应用,我大Android就有点悲催了,自Android 4.3(API 18)的系统就规定了BLE的API,但是仅限于中心,至于周边一直没有API的支持。直到2014.6.26 Android Lollipop(Android 5.0 API 21)的面世,才带来了周边API的支持(BluetoothLeAdvertiser)。
ble在抓包软件或硬件上又分为Master和Slave。Master可以同时与多个设备连接,通信;Slave只能和一个Master通信。Cnetral主机(常作为client端):如手机,PCPeripheral从机(常作为Service端):如心率计,血糖计
二、关键概念:
(1)Generic Attribute Profile (GATT)
通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。
(2)Attribute Protocol (ATT)
GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。
(3)Characteristic
Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。
(4)Descriptor
对Characteristic的描述,例如范围、计量单位等。
(5)Service
Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart rate measurement"的Characteristic。
(6)关联
BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY。
三、相关权限
<!-- 允许程序进行发现和配对新的蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 允许程序进行发现和配对新的蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
下面这两个权限是ScanBle需要的(二选一即可)
注意:标准蓝牙和低功耗在Android 6.0以上都是需要该权限才能扫描得到的,而Android 6.0以下不用该权限标准蓝牙也能扫描的到。
<!-- 访问CellID或WiFi,只要当前设备可以接收到基站的服务信号,便可获得位置信息 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 更精确的GPS需要该权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>代码判断是否支持BLE
getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
四、主要代码介绍
public class BluetoothBleDemo extends Activity { private static final int REQUEST_CODE_BLUETOOTH_ENABLE = 1; private static final int SCAN_PERIOD = 10000; private BluetoothAdapter bluetoothAdapter; //import android.bluetooth.le.ScanCallback; //Call requires API level 21 (current min is 18): new android.bluetooth.le.ScanCallback //java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results private ScanCallback scanCallback = new ScanCallback() { @Override public void onBatchScanResults(List<ScanResult> results) { //Batch 一批 for(ScanResult result : results) { System.out.println("onBatchScanResults " + result); } } @Override public void onScanResult(int callbackType, ScanResult result) { System.out.println("onScanResult " + result); BluetoothDevice device = result.getDevice(); System.out.println("Device name: " + device.getName()); //Returns the hardware address of this BluetoothDevice. For example, "00:11:22:AA:BB:CC". System.out.println("Device address: " + device.getAddress()); System.out.println("Device service UUIDs: " + device.getUuids()); //Get the Bluetooth device type of the remote device. System.out.println("Device type: " + device.getType()); //Possible values for the bond state are: BOND_NONE, BOND_BONDING, BOND_BONDED. System.out.println("Device bondState: " + device.getBondState()); ScanRecord record = result.getScanRecord(); //Returns the advertising flags indicating the discoverable mode and capability of the device. Returns -1 if the flag field is not set. System.out.println("Record advertise flags: 0x" + Integer.toHexString(record.getAdvertiseFlags())); /* txPowerLevel 发射功率等级 Returns the transmission power level of the packet in dBm. Returns Integer.MIN_VALUE if the field is not set. This value can be used to calculate the path loss of a received packet using the following equation: pathloss = txPowerLevel - rssi */ System.out.println("Record Tx power level: " + record.getTxPowerLevel()); System.out.println("Record device name: " + record.getDeviceName()); System.out.println("Record service UUIDs: " + record.getServiceUuids()); //Returns a map of service UUID and its corresponding service data. System.out.println("Record service data: " + record.getServiceData()); //Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific System.out.println("Record manufacturer specific data: " + record.getManufacturerSpecificData()); //RSSI 信号强度,可以用来测算距离 Returns the received signal strength in dBm. The valid range is [-127, 127]. System.out.println("result rssi: " + result.getRssi()); //Returns timestamp since boot when the scan record was observed. System.out.println("result timestampNanos: " + result.getTimestampNanos()); switch(callbackType) { case ScanSettings.CALLBACK_TYPE_ALL_MATCHES: break; case ScanSettings.CALLBACK_TYPE_FIRST_MATCH: break; case ScanSettings.CALLBACK_TYPE_MATCH_LOST: break; } } @Override public void onScanFailed(int errorCode) { System.out.println("onScanFailed errorCode = " + errorCode); } }; private BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status); } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { super.onReliableWriteCompleted(gatt, status); } }; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //判断是否支持BLE特性 if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { initBluetoothBle(); } else { showToast("this device can not support Bluetooth BLE"); } } private void initBluetoothBle() { //BluetoothAdapter是Android系统中所有蓝牙操作都需要的,它对应本地Android设备的蓝牙模块,在整个系统中BluetoothAdapter是单例的。当你获取到它的示例之后,就能进行相关的蓝牙操作了。 //BluetoothManager在Android4.3以上支持(API level 18) BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); } /** * 开启蓝牙 * 打开和关闭蓝牙模块, 都可以通过ACTION_STATE_CHANGED广播来监听 */ private void requestEnable() { //第一种方法打开蓝牙, 系统会提示应用要打开蓝牙,是否授权;disable不会有任何的提示。 //boolean result = bluetoothAdapter.enable(); //第二种方法发送广播, 会弹出一个对话框, 选择是否打开蓝牙, 选择是蓝牙才打开。 if(bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent, REQUEST_CODE_BLUETOOTH_ENABLE); } } /** * 扫描BLE设备.注意该方法无法扫描标准蓝牙,只能扫描BLE设备 */ private void scanBleDevice(final boolean enabled) { if(bluetoothAdapter == null) { return; } /* 为什么不能再使用单例的BluetoothAdapter? 原因如下: bluetoothAdapter.startLeScan() //deprecated http://stackoverflow.com/questions/30223071/startlescan-replacement-to-current-api Remember that the method: public BluetoothLeScanner getBluetoothLeScanner () isn't static. If you do: BluetoothAdapter.getBluetoothLeScanner() you will get an error, since getDefaultAdapter() is a static method, but getBluetoothLeScanner() isn't. You need an instance of a BluetoothAdapter. */ final BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); if(enabled) { //scan分为2类,而在android L之前,搜索条件只有uuid //(1)直接搜索全部周围peripheral(外围的)设备,搜索结果将通过这个callback返回 scanner.startScan(scanCallback); //(2)根据过滤条件搜索设备 final List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(); //uuid格式8-4-4-4-12(32位,128bit) //address格式(12位,48bit) scanFilters.add(new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("00000000-0000-0000-0000-000000000000")).setDeviceAddress("00:00:00:00:00:00").build()); ScanSettings scanSettings = new ScanSettings.Builder() //require API 23 //.setCallbackType(0).setMatchMode(0).setNumOfMatches(0) .setReportDelay(0).setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE).build(); scanner.startScan(scanFilters, scanSettings, scanCallback); handler.postDelayed(new Runnable() { @Override public void run() { scanner.stopScan(scanCallback); } }, SCAN_PERIOD); } else { scanner.stopScan(scanCallback); } } /** * 两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server。数据发送方向总是从server推送到client */ private void connectToGATTServer(BluetoothDevice device) { //函数成功,返回BluetoothGatt对象,它是GATT profile的封装。通过这个对象,我们就能进行GATT Client端的相关操作。BluetoothGattCallback用于传递一些连接状态及结果。 BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback); //连接远程设备 boolean connectResult = bluetoothGatt.connect(); //搜索连接设备所支持的service boolean discoverResult = bluetoothGatt.discoverServices(); //断开与远程设备的GATT连接 bluetoothGatt.disconnect(); //关闭GATT Client端 bluetoothGatt.close(); //读取指定的characteristic。 //boolean readResult = bluetoothGatt.readCharacteristic(characteristic); //设置当指定characteristic值变化时,发出通知 //boolean setResult = bluetoothGatt.setCharacteristicNotification(characteristic, enabled); //获取远程设备所支持的services List<BluetoothGattService> gattServices = bluetoothGatt.getServices(); } private void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } }
扫描结果如下图
(未完待续,上面讲了Android作为GattClient,剩下通信、Android做为GattServer还有数据分包的情况未讲——先记录一下,免得忘记)