低功耗藍牙 ATT/GATT/Service/Characteristic 規格解讀

 

  什麼是藍牙service和characteristic?如何理解藍牙profile? ATT和GATT兩者如何區分?什麼是attribute? attribute和characteristic的區別是什麼?藍牙的互聯互通為什麼能夠做的這麼成功?

 

本文除了闡述上述問題 ,並重點闡述藍牙協議棧中的ATT層和GATT層。

 

低功耗藍牙協議棧構架結構:

 

如圖所示,ATT和GATT是藍牙協議棧最重要的兩層,也是藍牙應用開發者打交道最多的兩層,用戶開發應用程式或者說service/profile的時候,調用的都是GATT  API,而GATT又調用ATT  API。在講解ATT 和  GATT之前 。我們先看一下藍牙核心規範中一個重要概念:Client/Server (客戶端/服務端)架構。

 

BLE  client/server(c/s)架構

  BLE採用了client/server (c/s)架構來進行數據交互,C/S架構是一種非常常見的架構,在我們身邊隨處可見,比如我們經常使用的瀏覽器和伺服器也是一種C/S架構 ,這其中瀏覽器都是客戶端client ,伺服器是服務端server,server比如淘寶伺服器,提供商品資訊,廣告,社交等服務,而瀏覽器就是客戶端,比如微軟的IE ,就可以用來請求這些服務,並使用server提供的服務。BLE與此相似,一般而言,設備提供服務,因此設備時server,手機使用設備提供的服務,因此手機就是client。比如藍牙體溫計,它可以提供 “體溫” 數據服務,因此是一個server,而手機則可以請求 “體溫” 數據以顯示在手機上,因此手機是一個 client.

服務是以數據為載體的,所以說server提供服務其實就是提供各種有價值的數據

             圖一: C/S架構

 

客戶端要訪問一個數據,就發送一個request/請求(其實就是一條命令或者PDU),服務端再把數據返回 給 客戶端(一條response/響應命令或者PDU),這就是 C/S架構。

 

 

ATT


 

  講解之前我們先講解attribute,那麼什麼是attribute?  其實就是一條條的數據。前面說過,每個藍牙設備就是來提供服務的,而服務就是眾多數據的集合,這個集合可以稱為資料庫,資料庫裡面每一個條目都是一個attribute。所以我們把attribute翻譯為數據條目。大家可以把藍牙設備 想像稱為一個表格,表格裡面每一行就是一個attribute。attribute可以用下圖來表示:

 

  • Attribute handle  ,Attribute句柄,16-bit長度。Client要訪問Server的Attribute都是通過這個 句柄來訪問,也就是說ATT  PUD一般都包含handle的值。用戶在軟體程式碼添加characteristic的時候,系統會自動按照順序的 為相關attribute生成句柄。
  • Attribute  type,Attribute類型 ,2位元組或者16位元組長。在BLE中我們使用UUID來定義數據的類型,UUID是128bit的,所以我們有足夠的UUID來表達萬事萬物 。其中有一個UUID非常特殊,他被藍牙聯盟官方UUID,這個UUID如下所示:

由於這個UUID眾所周知,藍牙 聯盟將自己定義的attribute或者數據只用16bit  UUID來表示,比如0x1234,其實他也是128bit,完整表示為:

 

Attribute  type一般是由service和characteristic規格來定義,站在藍牙協議棧角度來看,ATT層定義了一個通訊的基本框架,數據的基本結構,以及通訊的指令而GATT層就是定義serveice和characteristic,GATT層用來賦予每個數據一個具體的含義,讓數據變得有及結構和意義。換句話說,沒有GATT層 ,低功耗藍牙也可以通訊起來,但是會產生兼容性問題以及通訊效率低。

  • Attribute  value,就是數據真實的值,0到512位元組長。
  • Attribute  permissions,Attribute的許可權屬性,許可權屬性不會 直接 在 空口包中體現,在隱含 在ATT命令的操作結果中。假設一個attribute read屬性設為open(即讀操作不需要任何許可權),那麼client去讀這個attribute時server將直接返回attribute的值;如果這個attribute read屬性設為 authentication(即 需要配對才能訪問),如果client沒有與server配對而直接去訪問這個attribute,那麼server會返回一個錯誤碼:告訴client你的許可權不夠,此時client會對server發起配對請求,以滿足這個attribute的讀屬性要求,從而在第二次讀操作時 server將把相應的數據返回給client。目前主要有如下四種許可權屬性:
    • Open ,直接可以讀或者寫
    •  No  Access  ,禁止讀或者寫
    • Authentication ,需要 配對才能讀或者寫,由於配對有很多中類型,因此authentication又衍生多種子類型,比如帶不帶MITM,有沒有LESC
    •  Authorization,跟open一樣,不過server返回attribute的 值之前需要應用先授權,也就是說應用可以在回調函數裡面去修改讀或者寫的原始值。
    • Signed,簽名後才可以讀或者寫,這個用的比較少。

 

一個應用所有的 attribute組成一個database,也稱為attribute table,一個attribute  table示例如下 所示:

上圖:原始attribute資料庫(這個表格不能算是 原始attribute,因為它已經把bin數據 轉成字元了,大家可以 把相關字元都看成bin數據,就看成原始attribute表格)。

設備支援的服務不同,attribute  table就不同。這裡說明一下,當你添加,修改或者刪除服務時,那麼attribute table就會變,attribute table變了,它佔用的RAM空間就會變。

 

ATT,全稱attribute  protocol(數據交互協議)。說到底,ATT是由 一群ATT命令組成,就是上文 所述的request(請求)和response(響應)命令。ATT也是藍牙空口包中的最上層,也就是說,ATT就是大家對藍牙數據包進行分析的最多的地方。

 

ATT命令,正式稱謂ATT  PDU  (Protocol Data Unit,協議數據交互單元)包括4類:讀,寫,notify(通知)和 indicate(指示)。這些命令又可以分成兩種:如果它需要response,那麼會在相應命令後面加上request。相反,如果它只是需要ACK而不需要response,那麼它 的後面就不會帶request。這裡要特彆強調 一點,ATT所有命令都是 “必達” 的。也就是說每個命令發出去以後,就會立馬等ACK資訊,如果收到ACK包,發送方認為命令完成;否則發送方一直重發該命令到導致BLE連接斷開。換句話說,只要你的BLE連接沒有斷開,那麼 你之前發送的數據包,不管他是用什麼ATT  PDU來發送的,它肯定被對方收到了。

  有時候經常會出現一種情況,大家有時候會碰見  ”  丟包  “,其實不是在空中丟包或者被干擾了 ,而是我們 的 發送程式碼寫的有問題,導致包沒有被安全的送達到協議棧射頻 FIFO中,從而 出現所謂的 ” 丟包 “。

 

  既然每個ATT命令都必達對方,那麼還需要request類型的命令做什麼?

  如果一個命令帶有request後綴,那麼發起方就可以收到命令的response包,這個response包在應用層是有回調事件的,而前述的ACK包在應用層是沒有回調事件的。換句話說,不帶request的命令,雖然協議棧底層確保了該命令必達對方,但應用層其實並不知道(私有實現方法除外),當你需要實現一個通訊序列的時候,這種命令就顯得不足了。而採用request/response方式的命令時,request命令發出去之後,必須等到相應的response命令回復才能進行下一步操作,比如發送下一個request命令,這樣應用層可以嚴格按照規定邏輯執行一系列的操作,這個在很多應用場合是非常有用的。Request/response命令對還有一個副作用:大大降低通訊的有效速率(吞吐率)因為request/response命令必須在不同的連接間隔中出現,也就是說,你在間隔1中發送了一個request命令,那麼response包必須在間隔2或者稍後間隔中回復,而不能在間隔1中回復,這就導致一個數據包的發送需要跨兩個連接間隔甚至更多。而不帶request後綴的ATT命令就沒有這個限制,ACK可以在同一個連接間隔中回復,這樣一個連接間隔中可以同時發出多個數據包,這樣將大大提高通訊速率。大家可以參考下圖來理解request和非request命令的區別:

 

 

註:第1個連接間隔中的藍色包為request命令,旁邊的灰色包是該request的ACK;第2個連接間隔的綠色包是response包,而它的ACK是第3個連接間隔中的藍色包

 

 

註:圖中的綠色包就是非request命令,而緊隨其後的灰色包就是它的ACK

 

 

不帶request的命令只有2個:write command和notification,其餘的命令都是帶request:所有 read命令,所有write 命令,find命令以及indicate命令,完整的ATT命令(ATT PDU)列表如下所示:

 

 

GATT,Service(服務)和Characteristic(特徵數據)

在講解GATT之前,我們先看一下什麼是profile?

   Profile是一個大家經常見到的英文單詞,但是總感覺領會不到這個詞的內涵。Profile,英文本意就是 臉的側面輪廓,這裡大家注意一定要注意,臉的輪廓 不等於臉本身,但是profile本身是對臉的一種抽象,描述和定義,藍牙規範其實也是使用profile這個引申意義而已,換句話說 ,藍牙的profile跟英文字典中的profile是同一個意思。要定義藍牙,必須要有一個規範,這就是藍牙核心規範v4.2/v5.0/v5.1……藍牙規範非常複雜和龐大 ,大部分藍牙設備只實現了藍牙規範中很少一部分,那麼沒有實現的這些規範對這個藍牙設備來說 能不能稱為 規範?當然不能!所謂規範或者規格 ,就是強制的,就必須實現。針對這種情況,profile可以很好的應對。我們把藍牙某部分規範稱為profile,這個profile如果設備要實現它,那麼它就是強制的,如果設備不用它,也沒有關係,這就是profile。基於此,我們可以把profile翻譯成子規範或者條件規範或者剖面規範。「藍牙規範包含很多子規範」,這句話用中文說問題不是很大,但是你把它翻譯成英文,那就很難了!這就是 英文需要用profile的原因(而不是spec),  以及為什麼profile在規範中出現的如此頻繁。

    

GATT,全稱generic  attribute  profile,對數據進行一般化/抽象化的子規範,說白了就是對數據進行邏輯化表達的規定。前面說過了,attribute就是一條一條的數據,那麼這條數據表示什麼?如何對其進行分類?這就是GATT要做的事情,GATT將數據賦予含義,並呈現一定的邏輯結構

  Service和characteristic就是GATT層定義的,前面說過,server端提供 服務,服務就是數據,而數據就是一條一條的attribute,而service和characteristic就是數據的邏輯呈現,後者說用戶能看到的數據最終都轉化為 service和characteristic。比如,一個數據 「37」。有可能是說體溫 「37度」,也可能說心率 「37次」或者濕度 「37%」,因此必須對數據進行分類和定義。

  在藍牙規格中,每一個具體的藍牙應用是由多個service組成,而每一個service又是多個characteristic組成,這樣我們可以把上面的圖一轉化為圖3.

             圖三:service和characteristic

 那麼service/characteristic和attribute之間到底是一個怎樣的關係?如前所述,service/charateristic是attribute的邏輯表現形式,而attribute是service/characteristic具體實現方式。尤其要注意的是,一條characteristic不是對應一條attribute具體實現方式。尤其注意的是,一條chararcteristic不是對應一條attribute,而是由多條attribute組成。雖然一個數據最有價值的部分是它的值(value )。=,但是 僅有value是不夠的,比如27,到底是表示27°溫度還是27%濕度;如果表示的是溫度,那麼它的單位是攝氏度還是華氏度。同時每個數據還有相應的讀寫屬性以及許可權屬性,因此一個characteristic包含三種類型的數據條目(attribute):characteristic聲明體條目(declaration attribute),characteristic 值條目(value characteristic)以及characteristic描述符條目(descriptor attribute)  (一個characteristic可以有多個描述符條目),如下所示:

                               

  由於一個service可以包含多個characteristic,characteristic declaration就是每個characteristic的分界符,解析時一旦遇到characteristic  declaration,就可以認為接下來又是一個新的characteristic了,同時characteristic declaration還將包含value attribute的讀寫屬性等。Charateristic value就是數據的值了,他也是一個單獨的attribute,這個比較好理解就不再說了。Charactristic descriptor就是數據的額外資訊,比如 溫度的單位是什麼,數據是用小數表示還是百分比表示等之類的數據描述資訊。Descriptor屬於可選條目,也就是說,一個characteristic可以不包含任何一條descriptor。這裡著重提一種特殊的descriptor:CCCD。一般而言,都是client來訪問server的characteristic,即通過ATT讀或者寫PDU訪問相關數據。如果server想直接把自己的characteristic的值告訴client,就需要通過notiy或者indicate PDU,跟其他PDU相比,這兩個PUD是由server自己決定什麼時候開始傳送,而不是被動接收client的命令請求。但client畢竟是客戶,他得有自主權,所以引入了一個CCCD來幫助client控制server的行為。client可以通過禁止CCCD以允許notify或者 indicate命令,client可以通過禁止CCCD以允許notify或者indicate命令。重新總結一下。當CCCD使能的情況下,server可以隨時notify或者indicate數據 給client;當禁止的時候,哪怕server有數據,它也不能notify或者indicate給client。這裡強調一下。當characteristic具有notify或者indicate操作功能時,藍牙規範要求必須為其添加CCCD attribute.

  重複強調,不管是characteristic declaration,characteristic value還是characteristic descriptor,實現的時候,都是一條數據條目,即attribute.

  引入了GATT,我們就可以把圖2的 attribute table進行GATT化,得到下面有內涵,有層次,有定義的數據表格:

 

  所謂開發藍牙應用程式,其實就是開發service和characteristic。通過API,添加自己需要的characteristic和service,你自己的藍牙設備就誕生了。只要characteristic和service是符合GATT規範的,你可以隨意添加任何characteristic和service,並將他們組合成一個專門的藍牙設備。由於這個藍牙設備是按照規範來定義的,所以它可以與任何其他藍牙設備,比如手機,互聯互通,並完成所要求的的交互動作。這裡的藍牙設備,我們還可以進一步細分為藍牙profile設備和非profile藍牙設備。前面也提過,profile就是一個子規範,藍牙profile設備包含的所有實際service和characteristic都是按照profile規格來添加和定義的,比如說心率計profile,就是一個藍牙聯盟定義的藍牙設備,藍牙聯盟有一份專門的spec來定義心率計profile,在這份spec中規定了心率計profile除了包含心率service,還包含電池service,設備資訊service等。從這可以看出,心率profile和心率service是包含關係,前者包含後者。