顾名思义,和传统蓝牙相比,BLE 在功耗上有很大的优势。TI 的有些 BLE 模块的电流仅仅不到 1 微安( 数据来源 ),主流的 BLE 功耗应该都是这个数量级。
关键概念
GATT, Generic Attribute Profile
GATT Profile 是通过 BLE 链路传输小数据包的一个通用规范,这些小数据包被称为 attribute。目前所有的低功耗应用技术规范(application profile)都是基于 GATT 的。
The Bluetooth SIG defines many profiles for Low Energy devices. A profile is a specification for how a device works in a particular application. Note that a device can implement more than one profile. For example, a device could contain a heart rate monitor and a battery level detector.
ATT, Attribute Protocol
GATT 是基于 ATT 实现的,像 TCP/IP 一样,这套标准通常也被叫做 GATT/ATT。ATT 专门为运行在 BLE 设备上进行了优化。
Characteristic
A characteristic contains a single value and 0-n descriptors that describe the characteristic’s value. A characteristic can be thought of as a type, analogous to a class.
Descriptor
Descriptors are defined attributes that describe a characteristic value. For example, a descriptor might specify a human-readable description, an acceptable range for a characteristic’s value, or a unit of measure that is specific to a characteristic’s value.
Service
A service is a collection of characteristics. 比如有个 Service 叫”心率传感器” 包含 “心率测量” 这个 characteristic 。
You can find a list of existing GATT-based profiles and services on bluetooth.org.
角色,职责
接下来,看一下当 Android设备 和 BLE设备 交互的时候,他们之间可能的角色和对应的职能。
Central(中心) VS. Peripheral(外围),这个关系对应于 BLE 连接本身。“中心”设备扫描监听广播,“外围”设备发出广播。
GATT Server VS. GATT Client,这个关系对应当连接建立后两个设备怎么通信。
为了理解这里的区别,想象一下你有一个 Android 手机 和 一个 BLE 设备 —— 运动传感器。手机可以作为中心设备,运动传感器作为外围设备。(只有一个中心设备一个外围设备才可以,两个中心设备或者两个外围设备都是连不上的)
一旦手机和运动传感器建立连接后,二者之间开始传输 GATT 元数据。取决于传输数据类型的情况,手机和运动传感器都可能扮演 Server 的角色。比如,如果运动传感器想向手机发送传感器数据,一般就是运动传感器作为 Server;再比如,如果运动传感器想从手机接受任何的数据更新(更新校准传感器甚至是 OTA 升级),这种情况下,一般是手机作为 Server。
BLE 权限
任何跟蓝牙相关的功能都需要声明 BLUETOOTH 权限,如果要开始扫描其他或者修改蓝牙设置,还要声明 BLUETOOTH_ADMIN 权限。
// Use this check to determine whether BLE is supported on the device. Then // you can selectively disable BLE-related features. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
LE 信标通常跟地理位置联系在一起,为了使用 BluetoothLeScanner ,应该声明 ACCESS_COARSE_LOCATION 或者 ACCESS_FINE_LOCATION。不然的话,扫描不会返回结果。ops
配置 BLE
获取 BluetoothAdapter
1 2 3 4
privateval mBluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter }
开启蓝牙
1 2 3 4 5 6
// Ensures Bluetooth is available on the device and it is enabled. If not, // displays a dialog requesting user permission to enable Bluetooth. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
下面这个例子,app 这边有个 Activity 来连接、显示数据、显示 BLE 设备支持的 GATT 服务(Service) 和 特性(Characteristics)。这个 Activity 通过 BluetoothLeService 这个 Service 使用 BLE API 来跟 BLE 设备交互。
// A service that interacts with the BLE device via the Android BLE API. publicclassBluetoothLeServiceextendsService{ privatefinalstatic String TAG = BluetoothLeService.class.getSimpleName();
// Various callback methods defined by the BLE API. privatefinal BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override publicvoidonConnectionStateChange(BluetoothGatt gatt, int status, int newState){ String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
privatevoidbroadcastUpdate(final String action){ final Intent intent = new Intent(action); sendBroadcast(intent); }
privatevoidbroadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic){ final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data // parsing is carried out as per profile specifications. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } finalint heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); } else { // For all other profiles, writes the data formatted in HEX. finalbyte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } } sendBroadcast(intent); }
// Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read or notification operations. privatefinal BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override publicvoidonReceive(Context context, Intent intent){ final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { mConnected = true; updateConnectionState(R.string.connected); invalidateOptionsMenu(); } elseif (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { mConnected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); } elseif (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(mBluetoothLeService.getSupportedGattServices()); } elseif (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); } } };
读取 BLE 属性
一旦 Android App 连接到 GATT Server 和 发现的服务后,就可以读写属性了。下面的这段代码遍历 Server 的服务和属性并且显示出来:
// Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView on the UI. privatefundisplayGattServices(gattServices: List<BluetoothGattService>?) { if (gattServices == null) return var uuid: String? val unknownServiceString: String = resources.getString(R.string.unknown_service) val unknownCharaString: String = resources.getString(R.string.unknown_characteristic) val gattServiceData: MutableList<HashMap<String, String>> = mutableListOf() val gattCharacteristicData: MutableList<ArrayList<HashMap<String, String>>> = mutableListOf() mGattCharacteristics = mutableListOf()
// Loops through available GATT Services. gattServices.forEach { gattService -> val currentServiceData = HashMap<String, String>() uuid = gattService.uuid.toString() currentServiceData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownServiceString) currentServiceData[LIST_UUID] = uuid gattServiceData += currentServiceData
val gattCharacteristicGroupData: ArrayList<HashMap<String, String>> = arrayListOf() val gattCharacteristics = gattService.characteristics val charas: MutableList<BluetoothGattCharacteristic> = mutableListOf()