Android:BLE智能硬件開發詳解


目錄

  • 前言
  • BLE是個什么鬼
  • BLE中的角色分工
  • 主要的關鍵詞和概念
    • GATT(Generic Attribute Profile )
    • Characteristic
    • Service
  • Android如何使用BLE
    • 藍牙權限
    • APP和BLE外設交互流程
  • 后記

本文作者MichaelX,博客地址:http://blog.csdn.net/xiong_it 轉載請注明來源


前言

前些年,智能硬件炒的挺火的,那今天,咱就來說說智能硬件那些事。BLE是智能硬件的一種通訊方式,通過BLE連接,iOS & Android手機和智能硬件就可以進行自定義的交互了。交互的體驗如何,很大程度上取決於智能硬件的驅動工程師驅動寫的好不好,以及App的代碼質量如何。

筆者曾參與過多款BLE智能硬件的開發,許久不用,怕忘了,把自己的整理的一些知識記錄與此,同時也希望能夠給一些同學帶來幫助。本文將盡力向讀者講清楚BLE是什么,以及在實際Android開發中該如何使用BLE。

前方高能:文章有點長,筆者經歷了好幾次改版,也花費了好幾個月的業余時間,讀者可能需要點耐心。着急的讀者可直接跳轉至Android如何使用BLE

BLE是個什么鬼

BLE:Bluetooth Low Energy,低功耗藍牙。Android官方介紹如下:

Android 4.3 (API Level 18) introduces built-in platform support for Bluetooth Low Energy in the central role and provides APIs that apps can use to discover devices, query for services, and read/write characteristics. In contrast to Classic Bluetooth, Bluetooth Low Energy (BLE) is designed to provide significantly lower power consumption. This allows Android apps to communicate with BLE devices that have low power requirements, such as proximity sensors, heart rate monitors, fitness devices, and so on.

什么意思呢?自從API18/Android4.3開始,Android開始支持低功耗藍牙並給APP提供了一套api調用。相比傳統藍牙來說,BLE技術旨在降低藍牙功耗。至於我們Android開發者來說,要做的就是調用這套api,和具備藍牙的智能硬件溝通,通過藍牙讀寫操控智能硬件。

BLE技術允許APP和那些有着低功耗需求的BLE設備進行通訊,這些設備包括但不限於:距離傳感器設備,心跳率檢測儀,健身器材等。

約定:文中提到的”外設”,”BLE外設”和”智能硬件”是等價的.請讀者知悉.


角色分工

Once the phone and the activity tracker have established a connection, they start transferring GATT metadata to one another. Depending on the kind of data they transfer, one or the other might act as the server. For example, if the activity tracker wants to report sensor data to the phone, it might make sense for the activity tracker to act as the server. If the activity tracker wants to receive updates from the phone, then it might make sense for the phone to act as the server.

在Android APP和BLE外設進行交互時,他們分別扮演兩個角色.這兩個角色是不固定的.
GATT server:發送數據的一方.
GATT client:接收數據的一方.
當APP向外設寫入數據時,APP就是server,外設就是client;當APP讀取外設數據時,APP就是client.外設就是server.


主要的關鍵詞和概念

GATT(Generic Attribute Profile )

The GATT profile is a general specification for sending and receiving short pieces of data known as “attributes” over a BLE link. All current Low Energy APPlication profiles are based on GATT.

這個是BLE通訊的基本協議,這個協議定義了BLE發送和接收一小段數據的規范,這些被傳輸的小段數據被稱為”attributes”.

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.

博主的理解中,”Characteristic”是BLE通訊之間的溝通”搬運工”,因為這是我們從智能硬件直接讀寫的東西,它依附於下文的Service存在,有自己的標志碼:uuid。它『分為讀取BLE外設數據的Characteristic & 向BLE外設寫入數據的Characteristic』。
下面章節中將用代碼說話.

Service

A service is a collection of characteristics. For example, you could have a service called “Heart Rate Monitor” that includes characteristics such as “heart rate measurement.”

此Service非彼Android四大組件中的彼Service,而是BluetoothGattService.這個Service是一個characteristics的集合,它可以理解為針對某個信號的通訊線路。


Android如何使用BLE

藍牙權限

使用BLE需要兩個權限

<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  • 1
  • 2
  • 1
  • 2

如果你想要APP只適配具備BLE的手機,那個可以再添加一個硬件權限特性

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
  • 1
  • 1

APP和BLE外設交互流程

APP和BLE外設交互的一個大概流程就是:

  1. BLE外設打開電源
  2. APP初始化藍牙
  3. APP掃描周邊BLE外設
  4. APP連接到周邊BLE外設
  5. APP讀寫BLE外設
  6. 交互完成,APP向BLE外設寫入關機/待機指令(可選)
  7. BLE外設關機
  8. APP關閉本地藍牙連接

以下將逐步利用代碼進行講解APP和BLE外設交互.

初始化BLE

Java代碼判斷當前手機是否支持BLE低功耗藍牙

// 判斷手機是否支持BLE if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish();// 如果手機不支持BLE就關閉程序,僅供參考 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

初始化藍牙管理者和適配器,這2個對象是ble通訊的基石.

// 初始化藍牙管理者和適配器,這2個對象是ble通訊的基石. private BluetoothAdapter mBluetoothAdapter; ... final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

跳轉到系統藍牙設置界面

private BluetoothAdapter mBluetoothAdapter; ... // 驗證藍牙是否已打開,如果沒打開就提示用戶跳轉打開. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

 

APP掃描周邊BLE外設

需要實現一個BluetoothAdapter.LeScanCallback回調接口,得到掃描結果。該接口只有一個回調方法:

/** * @param device 被手機藍牙掃描到的BLE外設實體對象 * @param rssi 大概就是表示BLE外設的信號強度,如果為0,則表示BLE外設不可連接。 * @param scanRecord 被掃描到的BLE外圍設備提供的掃描記錄,一般沒什么用 */ public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由於掃描BLE設備比較消耗資源,官方推薦間歇性掃描,示例代碼如下

    private BluetoothAdapter mBluetoothAdapter; private boolean mScanning; private Handler mHandler; // 每掃描10s休息一下 private static final long SCAN_PERIOD = 10000; private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { // TODO 這里可以進行連接操作,連接操作見下一小節 if (device != null && device.getName() != null && device.getName().contain("你的產品名稱")){ // 連接設備 connectDevice(device); // 停止掃描 scanLeDevice(false); } } }); } }; ... /** * @param enable 是否進行掃描,false則停止掃描 */ private void scanLeDevice(final boolean enable) { if (enable) { // 利用Handler進行間歇性掃描,每次掃描時間:10s 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); } ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

 

APP連接周邊BLE外設

連接操作是進行手機和BLE外設交互的基礎,請看下面connectDevice(BluetoothDevice)方法實現。

分兩步走:
1. 判斷該設備是否連接過,連接過則首先嘗試直接連接:BluetoothGatt.connect()
2. 首次連接或者直連失敗使用:BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

public boolean connectDevice(final BluetoothDevice device) { if (mBluetoothAdapter == null || device == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } String address = device.getAddress(); // 之前連接過的設備,嘗試直接連接。mBluetoothDeviceAddress表示剛才連接過的設備地址 if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) {// 連接成功 // 修改連接狀態變量 mConnectionState = STATE_CONNECTING; return true; } else { return false; } } final BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address); if (remoteDevice == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } mBluetoothGatt = remoteDevice.connectGatt(context, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); // 將當前連接上的設備地址賦值給連接過的設備地址變量 mBluetoothDeviceAddress = address; // 改變連接狀態變量 mConnectionState = STATE_CONNECTING; return true; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

連接BEL外設時,需要一個實現回調接口以得到連接狀態,BluetoothGattCallback大概實現如下:

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { // 該方法在連接狀態改變時回調,newState即代表當前連接狀態 String intentAction; // 連接上了 if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; // 改變藍牙連接狀態變量 mConnectionState = STATE_CONNECTED; // 發送自定義廣播:連接上了 broadcastUpdate(intentAction); // 當前外設相當於前面章節提到的Server角色:提供數據被手機讀取 Log.i(TAG, "Connected to GATT server."); // 獲取讀/寫服務:Service。該方法會觸發下面的onServicesDiscovered()回調 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 // 該方法在藍牙服務被發現時回調。由上述的mBluetoothGatt.discoverServices()觸發結果。 public void onServicesDiscovered(BluetoothGatt gatt, int status) { // 發現服務。status表示發現服務的結果碼 if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); // TODO 從發現的Service來找出讀數據用的BluetoothGattCharacteristic和寫數據用的BluetoothGattCharacteristic。 initReadAndWriteCharacteristic(gatt.getServices()); } else {// 未發現服務 Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override // 讀取操作的回調結果 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } @Override // 寫入操作的回調結果 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { }; ... }; ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57



找出讀寫”數據包”的”搬運工”

下面是找出讀寫”搬運工”BluetoothGattCharacteristic的initReadAndWriteCharacteristic()代碼實現

BluetoothGattCharacteristic mReadCharacteristic;
BluetoothGattCharacteristic mWriteCharacteristic;

public void initReadAndWriteCharacteristic( List<BluetoothGattService> gattServices) { if (gattServices == null) return; // 遍歷所有的 GATT Services. for (BluetoothGattService gattService : gattServices) { if (!gattService.getUuid().toString().trim().equalsIgnoreCase("這里是你期望的Service的uuid,由你司智能外色的驅動工程師決定")) continue; List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); // 遍歷當前Service中所有的Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase(""這里是你期望的寫數據的uuid,由你司驅動工程師決定"")) { mWriteCharacteristic = gattCharacteristic; } else if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase("這里是你期望的讀數據的uuid,由你司驅動工程師決定")) { mReadCharacteristic = gattCharacteristic; } } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

至此,我們就拿到了可攜帶讀寫數據的“搬運工”-『mReadCharacteristic & mWriteCharacteristic』,下面就可以和智能硬件進行交互了。

APP讀取BLE外設藍牙數據

想要讀取BLE外設的數據時,比如:心跳速率,電量等等。可通過下面方式。

// 告訴”搬運工“我想知道BLE外設當前數據,將回調BluetoothGattCallback接口的onCharacteristicRead()方法 mBluetoothGatt.readCharacteristic(mReadCharacteristic); // 讀取BLE藍牙數據操作的回調方法 @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); // ”搬運工“把”數據包“搬出來了 byte[] data = characteristic.getValue(); // 根據驅動工程師給的協議文檔,解析該數組,該處假設數組0位上表示心跳速率 int heartRateR = data[0];// 得到心跳速率,做相應UI更新和操作 } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

 

APP向BLE外設寫入數據

比如說你想告訴BLE外設讓他鎖屏,或者進行某個動作,APP向操縱BLE外設時可通過以下方式

// 根據驅動工程師給的協議文檔,組織一個數組命令 byte[] data = getData(); // 將該條命令“數據包”給“搬運工" mWriteCharacteristic.setValue(data); // ”搬運工“將數據搬到BLE外設里面了,將回調BluetoothGattCallback接口的onCharacteristicWrite()方法 mBluetoothGatt.writeCharacteristic(characteristic); // 向BLE藍牙外設寫入數據操作的回調方法 @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if(status == BluetoothGatt.GATT_SUCCESS) { // 命令寫入成功,數據包成功寫入BLE外設中 } };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

多說一句,其實,手機關閉外設也是一條寫入命令,外設得到該命令后即進入省電待機狀態,一般外設也可以通過開/關機鍵徹底關機。

 

APP關閉藍牙連接

交互完了,不需要了,還是把APP藍牙連接給斷掉吧

public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

后記

Android官方在SDK中提供了許多demo供開發者參考(1年前左右),其實關於BLE api調用也是有的,不過只涉及了藍牙外設的連接,未涉及藍牙數據讀寫.BLE官方demo路徑:User/AndroidSDK/samples/android-19/connectivity/BluetoothLeGatt
以上路徑是筆者舉例的路徑,如果你的SDK目錄下沒有samples目錄,現在(20170308)SDK Manager已經不開放sample下載了,請點擊下載:android-sample-api19 文件提取密碼: y87g

====update====
根據評論區網友xinyang_code & weiyouren_c指正:

在Android 6.0+搜索藍牙是需要定位權限的,還有BLE搜索在Android 5.0以前和以后是不一樣的。最后你還會發現使用官方這套搜索在一些手機型號上也是搜不到的!只能通過傳統藍牙(非BLE方式)搜索然后過濾出BLE設備。

筆者當年做BLE開發時基於api 19,當時5.0還未風靡大陸,4.4大行其道,非常感謝以上兩位網友指正賜教!

歡迎各位朋友評論區留言交流。

本文原創作者:MichaelX,博客地址:http://blog.csdn.net/xiong_it.轉載請注明來源

歡迎光臨:MichaelX’s Blog

參考鏈接

https://developer.android.com/guide/topics/connectivity/bluetooth-le.html#terms


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM