Qiita初投稿です。独学のため用語や説明が間違っているかもしれませんが、ググっても初心者の私が理解できる情報が少なくて悲しくなったので記事にしていこうと思います。
はじめに
LinuxでBluetoothを使用する開発をする上で、Bluetoothスタックの選択肢はほぼBlueZ一択なのが現状です。しかし最近のBlueZはhcitool等のツール群が廃止され、少し難易度の高いbluetoothctlしか提供されていないようです。使いづらかったのか、現在でもディストリビューションによってはhcitool等が配布されていることもあるようです。
私も最初はbluetoothctlで動かしていましたが、動作確認以上のことをしようとすると途端に何もわからなくなってしまったため、BlueZがD-busで提供しているAPIを叩いて自分で開発することにしました。
BlueR
BlueZ公式クレートとしてGitHubに公開されています。
BlueRからBluetoothモジュールまでの流れです。カーネルは登場してないですがBlueZはLinuxカーネルにも含まれており、実装したい機能によってはカーネルコンフィグを触って有効にする必要が出てきます。
Bluetoothモジュール
| USB接続とかUART接続とか
デバイスドライバ
| hciインターフェース
BlueZ
| D-bus通信
BlueR
D-Bus通信から先を実装する方法は何を使用してもよかったのですが、Rustに興味があったのとサンプルコードが良さそうに見えたこと、BlueZ公式の提供という安心感があったので選択しました。参考までにD-Bus通信の実装方法を比較した感想を書いておきます。
dbus-send
コマンドラインツール。動作確認ぐらいはできそう。
dbus-send --print-reply --system --dest=org.bluez /org/bluez/hci0 --type=method_call org.freedesktop.DBus.Properties.GetAll string:org.bluez.Adapter1
C/C++
D-Bus通信の利便性が弱く、PythonやRustでは隠蔽できる部分まで実装する必要がありそう。仮に選んだ場合はsdbus-cppを使用すると良さそう。
sdbus-cppを使用した例
https://github.com/weareaudiofile/bluez-dbus-cpp
Python
検索すると一番情報が出てくる。d-busを使用する例はBlueZのexampleに入っているが、私の環境では動かなかった。bluezeroというライブラリが良さそう。
ビルド環境の作成
今回のビルド環境とターゲット環境です。
ビルド環境:Debian 11 (amd64)
ターゲット環境:Debian 11 (aarch64)
Debian 11で揃える必要はありませんが、glibcのバージョン問題があるので揃えたほうが楽です。基本的にはターゲット環境と同じか古い環境にしておけば大丈夫のはずです。私は古いPCが余っていたので直接Debianをインストールしていますが、仮想環境で構築してもよかったと思います。
また、Rustのクロスコンパイルで検索するとCrossを使用する方法が出てきますが、Crossは用意してくれているDockerのイメージをダウンロードしてビルドに使用します。BlueRのビルドにはsysrootにD-busのライブラリを入れる必要があるので、ビルド用のイメージを編集、もしくは新しく用意する必要があります(私が手段を知らないだけかも)。Dockerイメージを編集するより手元の環境で作ってしまったほうが早そうなので、Cargoでクロスコンパイルしています。
コンパイラ等のインストール
ビルド環境に導入します。
sudo apt install -y build-essential g++-aarch64-linux-gnu
Rust環境のインストール
ビルド環境にRustをインストールします。公式のスクリプトを使用することをお勧めします。aptで入れたら後で詰みました。途中に出てくる選択肢は"1"のdefaultを選んでます。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
Cargoのコンフィグ編集
configファイルを新規作成してください。
Proxy環境の人はコメントアウトしているproxy設定を使用してください。
#[http]
#proxy = "http://(ip addr):(port)"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
Rust環境にaarch64環境を追加
rustupを使用してインストールします。おそらく要インターネット接続。
rustup target add aarch64-unknown-linux-gnu
Hello Worldで動作確認
cargoで新規プロジェクトを作成すると初期状態でHello Worldが動くようになっているので、これをクロスコンパイルしてターゲット環境で実行してみます。
cargo new --bin hello
cd hello
cargo build --target=aarch64-unknown-linux-gnu
ビルドされた実行ファイルは./target/aarch64-unknown-linux-gnu/debug/helloに配置されているはずです。
参考まで、---releaseオプションを付けるとリリース用のビルドになり、./target/aarch64-unknown-linux-gnu/release/helloに配置されます。
実行ファイルをターゲット環境に転送したら、実行権限を付けて動かしてみましょう。
chmod +x hello
./hello
Hello, Worldが表示されたら成功です。
BlueRのクロスコンパイル
それではBlueRを持ってきて、同梱のサンプルアプリをクロスコンパイルします。
まず、依存ライブラリをsysrootに追加します。
sudo dpkg --add-architecture arm64
sudo apt update
sudo apt install libdbus-1-dev:arm64
これで/usr/aarch64-linux-gnuにd-busのライブラリがダウンロードされます。
次にBlueRのダウンロードは公式の指示に従って、gitに--recursiveオプションを付けてダウンロードします。
git clone --recursive https://github.com/bluez/bluer.git
ダウンロードが終わったら、bluerフォルダに移動して、cargoでビルドしていきます。まず環境変数にsysrootの設定を行います。cargoのコマンドは--targetでターゲット環境を指定してクロスコンパイル、--all-featuresはBlueRの指示、--exampleでビルドするサンプルアプリを指定しています。今回ビルドするle_advertiseはBluetooth Low Energyのビーコンを送信するアプリです。送信しているビーコンはスマートフォンのアプリ等で確認できます。
export PKG_CONFIG_PATH=/usr/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu
cargo build --target=aarch64-unknown-linux-gnu --all-features --example le_advertise
ビルドされたサンプルアプリの配置先は./target/aarch64-unknown-linux-gnu/debug/examples/le_advertiseです。Hellow Worldの時と同じように転送して実行権限を付与して実行してみましょう。
chmod +x le_advertise
./le_advertise
実行に成功すると下記のようなログが出てBluetoothビーコンが送信されます。XX:XX:XX:XX:XX:XXにはBluetoothモジュールのMACアドレスが入ります。サンプルアプリはEnterを押して実行を終了するとビーコンの送信も止まります。ビーコンには、固定のUUIDと"le_advertise"というデバイス名のデータが入るようです。
Advertising on Bluetooth adapter hci0 with address XX:XX:XX:XX:XX:XX
Advertisement { advertisement_type: Peripheral, service_uuids: {123e4567-e89b-12d3-a456-426614174000}, manufacturer_data: {}, solicit_uuids: {}, service_data: {}, advertising_data: {}, discoverable: Some(true), discoverable_timeout: None, system_includes: {}, local_name: Some("le_advertise"), appearance: None, duration: None, timeout: None, secondary_channel: None, min_interval: None, max_interval: None, tx_power: None, _non_exhaustive: () }
Press enter to quit
スマートフォンで確認する場合
スマートフォンでビーコンを簡単に確認したいときはnRFのツールとかルネサスのGATT Browserとかのアプリが使えるはずです。
2回目とか何回か実行していると、ビーコンの挙動がおかしくなることがあるようです。その際はbluetoothctl等からアダプタを手動でpower offしてから再度実行するとうまくいきます。
bluetoothctl
[bluetooth]# power off
Changing power off succeeded
[bluetooth]# exit
以上でBlueRのサンプルアプリをクロスコンパイルするとこまで終了です。
次の記事はle_advertiseをもとに好きなビーコンを出せるアプリを作成してみたいと思います。
おわりに
ググった情報をかき集めながら、何とかサンプルアプリをビルドするのに1週間もかかりました。
Rustを初めて触りましたが、CやPythonよりも一度動けばすんなり動いてくれる印象です。また、ビルドエラーのメッセージが情報満載で感動しました。
BlueZはとにかくとっつきにくくて、公式のドキュメントは少なく、わからなかったらコードを読めの文化のようです。ただ公式がBlueRという使いやすいものを提供し始めたので、今後はもっとBluetooth開発が流行るといいな。
記事書くのって疲れるね。Markdownでこんなに書いたのも初めてかも。プログラムの作成を進めてますが、Rustが想像よりも良かったので本を買って勉強中です。
参考記事