用樹莓派玩轉藍芽

Vamei發表於2017-04-29

作者:Vamei 出處:http://www.cnblogs.com/vamei 嚴禁轉載。

 

藍芽是一個使用廣泛的無線通訊協議,這兩年又隨著物聯網概念進一步推廣。我將介紹藍芽協議,特別是低功耗藍芽,並用樹莓派來實踐。樹莓派3中內建了藍芽模組。樹莓派通過UART介面和該模組通訊。樹莓派1和樹莓派2中沒有內建的藍芽模組,不過你可以通過USB安裝額外的藍芽介面卡。

 

藍芽介紹 

藍芽最初由愛立信創制,旨在實現可不同裝置之間的無線連線。藍芽無線通訊的頻率在2.4GHz附近,和WiFi一樣,都屬於特高頻。相對於低頻訊號來說,高頻傳輸的速度比較快,穿透能力強,但傳輸距離比較受限。在沒有遮蔽和干擾的情況下,藍芽裝置的最大通訊距離能達到30米。但在大多數情況下,藍芽的實際通訊距離在2到5米。相比之下,低頻433MHz裝置的通訊距離很容易超過百米。因此,藍芽常用於近距離的無線裝置,比如無線滑鼠和鍵盤。

藍芽的標誌 

 

藍芽的基本工作流程如下:

  1. 廣播/掃描:通訊的一方向外廣播自己的資訊。另一方通過掃描知道自己周邊有哪些藍芽裝置在廣播,這些裝置的地址是什麼,以及是否可以連線。
  2. 連線:通訊的一方向另一方發起連線請求。雙方通過一系列的資料交換建立連線。
  3. 資料通訊

根據細節上的差別,藍芽通訊又細分為兩種:經典藍芽和低功耗藍芽。早期的藍芽通訊方式稱為經典藍芽(classic bluetooth)。經典藍芽中的資料傳輸協議是序列模擬協議RFCOMM。RFCOMM模擬了常見的串列埠連線。資料從一端輸入,從另一端取出。經典藍芽的開發非常簡單。基於串列埠開發的有線鍵鼠程式,就可以直接用於RFCOMM連線的無線鍵鼠。此外,經典藍芽可以快速傳輸資料。因此,諾基亞N95這樣的早期智慧手機,也用RFCOMM來互傳圖片和檔案。

RFCOMM通訊

 

經典藍芽的缺點是比較耗電。後來,諾基亞發明了一種可以降低功耗的藍芽通訊方式。2010年出臺的藍芽4.0把這種通訊方式規範為“低功耗藍芽”(BLE,Bluetooth Low Energy)。BLE把通訊雙方分為非對稱的雙方,儘量讓其中的一方承擔主要的開銷,減少另一方的負擔。舉例來說,手環電量少,而且需要長時間待機。BLE通訊的主要負擔可以放在電量較充裕且充電方便的手機一側,從而減少手環的能耗。

手環作為外設

BLE通訊一般也包含廣播/掃描的步驟。主動發起廣播的裝置稱為外設(Peripheral),掃描裝置稱為中心裝置(Central)。BLE連線成功之後,就可以開始資料傳輸。BLE的資料傳輸協議是ATT和GATT協議。ATT是GATT的基礎。ATT協議把通訊雙方分為伺服器(server)和客戶(client)。客戶主動向伺服器發起讀寫操作。需要注意的是,ATT中的伺服器和客戶,與廣播階段的外設和中心裝置相互獨立。當然,在手環這樣的應用場景下,外設通常也是伺服器。ATT協議以屬性(attribute)為單位進行該資料傳輸。一個屬性的格式如下:

ATT屬性

我們分別來理解屬性的不同部分:

  1. handle:屬性的唯一編號,長度為16位。
  2. type:屬性的型別。每種型別用一個UUID編號。
  3. value:屬性的值。
  4. permission:屬性的許可權,分為無、可讀、可寫、可讀寫。

伺服器儲存了多個屬性。當客戶向伺服器請求時,伺服器會把自己的屬性列表發給客戶。隨後,客戶可以向伺服器讀取或寫入某一個屬性值。用讀寫的方式,通訊雙方實現了雙向通訊。

以智慧手錶為例。智慧手錶和手機配對後,手機可以用讀的方式獲得智慧手錶中某個屬性下儲存的步數,也可以用寫的方式寫入另一個屬性負責的時間。在讀寫操作中,都是由客戶採取主動,伺服器只能被動應答。ATT還提供了通知(notification)的工作方式。當伺服器改變了某個屬性值時,可以主動通知訂閱了該屬性值的客戶。智慧手錶中的手勢識別,就可以通過通知的方式告知手機。這樣的話,手機就可以實時地獲知手勢改變資訊。

GATT協議構建在ATT協議之上,為屬性提供了組織形式。GATT的最小組織單元是Characteristic,可以由數條屬性組成。下圖中就是一個Characteristic,用於傳輸紅外測溫獲得的資料。這個例子來自TI的SensorTag

 從左到右:handle(16進位制),handle(10進位制),type(16進位制),type(文字說明),value(16進位制),permission,備註

 

Characteristc的第一條屬性用於宣告屬性,其型別總是0x2803。這條宣告的value部分又可以細分為三部分。第一個部分是0x12,稱為Characteristic Properties,是GATT協議層面上的許可權控制。其具體含義可參考資料。第二部分0x0025,是Characteristic值的handle。找到handle為0x0025的屬性,就在宣告屬性的下面一行。0x0025的value部分就是紅外溫度的真正數值。剩下的部分是該Characteristic的UUID,總共128位:

F000-AA01-0451-4000-B000-000000000000

檢查Characteristic值的那一行屬性,也就是0x0025屬性。它的型別也是該Characteristic UUID。除了128位的UUID,藍芽官方還提供了16位的UUID可供使用,可參考資料

 

可以看到,一個Characterstic至少需要兩個屬性,一個用於宣告,一個用於儲存它的資料。除此之外,Characteristic還有稱為Descriptor的額外描述資訊。每個Decriptor佔據一行。比如0x0027這個Descriptor,其屬性值是54:65:6D:70:7E:20:44:61:74:61,翻譯成ASCII就是:

Temp~ Data

此外,溫度單位、測量頻率等描述資訊也經常會以Descriptor的形式放入到Characteristic中。在下一個Characteristic宣告出現前的屬性,都是該Characteristic的Descriptor。

 

我們再來看更高階的組織單位Service。一個Service也有行屬性作為宣告,其型別UUID是0x2800。宣告屬性的值就是該Service的128位UUID。藍芽官方也提供了16位的UUID,預留給特定的Service,可參考資料。在下一個Service宣告出現前的屬性,都屬於該Service,比如下圖中從0x0023到0x002D的屬性:

圖中包含了一個與紅外溫度計相關的Service。Service裡又有三個Characteristic,分別0x0024-0x0027、0x0028-0x002A、0x002B-0x002D。我已經介紹過第一個Characteristic。第二個Characteristic用於傳輸溫度計引數,第三個用於設定測溫頻率。

 

Service和Characteristic都是屬性的組織形式。客戶可以向伺服器請求Service和Characteristic列表,然後對其進行操作。GATT還提供了Profile,可以包括多個Service。不過,Profile並不像前面兩者那樣存在於伺服器。Profile是一種標準,用於說明一個特型裝置應該有哪些Service。比如說,HID(Human Interface Device)這種Profile,就說明了藍芽輸入裝置應該提供的Service。藍芽官方定義的Profile可參考資料

 

BlueZ

我們用樹莓派來深入實踐上面學到的藍芽知識。首先要在樹莓派上安裝必要的工具。BlueZ是Linux官方的藍芽協議棧。你可以通過BlueZ提供的介面,進行豐富的藍芽操作。Raspbian中已經安裝了BlueZ。我使用的版本是5.43。你可以檢查自己的BlueZ版本:

bluetoothd -v

低版本的BlueZ對低功耗藍芽的支援有限。如果你的使用版本低於5.43,那麼我建議你升級BlueZ。

 

你可以用下面的命令檢查BlueZ的執行狀態:

systemctl status bluetooth

我的返回結果是:

● bluetooth.service - Bluetooth service
   Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled)
   Active: active (running) since Sun 2017-04-23 19:03:08 CST; 1 day 6h ago
     Docs: man:bluetoothd(8)
 Main PID: 709 (bluetoothd)
   Status: "Running"
   CGroup: /system.slice/bluetooth.service
           └─709 /usr/lib/bluetooth/bluetoothd -C

可以看到,藍芽服務已經開啟,並在正常執行。

 

你可以用下面命令手動啟動或關閉藍芽服務:

sudo systemctl start bluetooth
sudo systemctl stop bluetooth

 

此外,你還可以讓藍芽服務隨系統啟動:

sudo systemctl enable bluetooth

 

瞭解樹莓派上的藍芽

在Raspbian中,基本的藍芽操作可以通過bluez中的bluetoothctl命令進行。該命令執行後,將進入到一個新的Shell。在這個shell中輸入:

 

list

 

將顯示樹莓派上可用的藍芽模組,例如:

Controller B8:27:EB:72:47:5E raspberrypi [default]

 

執行scan命令,開啟掃描:

scan on

掃描啟動後,用devices命令,可以列印掃描到藍芽裝置的MAC地址和名稱,例如:

Device 00:9E:C8:62:AF:55 MiBOX3
Device 4D:CE:7A:1D:B8:6A vamei

此外,你還可以用help命令獲得幫助。使用結束後,你可以用exit命令推出bluetoothctl。

 

 

除了bluetoothctl,在Raspbian是shell中可以通過hciconfig來控制藍芽模組。比如開關藍芽模組:

sudo hciconfig hci0 up   #啟動hci裝置
sudo hciconfig hci0 down #關閉hci裝置

命令中的hci0指的是0號HCI裝置,即樹莓派的藍芽介面卡。 

 

與此同時,你可以用下面命令來檢視藍芽裝置的工作日誌: 

hcidump

bluez本身還提供了連線和讀寫工具。但不同版本的bluez相關功能的差異比較大,而且使用起來不太方便,所以我下面使用Node.js的工具來實現相關功能。

 

樹莓派作為BLE外設 

下一步,我們嘗試用樹莓派進行BLE通訊。我們先把一個樹莓派改造成BLE外設,同時它也將充當連線建立後的伺服器。這個過程較為複雜。你可以借用Node.js下的bleno庫。首先,安裝Node.js: 

curl -sL https://deb.nodesource.com/setup_5.x | sudo bash -
sudo apt-get install nodejs

第一行的命令是為了確保安裝高版本的Node.js。

 

安裝bleno: 

mkdir ble-test-peripheral
cd ble-test-peripheral
npm install bleno

 

執行pizza的例子: 

sudo node node_modules/bleno/examples/pizza/peripheral

你可以在node_modules/bleno/examples/pizza/看到原始碼,或者到github檢視。這個例子提供了一個Service,它的UUID是1333-3333-3333-3333-3333-333333333337。Service中包含了三個Characteristics,分別是用於披薩餅引數、配料引數和烤披薩:

功能 許可權 UUID
披薩餅選項 讀/寫 13333333333333333333333333330001
配料 讀/寫 13333333333333333333333333330002
烤披薩 寫/通知 13333333333333333333333333330003

 

通過這些Characteristic,我們可以對樹莓派進行BLE讀寫。讀寫操作會作用於一個代表比薩的物件。披薩餅選項有:

數值 描述
0x00 正常
0x01
0x02

 

配料是一個8位的引數,每一位代表了一種配料。當這一位是1時,那麼說明新增該配料:

第n位 7 6 5 4 3 2 1 0
描述 SAUSAGE BELL_PEPPERS PINEAPPLE CANADIAN_BACON BLACK_OLIVES EXTRA_CHEESE MUSHROOMS PEPPERONI

 

因此,0x1A代表了新增MUSHROOMS、BLACK_OLIVES、CANADIAN_BACON,感覺味道還不錯。

 

對於烤披薩來說,寫操作設定了烘烤的溫度和時間。時間到了之後,中心裝置會發出通知,告訴客戶端烘烤完成。我們下一步將用另一個樹莓派作為BLE中心裝置。不過,即使你沒有額外的樹莓派,你可以用iPhone上LightBlue這樣的App來測試這一部分完成的BLE外設。

 

樹莓派作為BLE中心裝置

我們拿另一個作為BLE的中心裝置進行掃描,併發起連線請求。連線建立後,該伺服器將充當客戶。和bleno對應,Node.js下有一個叫noble的專案,可以便捷地完成這一任務。首先,安裝noble:

mkdir ble-test-central
cd ble-test-central
npm install noble

  

noble中有一個同樣名為pizza的例子,不過這個例子實現的是客戶端。執行該例子:

sudo node node_modules/noble/examples/pizza/peripheral

這個例子將自動執行掃描、連線、服務發現、資料傳輸的全過程。如果你把bleno和noble部署到兩個樹莓派上,就可以在這兩個樹莓派之間進行藍芽通訊了。如果你想自定義開發,那麼可以在node_modules/noble/examples/pizza/參考原始碼,或者到github檢視。

 

樹莓派作為Beacon

蘋果在BLE的基礎上推出了iBeacon協議。iBeacon使用了BLE的廣播部分,但不建立連線。一個遵守iBeacon協議的外設稱為Beacon。Beacon會廣播自己的身份資訊和發射訊號的強度。中心裝置接收到廣播之後,除了可以獲知Beacon的身份之外,還能通過訊號的衰減算出自己與Beacon的距離。在一個典型的超市應用場景中,每件商品可以帶上一個Beacon。消費者可以用手機看到自己周圍有哪些商品,工作人員也可以用手機來清點貨物。商家還可以在伺服器上提供商品相關的質保、促銷等資訊。使用者可以根據Beacon的編號,獲得這些附加資訊。

 

我們把配備了藍芽模組的樹莓派改造成一個Beacon。既然Beacon只使用了藍芽中的廣播,那麼應該關閉樹莓派的掃描,開啟廣播,並且不接受藍芽連線:

sudo hciconfig hci0 noscan    # 不再掃描
sudo hciconfig hci0 leadv 3   # 開始廣播,並且不接受連線

 

下一步,把廣播資訊改為符合iBeacon協議的內容:

sudo hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5 00 01 00 02 C5

上面的命令附加了一串16進位制資訊。其中0x08說明了整條資訊是藍芽命令,0x0008說明後面的內容將作為廣播資訊。

 

1E是廣播資訊開始的標誌。按照藍芽通訊的規定,廣播資訊最多有31個位元組。1E後面的廣播資訊分為兩組:

02 01 1A
1A FF 4C 00 02 15 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5 00 01 00 02 C5

每一組一開始的一個位元組說明了該組資訊的長度。02說明了2個位元組,1A說明是26個位元組。隨後一個位元組說明了改組資訊的型別。第一組的01說明了該組資訊是藍芽控制標誌,第二組的FF說明了該組是藍芽製造商相關資訊。

 

我們來看第二組資訊的細節:

  • 4C 00是製造商資訊,即蘋果。
  • 02 15是iBeacon協議標識。
  • 63 6F 3F 8F 64 91 4B EE 95 F7 D8 CC 64 A8 63 B5部分是裝置的UUID,通常是使用者編號。
  • UUID後面的00 01是主編號(Major)。
  • 再往後的00 02是次編號(Minor)。通過UUID、主編號、次編號的組合,我們可以唯一地確定iBeacon裝置。
  • 最後的C5說明了藍芽訊號強度,即在1米處測得的該Beacon的RSSI值。中心裝置把接收到的訊號強度和該訊號強度對比,就可以知道訊號衰減了多少,從而推算出自己與Beacon的距離。由於我這裡寫入的C5沒有經過校準,所以距離測量很可能不準確。

 

在iPhone上安裝應用Locate Beacon來測試。當我進入到樹莓派的廣播範圍時,該應用就會顯示出手機距離樹莓派的距離。

 

使用結束後,可以用下面命令來恢復掃描和停止廣播:

sudo hciconfig hci0 piscan   # 恢復掃描
sudo hciconfig hci0 noleadv  # 停止廣播

 

總結

這裡簡單介紹了藍芽協議,特別是低功耗藍芽。我以樹莓派的藍芽模組為基礎,實現了BLE通訊。

 

歡迎閱讀“騎著企鵝採樹莓”系列文章 

 

我的部落格即將搬運同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan

 

相關文章