Android 藍牙BLE初學筆記



這是我剛開始接觸BLE開發總結的一些網上的資料,結合到和一起,這是某位大神的demo講解連接,可以結合一起看,biji

http://blog.csdn.net/dandan_dany/article/details/51698861

一、Android藍牙層次結構

1.1物理層(Physical Layer)

物理層,負責提供數據傳輸的物理通道(通常稱爲信道)。通常情況下,一個通信系統中存在幾種不同類型的信道,如控制信道、數據信道、語音信道等等。

BLE的ISM頻段是2.4-2.4835GHz

頻帶分爲40份,每份帶寬2M,用於支持多設備連接

邏輯層(Logical Layer)

在物理層的基礎上,提供兩個或多個設備之間、和物理無關的邏輯傳輸通道(也稱作邏輯鏈路)。

L2CAP Layer(邏輯鏈路控制和適配協議)

負責管理邏輯層提供的邏輯鏈路。基於該協議,不同Application可共享同一個邏輯鏈路。類似TCP/IP中端口(port)的概念。

L2CAP的功能要求包括協議/信道複用,分段和重組,每個信道的流控制、差錯控制和組管理。

協議/信道複用:L2CAP必須能夠區分高層協議,在信道建立時,協議複用功能用來發送請求來連接正確的上層協議;數據傳輸時,邏輯信道複用必須能夠把用同一協議的不用幾個高層實體區分出來。

分段和重組:通過由資源管理器所提供的幀中繼服務,在L2CAP層之上個人應用程序可以控制傳輸幀的大小。如果L2CAP控制了PDU的長度,那麼就可爲多路複用提供更好的服務,它提供下面的好處:

<1>分段將允許應用數據單元的交錯,這樣可以滿足延時要求;

<2>在L2CAP控制了分組大小以後,內存和緩衝區管理就會變得更加簡單;

<3>重傳糾錯更有效;

<4>減少丟失數據

<5>上層分組映射在低層分組中,並可以從底層分組中剝離出來。

  每個L2CAP信道的流控制,在同一個L2CAP邏輯鏈路上有幾個數據流傳輸時,每個信道需要各自的流控制,L2CAP也對需要流控制的應用提供流控制。L2CAP連接建立過程,允許交換有關兩藍牙單元之間服務質量的信息。每個L2CAP設備必須監視由協議使用的資源並保證服務質量(QoS)的完整實現。

1.2應用層(APP Layer)

理解藍牙協議中的應用層,基於L2CAP提供的channel,實現各種各樣的應用功能。Profile是藍牙協議的特有概念,爲了實現不同平臺下的不同設備的互聯互通,藍牙協議不止規定了核心規範(稱作Bluetooth core),也爲各種不同的應用場景,定義了各種Application規範,這些應用層規範稱作藍牙profile。

爲了實現不同平臺下的不同設備的互聯互通,藍牙協議爲各種可能的、有通用意義的應用場景,都制定的了規範,如SPP、HSP、HFP、FTP、IPv6/6LoWPAN等等。

Profiles基於L2CAP提供的L2CAP channel endpoints實現,在它們對應的層次上進行數據通信,以完成所需功能。

BLE涉及的協議:

 

BLE的協議可分爲Bluetooth Application和Bluetooth Core兩大部分。Bluetooth Core又包含BLE Controller和BLE Host兩部分BLE Host兩個關鍵協議:GAP(通用訪問協議)、GATT(通用屬性協議)GAP主要負責連接前的數據廣播,GAP層4種不同類型的廣播:通用的、定向的、不可連接的、可發現的。

設備每次廣播時,會在3個廣播信道上發送相同的報文。這些報文被稱爲一個廣播事件

通用廣播:通用廣播是用途最廣的廣播方式。進行通用廣播的設備能夠被掃描設備掃描到,或者在接收到連接請求時作爲從設備進入一個連接。通用廣播可以在沒有連接的情況下發出,換句話說,沒有主從設備之分。

定向廣播:有時候,設備間需要快速建立連接。如果從設備想這麼做,就需要進行廣播。定向廣播事件就是爲了儘可能快的建立連接。這種報文包含兩個地址:廣播者的地址和發起者的地址。發起設備收到發紿自己的定向廣播報文後,可以立即發送連接請求作爲迴應。

不可連接廣播:不想被連接的設備使用不可連接廣播事件。這種廣播的典型應用包括設備只想廣播數據,而不想被掃描或者連接。速也是唯一可用於只有發射機而沒有接收機設備的廣播類型。不可連接廣播設備不會進入連接態,因此,它只能根據主機的要求在廣播態和就緒態之間切換。

可發現廣播:最後一種廣播事件是可發現廣播。這種廣播不能用於發起連接,但允許其他設備掃描該廣播設備。這意味着該設備可以被發現,既可以廣播數據,又可以響應掃描,但不能建立連接。這是一種適用於廣播數據的廣播形式,動態數據可以包含於廣播數據之中,而靜態數據可以包含於掃描響應數據之中。可發現廣播不會進入連接態,而只能在停止後回到就緒態。

SM(security manager),藍牙的安全管理,就是使用一種密鑰分發的方式來實現識別ble數據加密和解密的功能。連接建立之後,雙方通過某些方式協商共同的密鑰,然後將後續要傳輸的數據用這個密鑰通過加密算法進行加密,那實際傳送到空中的數據是加密後的數據,在接收到這些數據後,就必須用正確的密鑰來解密,才能得到正確的數據了。當然,這種方式的加密,破解的方式就是得到其密鑰即可。

GATT基於ATT協議。所有的BLE profile一定基於GATT。也就是所有的BLE服務都使用ATT作爲應用協議。

ATT協議的唯一基礎是屬性。每個屬性由三個元素構成:

1,一個16bit handle;

2,一個UUID來定義屬性的類型;

3,確定長度的屬性值

在ATT中,屬性值可以是任意長度的byte數組。屬性值的實際意義依賴於UUID,而且ATT並不會檢查屬性值長度是否與給定的UUID定義一致。

Handle是用來唯一識別屬性的數字,因爲在一個BLE 設備中可能存在多個屬性具有相同的UUID。

ATT協議本身沒有定義任何UUID。這部分工作留給了GATT和上層協議。

Attribute可以定義一些權限(Permissions),以便server控制client的訪問行爲:

1,訪問有關的權限(access permissions),Readable、Writeable以及Readable and writable;

2,加密有關的權限(encryption permissions),Encryption required和No encryption required;

3,認證有關的權限(authentication permissions),Authentication Required和No Authentication Required;

4,授權有關的權限(authorization permissions),Authorization Required和No Authorization Required。

ATT採用client-server的形式。提供信息的一方稱作ATT server(一般是那些傳感器節點),訪問信息的一方稱作ATT client。

一個ATT屬性由Attribute Type、Attribute Handle和Attribute Value組成。

 

 

二、GATTprofile層次

GATT 連接,必需先經過 GAP 協議

GATT服務、特徵以及描述符的一個共同點是他們都是使用一個通用唯一標識符(UUID)標識。正如UUID名稱所表現的那樣,UUID是一個簡單且唯的標識符,用來找到GATT服務、特徵以及描述符。

一個服務包括多個特性,特性可能還有描述值、特性單位

服務還有訪問控制屬性。

Characteristic是GATT profile中最基本的數據單位,由一個Properties、一個Value、一個或者多個Descriptor組成。

Characteristic Properties定義了characteristic的Value如何被使用,以及characteristic的Descriptor如何被訪問。

Characteristic Value是特徵的實際值,例如一個溫度特徵,其Characteristic Value就是溫度值就。

Characteristic Descriptor則保存了一些和Characteristic Value相關的信息。

 

三、Android BLE開發相關

3.1、藍牙開發權限

<uses-permissionandroid:name="android.permission.BLUETOOTH" />

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />

BLE功能開啓

<uses-feature

   android:name=”android.hardware.bluetooth_le”android:required=”true” />

3.2 獲取藍牙適配器對象

檢查可通過BLE無線電與其他設備進行通信:

getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)

需要獲取 BluetoothService 實例,這是一個Android系統服務。通過 BluetoothService 對象可以得到一個代表了設備上藍牙無線電的 BluetoothAdapter 對象的一個實例。

獲取藍牙適配器對象的兩種方式:

(1)獲取系統藍牙管理器,再獲取藍牙適配器對象

BluetoothManager bluetoothManager =                     (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);

bluetoothAdapter = bluetoothManager.getAdapter();

(2)通過靜態接口直接獲取

BluetoothAdapter =BluetoothAdapter.getDefaultAdapter();

3.3 搜索BLE設備

BluetoothAdapter 類正好包含一個startLeScan()方法!該方法接收一個BluetoothAdapter.LeScanCallback 的實例作爲參數,該參數會在掃描過程中接收回調:

(1)startLeScan();開始搜索

(2)stopLeScan();停止搜索

BluetoothAdapter.LeScanCallback搜索的回調,在回調接口中獲取搜索到的BLE設備。

掃描持續一個固定的時間,所以使用 postDelayed() 在某個時間點調用 stoplescan() 停止掃描。

在掃描的過程中每次藍牙適配器接收來自BLE設備的廣播信息 onLeScan() 方法都會被調用。當藍牙設備處於廣播模式的時候通常每秒會發出十次廣播信息,所以在掃描的工程中必須小心地只回應新的設備。通過維護一個已發現設備的映射表來實現,在掃描過程中如果 onLeScan() 方法被調用,檢查是否知道這個設備device.getName()、device.getAddress()。

一旦藍牙被發現並建立起主設備與傳感器之間的相互信任關係,那麼之後主設備就可以直接連接到傳感器而無需將其置入廣播模式下。

如果只是要掃描到特定類型的設備,則使用接口 startLeScan(UUID[],BluetoothAdapter.LeScanCallback),通過UUID來查找設備。

3.4 BLE連接

通過接口BluetoothDevice. connectGatt(device)連接BLE設備

在回調函數中監聽連接狀態的變化:

onConnectionStateChange()函數是連接狀態的回調

參數:int oldStatus,int newStatus;0表示未連接,2表示已連接

連接到GATT服務器:創建和打開一個GATT服務器的本地代理實例的連接,通過這個代理連接到傳感器上的GATT服務器。創建代理,需要調用已發現的藍牙設備BluetoothDevice 實例的connectGatt()  方法 。參數:Context ; boolean;BluetoothGattCallback  。 connectGatt() 的返回值是一個BluetoothGatt的實例,通過這個本地的代理對象,和GATT服務器服務器通信。

與GATT服務器的連接由兩部分組成:傳感器上的GATT服務器,和一個本地代理。雖然可能知道GATT服務,本地代理不知道,所以要讓本地代理從傳感器中獲取其服務列表。這個通過使用 discoverservices() 方法實現

BluetoothGatt.discoverServices();發現服務和每個服務的特徵:

List<BluetoothGattCharacteristic> gattCharacteristics=supportedGattServices.getCharacteristics();

for循環遍歷得知每個特徵所具有的屬性:可讀或者可寫或者具備通知功能或者都具備等

int charaProp =gattCharacteristic.getProperties();

可讀

if ((charaProp |BluetoothGattCharacteristic.PROPERTY_READ) > 0) {}

可寫
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {}

通知

if ((charaProp |BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {}

 

3.5 數據獲取

通過獲取到的BLE Service列表,通過需要的UUID服務,對service中的信息進行數據的處理:

獲取Characteristic:

BluetoothGattService service  =  mBluetoothGatt.getService(UUID);

BluetoothGattCharacteristiccharacteristic= service.getCharacteristic(UUID);

(1) readCharacteristic(chara)

       主動讀取對應特徵值中存貯的數據內容。在onCharacteristicRead()回調接口中獲取對應的數據。

   (2) BluetoothGatt.setCharacteristicNotification()

       將對應UUID的characteristic設置爲Notification狀態,則當對應的Characteristic中的value發生數據改變時,在回調對象中觸發onCharacterisicChanged接口,從接口中獲取對應的數據變化。

BluetoothGattCharacteristi. getValue()。

3.6數據寫出

(1)BluetoothGattCharacteristi類型參數爲接收的藍牙設備數據

(2)BluetoothGattCharacteristi. getValue()獲得byte數組數據

(3)BluetoothGattCharacteristi.setValue(byte []) 設置需要寫出的數據

(5)onCharacteristicRead 當調用readCharacteristic時觸發,獲取value

(4)BluetoothGatt. writeCharacteristic()函數可以把數據寫入遠程藍牙設備

在writeCharacteristic之前,characteristic可以設置幾種應答方式:

1、WRITE_TYPE_NO_RESPONSE

   純粹的寫出,不需要對端設備提供應答信息。

2、WRITE_TYPE_SIGNED

寫出時需要添加認證簽名。

3、WRITE_TYPE_DEFAULT

默認需要BLE 設備返回應答信息。

3.7 MTU

gatt client上(一般是手機上)傳輸長一點的數據給gatt server(一般是一個Bluetooth smart設備,即只有BLE功能的設備),但通過

writeCharacteristic(BluetoothGattCharacteristic) 

最多隻能寫入20個byte的數據,若超過20個字節,必須分段發送,

否則回調狀態status回出錯,一般爲9。

需要傳輸多數據的時候,需要改變傳輸的ATT的MTU,requestMtu(int mtu)函數修改MTU值

回調函數onMtuChanged(BluetoothGatt gatt, int mtu,int status)

一般MTU的值會+3,因爲ATT的opcode一個字節以及ATT的handle 2個字節

讀取或者發送數據時可能較長,所以就需要拼接或者分包處理,在線程中 進行數據的分包,把分包的數據加入隊列,輪詢隊列來發送數據。

3.8 數據操作隊列

數據操作過程中,必須等待一個操作完成後纔可以開始下一個,通過維護一個寫的隊列來達到完成一個操作後觸發下一個操作的目的。

ConcurrentLinkedQueue

如果隊列爲空就執行操作,如果上一次的操作未完成,本次操作則add到隊列,當上一次執行完成後

onCharacteristicChanged回調中實現隊列的監聽,

ConcurrentLinkedQueue.poll()移除第一次操作並觸發下一次操作

 

四、BLE報文結構

4.1.1 前導:

前導是一個8比特的交替序列。它不是01010101就是10101010,取決於接入地址的第一個比特。

若接入地址的第一個比特爲0:01010101

若接入地址的第一個比特爲1:10101010

這樣就保證了任一報文的前9個比特都是交替的,即要麼爲:101010101,要麼爲:010101010;

接收機可以根據前導的無線信號強度來配置自動增益控制。

4.1.2 接入地址:

接入地址有兩種類型:廣播接入地址和數據接入地址。

廣播接入地址:固定爲0x8E89BED6,在廣播、掃描、發起連接時使用。

數據接入地址:隨機值,不同的連接有不同的值。在連接建立之後的兩個設備間使用。

對於數據信道,數據接入地址是一個隨機值,但需要滿足下面幾點要求:

 1)  數據接入地址不能超過6個連續的“0”或“1”。

 2)  數據接入地址的值不能與廣播接入地址相同。

 3)  數據接入地址的4個字節的值必須互補相同。

 4)  數據接入地址不能有超24次的比特翻轉(比特0到1或1到0,稱爲1次比特翻轉)。

 5)  數據接入地址的最後6個比特需要至少兩次的比特翻轉。

 6)  符合上面條件的有效隨機數據接入地址大概有231個。

4.1.3報頭:

廣播報文的報頭包含4bit廣播報文類型、2bit保留位、1bit發送地址類型和1bit接收地址類型。

每種廣播報文類型都具有不同的數據格式及行爲

4.1.4長度:

廣播報文:長度域包含6個比特,有效值的範圍是6~37。

數據報文:長度域包含5個比特,有效值的範圍是0~31。

廣播報文和和數據報文的長度域有所不同,主要原因是:廣播報文除了最多31個字節的數據之外,還必須要包含6個字節的廣播設備地址。6+31=37,所以需要6比特的長度域。

       4.1.5數據:

       廣播和掃描響應由有效數據部分和無效數據部分組成

4.2.1總體結構:

preamble(1 Byte)+ Access Address(4 Bytes)+ PDU + CRC(3 Bytes)

preamble = 10101010 or 01010101

Access Address = 0x8e89bedd6

4.2.2廣播包

PDU = Header(2 Bytes)+ Payload (37 Bytes max.)

Header:

1)0000 - connected undirected advertising event 可連接非定向廣播事件

2)0001 - connected directed advertising event 可連接定向廣播事件

3)0010 - non-connected undirected advertising event 不可連接非定向廣播事件

4)0011 - response to scan request form scanner掃描請求響應

5)0101 - connect request by initiator連接請求

6)0110 -connected directed advertising event 可發現非定向廣播事件

     a.非定向廣播包:

Payload = AdvA (6 Bytes) + AdvData (31 Bytes max.) ;

b.定向廣播包:

Payload  = AdvA (6 Bytes) + InitA(6Bytes) ;

 

五、BLE開發注意點

5.1、BluetoothAdapter.startLeScan() 的時候,

在 BluetoothAdapter.LeScanCallback.onLeScan() 中不能做太多事情,特別是周圍的BLE設備多的時候,容易導致錯誤。

5.2、Android 從 4.3(API Level 18) 開始支持低功耗藍牙,但是隻支持作爲中心設備 (Central) 模式,這就意味着 Android 設備只能主動掃描和鏈接其他外圍設備 (Peripheral)。從Android 5.0(API Level 21)開始兩種模式都支持。

5.3、在使用BluetoothDevice.connectGatt() 或者 BluetoothGatt.connect() 等建立 BluetoothGatt 連接的時候,在任何時刻都只能最多一個設備在嘗試建立連接。如果同時對多個藍牙設備發起建立 Gatt 連接請求。如果前面的設備連接失敗了,後面的設備請求會被永遠阻塞住,不會有任何連接回調。

如果要對多個設備發起連接請求,最好是有一個同一個的設備連接管理,把發起連接請求序列化起來。前一個設備請求建立連接,後面請求在隊列中等待。如果連接成功了,就處理下一個連接請求。如果連接失敗了(例如出錯,或者連接超時失敗),就馬上調用  BluetoothGatt.disconnect() 來釋放建立連接請求,然後處理下一個設備連接請求。

5.4、BLE 設備的建立和斷開連接的操作,例如  

 BluetoothDevice.connectGatt(),

 BluetoothGatt.connect() ,

 BluetoothGatt.disconnect() 等操作最好都放在主線程中。

5.5、在開發 BLE 應用的時候,有時候會發現系統的功耗明顯增加了,查看電量使用情況,藍牙功耗佔比非常高,因爲BluetoothRemoteDevices 的  WakeLock 鎖持有時間非常長,導致系統進入不了休眠,在連接 BLE 設備的過程中,系統會持有 (Aquire) 這個  WakeLock ,直到連接上或者主動斷開連接(調用  disconnect() )纔會釋放。如果BLE設備不在範圍內,這個超時時間大約爲30s,而這時可能又要嘗試重新連接,這個  WakeLock 有被重新持有,這樣系統就永遠不能休眠了。

對BLE設備連接,連接過程要儘量短,如果連接不上,不要盲目進行重連,否這電池會很快被消耗掉。這個情況,實際上對傳統藍牙設備連接也是一樣。

5.6、按照需要連接設備,如果設備使用完了,或者過程中任何的出錯和超時,應該馬上釋放連接(調用 BluetoothGatt.close() ,BluetoothGatt. disconnect()),騰出系統資源給其他可能的設備連接

 

    

      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章