Texas InstrumentsのSensorTag (CC2650STK) という様々なセンサーを持ったBluetooth LEの開発者向けデバイスを結構前ですが入手したので、それをUniversal Windows PlatformのAPI(アプリとは言っていない)を使ってコンソールアプリケーションから読み取ってみました。
SensorTagの詳細はTIのサイトにあるのですが、「光、デジタル・マイク、磁気センサ、湿度、圧力、加速度計、ジャイロスコープ、磁力計、物体の温度、周囲温度の検出が可能な 10 個のセンサ」を持ち、コイン電池で動作し、Bluetooth Low Energyでホストと通信できる小さなデバイスです。そしてこれだけ詰まって4,000円程度と比較的安価なのも特徴です。
マクニカオンラインストア やTIのサイト から購入することができ、なによりちゃんと技適を通っているので合法です。ごうほうです!ご・う・ほ・う!
そんなステキデバイスをコンソールアプリケーション(というかLINQPad)から使ってみたいなーと思ったので、Universal Windows Platform APIを使ってアクセスしてみました。
まずはじめに あらかじめWindowsの設定画面からペアリングをしておきます。というのもペアリングする処理を入れるのはとても面倒というかそもそもUWPアプリ以外からはできないのです。
参考: アドバタイズしているBluetooth LEデバイスをUniversal Windows Platform APIでペアリングしたい
GATTサービス/キャラクタリスティックを取得する それではデバイスからデータをとるためのコードを書いていきます。
まずはWindowsが認識しているデバイスの一覧から利用したいセンサーのGATTサービスを探してくる必要があります。センサーのGATTサービスのUUIDはTIのWiki とPDF にあります。
今回は湿度センサーを使ってみるのでそのUUIDをセンサーのドキュメントから調べておきます。
var uuidHumidityService = new Guid("f000aa20-0451-4000-b000-000000000000" );var uuidHumidityData = new Guid("f000aa21-0451-4000-b000-000000000000" );var uuidHumidityConfig = new Guid("f000aa22-0451-4000-b000-000000000000" );var uuidHumidityPeriod = new Guid("f000aa23-0451-4000-b000-000000000000" );
UUIDが分かったら、デバイス一覧から対応するデバイスを探して、さらにGATTサービスと設定用、データ通知用のキャラクタリスティックを取得します。
var gattHumidityServices = await DeviceInformation.FindAllAsync( GattDeviceService.GetDeviceSelectorFromUuid(uuidHumidityService), null );var gattHumidityService = await GattDeviceService.FromIdAsync(gattHumidityServices[0 ].Id);var charHumidityData = gattHumidityService.GetCharacteristics(uuidHumidityData)[0 ];var charHumidityConfig = gattHumidityService.GetCharacteristics(uuidHumidityConfig)[0 ];
データを受け取る キャラクタリスティックを取得したら、データを取得するためにデータが流れてくるキャラクタリスティックにイベントハンドラーを設定します。
charHumidityData.ValueChanged += (sender, eventArgs) => { var data = eventArgs.CharacteristicValue.ToArray(); var temperature = (BitConverter.ToUInt16(data, 0 ) / 65536 d) * 165 - 40 ; var humidity = (BitConverter.ToUInt16(data, 2 ) / 65536 d) * 100 ; Console.WriteLine($"Temperature={temperature:0.0 } ℃, Humidity={humidity:0.0 } %" ); };
イベントハンドラーではデータのバイト列を取得できるのでそれをセンサーごとの処理方法に従って処理して値を取り出します。例えば湿度センサーの場合にはunsigned int16 が二つで、片方が温度、片方が湿度の元データとなります。
受信の準備ができたので最後は通知を有効にします。一つはCC2650の設定に値を書き込み、もう一つは通知を有効にする WriteClientCharacteristicConfigurationDescriptorAsync メソッドの呼び出しです。
await charHumidityConfig.WriteValueAsync(new byte [] { 1 });await charHumidityData.WriteClientCharacteristicConfigurationDescriptorAsync( GattClientCharacteristicConfigurationDescriptorValue.Notify);
これで実行するとデータが流れてくるのを確認できたら出来上がりです。おめでとうございます。
ちなみにペアリングした後何もしていないとSensorTagの電源が落ちるのでボタンを押してあげてください。
というわけで以上のC#(LINQPad用)のコードの全体 はGistに置いてあります。
SensorTag as Observable ところでセンサーのデータはある一定の間隔で送られてValueChangedイベントハンドラーに流れてきます。 データが流れてきて処理するといえばみなさん大好きなReactive Extensions、いわゆるRxの出番です。
というわけでRxの形にしてみましょう…といっても ValueChanged イベントハンドラーがあるので Observable.FromEventPattern メソッドを使えば簡単です。
var humidityObservable = Observable.FromEventPattern<TypedEventHandler<GattCharacteristic, GattValueChangedEventArgs>, GattValueChangedEventArgs>( h => charHumidityData.ValueChanged += h, h => charHumidityData.ValueChanged -= h) .Select(x => { var data = x.EventArgs.CharacteristicValue.ToArray(); var temperature = (BitConverter.ToUInt16(data, 0 ) / 65536 d) * 165 - 40 ; var humidity = (BitConverter.ToUInt16(data, 2 ) / 65536 d) * 100 ; return new { Temperature = temperature, Humidity = humidity }; }) .Publish() .RefCount();
要するにイベントハンドラーの代わりに FromEventPattern でObservableにして、Select オペレーターでデータを処理して整えた形のストリームに変換します。
Observableになったらあとは好きなオペレーターを組み合わせて、Subscribe するだけです。
humidityObservable .Buffer(TimeSpan.FromSeconds(10 )) .Select(x => x.Average(y => y.Humidity)) .DistinctUntilChanged(x => Math.Round(x, 1 )) .Subscribe(x => { Console.WriteLine($"最近の平均湿度 {x:0.0 } %" ); });
上記の例は湿度の平均を出力する例ですが、これをイベントハンドラーで実装しようとするとすっきりしないというのが想像できるかと思います。
var sumHumidity = 0 ;var lastAvgHumidity = 0 ;var count = 0 ; charHumidityData.ValueChanged += (sender, eventArgs) => { var data = eventArgs.CharacteristicValue.ToArray(); var temperature = (BitConverter.ToUInt16(data, 0 ) / 65536 d) * 165 - 40 ; var humidity = (BitConverter.ToUInt16(data, 2 ) / 65536 d) * 100 ; sumHumidity += humidity; if (++count == 5 ) { var avgHumidity = sumHumidity / 5 ; if (avgHumidity != lastAvgHumidity) { lastAvgHumidity = avgHumidity; sumHumidity = 0 ; count = 0 ; Console.WriteLine($"最近の平均湿度 {avgHumidity:0.0 } %" ); } } };
このあたりデータが流れてくるセンサーというものの特性とRxの相性の良さを感じますね。センサーも一つではないですし、それ以外の外部のイベントもあるのでこういうものを組み合わせるときには実に強力です。
RxでSensorTagのセンサーのデータを一通り受信できるようにする雑なクラス も同じくあげてありますので参考になるかもしれません。 通知間隔を変更するといったものは実装してませんのでそのあたりも試してみると良さそうです。
Windows 10以降にはBluetooth LEを扱うAPIが備わっていて、そのAPIを使うことでアドバタイジングパケットを送受信したり、ペアリングしたりできます。
そんなわけでBLEデバイスをペアリングしたいと思って試してみたのですが意外と罠が多いです。
ペアリングの流れ UWP APIを利用してペアリングして使う流れは次のようになっています。
BluetoothLEAdvertisementWatcher クラスでアドバタイジングしているデバイスを探す
見つけたらUIスレッド内でペアリングを実行する
BluetoothLEDevice.FromBluetoothAddressAsync メソッドでBLEデバイスを取得する
取得したオブジェクトの DeviceInformation.Pairing.PairAsync メソッドでペアリング実行
DeviceWatcher クラスで追加/更新されたデバイスを監視
GATTサービスを取得する
まずUIスレッド内でペアリングを実行する必要がある、という点に注意が必要です。
次にペアリングが完了してもすぐ使えると思ったらそんなことはなく、接続完了するまで待たなければなりません。ペアリングを待って(await)も接続を待ったわけではないので今度はDeviceWatcherでWindows側の準備が完了をするのを待ちます。めんどくさい…。 ちなみにBluetoothConnectionStatusは利用可能な接続状態とは違うようです。
ここからはUIにデバイスリストを表示してそこにデバイスを表示して選択するのとおおよそ同じだと思います。
デバイスを監視しているとペアリング後になにやらいろいろ追加されます。主にGATTのサービスなどがデバイスとしてAdded/Updatedイベントにやってきます。 なおデバイスがインストールされると途中で名前が変更される可能性があるのでAddedとUpdated両方見た方がよさそうです。
しかしやってきたデバイスIDは BluetoothLEDevice や GattDeviceService として開けるかどうかは開いてみないとわかりません。なんというめんどくささ…。実際StackOverflowにもtry/catchしとき みたいな回答がついてます。
…のでDeviceWatcherで監視するときにFilterで利用したいGATTのUUIDを指定することをお勧めします。
DeviceInformation.CreateWatcher(GattDeviceService.GetDeviceSelectorFromUuid(new Guid("f000aa00-0451-4000-b000-000000000000" )));
これであればAdded/Updatedイベントで確実に指定したもののデバイスIDが来るので取得できます。
var gattService = await GattDeviceService.FromIdAsync(new Guid("f000aa00-0451-4000-b000-000000000000" ));
ちなみに GattDeviceService.FromIdAsync メソッドはデバイスが存在しないとExceptionを投げますが、誰か別なプロセスが利用中の場合 null を返すので完全に罠です。
いずれにせよペアリングとデバイス列挙は別なので、ペアリングしてもそのままの流れで使い始めることはできないということのようです。
コンソールアプリケーションで動かしたい ところでUWPのAPIはコンソールアプリケーションからも扱えて、BLEで通信も確かにできるのですがコンソールアプリケーションからはペアリングできません。
想像するにペアリング用のUIを提供している都合なのだと思いますが DeviceInformationPairing.PairAsync メソッドを呼ぶと失敗します(そもそも元からDispatcher経由で呼ばないといけない)。
DeviceInformationCustomPairing クラス なら?と思ったのですが現状対応していないようで、将来的には DeviceInformationCustomPairing クラスで対応するらしい ので現状はあらかじめペアリングしておくとかが必要です。
« 新しいエントリ 1 … 70 71 72 73 74 … 84 古いエントリ »