Developer's Blog

【連載】Bluetooth LE (4) Windows 8.1 の Windows ストアアプリで BLE を使う

WinRT-Gatt

こんにちは。共同開発部 開発担当の伊藤です。

Bluetooth LE (以降 BLE)の連載、第4回です。今回は Windows 8.1 で BLE 機器を使う方法をご紹介します。
連載の第1回でもご紹介しましたが、Windows 8 では BLE のサポートはかなり限定的でしたが、Windows 8.1 になってから正式に WinRT のクラスライブラリに BLE アクセス用のクラス群が追加されました。

ちょうど本日10月17日午後8時(日本時間)に Windows 8.1 が一般向けにリリースされ、Windows Store からダウンロードできるようになります
連載をはじめる時はまったく想定に入れてなかったのですが、Windows 8.1 の新機能を使う方法を公開日にご紹介できるとはなんというナイスタイミング!

今回は、Windows 8.1 RTM と Visual Studio 2013 Professional RC を使って、XAML ベース Windows ストアアプリから BLE デバイスにアクセスして値の読み書きを行う方法をご紹介します。

権限の付与

Windows ストアアプリで Bluetooth にアクセスするには、DeviceCapability を Package.appxmanifest に宣言する必要があります。
この宣言は Visual Studio のアプリケーション マニフェスト デザイナーを使用することはできないため、テキストエディタで直接開いて編集する必要があります。Visual Studio 上で編集するには、Package.appxmanifest を右クリックして、「コードの表示」で開くと、XML を直接編集できるようになります。

DeviceCapability は <Capabilities> タグの中に、下記のように定義します。Function タグに書いてある serviceId: はアクセスしたいサービスの UUID を指定します。また、基本的なものは name: で指定可能です。詳しくは How to specify device capabilities for Bluetooth を参照してください。

<Capabilities>
  <m2:DeviceCapability Name="bluetooth.genericAttributeProfile">
    <m2:Device Id="any">
      <m2:Function Type="serviceId:E18F21CA-63DE-4BBB-8881-B12CF538D25A" />
    </m2:Device>
  </m2:DeviceCapability> 
</Capabilities>

機器の検索

まず、DeviceInformation.FindAllAsync でデバイス・サービスを検索します。このメソッドは検索条件次第で BLE 以外のデバイスも列挙できるのですが、特定のサービスを公開している BLE 機器を検索する条件を GattDeviceService.GetDeviceSelectorFromUuid で作成できます。BLE デバイスの場合はサービス自体が個々のデバイスとして列挙されます。

次に、取得したデバイスからIDを取得し、そのIDをもとに GattDeviceService.FromIdAsync でサービスを扱う GattDeviceService のインスタンスを取得できます。GattDeviceService.GetCharacteristics でキャラクタリスティックを扱う GattCharacteristic のインスタンスを取得できます。

ここまでの説明を実装した処理が下記の通りです。async/await のおかげでかなりシンプルですね。

using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;

// -- 中略 --

private GattDeviceService service;
private GattCharacteristic characteristic;

private async void Button_Click(object sender, RoutedEventArgs e)
{
    // デバイスを検索
    var devices = await DeviceInformation.FindAllAsync(
        GattDeviceService.GetDeviceSelectorFromUuid(new Guid("2AC94B65-C8F4-48A4-804A-C03BC6960B80")));
    if (devices.Count > 0)
    {
        // サービスを作成
        this.service = await GattDeviceService.FromIdAsync(devices.First().Id);

        // キャラクタリスティックを取得
        var characteristics = service.GetCharacteristics(new Guid("4FD800F8-D3B6-48F9-B232-29E95984F76D"));
        if (characteristics.Count > 0)
        {
            this.characteristic = characteristics.First();

            // 通知イベントを登録
            characteristic.ValueChanged += characteristic_ValueChanged;

            var dialog = new MessageDialog("Connected!");
            await dialog.ShowAsync();
        }
    }
    else
    {
        var dialog = new MessageDialog("Device not found");
        await dialog.ShowAsync();
    }
}

値の取得

値の取得は GattCharacteristic の ReadValueAsync メソッドを呼ぶことで取得できます。また、Notify があるキャラクタリスティックの場合は ValueChanged イベントにイベントハンドラを登録しておくことで、Notify 受信時にイベントハンドラが実行されるようになります。

今回は ValueChanged イベントを使って値を取得しました。値は IBuffer でとれるので、byte の配列にして使用しました。

void characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
    var buffer = args.CharacteristicValue.ToArray();
    if (buffer.Last() == 1)
    {
        leftTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
        rightTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
    else if (buffer.Last() == 2)
    {
        leftTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
        rightTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    }
    else
    {
        leftTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
        rightTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    }
}

値の書き込み

キャラクタリスティックに値を書き込むには WriteValueAsync を使用します。

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    if (this.characteristic != null)
    {
        var buffer = Encoding.UTF8.GetBytes(this.characteristicValueTextBox.Text).AsBuffer();
        await this.characteristic.WriteValueAsync(buffer);
    }
}

実際には考慮すべき事項

DeviceInformation.FindAllAsync は「周辺にデバイスがない状況」も「BLEが使えない状況」のどちらも空のコレクションを返しますので、エラーメッセージ等はそれを見越したものにする必要があります。特に、Windows は iOS デバイスに比べて環境が多種多様なので、Windows 8.1 の端末でも必ず使用できる状態にはないことが重要です。

また、各メソッドは例外を返す可能性がありますので、適切に例外処理を記述する必要があります。

使える環境について

今回試してみようと思い、BLE 対応の USB ドングルを Windows 8.1 のマシンに差し込んだところ、BLEをうまく動かすことができませんでした。USB ドングルを使用した場合だと BLE の使用に必要な「Microsoft Bluetooth LE Enumerator」がデバイスマネージャーに追加されないようで、オンボードでBLEアダプタが搭載されている機種に限定されているのかもしれません。

いかがでしたでしょうか。Core Bluetooth に比べると Windows ストアアプリの BLE は簡単に処理を記述できます。Surface などでは問題なく動かせるはずなので、BLE 機器をお持ちの方は今夜 Windows 8.1 がリリースされたら Visual Studio 2013 Express を入れて試してみてはいかがでしょうか。

追記 (2014/11/27)

その後、Bluetooth LE に対応したデバイスがオンボードで搭載されている Windows 8.1 Update 端末(Surface Pro3)が入手できたので、実機で検証することができました。その結果以下のことがわかっています。そのため、記事内の一部の表現についても変更させていただきました。

・Windows ストアアプリからはペアリング操作はできず、事前に「PC設定」のデバイスから、通常の Bluetooth 機器と同様にペアリングを実施しておく必要があります。
・iOS の CBPeripheralManager で実装したデバイスに対しては接続を確認することができていません。Windows の標準 Bluetooth スタックでは Bluetooth LE のみを実装したデバイスに対しては接続できるものの、Bluetooth SMART Ready であるデバイスに対してペアリングできないのではないかと予想しています。(SensorTag や .NET Gadgeteer の Bluetooth SMART モジュールなどとは接続できました)

フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

Copyright © 2019 Fenrir Inc. All rights reserved.