BLE Host解析: ATT/GATT

本部分是從各位前輩的學習經驗中,總結過來的,希望對初學者有益。

從藍牙Spec 4.0開始,推出了低功耗(BLE)規範,BLE的協議可分爲Bluetooth Application和Bluetooth Core兩大部分,而Bluetooth Core又包含BLE Controller和BLE Host兩部分,整體架構如下圖所示。本章節,先來看一下Host部分中的兩個核心協議:ATT(Attribute Protocol)和GATT(Generic Attribute Protocol).

ble_stack

1. 總綱

這兩個協議主要目標是BLE,但是也可以運行在傳統藍牙上(BR/EDR).

ATT提供了一種無線應用協議,GATT基於ATT協議,相當於ATT的framewrok層,而所有的BLE profile又基於GATT。同時ATT/GATT定義在host中,即協議棧裏面, 而profiles則定義在應用層,這樣的結構決定了ATT/GATT要實現基本而common的功能實現,而profiles來完善各具特色的具體應用功能。

BLE分層使用這兩個協議的好處是:

 

  1. 對於軟件實現的協議棧來說,ATT/GATT在協議棧裏實現,省去了應用的麻煩。
  2. 開發和實現新的BLE profile更加容易,因爲不需要從頭實現wire protocol。
  3. ATT針對BLE 設備進行了特別的優化:使用儘可能少的字節,因此可能在存儲中使用定長結構來生成PDU。
  4. ATT/GATT的簡單意味着固件可能提供某種程度的協議支持,省去了微處理器軟件的麻煩。
  5. 即使有的場景下,ATT/GATT不夠理想。也可以在L2CAP連接上實現平行於ATTchannel的協議。

 

2. ATT: Attribute Protocol

該協議將數據以“Attribute(屬性)”的形式抽象出來,並提供一些方法,供遠端設備(remote device)讀取、修改這些屬性的值(Attribute value)。ATT協議的唯一基礎是屬性。每個屬性由三個元素構成:

 

  1. one 16bits handle;
  2. one 16bits/32bits/128bitsUUID來定義屬性的類型;
  3. 確定長度的屬性值

 

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

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

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

和屬性相關的還有讀寫權限。讀寫權限存在屬性值裏,由高層協議確定。ATT本身不會關心,也不會試圖解釋屬性值來確定權限。這部分工作也留給了GATT和上層協議。

ATT有一些良好的特徵,比如通過UUID來搜索屬性,通過handle區間範圍來獲取所有區間內的屬性,因此client不需要提前獲得handle的值,也不需要高層協議硬編碼這些值。

但是在特定的設備上handle的取值最好保持不變,這樣的話client能夠緩衝信息。在第一個discovery以後,client能夠使用緩衝信息,這樣能夠減少傳輸的包數量,也能夠節約能量。如果服務端的屬性佈局已經發生了變換,高層協議應該能夠”暗示”client,比如固件升級。

ATT有兩個角色,Client和Server,大多數情況下ATT協議都是純C/S架構,即server存儲屬性,client什麼也不存儲,client主動發起請求讀寫server端的屬性,server被動響應。但是服務端也有通知的能力,在服務端屬性發生變化時,server能夠通知client,這樣避免了client不停的poll

ATT協議不會顯式發送屬性值的長度,只能從PDU長度裏面獲得。因此client最好能夠知道某種UUID類型所代表的屬性的精確結構。

不發送屬性值長度,是爲了減少發送的字節,因爲LE的MTU只有23bytes

23bytes的MTU對於較長的屬性值來說是個麻煩。因此不得采用“read long”,”write long“這樣的操作。

ATT是如此通用,意味着高層協議有太多工作要做。過度的自由也會帶來問題,比如:如果一個設備提供多個服務怎麼辦?對每一個設備只有一個ATT handle空間,多個服務不得不共享同一份空間。

幸運地是,我們還有GATT,它爲我們提供了屬性用法,並解除了這些限制。

3. GATT:Generic Attribute Profile

 

GATT是所有LE頂層協議的基礎。它定義了怎麼把一堆ATT屬性分組成爲有意義的服務。
縱向看,GATT Profile包含一個或多個GATT Services, 每個GATT service又包含一個或多個GATT Characteristics, 同時每個Characteristic又對應一個或多個GATT Descriptors。

3.1 GATT service

 

GATT service的基礎是UUID值爲0x2800的屬性。所有跟在這個屬性後面的屬性都屬於這個屬性定義的服務,直到另一個0x2800屬性出現。

比如說,一個設備裏面的三個屬性佈局如下: 

 

Handle UUID Description
0x0100 0x2800 Service A definition
... ... Service details
0x0150 0x2800 Service B definition
... ... Service details
0x0300 0x2800 Service C definition
... ... Service details

每一個屬性不知道它自己屬於哪個服務,GATT需要根據0x2800屬性作爲標記來識別出哪個屬性屬於哪個服務。

按照這個定義,handle值就有意義了。在上面的例子中,屬於service B的屬性handle必須位於0x0151和0x02ff之間。

UUID 0x2800定義了primary服務,

也可以使用0x2801來定義secondary服務。

Secondary服務表示包含於primary服務。

然後我們怎麼能知道一個服務是溫度計,智能鑰匙或者GPS?答案是通過讀取屬性值。服務屬值包含了一個UUID,通過這個UUID區分服務。

因此,每個屬性定義事實上包含了兩個UUID,0x2800或者0x2801作爲屬性UUID,另外一個屬性值裏面存儲的UUID。後面這個UUID是服務ID。

舉個例子: 

 

Handle UUID Description Value
0x0100 0x2800 Thermometer service definition UUID 0x1816
... ... Service details ...
0x0150 0x2800 Service B definition 0x18xx
... ... Service details ...
0x0300 0x2800 Service C definition 0x18xx
... ... Service details ...

 

在圖中, thermometer service的UUID是0x1816。

是不是感覺怪怪的?兩個UUID定義一個服務?這是GATT/ATT分層方式導致的後果。

UUID0x2800被GATT用來尋找服務定義邊界。一旦找到了邊界,屬性值,也就是第二個UUID用來指定服務。這樣client能夠找到所有的服務而不需要知道服務的具體定義。

3.2 GATT service characteristics

每一個服務有幾個特徵。特徵存儲了有用的值以及權限。

比如,一個溫度計可能有隻讀的溫度特徵,也可能有可讀寫的時間戳。

Handle UUID Description Value
0x0100 0x2800 Thermometer service definition UUID 0x1816
0x0101 0x2803 Characteristic: temperature UUID 0x2A2B
Value handle: 0x0102
0x0102 0x2A2B Temperature value 20 degrees
0x0110 0x2803 Characteristic: date/time UUID 0x2A08
Value handle: 0x0111
0x0111 0x2A08 Date/Time 1/1/1980 12:00

每一個服務可能有幾個特徵,這些特徵也是通過路碑屬性來發現的。

主特徵的UUID是0x2803,然後主特徵的屬性值用來定義特徵。比如圖中 0x2803用來找到特徵,0x2A2B用來找到特徵包含的信息。

每一個特徵至少包含兩個屬性,主屬性0x2803和真正的值屬性。主屬性知道屬性值的handle和UUID。這能夠進行一定程度的交叉檢測。

特徵值的真正格式是由UUID決定的。因此,如果客戶端知道如何解釋UUID爲 0x2A08的特徵值,就能夠從包含這個特徵任何服務裏面讀取日期和時間。當然如果客戶端不知道如何解釋這個UUID的話,也可以選擇忽略。

3.3 Characteristic descriptors

 除了特徵值,我們也可以爲每個特徵增加更多的屬性。在GATT語法裏,這個額外的屬性成爲描述符。

 舉個例子子,我們也許需要指定溫度的計量單位。 

 

Handle UUID Description Value
0x0100 0x2800 Thermometer service definition UUID 0x1816
0x0101 0x2803 Characteristic: temperature UUID 0x2A2B
Value handle: 0x0102
0x0102 0x2A2B Temperature value 20 degrees
0x0104 0x2A1F Descriptor: unit Celsius
0x0110 0x2803 Characteristic: date/time UUID 0x2A08
Value handle: 0x0111
0x0111 0x2A08 Date/Time 1/1/1980 12:00

GATT知道handle 0x0104是特徵0x0101的描述符,因爲:

1, 他不是特徵的值,因爲特徵值的handle應該是0x0102

2, 他的handle落在了0x0103-0x010f之間,因此也不屬於下一個特徵。

描述符值的意義依賴於屬性UUID。例子中,描述符的UUID是0x2A1F,客戶端如果不能識別這個UUId,他可以選擇忽略。這樣可以實現向下兼容。

每個服務可能定義自己的描述符,但是GATT已經定義了能夠覆蓋大多數情況的標準描述符,比如:

數值格式和表示;

人類可讀的描述;

合理範圍擴展屬性等等。其中特別重要的描述符是client characteristic configuration。

Client Characteristic Configurationdescriptor

Client Characteristic Configurationdescriptor的UUID是0x2902,具有一個16bit的可讀寫值,作爲一個bitmap來使用。

這個屬性被server用來存儲和代表每個已經綁定的client的獨立實例,每個client只能看到它自己的拷貝。

前兩個bit被GATT用來定義通知和暗示。其他bit暫時未使用。

通過設置CCC,client能夠讓server在特徵發生改變時得到通知。比如包含了CCC的屬性佈局如下: 

 

Handle UUID Description Value
0x0100 0x2800 Thermometer service definition UUID 0x1816
0x0101 0x2803 Characteristic: temperature UUID 0x2A2B
Value handle: 0x0102
0x0102 0x2A2B Temperature value 20 degrees
0x0104 0x2A1F Descriptor: unit Celsius
0x0105 0x2902 Client characteristic configuration descriptor 0x0000
0x0110 0x2803 Characteristic: date/time UUID 0x2A08
Value handle: 0x0111
0x0111 0x2A08 Date/Time 1/1/1980 12:00

 

3.4 Service discovery in Low Energy

因爲GATT中所有的服務細節通過ATT來描述,所以不需要像BR/EDR那樣設置專門的服務發現協議。ATT負責一切:發現服務,查找特徵,讀寫值等等。

3.5 GATT and vanilla Bluetooth

GATT也可以工作在傳統藍牙上面,但是規範規定傳統藍牙仍然使用SDP發送服務,即使通過GATT來進行實際數據交換。

這樣的好處是在雙模設備上不用設置標識來識別LE-only服務。如果一個服務只能通過GATT發現,就是LE-only。如果能夠通過GATT和SDP發現,就是雙模。

         如果一個profile通過GATT來進行數據交換,並且是雙模的,它必須首先發布SDP record。然後這個服務通過SDP來發現,然後通過GATT來查找特徵。

         當然,現在沒有雙模的profile。以前的profile是BR/EDR only,並且沒有適配到GATT;LE-only只有LE。

         如果想要測試GATT而沒有LE硬件,可以修改藍牙協議棧來使BR/EDR可以進行GATT discovery。這是規範不運行的,但是開發者可以。

3.6 Notification and Indication

通知和暗示使得server可以發送消息給client。這樣客戶端不需要poll server來獲取新的數據。

另外,典型的GATT server是“小的“外設,像非常需要節能的傳感器之類。因此,外設的LE 設備不能發起連接。那麼通知怎麼發送呢?

在BLE協議棧,如果server有數據發送,它就進入廣播模式,並且發送一些信號。每個profile定義了廣播時長和頻率。時長和頻率應該根據使用場景進行了節能和及時性的權衡。

處於中心模式的設備隨時處於監聽模式。當它監聽到廣播後,如果發現廣播設備是認識的(配對過或者白名單中的),就會向外設發起連接。

連接建立以後,GATT通信能夠進行,通知得以發送。所以典型的序列是:1,server發送廣播 2,client連接 3,server通知

如果沒有更多的數據發送,server和client就會超時斷開。最佳超時時間依賴於用例;如果服務不會頻繁發送通知並且沒有實時性要求的話,可以立馬斷開。因爲BLE重連是非常快的。

典型的GATT server是外設設備,但是不是必須的。也可以外設做client,center做server。在這種場景下,client想要讀寫數據的時候,需要先進入廣播模式。

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