認識BLE 5協議棧 —— 通用屬性規範層 (GATT,Generic Attribute Profile)
轉自 http://www.sunyouqun.com/2017/04/understand-ble-5-stack-generic-attribute-profile-layer/
通用屬性規範GATT(Generic Attribute Profile)將ATT層定義的屬性打包成不同的屬性實體,包括服務項、特徵項和描述符,這些屬性實體組合在一起組成規範,即GATT規範。GATT規範是服務項的集合,服務項是特徵項的集合,特徵項攜帶了屬性引數和資料,描述符協助特徵項描述特徵值的形式和功能。
GATT層按照命令的傳輸方向將裝置分成GATT客戶端和GATT服務端。客戶端發起命令,服務端發出資料。GATT規範定義了客戶端裝置發現服務端裝置的服務項的方法,建立連線以後,客戶端裝置可以通過發現方法檢索服務端裝置的GATT服務項和特徵項,進而傳送命了或資料。
服務端向客戶端傳送資料以通知和指示的形式傳送,客戶端收到指示資訊需要返回確認資訊。
服務端可以向客戶端傳送通知和指示,客戶端按需返回響應。
1. 屬性
1.1 GATT角色
客戶端:裝置發起命令、請求並接受響應、通知和指示。
服務端:裝置接收命令、 請求併發出響應、通知和指示。
裝置可以同時屬於客戶端和服務端。
GATT角色與執行過程相關,它不與裝置繫結。裝置在執行一個過程時,根據發起命令或接收命令而決定它是服務端還是客戶端,該過程結束後就釋放GATT角色。
GATT角色不與鏈路層的主機和從機角色繫結。一個鏈路層的主機,通常擔任GATT客戶端角色,也可以擔任GATT服務端角色。
1.2 屬性PDU
屬性實體PDU如下:
欄位 | Attribute Handle | Attribute Type | Attribute Value | Attribute Permissions |
---|---|---|---|---|
長度 | 2 octets | 2 or 16 octets | variable | implementation specific |
一個屬性包含四個欄位:屬性控制代碼、屬性型別、屬性值和屬性許可權。
屬性控制代碼用於指定具體的屬性。屬性控制代碼有效範圍為0x0000-0xFFFF,屬性控制代碼按步進1的增序排列,但有時可能會出現空缺。
屬性型別為2位元組、4位元組或16位元組的UUID。 如果是4位元組UUID,在封裝成屬性PDU時根據藍芽基礎UUID轉換成16位元組標準UUID。
屬性值欄位包含了屬性的具體資料。
屬性許可權決定了屬性是否可讀或可寫。
1.3 屬性協議PDU
兩個裝置屬性層之間根據屬性協議傳輸資料,屬性協議包括幾種型別:命令、請求、響應、通知、指示和確認。
屬性協議PDU如下:
欄位 | Opcode | Attribute Parameters | Authentication Signature |
---|---|---|---|
長度 | 1 octet | variable | 12 octets |
操作碼Opcode決定了該PDU的操作過程型別。另外,操作碼中包含一個認證標誌位。
屬性引數中包含了命令或請求的引數,或響應的資料。
最後欄位的認證簽名為可選欄位,僅用於帶簽名的寫操作,當操作碼的認證標誌位為1,則需要認證簽名欄位,否則不需要改欄位。
1.4 屬性快取
客戶端與服務端建立連線後,執行發現過程,以獲取服務端所攜帶的全部屬性。屬性快取功能用於儲存服務端裝置的屬性控制代碼,使下一次重新連線時無需執行發現過程。
一般情況下,服務端裝置的屬性不會改變,但是執行韌體升級則可以改變裝置的屬性。
如果改變裝置的屬性,將從Service Changed characteristic發出一個指示PDU,告知客戶端裝置服務端裝置的屬性發生了改變。該指示PDU中包含了發生改變的屬性控制代碼範圍,客戶端裝置收到該指示,重新執行發現過程,獲取更新後的服務端裝置的屬性控制代碼。
如果裝置的屬性確定不能發生改變,則無需增加Service Changed characteristic屬性。
如果兩端裝置完成繫結,則屬性快取資訊一直有效,直到收到了Service Changed characteristic發出的指示。如果在服務端的屬性在斷開後發生了改變,則服務端在下次重連時候傳送指示給客戶端裝置重新快取屬性控制代碼。
1.5 屬性分組
GATT定義了三種屬性分組:主要服務、次要服務和特徵項。
一個屬性分組包括宣告和定義。
主要服務和次要服務可以使用“按組型別讀取”請求獲得,特徵項不可以。
1.6 屬性結構
藍芽協議中包含了許多種GATT規範,每個規範適配一種使用者案例,比如FindMe規範適配查詢物件的場景,心率感測器規範適配心率測量場景。
每個規範均中均有若干服務項和特徵項,服務項和特徵項都屬於屬性實體,它們攜帶了通訊中傳輸的資料。
服務項分為主要服務和次要服務,主要服務可以引用(Include)另一個主要服務或次要服務,客戶端裝置可以通過“主要服務發現過程”獲取主要服務資訊。
特徵項包括一個宣告、配置、資料和描述符。描述符用於描述特徵項的資料如何被訪問和展示。
規範、服務項和特徵項之間有明確的包含關係,一個GATT規範中可以包括多個服務項,一個服務項中可以包括多個特徵項。
GATT的規範結構框圖如下:
2. 屬性型別
屬性的型別由UUID表示,協議棧預留了一些16-bit的UUID來表示常用的屬性型別。
2.1 服務項
服務項必須包含一個服務項宣告,可選地包含多個其他服務項和特徵項。所包含的其他服務項和特徵項均是該服務項的一部分。
服務項的宣告格式如下:
服務項可以是主要服務項(UUID=0x2800)或次要服務項(UUID=0x2801)。
主要服務項可以獨立使用,次要服務項一定要被其他服務項包含引用。
協議棧文件中對次要服務項的使用場景解釋有限,在絕大多數情況下均可以不使用次要服務項,僅使用主要服務。
2.2 包含
包含現了一個引用機制,比如需要擴充套件一個現有的服務項,可以在新的服務項中引用該服務項。
假如服務項中包含了其他服務項,則需要加入包含的宣告(UUID=0x2802)。
協議棧文件中對包含的使用場景解釋有限,在絕大多數情況下均可以不使用包含功能。
2.2 特徵項
特徵項是GATT資料的載體。
特徵項包括:特徵項的宣告(UUID=0x2803),特徵值的宣告,以及若干描述符。特徵值也是一個屬性,它的控制代碼和UUID在特徵項的宣告中給出。
特徵項始於該特徵項的宣告,結束語下一個特徵項的宣告。
特徵項的宣告資料格式如下:
其中屬性值欄位包括了特徵值功能特性(Characteristic Properties),特徵值的屬性控制代碼和特徵值的UUID。
特徵值功能特性如下表所示:
特徵值功能 | 值 | 描述 |
---|---|---|
Broadcast | 0x01 | 允許廣播該特徵值 |
Read | 0x02 | 允許讀該特徵值 |
Write Without Response | 0x04 | 允許寫該特徵值,不需要Response |
Write | 0x08 | 允許寫該特徵值,需要Response |
Notify | 0x10 | 允許該特徵值傳送通知 |
Indicate | 0x20 | 允許該特徵值傳送指示 |
Authenticated Signed Writes | 0x40 | 允許帶認證簽名的寫該特徵值 |
Extended Properties | 0x80 | 擴充套件特性 |
其中,Broadcase(0x01)、Notify(0x10)和Indicate(0x20)要求該特徵值具有服務端特徵項配置描述符(CCCD)。
特徵項的宣告中屬性欄位的特徵值UUID跟特徵值的宣告中的UUID一致。
特徵值的宣告中包含了特徵值所攜帶的資料內容,其格式如下:
2.3 描述符
描述符也是一種屬性,它是特徵項的一部分,用以提供特徵值的額外資訊。協議棧定義了6種不同的描述符,如下:
屬性型別 | UUID | 描述 |
---|---|---|
«Characteristic Extended Properties» | 0x2900 | 特徵項的擴充套件描述符 |
«Characteristic User Description» | 0x2901 | 特徵項的使用者描述符 |
«Client Characteristic Configuration» | 0x2902 | 客戶端特徵項配置描述符 |
«Server Characteristic Configuration» | 0x2903 | 服務端特徵項配置描述符 |
«Characteristic Format» | 0x2904 | 特徵項資料格式描述符 |
«Characteristic Aggregate Format» | 0x2905 | 聚合特徵項資料格式描述符 |
0x2900 擴充套件性描述符,用於Reliable Write和Writable Auxiliaries這兩類寫屬性。
0x2901 使用者描述符,用於給出該特徵值的文字描述。
0x2902 客戶端特徵項配置描述符,簡稱為CCCD,客戶端裝置通過一個標誌引數,設定該特徵值能否傳送通知和指示。如果該標誌引數為0x0001,表示該特徵值允許傳送通知;如果該標誌引數為0x0002,表示該特徵值允許傳送指示。如果該標誌引數為0x0000,表示該特徵值不能傳送通知和指示。
每個特徵項最多能包含一個CCCD,對於具有Broadcast、Notify和Indicate功能的特徵項,必須擁有一個CCCD。在兩個建立了繫結的裝置中,斷開連線不會丟失CCCD資訊。
0x2903 服務端特徵項配置描述符,服務端裝置通過一個標誌引數,設定該特徵值是否在廣播中發出。如果該標誌引數為0x0001,則廣播訊息中應該包含該特徵值;如果該標誌引數為0x0000,則廣播訊息中不包含該特徵值。
0x2904 特徵值格式描述符,用於提供特徵值的資料格式。可選的資料型別包括:Boolean、1/4位元組、1/2位元組、1位元組、2位元組、3位元組、4位元組、8位元組、16位元組、帶符號整數、無符號整數、浮點數、字串、結構體等。還可以指定資料的指數、單位、名字空間、描述資訊等。
0x2905 聚合特徵項格式描述符,專用於聚合特徵項。所謂聚合特徵值,是指多個特徵值共同組合成一個數值,每個特徵值僅是該聚合數值的一部分。
3. GATT功能
GATT規範實現了以下功能:
- Server Configuration
- Primary Service Discovery
- Relationship Discovery
- Characteristic Discovery
- Characteristic Descriptor Discovery
- Reading a Characteristic Value
- Writing a Characteristic Value
- Notification of a Characteristic Value
- Indication of a Characteristic Value
- Reading a Characteristic Descriptor
- Writing a Characteristic Descriptor
這些功能利用了“深入BLE協議棧 —— 屬性協議”中的屬性協議PDU一節中的多種讀寫屬性PDU。
下面具體分析。
3.1 服務端配置
該功能呢包含一個子功能:交換兩端裝置的ATT_MTU。
客戶端裝置傳送Exchange MTU Request,其中包含了該裝置的ATT_MTU,服務端裝置返回Exchange MTU Response,其中包含了該裝置的ATT_MTU,取二者的較小值作為協商的ATT_MTU值。
3.2 發現主要服務項
該功能包含兩個子功能:發現全部主要服務項,按UUID發現主要服務項。
發現全部主要服務項
該功能向服務端裝置傳送Read By Group Type Request,起始控制代碼為0x0001,結束控制代碼為0xFFFF,屬性型別為0x2800(主要屬性的UUID),查詢全部符合條件的首要服務項。
服務端返回Read By Group Type Response,響應中包含多個屬性資訊組成的列表,單個屬性資訊包含三個引數:元素長度、屬性組首尾控制代碼、屬性的UUID。
因為該列表長度不能超過一個屬性層的PDU長度,所以需要多次執行請求和響應,直到服務端裝置返回“未找到屬性項”或到達結束控制代碼,才能獲取全部的主要服務項,如下圖所示:
觀察上圖,客戶端第一次發起請求,查詢主要服務項,首末控制代碼分別是0x0001和0xFFFF。
服務端返回響應中包含三個元素,每個元素代表一個首要服務項。每個元素的長度為0x06。第一個首要服務項的屬性控制代碼為0x0001,型別為UUID1,末尾控制代碼為0x000F。第二個服務項的屬性控制代碼為0x0010,型別為UUID2,末尾控制代碼為0x0017。第三個服務項的屬性控制代碼為0x0100,型別為UUID3,末尾控制代碼為0x01FF。
客戶端發起第二次請求,起始控制代碼設為0x2000。
服務端返回響應中仍然包含三個元素,每個元素的長度為0x06。三個元素分別表示三個首要服務項,其UUID分別為UUID4、UUID5和UUID6,UUID6的末尾控制代碼為0x04FF。
客戶端接著發起第三次請求,起始控制代碼設為0x0500。
服務端返回錯誤,錯誤原因是未找到屬性。客戶端根據該錯誤原因,判斷已經獲取服務端裝置的全部主要服務項。
按UUID發現主要服務項
該功能向服務端裝置傳送Read By Group Type Request,起始控制代碼為0x0001,結束控制代碼為0xFFFF,屬性型別為0x2800(主要屬性的UUID),指定的UUID為xxxx,查詢全部符合條件的主要服務項。
具體的操作步驟與“發現全部主要服務”一致。
通常具有指定UUID的服務項僅有一個。
3.3 發現關係
該功能呢包含一個子功能:查詢包含的服務項。
該功能向服務端裝置傳送Read By Type Request,起始控制代碼為0x0001,結束控制代碼為0xFFFF,屬性型別為0x2802(包含的宣告UUID),查詢全部符合條件的被包含服務項。
服務端返回響應,包含了滿足條件的服務項的控制代碼和屬性值。
3.4 發現特徵項
該功能包含兩個子功能:發現服務項下的全部特徵項,按UUID發現特徵項。
發現服務項下的全部特徵項
該功能向服務端裝置傳送Read By Type Request,設定已知的服務項首末控制代碼,屬性型別為0x2803(特徵項宣告的UUID),查詢全部符合條件的特徵項。
服務端返回Read By Type Response,響應中包含多個“屬性控制代碼 – 值”元素組成的列表。單個元素包含三個引數:元素長度、特徵項宣告的控制代碼和特徵值引數。特徵值引數包括特徵值的功能特性、特徵值控制代碼和UUID。
因為該列表長度不能超過一個屬性層的PDU長度,所以需要多次執行請求和響應,直到服務端裝置返回“未找到屬性項”或到達結束控制代碼,才能獲取全部的特徵項,如下圖所示:
觀察上圖,客戶端第一次發起請求,查詢特徵項,首末控制代碼分別是0x0200和0x0214。
服務端返回響應中包含兩個元素,每個元素代表一個特徵項。每個元素的長度為0x07。第一個特徵項的宣告控制代碼為0x0203,特徵值的功能特性為0x02,即具有Read功能,特徵值的控制代碼為0x0204,特徵值的UUID為UUID1。第二個特徵項的宣告控制代碼為0x0210,特徵值的功能特性為0x02,即具有Read功能,特徵值的控制代碼為0x0212,特徵值的UUID為UUID2。
由於每個元素的長度為7,表明該兩個特徵值的UUID均是2位元組UUID,如果是16位元組UUID,則每個元素的長度應該為0x15。
按UUID發現特徵項
該功能根據已知的特徵項UUID和首末控制代碼範圍,查詢滿足條件的 特徵項。
具體與“發現服務項下的全部特徵項”完全一致。
3.5 發現描述符
該功能包含一個子功能:發現全部描述符。
該功能向服務端裝置傳送Find Information Request,設定已知的特徵項首末控制代碼,查詢全部符合條件的描述符。
服務端返回Find Information Response,響應中包含多個“屬性控制代碼 – 值”元素組成的列表。單個元素包含三個引數:UUID格式、特徵值的控制代碼和描述符的UUID。如果UUID格式引數等於1,表示描述符的UUID為2位元組UUID,如果等於2,表示描述符的UUID為16位元組UUID。
因為該列表長度不能超過一個屬性層的PDU長度,所以需要多次執行請求和響應,直到服務端裝置返回“未找到屬性項”或到達結束控制代碼,才能獲取全部的描述符,如下圖所示:
觀察上圖,服務端的響應資料第一個引數0x01表示UUID1和UUID2均為2位元組UUID,第二個引數0x0205表示該描述符上級的特徵值的控制代碼。
3.6 讀特徵值
該功能包含四個子功能:讀特徵值,按UUID讀特徵值,讀長包特徵值,讀多個特徵值。
讀特徵值
客戶端已知特徵值控制代碼,向服務端傳送Read Request讀取該控制代碼的特徵值。
服務端返回指定控制代碼的特徵值。該特徵值長度應小於等於(ATT_MTU-1),如果大於該限制,則僅返回前(ATT_MTU-1)個數據。
下圖為一次讀取過程:
按UUID讀特徵值
客戶端已知特徵值的UUID,不知道其控制代碼,向服務端傳送Read By Type Request讀取該特徵值。
具體操作與“按UUID發現特徵項”一致。
讀長包特徵值
客戶端已知特徵值的控制代碼,但是特徵值的長度大於(ATT_MTU-1),向服務端傳送Read Request以讀取前(ATT_MTU-1)個位元組,然後傳送Read Blob Request並設定合適的偏移量,以讀取隨後的(ATT_MTU-1)個位元組,重複執行Read Blob Request直到服務端的Read Blob Response內容小於(ATT_MTU-1),表明該特徵值完全被讀取。
具體步驟如下:
讀多個特徵值
客戶端已知多個特徵值的控制代碼,向服務端傳送Read Multiple Request,引數為多個特徵值控制代碼。
服務端返回Read Multiple Response,包含了多個指定的特徵值資料。
3.7 寫特徵值
該功能包含五個子功能:寫命令,帶簽名的寫命令,寫請求,寫長包請求,可靠的寫請求。
寫命令
客戶端已知特徵值控制代碼,向服務端傳送Write Command,寫入指定資料。
資料長度不能超過(ATT_MTU-3)位元組,如果超過,僅寫入前(ATT_MTU-3)個位元組。
該命令無需服務端返回響應。
帶簽名的寫命令
客戶端已知特徵值控制代碼,且連結沒有經過認證,向服務端傳送Write Command,並設定簽名認證標誌位,實現帶簽名的寫命令。
資料長度不能超過(ATT_MTU-3-12)位元組,其中12表示認證簽名的長度,如果超過,僅寫入前(ATT_MTU-3-12)個位元組。
該命令無需服務端返回響應。
寫請求
客戶端已知特徵值控制代碼,向服務端傳送Write Request,寫入指定資料。
資料長度不能超過(ATT_MTU-3)位元組,如果超過,僅寫入前(ATT_MTU-3)個位元組。
該命令需要服務端返回響應Write Response。
寫長包請求
客戶端已知特徵值控制代碼,但待寫入資料長度過長,向服務端傳送Prepare Write Request,設定適當的偏移量,將資料傳送至服務端快取起來,資料傳送完畢後,項服務端傳送Execute Write Request執行寫請求。
待寫資料總長度不受限制,但是分步傳送資料每次資料長度不得超過(ATT_MTU-3)。
兩種請求均需要對應的服務端響應。
一個寫長包請求流程如下:
可靠的寫請求
客戶端已知特徵值控制代碼,希望一次性寫入多位元組的資料,或者要求資料的每個位元組都必須被安全寫入服務端裝置,向服務端傳送Prepare Write Request,偏移量永遠等於0,一次性只發送一個數據,帶多位元組資料快取完畢,再發送Execute Write Request執行寫請求。
具體的操作與“寫長包請求”完全一致。
3.8 通知
該功能包含一個子功能:通知。
服務端執行Handle Value Notification,引數為特徵值控制代碼和通知資料,向客戶端推送通知。
執行通知前,該特徵值需要已經使能通知,並且將通知資料寫入該特徵值。
該命令無需客戶端返回響應。
3.9 指示
該功能包含一個子功能:指示。
服務端執行Handle Value Indication,引數為特徵值控制代碼和指示資料,向客戶端推送指示。
執行指示前,該特徵值需要已經使能指示,並且將指示資料寫入該特徵值。
該命令需要客戶端返回響應Handle Value Confirmation。
3.10 讀寫描述符
該功能包含四個子功能:讀描述符,讀長包描述符,寫描述符,寫長包描述符。
讀描述符與讀特徵值一致。
讀長包描述符與讀長包特徵值一致。
寫描述符與寫請求一致。
寫長包描述符與寫長包請求一致。
4. 與L2CAP層互操作
GATT使用的L2CAP固定通道傳輸屬性資料。
GATT的PDU長度限制ATT_MTU預設大小為23,它與L2CAP層的MTU值保持一致。