物聯網入口之一Android藍牙4.0

轉載請註明本文出自Cym的博客(http://blog.csdn.net/cym492224103),謝謝支持!


如果還有同學不知道藍牙4.0可以做什麼請查看Android+藍牙 4.0 將帶來什麼?,現在可以穿戴設備也大多用的是藍牙4.0,如 智能體質秤,智能手環,智能血壓計等等。

原文地址:http://developer.android.com/guide/topics/connectivity/bluetooth-le.html

安卓4.3(API 18)爲BLE的核心功能提供平臺支持和API,App可以利用它來發現設備、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特點是低功耗。這一優點使android App可以與具有低功耗要求的BLE設備通信,如近距離傳感器、心臟速率監視器、健身設備等。

關鍵術語和概念


  • Generic Attribute Profile(GATT)—GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲“屬性”的數據塊。目前所有的BLE應用都基於GATT。 藍牙SIG規定了許多低功耗設備的配置文件。配置文件是設備如何在特定的應用程序中工作的規格說明。注意一個設備可以實現多個配置文件。例如,一個設備可能包括心率監測儀和電量檢測。
  • Attribute Protocol(ATT)—GATT在ATT協議基礎上建立,也被稱爲GATT/ATT。ATT對在BLE設備上運行進行了優化,爲此,它使用了儘可能少的字節。每個屬性通過一個唯一的的統一標識符(UUID)來標識,每個String類型UUID使用128 bit標準格式。屬性通過ATT被格式化爲characteristics和services。
  • Characteristic 一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor,characteristic可以被認爲是一個類型,類似於類。
  • Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變量可接受的範圍,或者一個characteristic變量特定的測量單位。
  • Service service是characteristic的集合。例如,你可能有一個叫“Heart Rate Monitor(心率監測儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測量)”等。你可以在bluetooth.org 找到一個目前支持的基於GATT的配置文件和服務列表。

角色和責任

以下是Android設備與BLE設備交互時的角色和責任:

  • 中央 VS 外圍設備。 適用於BLE連接本身。中央設備掃描,尋找廣播;外圍設備發出廣播。
  • GATT 服務端 VS GATT 客戶端。決定了兩個設備在建立連接後如何互相交流。

爲了方便理解,想象你有一個Android手機和一個用於活動跟蹤BLE設備,手機支持中央角色,活動跟蹤器支持外圍(爲了建立BLE連接你需要注意兩件事,只支持外圍設備的兩方或者只支持中央設備的兩方不能互相通信)。

當手機和運動追蹤器建立連接後,他們開始向另一方傳輸GATT數據。哪一方作爲服務器取決於他們傳輸數據的種類。例如,如果運動追蹤器想向手機報告傳感器數據,運動追蹤器是服務端。如果運動追蹤器更新來自手機的數據,手機會作爲服務端。

在這份文檔的例子中,android app(運行在android設備上)作爲GATT客戶端。app從gatt服務端獲得數據,gatt服務端即支持Heart Rate Profile(心率配置)的BLE心率監測儀。但是你可以自己設計android app去扮演GATT服務端角色。更多信息見BluetoothGattServer

BLE權限


爲了在app中使用藍牙功能,必須聲明藍牙權限BLUETOOTH。利用這個權限去執行藍牙通信,例如請求連接、接受連接、和傳輸數據。

如果想讓你的app啓動設備發現或操縱藍牙設置,必須聲明BLUETOOTH_ADMIN權限。注意:如果你使用BLUETOOTH_ADMIN權限,你也必須聲明BLUETOOTH權限。

在你的app manifest文件中聲明藍牙權限。

[html] view plain copy
 print?
  1. <uses-permission android:name="android.permission.BLUETOOTH"/>  
  2. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>  

如果想聲明你的app只爲具有BLE的設備提供,在manifest文件中包括:

[html] view plain copy
 print?
  1.       
  2. <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>  

但是如果想讓你的app提供給那些不支持BLE的設備,需要在manifest中包括上面代碼並設置required="false",然後在運行時可以通過使用PackageManager.hasSystemFeature()確定BLE的可用性。

[java] view plain copy
 print?
  1. <span style="font-size:14px;font-weight: normal;">// 使用此檢查確定BLE是否支持在設備上,然後你可以有選擇性禁用BLE相關的功能  
  2. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {  
  3.     Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();  
  4.     finish();  
  5. }</span>  


設置BLE

你的app能與BLE通信之前,你需要確認設備是否支持BLE,如果支持,確認已經啓用。注意如果<uses-feature.../>設置爲false,這個檢查纔是必需的。

如果不支持BLE,那麼你應該適當地禁用部分BLE功能。如果支持BLE但被禁用,你可以無需離開應用程序而要求用戶啓動藍牙。使用BluetoothAdapter兩步完成該設置。

  1. 獲取 BluetoothAdapter

    所有的藍牙活動都需要藍牙適配器。BluetoothAdapter代表設備本身的藍牙適配器(藍牙無線)。整個系統只有一個藍牙適配器,而且你的app使用它與系統交互。下面的代碼片段顯示瞭如何得到適配器。注意該方法使用getSystemService()]返回BluetoothManager,然後將其用於獲取適配器的一個實例。Android 4.3(API 18)引入BluetoothManager。

[java] view plain copy
 print?
  1. // 初始化藍牙適配器  
  2. final BluetoothManager bluetoothManager =  
  3.         (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);  
  4. mBluetoothAdapter = bluetoothManager.getAdapter();  
  1. 開啓藍牙

    接下來,你需要確認藍牙是否開啓。調用isEnabled())去檢測藍牙當前是否開啓。如果該方法返回false,藍牙被禁用。下面的代碼檢查藍牙是否開啓,如果沒有開啓,將顯示錯誤提示用戶去設置開啓藍牙。

[java] view plain copy
 print?
  1. // 確保藍牙在設備上可以開啓  
  2. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {  
  3.    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
  4.    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  
  5. }  

發現BLE設備


爲了發現BLE設備,使用startLeScan())方法。這個方法需要一個參數BluetoothAdapter.LeScanCallback。你必須實現它的回調函數,那就是返回的掃描結果。因爲掃描非常消耗電量,你應當遵守以下準則:

  • 只要找到所需的設備,停止掃描。
  • 不要在循環裏掃描,並且對掃描設置時間限制。以前可用的設備可能已經移出範圍,繼續掃描消耗電池電量。

下面代碼顯示瞭如何開始和停止一個掃描:

[java] view plain copy
 print?
  1. /** 
  2.  * 掃描和顯示可以提供的藍牙設備. 
  3.  */  
  4. public class DeviceScanActivity extends ListActivity {  
  5.     private BluetoothAdapter mBluetoothAdapter;  
  6.     private boolean mScanning;  
  7.     private Handler mHandler;  
  8.     // 10秒後停止尋找.  
  9.     private static final long SCAN_PERIOD = 10000;  
  10.     ...  
  11.     private void scanLeDevice(final boolean enable) {  
  12.         if (enable) {  
  13.             // 經過預定掃描期後停止掃描  
  14.             mHandler.postDelayed(new Runnable() {  
  15.                 @Override  
  16.                 public void run() {  
  17.                     mScanning = false;  
  18.                     mBluetoothAdapter.stopLeScan(mLeScanCallback);  
  19.                 }  
  20.             }, SCAN_PERIOD);  
  21.             mScanning = true;  
  22.             mBluetoothAdapter.startLeScan(mLeScanCallback);  
  23.         } else {  
  24.             mScanning = false;  
  25.             mBluetoothAdapter.stopLeScan(mLeScanCallback);  
  26.         }  
  27.         ...  
  28.     }  
  29. ...  
  30. }  

作爲BLE掃描結果的接口,下面是BluetoothAdapter.LeScanCallback的實現。如果你只想掃描指定類型的外圍設備,可以改爲調用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),需要提供你的app支持的GATT services的UUID對象數組。

[java] view plain copy
 print?
  1. private LeDeviceListAdapter mLeDeviceListAdapter;  
  2. ...  
  3. // Device scan callback.  
  4. private BluetoothAdapter.LeScanCallback mLeScanCallback =  
  5.         new BluetoothAdapter.LeScanCallback() {  
  6.     @Override  
  7.     public void onLeScan(final BluetoothDevice device, int rssi,  
  8.             byte[] scanRecord) {  
  9.         runOnUiThread(new Runnable() {  
  10.            @Override  
  11.            public void run() {  
  12.                mLeDeviceListAdapter.addDevice(device);  
  13.                mLeDeviceListAdapter.notifyDataSetChanged();  
  14.            }  
  15.        });  
  16.    }  
  17. };  

連接到GATT服務端注意:只能掃描BLE設備或者掃描傳統藍牙設備,不能同時掃描BLE和傳統藍牙設備。


與一個BLE設備交互的第一步就是連接它——更具體的,連接到BLE設備上的GATT服務端。爲了連接到BLE設備上的GATT服務端,需要使用connectGatt( )方法。這個方法需要三個參數:一個Context對象,自動連接(boolean值,表示只要BLE設備可用是否自動連接到它),和BluetoothGattCallback調用。

[java] view plain copy
 print?
  1. mBluetoothGatt = device.connectGatt(thisfalse, mGattCallback);  

在這個例子中,這個BLE APP提供了一個activity(DeviceControlActivity)來連接,顯示數據,顯示該設備支持的GATT services和characteristics。根據用戶的輸入,這個activity與BluetoothLeService通信,通過Android BLE API實現與BLE設備交互。連接到GATT服務端時,由BLE設備做主機,並返回一個BluetoothGatt實例,然後你可以使用這個實例來進行GATT客戶端操作。請求方(Android app)是GATT客戶端。BluetoothGattCallback用於傳遞結果給用戶,例如連接狀態,以及任何進一步GATT客戶端操作。

[java] view plain copy
 print?
  1. //通過BLE API服務端與BLE設備交互  
  2. public class BluetoothLeService extends Service {  
  3.     private final static String TAG = BluetoothLeService.class.getSimpleName();  
  4.     private BluetoothManager mBluetoothManager; //藍牙管理器  
  5.     private BluetoothAdapter mBluetoothAdapter; //藍牙適配器  
  6.     private String mBluetoothDeviceAddress; //藍牙設備地址  
  7.     private BluetoothGatt mBluetoothGatt;   
  8.     private int mConnectionState = STATE_DISCONNECTED;  
  9.     private static final int STATE_DISCONNECTED = 0//設備無法連接  
  10.     private static final int STATE_CONNECTING = 1;  //設備正在連接狀態  
  11.     private static final int STATE_CONNECTED = 2;   //設備連接完畢  
  12.     public final static String ACTION_GATT_CONNECTED =  
  13.             "com.example.bluetooth.le.ACTION_GATT_CONNECTED";  
  14.     public final static String ACTION_GATT_DISCONNECTED =  
  15.             "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";  
  16.     public final static String ACTION_GATT_SERVICES_DISCOVERED =  
  17.             "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";  
  18.     public final static String ACTION_DATA_AVAILABLE =  
  19.             "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";  
  20.     public final static String EXTRA_DATA =  
  21.             "com.example.bluetooth.le.EXTRA_DATA";  
  22.     public final static UUID UUID_HEART_RATE_MEASUREMENT =  
  23.             UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);  
  24.     //通過BLE API的不同類型的回調方法  
  25.     private final BluetoothGattCallback mGattCallback =  
  26.             new BluetoothGattCallback() {  
  27.         @Override  
  28.         public void onConnectionStateChange(BluetoothGatt gatt, int status,  
  29.                 int newState) {//當連接狀態發生改變  
  30.             String intentAction;  
  31.             if (newState == BluetoothProfile.STATE_CONNECTED) {//當藍牙設備已經連接  
  32.                 intentAction = ACTION_GATT_CONNECTED;  
  33.                 mConnectionState = STATE_CONNECTED;  
  34.                 broadcastUpdate(intentAction);  
  35.                 Log.i(TAG, "Connected to GATT server.");  
  36.                 Log.i(TAG, "Attempting to start service discovery:" +  
  37.                         mBluetoothGatt.discoverServices());  
  38.             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//當設備無法連接  
  39.                 intentAction = ACTION_GATT_DISCONNECTED;  
  40.                 mConnectionState = STATE_DISCONNECTED;  
  41.                 Log.i(TAG, "Disconnected from GATT server.");  
  42.                 broadcastUpdate(intentAction);  
  43.             }  
  44.         }  
  45.         @Override  
  46.         // 發現新服務端  
  47.         public void onServicesDiscovered(BluetoothGatt gatt, int status) {  
  48.             if (status == BluetoothGatt.GATT_SUCCESS) {  
  49.                 broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);  
  50.             } else {  
  51.                 Log.w(TAG, "onServicesDiscovered received: " + status);  
  52.             }  
  53.         }  
  54.         @Override  
  55.         // 讀寫特性  
  56.         public void onCharacteristicRead(BluetoothGatt gatt,  
  57.                 BluetoothGattCharacteristic characteristic,  
  58.                 int status) {  
  59.             if (status == BluetoothGatt.GATT_SUCCESS) {  
  60.                 broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);  
  61.             }  
  62.         }  
  63.      ...  
  64.     };  
  65. ...  
  66. }  

當一個特定的回調被觸發的時候,它會調用相應的broadcastUpdate()輔助方法並且傳遞給它一個action。注意在該部分中的數據解析按照藍牙心率測量配置文件規格進行。

[java] view plain copy
 print?
  1. private void broadcastUpdate(final String action) {  
  2.     final Intent intent = new Intent(action);  
  3.     sendBroadcast(intent);  
  4. }  
  5. private void broadcastUpdate(final String action,  
  6.                              final BluetoothGattCharacteristic characteristic) {  
  7.     final Intent intent = new Intent(action);  
  8.     // 這是心率測量配置文件。  
  9.     if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {  
  10.         int flag = characteristic.getProperties();  
  11.         int format = -1;  
  12.         if ((flag & 0x01) != 0) {  
  13.             format = BluetoothGattCharacteristic.FORMAT_UINT16;  
  14.             Log.d(TAG, "Heart rate format UINT16.");  
  15.         } else {  
  16.             format = BluetoothGattCharacteristic.FORMAT_UINT8;  
  17.             Log.d(TAG, "Heart rate format UINT8.");  
  18.         }  
  19.         final int heartRate = characteristic.getIntValue(format, 1);  
  20.         Log.d(TAG, String.format("Received heart rate: %d", heartRate));  
  21.         intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));  
  22.     } else {  
  23.         // 對於所有其他的配置文件,用十六進制格式寫數據  
  24.         final byte[] data = characteristic.getValue();  
  25.         if (data != null && data.length > 0) {  
  26.             final StringBuilder stringBuilder = new StringBuilder(data.length);  
  27.             for(byte byteChar : data)  
  28.                 stringBuilder.append(String.format("%02X ", byteChar));  
  29.             intent.putExtra(EXTRA_DATA, new String(data) + "\n" +  
  30.                     stringBuilder.toString());  
  31.         }  
  32.     }  
  33.     sendBroadcast(intent);  
  34. }  

返回DeviceControlActivity, 這些事件由一個BroadcastReceiver來處理:

[java] view plain copy
 print?
  1. // 通過服務控制不同的事件  
  2. // ACTION_GATT_CONNECTED: 連接到GATT服務端  
  3. // ACTION_GATT_DISCONNECTED: 未連接GATT服務端.  
  4. // ACTION_GATT_SERVICES_DISCOVERED: 未發現GATT服務.  
  5. // ACTION_DATA_AVAILABLE: 接受來自設備的數據,可以通過讀或通知操作獲得。  
  6. private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {  
  7.     @Override  
  8.     public void onReceive(Context context, Intent intent) {  
  9.         final String action = intent.getAction();  
  10.         if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {  
  11.             mConnected = true;  
  12.             updateConnectionState(R.string.connected);  
  13.             invalidateOptionsMenu();  
  14.         } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {  
  15.             mConnected = false;  
  16.             updateConnectionState(R.string.disconnected);  
  17.             invalidateOptionsMenu();  
  18.             clearUI();  
  19.         } else if (BluetoothLeService.  
  20.                 ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {  
  21.             // 在用戶接口上展示所有的services and characteristics  
  22.             displayGattServices(mBluetoothLeService.getSupportedGattServices());  
  23.         } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {  
  24.             displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));  
  25.         }  
  26.     }  
  27. };  


讀取BLE變量

你的android app完成與GATT服務端連接和發現services後,就可以讀寫支持的屬性。例如,這段代碼通過服務端的services和 characteristics迭代,並且將它們顯示在UI上。

[java] view plain copy
 print?
  1. public class DeviceControlActivity extends Activity {  
  2.     ...  
  3.     // 演示如何遍歷支持GATT Services/Characteristics  
  4.     // 這個例子中,我們填充綁定到UI的ExpandableListView上的數據結構  
  5.     private void displayGattServices(List<BluetoothGattService> gattServices) {  
  6.         if (gattServices == nullreturn;  
  7.         String uuid = null;  
  8.         String unknownServiceString = getResources().  
  9.                 getString(R.string.unknown_service);  
  10.         String unknownCharaString = getResources().  
  11.                 getString(R.string.unknown_characteristic);  
  12.         ArrayList<HashMap<String, String>> gattServiceData =  
  13.                 new ArrayList<HashMap<String, String>>();  
  14.         ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData  
  15.                 = new ArrayList<ArrayList<HashMap<String, String>>>();  
  16.         mGattCharacteristics =  
  17.                 new ArrayList<ArrayList<BluetoothGattCharacteristic>>();  
  18.         // 循環可用的GATT Services.  
  19.         for (BluetoothGattService gattService : gattServices) {  
  20.             HashMap<String, String> currentServiceData =  
  21.                     new HashMap<String, String>();  
  22.             uuid = gattService.getUuid().toString();  
  23.             currentServiceData.put(  
  24.                     LIST_NAME, SampleGattAttributes.  
  25.                             lookup(uuid, unknownServiceString));  
  26.             currentServiceData.put(LIST_UUID, uuid);  
  27.             gattServiceData.add(currentServiceData);  
  28.             ArrayList<HashMap<String, String>> gattCharacteristicGroupData =  
  29.                     new ArrayList<HashMap<String, String>>();  
  30.             List<BluetoothGattCharacteristic> gattCharacteristics =  
  31.                     gattService.getCharacteristics();  
  32.             ArrayList<BluetoothGattCharacteristic> charas =  
  33.                     new ArrayList<BluetoothGattCharacteristic>();  
  34.            // 循環可用的Characteristics.  
  35.             for (BluetoothGattCharacteristic gattCharacteristic :  
  36.                     gattCharacteristics) {  
  37.                 charas.add(gattCharacteristic);  
  38.                 HashMap<String, String> currentCharaData =  
  39.                         new HashMap<String, String>();  
  40.                 uuid = gattCharacteristic.getUuid().toString();  
  41.                 currentCharaData.put(  
  42.                         LIST_NAME, SampleGattAttributes.lookup(uuid,  
  43.                                 unknownCharaString));  
  44.                 currentCharaData.put(LIST_UUID, uuid);  
  45.                 gattCharacteristicGroupData.add(currentCharaData);  
  46.             }  
  47.             mGattCharacteristics.add(charas);  
  48.             gattCharacteristicData.add(gattCharacteristicGroupData);  
  49.          }  
  50.     ...  
  51.     }  
  52. ...  
  53. }  


接收GATT通知


當設備上的特性改變時會通知BLE應用程序。這段代碼顯示瞭如何使用setCharacteristicNotification( )給一個特性設置通知。

[java] view plain copy
 print?
  1. private BluetoothGatt mBluetoothGatt;  
  2. BluetoothGattCharacteristic characteristic;  
  3. boolean enabled;  
  4. ...  
  5. mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);  
  6. ...  
  7. BluetoothGattDescriptor descriptor = characteristic.getDescriptor(  
  8.         UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));  
  9. descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);  
  10. mBluetoothGatt.writeDescriptor(descriptor);  

如果對一個特性啓用通知,當遠程藍牙設備特性發送變化,回調函數onCharacteristicChanged( ))被觸發。

[java] view plain copy
 print?
  1. @Override  
  2. // 廣播更新  
  3. public void onCharacteristicChanged(BluetoothGatt gatt,  
  4.         BluetoothGattCharacteristic characteristic) {  
  5.     broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);  
  6. }  

關閉客戶端App


當你的app完成BLE設備的使用後,應該調用close( )),系統可以合理釋放佔用資源。

[java] view plain copy
 print?
  1. public void close() {  
  2.     if (mBluetoothGatt == null) {  
  3.         return;  
  4.     }  
  5.     mBluetoothGatt.close();  
  6.     mBluetoothGatt = null;  
  7. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章