54
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BlueZのAPI/サンプルコードのメモ

Last updated at Posted at 2018-12-12

この記事は Aptpod Advent Calendar 2018の13日目の記事です.

Aptpodで、エンベデッドエンジニアをしている @ffmatsuです.

弊社ではエッジデバイス(データ収集用の車載機/ラズパイ)の開発をしており、これらのエッジデバイスの動作状態を簡単に把握する仕組みのひとつとして、Bluetooth(BLE)を用いたiOS/Androidアプリとの通信機能も開発しています.

今回のAdvent Calendarでは、BlueZのサンプルコードを使ってBLE通信する方法を紹介しようと思ったのですが、そもそも BlueZのサンプルコードを紹介した記事が少なかったので、本記事では BlueZのAPIの概略やサンプルコードの解説をお送りしたいと思います.

What is BlueZ ?

Linuxの標準のBluetoothプロトコルスタックです.
ラズパイOSのRaspbianでもBlueZが使われているので、このBlueZを制御する事でラズパイ上でBluetoothの各機能(BLE通信など)を使う事ができます.

BlueZのソースコード

BlueZのソースコードは以下のリポジトリで公開されています.
https://git.kernel.org/pub/scm/bluetooth/bluez.git

今回は、最新のRaspbianでも採用されている、 v5.47をターゲットにお話します.
以下のページから実際にソースコードをダウンロードした上で記事を読んで頂けると理解しやすいかと思います.
http://www.bluez.org/release-of-bluez-5-47/

BlueZのフォルダ構成

BlueZのソースコードを見ると、以下のようなフォルダ構成になっています.

├── client
├── doc
├── emulator
├── gdbus
├── gobex
├── monitor
├── obexd
├── peripheral
├── plugins
├── profiles
├── src
├── test
├── test-driver
├── tools
└── unit
  and more ...

この中で、注目すべきフォルダを以下に示します.

  • doc
    • D-Bus InterfaceのAPI仕様を記載したテキスト
  • test
    • 動作確認用のPythonのサンプルコード
  • tools
    • hciconfigなどの、コマンドツールのソースコード
  • client
    • bluetoothctl のソースコード

BlueZのAPI仕様について

他のBlueZを使用したBLE機能の記事を見ると、 hciconfiggattoolsなどのコマンドツールを使った実例が多いのですが、BlueZの機能を十分に活用するのであれば、BlueZのAPIを使った方がよいかと思います.

BlueZのAPI仕様は、 doc/フォルダに展開されています.
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc?h=5.47

これらのAPI仕様ですが、D-Busのインターフェースで記載されています.

D-Bus

D-Busについての解説は、以下の記事がとても詳しいです.
https://www.silex.jp/library/blog/20170126-1

D-Bus制御のプログラミングは上述の記事通り、とても面倒くさいです.
ただし、Pythonに関しては割と直感的に記述でき、BlueZのサンプルコードには各機能毎のAPIを実際に動かす事ができるPythonのコードがあるので、これらのサンプルコードを使ってAPIの実動作を確認しながらプログラミングしていくと幸せになれます.

docフォルダの内容

doc/フォルダには、BlueZのD-Bus APIの仕様が記載されたドキュメントが格納されています.
以下に各Profileに関連するファイルの要約を示します.
各Profileの詳細は、実際にファイルを参照してください.

共通

BLE/ Classic 両方で使用するAPIです

file 概略
adapter-api.txt 自デバイス(Adapter)を制御するAPI
agent-api.txt ペアリング処理のAPI
device-api.txt 接続相手のデバイスを制御するAPI

BLE関連

BLEのGATT/Advertisingの制御や、BLE専用のProfile/Serviceを実装したAPIです.

file 概略
advertising-api.txt Advertising関連のAPI
gatt-api.txt GATT Prfileの設定API
alert-api.txt Phone Alert Status (PASP)のAPI
cyclingspeed-api.txt Cycling Speed and Cadence のAPI
health-api.txt HealthのAPI
heartrate-api.txt Heart RateのAPI
proximity-api.txt Proximity のAPI
thermometer-api.txt Health Thermometer のAPI

Classic関連

Classic (Bluetooth BR/EDR)関連のProfileを制御するAPIです.

file 概略
input-api.txt HID ProrileのAPI
media-api.txt A2DP/AVRCPを抽象化したAPI
network-api.txt PANなどのNetwork 関連のAPI
obex-agent-api.txt PBAPなどでも使われるOBEX protocolのAPI
obex-api.txt OBEX全体を制御するAPI
profile-api.txt SPPなど、RFCOMMをProfileとして登録するAPI
sap-api.txt SIM Access ProfileのAPI

ちなみに、Hands Free Profile (HFP)関連のAPIは
ofonoを使う事になります.

testフォルダの内容

test/フォルダ内には、上述のD-Bus APIをpythonから呼び出すサンプルコードが展開されています.
これらのコードを実際に実行すると、各APIの簡単な動作を確認する事が可能になります.

以下に、各サンプルコードの概要を記載します

共通

file                                  概略
bluezutils.py 他のpythonコードからimportされるutil群
dbusdef.py bluezutils.py内のfind_adapter()を試しに呼ぶスクリプト.
BlueZが認識しているBluetoothアダプタ(hci*)を一覧表示する
test-adapter Adapter インターフェースの各Property (デバイス名やアドレス)を取得/表示するスクリプト
monitor-bluetooth Adapter, Deviceのinterfaceから発行される
- PropertyChanged
- Added
- Removed
の各シグナルをシグナルハンドラに登録し、メッセージ内容を表示するスクリプト
test-manager org.freedesktop.DBus.ObjectManager(D-Busの共通インターフェース)の
- Added
- Removed
を表示する
test-discovery Adapterに対して StartDiscovery()を要求し、周辺に存在する相手デバイスを探索する
list-devices Adapterのinterfaceに対して GetManagedObject()を要求し、現在、BlueZが認識している相手デバイス(ペアリング済み、発見済み)の一覧を表示する
simple-agent ペアリング処理を代行するAgentをD-Busに登録し、ペアリング処理(Numeric Comparisonなど)を実行するスクリプト
test-device 相手デバイス(ペアリング済み、発見済み)に対して、接続要求や削除、リスト取得などのAPIを要求する

BLE (Low Energy向け)

BLE向けのAPIのテストコードです.
後半のtest-*で始まる、Profile毎のテストコードの詳細は省略します.
(GATT Profileの一覧はこちら)

file                                              概略
example-advertisement BLEのAdvertisingのサンプル. v.5.43ではExperimental扱いなので、そのままでは動きません.
example-gatt-client 接続中のデバイス(GATT Server)からHeart Rate Serviceを受信するClientを起動
example-gatt-server 擬似的にGATT Server を立ち上げる
test-gatt-profile GATT Profile APIのテスト. Gatt Clientを登録する事で、自動接続などの機能が提供される
test-cyclingspeed Cycling Speed and Cadenceのテスト
test-health Healthのテスト
test-heartrate Heart Rateのテスト
test-proximity Proximity Profileのテスト
test-thermometer Health Thermometerのテスト
test-alert Phone Alert Statusのテスト

Classic向け

こちらは従来型と呼ばれるProfileのサンプル/テストコードです.
需要も少なそうなので、さらっと紹介します。

(Classic Profileの一覧はこちら)

file                                概略
test-profile シリアル通信プロファイル(SSP)やHands Free Profile(HFP)などで使われる共通のProtocolであるRFCOMM.
これをProfileという概念で制御しています. このテストでは引数でパラメータを指定して、Profileを登録します.
(そのあと、Profileに対して NewConnection()を要求すれば接続されるはず)
pbap-client PhoneBook Access Profile (PBAP)のOBEX Clientを接続するサンプルです. その後、対話方式で電話帳のindexやサイズを選択すると、接続中の携帯電話の電話帳情報(vCard)を表示できます.
map-client Message Access Profile (MAP)のClientサンプル. こちらも中身は OBEXのClientを接続します.
ftp-client File Transfer Profile(FTP)のClientサンプル
opp-client Object Push Profile (OPP)の Clientサンプルです. 古い仕様なので省略
sap_client.py SIM Access Profileです. かなり古いプロファイルですね
simple-endpoint 電話音声(HFP)やストリーミング転送(A2DP)の音声出力先も制御する MediaのAPIのサンプル
simple-player 上述のMediaを制御するMediaPlayerのサンプル. このサンプルを使って、ラズパイでBluetoothスピーカにする記事がありますね
test-hfp Profileを使ってHFPのRFCOMMを接続し、ATコマンドで制御するサンプルです.
test-network 接続中のデバイスに対して Personal Area Network(PAN)などの接続を要求するテスト. Network APIのConnect()を実行しています.
test-nap こちらはPANなどのServer側のテスト. NetworkServer APIの Register()を実行しています.

サンプルコードを実際に動かしてみる

実際に、test/フォルダ以下の各pythonのコードを動かしてみましょう

list-devices

testフォルダのlist-devicesを覗いてみると、以下の実装を確認できます

manager = dbus.Interface(bus.get_object("org.bluez", "/"),
					"org.freedesktop.DBus.ObjectManager")

...

objects = manager.GetManagedObjects()

D-Busのサービス org.bluezで管理しているObjectsから、デバイス情報を出力するコードだとわかります。

実際に動かしてみます.

$ ./list-devices
[ /org/bluez/hci0 ]
    Name = ubuntu
    Powered = 1
    Modalias = usb:v1D6Bp0246d052B
    DiscoverableTimeout = 0
    Alias = ubuntu
    PairableTimeout = 0
    Discoverable = 0
    Address = 00:XX:XX:XX:XX:XX
    Discovering = 0
    Pairable = 1
    Class = 786700
    UUIDs = 0x1112 0x1801 0x110e 0x1800 0x1200 0x110c 0x110a 0x110b
    [ /org/bluez/hci0/dev_XX_XX_XX_XX_XX_EC ]
        Name = M720 Triathlon
        Paired = 1
        Modalias = usb:v046DpB015d0007
        Adapter = /org/bluez/hci0
        ServicesResolved = 1
        Appearance = 962
        LegacyPairing = 0
        Alias = M720 Triathlon
        Connected = 1
        UUIDs = 0x1800 0x1801 0x180a 0x180f 0x1812 00010000-0000-1000-8000-011f2000046d
        Address = XX:XX:XX:XX:XX:EC
        Blocked = 0
        Trusted = 1
        Icon = input-mouse
    [ /org/bluez/hci0/dev_XX_XX_XX_XX_XX_26 ]
    ...

bluezの getManagedObjects()の結果を表示できました.筆者の環境では、前半が自デバイス(ubuntu)、後半がペアリング済みデバイス(M720)の情報になりました.

どのようにPythonからD-Busを呼び出しているのか?を詳細に説明するのは難しいのですが、ここではPythonのサンプルコードでBlueZを制御できている. という事を実感して頂ければと思います.
(ちなみに、dbus-pythonのチュートリアルはこちらを参照してください.)

monitor-bluetooth

test/フォルダ以下のmonitor-bluetoothは、BlueZのD-Busのやり取りを出力するテストコードです.

試しに、周辺のBluetooth機器を探索して、D-Busの受信を確認してみます.
ターミナルを2個開いて、一つのターミナルで monitor-bluetoothを実行します.
次に、別のターミナルで、bluetoothctlを使ってscan onを実行します.

$ bluetoothctl
[HHKB-BT]# scan on
Discovery started
[CHG] Controller 00:E1:8C:23:60:C1 Discovering: yes
[NEW] Device XX:8A:9F:9D:AB:EE XX-8A-9F-9D-AB-EE
[NEW] Device 4F:XX:9D:85:59:2D 4F-XX-9D-85-59-2D
[NEW] Device 66:15:XX:01:FB:FD 66-15-XX-01-FB-FD
[NEW] Device 46:E8:80:XX:20:5A 46-E8-80-XX-20-5A
[NEW] Device 56:DF:07:F3:XX:F0 56-DF-07-F3-XX-F0
[NEW] Device 8C:85:90:77:1A:XX 8C-85-90-77-1A-XX
[NEW] Device XX:43:62:9D:FE:AA XX-43-62-9D-FE-AA
[NEW] Device 4D:XX:14:F0:4B:78 4D-XX-14-F0-4B-78

すると、monitor-bluetoothを実行しているターミナル側に、
BlueZに関するD-Busのメッセージの内容が出力されてきます.

$ ./monitor-bluetooth
{Adapter1.PropertyChanged} [/org/bluez/hci0] Discovering = 1
{Added org.bluez.Device1} [/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX]
      Paired = 0
      ServicesResolved = 0
      Adapter = /org/bluez/hci0
      LegacyPairing = 0
      Alias = XX_XX_XX_XX_XX_XX
      Connected = 0
      UUIDs = dbus.Array([], signature=dbus.Signature('s'), variant_level=1)
      Address = XX:XX:XX:XX:XX:XX
      RSSI = -65
      Trusted = 0
      Blocked = 0
{Added org.bluez.Device1} [/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX]
      Paired = 0
      ServicesResolved = 0
      Adapter = /org/bluez/hci0
      LegacyPairing = 0
      Alias = XX-XX-XX-XX-XX-XX
...
...

実際に通信してみると、BlueZがどのようなメッセージ(Signal)をD-Bus上で通知してくるのかがわかるので、使えそうなメッセージがあれば、signalハンドラをdbus-pythonに登録して検出する事ができます.
また、デバッグ用途でも使えるかも.

example-gatt-server

今回、一番の目的だった、BLEのGATT通信が動作するサンプルコードです.
このサンプルコードは、GATT Serverが動作します.

コードを覗いて見ると、以下のGATT Serviceを立ち上げています.

  • Battery Service (UUID=0x1801)
  • Heart Rate (UUID=0x180D)
  • Test Service (UUID=12345678-1234-5678-1234-56789abcdef0)

実際にBattery ServiceやHeart Rate Serviceは実装しておらず、あくまでもエミュレートしたものになっています.
例えばHeart Rateはランダムな値を通知しますし、Battery Serviceは5秒毎に値が減っていき、その変化を通知する実装になっています.


    GObject.timeout_add(5000, self.drain_battery)

    def drain_battery(self):
        if self.battery_lvl > 0:
            self.battery_lvl -= 2
            if self.battery_lvl < 0:
                self.battery_lvl = 0
        print('Battery Level drained: ' + repr(self.battery_lvl))
        self.notify_battery_level()
        return True

では、本当にBLEの通信ができるか試してみましょう.

サンプルプログラムを、以下の通りターミナルで実行します.

$ ./example-gatt-server
Registering GATT application...
GetManagedObjects
GATT application registered
Battery Level drained: 98
Battery Level drained: 96
Battery Level drained: 94
Battery Level drained: 92
Battery Level drained: 90

Battery Levelが徐々に減ってきていますね.

早速BLEを接続したいのですが、GATT Serverを起動しただけでは、Client側から発見できません.なので、Advertisingする事で周囲のデバイスに対して自分の存在を通知します.
別のターミナルを立ち上げて、以下のコマンドを実行してAdvertisingを開始します.

sudo hciconfig hci0 leadv

この状態で、スマートフォンのBLEテストアプリから、GATT Serverに接続します.
確認には、以下のアプリを使用しました。

実際に接続した時のスクショです。

Battery LevelやHeart Rateが取れていますね!

最後に

誤記/追記情報がありましたらご指摘頂けるとうれしいです.
次回は、実際にこのサンプルコードを使って、ラズパイに接続した温度センサーの値を通知するGATT Serverを構築できればと思います.

54
44
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?