はかせのラボ

私の頭の中を書いていく雑記ブログです

C# コンソールアプリでBluetooth通信を行う

あいさつ

お久しぶりです。はかせです。
この記事は、C# その2 Advent Calendar 2019 の22日目の記事となります。

今回はコンソールアプリでBluetooth通信をやってみます。
やることとしては特定のBluetooth端末と通信し、
その端末からセンサーの値なりなんなりを取得できるようにします。

前準備

開発はVS2017の最新版を使っています。
C#にはBluetooth周りのAPIがあるっちゃあるんですが、
どれもこれもUWP専用らしいです。

UWPでやっても良いんですが、
どうせなら慣れているコンソールアプリの方が扱いやすいです。
ということでNuGetからUWP専用APIを通常アプリでも使えるようにする
「UwpDesktop」というものを入れています。

下記の画像の所をクリックして
検索窓にUwpDesktopと打ち込めば出てきます。
f:id:hakase0274:20191222003721p:plain

Bluetooth通信のやり方

まずざっくりと流れです。
・周囲のBluetooth端末をスキャン
・スキャンした端末が通信したい端末かチェックし値の購読を始める
・あとは煮るなり焼くなりお好きにどうぞ

それぞれ説明していきます。

周囲のBluetooth端末をスキャン

Bluetoothってのは「アドバタイズパケット」っていう
「俺は…ここにいるッ!!」ということを伝えるためのデータを発信します。

そしてそのデータをスキャンすることでBluetooth端末を認識し
接続することができます。

ではコードを

private BluetoothLEAdvertisementWatcher advWatcher;

advWatcher = new BluetoothLEAdvertisementWatcher();
advWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(1000);
advWatcher.ScanningMode = BluetoothLEScanningMode.Passive;
advWatcher.Received += WatcherReceived;

advWatcher.Start();

やってることは
BluetoothLEAdvertisementWatcherに用意されているプロパティの値を
うまい感じに設定した後、
Start関数を呼んで実際にスキャンします。

スキャンした端末が通信したい端末かチェックし値の購読を始める

さてスキャンが出来たところで
次にそれが通信したい端末か調べます。

さっきのWatcherReceivedにスキャンされた端末の情報が流れてきます。
その情報をもとに判定します。

private GattDeviceService gattService;
private GattCharacteristic vroomService;

private async void WatcherReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
   CheckArgs(args);
}

private async void CheckArgs(BluetoothLEAdvertisementReceivedEventArgs args)
{
    bool find = false;

    var bleServiceUUIDs = args.Advertisement.ServiceUuids;
    foreach (var uuidone in bleServiceUUIDs)
    {
        //serviceGuid 端末のGuid
        if (uuidone == serviceGuid)
        {
            // 発見
            find = true;
            break;
        }
     }

     if (find)
     {
         try
         {
            // スキャンStop
            advWatcher.Stop();
            //接続
            BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
            //サービスUUIDを使って目的のサービスを取得
            gattService = device .GetGattService(serviceGuid);
            //キャラクタリスティックUUIDを使って目的のキャラクタリスティックを取得
            var characteristics = gattService.GetCharacteristics(new Guid("キャラクタリスティックのGuid"));
            //戻ってくるのが配列なので空じゃないか確認
            if (characteristics.Count > 0)
            {
                vroomService = characteristics.First();
                if (vroomService == null)
                {
                     throw new Exception("そんなサービスはねぇ!");
                }
                //読み取り可能ならばキャラクタリスティックにアクセスしコールバックを設定する
                if (vroomService.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read))
                {
                     vroomService.ValueChanged += CharacteristicChangedVroomController;
                     Console.WriteLine("Connect");
                     await vroomService.ReadClientCharacteristicConfigurationDescriptorAsync();
                }

            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception...{ex.Message})");
        }
    }
}

さてやってることは単純で
・スキャンした端末がもつサービスのUUIDと使いたいサービスのUUIDを比較する
・同じならスキャンを停止しその端末と接続
・接続した端末のサービスの中から目的のデータの読取を試みる

こんな感じです。

なんでUUIDなんかを調べるのかと言うと
それはBluetooth通信に使われている
GATTというものが絡んできます。
こいつはBluetoothで標準的に使われているデータフォーマットです。

中身は
「サービス」「キャラクタリスティック」
二つからできていて、
それぞれがUUIDで設定されています。

Windowsユーザーに分かりやすく説明すると、
サービス=フォルダ
キャラクタリスティック=ファイル

って感じです。

GATTに則っているBluetooth端末は必ずこの形でデータを発信するので、
UUIDでサービスを調べることで目的の端末かわかるわけですね。
ちなみに使いたいサービスのUUIDはどうやって知るのという話ですが、
販売元が公開している場合もありますし、
自分で全パケットを調べて判断するしかない場合もあります。
(私が使おうとしてたものは公開されていなかったので全パケット調べましたw
疲れた・・・)

そんなこんなで目的の端末と接続が出来たら
これ以上スキャンかける必要がないので
スキャンを止めます。

その後サービスのUUIDとキャラクタリスティックのUUIDを用いて
目的のデータにアクセスします。

アクセスに成功したらコールバックを設定し
値の購読を開始します。
CharacteristicChangedVroomControllerってのは自作関数で
流れてきたバイト列を好きに煮るやつです。

バイト列を使いやすいよう加工したり、
データをUDPで送信したりしてます。
ここはそれぞれのプロジェクト状況に応じて
好きな関数作って登録してください。

あとがき

今回はコンソールアプリでBluetooth通信を行ってみました。

今回の実装は難しくはなかったですけど、
とにかく目的のサービスUUIDを探すのが大変でしたね・・・
まぁ公式に公開されていないものを使おうとしているわけですから、
大変なのは当たり前なんですけど。
(むしろそういうの好んでやっているまである)

Bluetooth通信はかなり身近なものではありますが、
あまり自作のアプリとかに組み込んでみたとかの事例を聞きません。

Bluetooth通信使えばお手軽にIoTができるので
やってみてはいかがでしょうか?

実際にモノが動いたり
モノの動きがソフトに反映されたりするのって
存外楽しいですよ。

それでは今回はこの辺でノシ

参考

qiita.com