文章轉載自:http://www.sunyouqun.com/2017/04/page/2/
通用屬性規范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值保持一致。
(完)
