蓝牙BLE相关知识
2017-09-22 09:52
190 查看
蓝牙BLE
Android 4.3(API level 18)介绍了核心中对低功耗蓝牙(BLE)的平台支持,并提供API使应用程序可以用来发现设备、查询服务和传输信息。常见用例包括以下内容:
在附近的设备之间传输少量数据
与像谷歌标准这样的接近传感器进行交互,可以根据用户的当前位置给用户提供定制的体验。
与经典的蓝牙相比,蓝牙低能量(BLE)被设计用来提供明显的低功耗。这使得Android应用程序可以与更严格的电源需求的设备进行通信,比如接近传感器、心率监控器和健身设备。
关键术语和概念
通用属性概要(GATT) — GATT概要是一个通用规范,用于发送和接收被称为“属性”的短块数据。目前所有低能耗的应用都基于GATT。蓝牙SIG为低能量设备定义了许多配置文件。概要文件是一个设备在特定应用程序中工作的规范。注意,一个设备可以实现多个配置文件。例如,一个设备可以包含心率监视器和电池水平检测器。
属性协议(ATT) — GATT建立在属性协议(ATT)之上。
这也称为关贸总协定。ATT被优化为在BLE设备上运行。为此,它使用尽可能少的字节。每个属性都由一个通用惟一标识符(UUID)惟一标识,它是用于唯一标识信息的字符串ID的标准化128位格式。由ATT传送的属性被格式化为特征和服务。
特征 — 一个特征包含一个单独的值和 0-n 描述特征值的描述符。特征可以被认为是一种类型,类似于类。
描述符 — 描述符是描述特征值的属性。
例如,描述符可以指定一个人可读的描述,一个特征值的可接受范围,或一个特定于特征值的度量单位。
服务 — 服务是特性的集合。例如,您可以有一个名为“心率监测器”的服务,其中包括“心率测量”等特征。“你可以在 bluetooth.org 上找到现有的基于GATT的文件和服务的列表。”。
角色和职责
以下是当Android设备与一个BLE设备交互时应用的角色和职责:中央 vs 外围 这适用于BLE连接本身。中央角色扫描的设备,寻找广告,以及外围设备中的设备制作广告。
GATT server vs. GATT client。这决定了两个设备在建立连接后如何互相通信。
为了理解这一区别,假设你有一个Android手机和一个活动跟踪器,这是一个BLE设备。电话支持中心角色;活动跟踪器支持外围角色(为了建立一个可连接的连接,你需要一个-两个只支持外围的东西不能互相对话,也不能只支持中心的东西)。
一旦手机和活动跟踪器建立了连接,他们就开始将GATT元数据传输给另一个。根据他们传输的数据类型,一个或另一个可能充当服务器。例如,如果活动跟踪器想要向手机报告传感器数据,那么活动跟踪器作为服务器可能是有意义的。如果活动跟踪器想从手机接收更新,那么手机充当服务器就有意义了。
在本文档中使用的示例中,Android应用程序(运行在Android设备上)是GATT客户端。该应用程序从GATT服务器获取数据,这是一种可以支持心率的BLE心率监测器。但你也可以设计你的Android应用程序来扮演GATT服务器的角色。更多信息请参见蓝牙Gatt服务器。
BLE 权限
为了在您的应用程序中使用蓝牙功能,您必须声明蓝牙许可蓝牙。您需要此权限来执行任何蓝牙通信,例如请求连接、接受连接和传输数据。如果你想让你的应用程序启动设备发现或操作蓝牙设置,你还必须声明蓝牙管理权限。注意:如果您使用蓝牙管理员权限,那么您还必须拥有蓝牙权限。
在应用程序清单文件中声明蓝牙权限。例如:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果你想声明你的应用程序只适用于可BLE功能的设备,请在你的应用程序清单中包含以下内容:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
然而,如果你想让你的应用程序可以使用那些不支持BLE的设备,你仍然应该在你的应用程序的清单中包含这个元素,但是设置required =” false “。然后在运行时,您可以通过使用 PackageManager.hasSystemFeature() 确定的可用性
// Use this check to determine whether BLE is supported on the device. Then you can selectively disable BLE-related features. // 使用此检查来确定设备是否支持BLE。然后你可以选择性地禁用与BLE相关的功能。 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
注意:LE标准经常与位置联系在一起。为了在没有过滤器的情况下使用蓝牙Le扫描器,您必须通过声明访问粗糙位置或在应用程序的清单文件中访问精细位置权限来请求用户的权限。如果没有这些权限,扫描不会返回任何结果。
设置BLE
在您的应用程序可以通信之前,您需要验证该设备是否支持BLE,如果有,请确保它是启用的。注意,此检查只有在private BluetoothAdapter mBluetoothAdapter; ... // Initializes Bluetooth adapter. // 初始化蓝牙适配器。 final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
支持蓝牙
接下来,您需要确保启用了蓝牙。调用isEnabled()检查是否当前启用了蓝牙。如果该方法返回false,则禁用蓝牙。下面的代码片段检查是否启用了蓝牙。如果它不是,代码段会显示一个错误提示用户去设置蓝牙:
// 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); }
注意: REQUEST_ENABLE_BT 是一个本地定义的整数(必须大于0) 通过 startActivityForResult(android.content.Intent, int) 系统在onActivityResult(int, int, android.content.Intent)实现作为请求代码参数。
发现BLE设备
要找到BLE设备,可以使用startLeScan()方法。该方法采用BluetoothAdapter.LeScanCallback作为参数。您必须实现这个回调,因为它是如何返回扫描结果的。因为扫描是高消耗的,你应该遵守以下的指导方针:
一旦找到所需的设备,停止扫描
不要在循环上进行扫描,并设置扫描的时间限制。一种以前可用的设备可能已经移出了范围,并继续扫描电池。
以下代码片段显示了如何启动和停止扫描:
/** * Activity for scanning and displaying available BLE devices. * 用于扫描和显示可用的BLE设备的活动。 */ public class DeviceScanActivity extends ListActivity { private BluetoothAdapter mBluetoothAdapter; private boolean mScanning; private Handler mHandler; // Stops scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; ... private void scanLeDevice(final boolean enable) { if (enable) { // Stops scanning after a pre-defined scan period. mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, SCAN_PERIOD); mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } ... } ... }
如果您想要扫描特定类型的外围设备,则可以调用startLeScan(UUID[], BluetoothAdapter.LeScanCallback),提供一个UUID对象数组,指定应用程序支持的GATT服务。
下面是蓝牙适配器的实现。Le Scan Callback,它是用来传送BLE扫描结果的接口:
private LeDeviceListAdapter mLeDeviceListAdapter; ... // Device scan callback. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { mLeDeviceListAdapter.addDevice(device); mLeDeviceListAdapter.notifyDataSetChanged(); } }); } };
注意:你只能扫描蓝牙设备,或者扫描蓝牙设备上的经典蓝牙设备。你不能同时扫描蓝牙LE和classic设备。
连接到一个GATT服务器
与一个BLE设备交互的第一步是连接到它——更具体地说,连接到设备上的GATT服务器。要在一个BLE设备上连接到一个GATT服务器,您需要使用connectGatt()方法。这个方法接受三个参数:一个上下文对象,自动连接(布尔指示是否在它可用时自动连接到BLE设备),以及一个对蓝牙Gatt回调的引用:mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
它连接到由BLE设备托管的GATT服务器,并返回一个蓝牙GATT实例,然后您可以使用它来进行GATT客户端操作。调用者(Android应用程序)是GATT客户端。蓝牙Gatt回调用于将结果传递给客户端,例如连接状态,以及任何进一步的Gatt客户端操作。
在这个示例中,BLE应用程序提供了一个活动(设备控制活动)来连接、显示数据,以及显示设备支持的GATT服务和特性。基于用户输入,该活动与一个名为蓝牙Le Service的服务通信,该服务通过Android BLE API与BLE设备进行交互:
// A service that interacts with the BLE device via the Android BLE API. // 通过Android BLE API与BLE设备交互的服务。 public class BluetoothLeService extends Service { private final static String TAG = BluetoothLeService.class.getSimpleName(); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Various callback methods defined by the BLE API. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(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()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } @Override // New services discovered public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // Result of a characteristic read operation public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } ... }; ... }
当一个特定的回调被触发时,它调用适当的广播更新()帮助方法,并通过它一个动作。请注意,本节中的数据解析是按照蓝牙心率测量概要文件规范执行的:
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); } private void broadcastUpdate(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."); } final int 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. final byte[] 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. private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { mConnected = true; updateConnectionState(R.string.connected); invalidateOptionsMenu(); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { mConnected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); } else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(mBluetoothLeService.getSupportedGattServices()); } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); } } };
阅读BLE属性
一旦您的Android应用程序连接到GATT服务器并发现了服务,它就可以在支持的地方读取和写入属性。例如,这段代码将遍历服务器的服务和特性,并在UI中显示它们:public class DeviceControlActivity extends Activity { ... // 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. private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... } ... }
接收关贸总协定通知
当设备上的特殊特性发生变化时,可以要求通知应用程序。该代码片段显示了如何使用setCharacteristicNotification()方法设置一个特征的通知:private BluetoothGatt mBluetoothGatt; BluetoothGattCharacteristic characteristic; boolean enabled; ... mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); ... BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor);
一旦对一个特性启用了通知,如果远程设备上的特征改变,就会触发一个 onCharacteristicChanged() 回调。
@Override // Characteristic notification public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); }
关闭客户端程序
一旦你的应用程序使用了一个BLE设备,应该调用close(),这样系统就可以适当地释放资源:public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; }
相关文章推荐
- 蓝牙相关知识梳理
- BLE低功耗蓝牙开发相关概念问题记录
- iOS蓝牙开发(一)蓝牙相关基础知识(转)
- IOS 蓝牙相关-基础知识(1)
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识
- Android蓝牙BLE低功耗相关简单总结
- iOS 关于蓝牙开发的相关知识
- Android蓝牙BLE低功耗相关简单总结
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一):蓝牙相关基础知识
- iOS蓝牙开发(二)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS 关于蓝牙开发的相关知识
- BLE 广播、扫面、连接相关基础知识
- ios蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识
- iOS蓝牙开发(一)蓝牙相关基础知识