TES Blog

株式会社テクニカルエンジニアリングサポートに所属する社員が、自身が携わるテクノロジーやイベントに関する情報を発信しています。

C# と Bluetooth Low Energy(BLE)の連携

業務にてWindowsフォームアプリケーションとBluetooth Low Energy(BLE)の連携する機会があったので、備忘の意味合いを込めて本記事を作成しました。
サンプル自体はMicrosoftが提供しているコードがあるので、そちらも参考にすると良いと思います。
https://github.com/microsoft/Windows-universal-samples/tree/master/Samples/DeviceEnumerationAndPairing/cs

環境

  • Windows 10
  • Visual Studio 2017

デバイスの検索

アプリケーションが動作するPCの周囲に存在するデバイスを検索する処理です。 DeviceInformation.CreateWatcher() メソッドに渡すセレクタの要素に、抽出したいデバイス種別のIDを渡すことで検索対象の絞り込みが可能です。

// 検索条件を設定
// Bluetooth: e0cbf06c-cd8b-4647-bb8a-263b43f0f974
// BLE:       bb7bb05e-5972-42b5-94fc-76eaa7084d49
string selector = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";

// private DeviceWatcher Watcher { get; set; }
this.Watcher = DeviceInformation.CreateWatcher(selector, null, DeviceInformationKind.AssociationEndpoint);

検知したデバイス情報を画面に表示する等の要件がある場合、 デバイスを検知した時・デバイスの検索が完了した時のイベントをそれぞれ定義しておくと良いですね。

// デバイス検知時のイベント
this.Watcher.Added += this.WatcherDeviceAdded;

// 検索完了時のイベント
this.Watcher.EnumerationCompleted += this.DeviceWatcherEnumerationCompleted;

// デバイス検索の開始
this.Watcher.Start();

デバイス検知時のイベント

private void WatcherDeviceAdded(DeviceWatcher sender, DeviceInformation deviceInfo)
{
    if (deviceInfo.Name != string.Empty)
    {
        // デバイス情報をListに保存
        this.devices.Add(deviceInfo);
    }
}

検索完了時のイベント

private void DeviceWatcherEnumerationCompleted(DeviceWatcher sender, object args)
{
    // メインスレッド外なので、画面に出力する場合はDelegateを使用する
    this.Watcher.Stop();
    // private delegate void BindDataSource();
    var bindData = new BindDataSource(this.DataGridViewBindDataSource);

    try
    {
        if (!this.dgvDevices.IsDisposed)
        {
            this.dgvDevices.Invoke(bindData);
        }
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

ペアリング

検索して取得したデバイスとペアリングする場合、 PairAsync() を呼び出します。
処理の前後に PairingRequested に対してイベントを登録していますが、私が対応したデバイスではカスタムペアリングを使用した場合にペアリングが可能な状態でしたので、今回は Custom.PairAsync を使用しました。

var deviceInfo = this.devices[selectedIndex];
deviceInfo.Pairing.Custom.PairingRequested += this.PairingRequestedHandler;
DevicePairingResult result = await deviceInfo.Pairing.Custom.PairAsync(
    DevicePairingKinds.ConfirmOnly, DevicePairingProtectionLevel.None);
deviceInfo.Pairing.Custom.PairingRequested -= this.PairingRequestedHandler;

if (result.Status == DevicePairingResultStatus.Paired || result.Status == DevicePairingResultStatus.AlreadyPaired)
{
    Console.WriteLine("ペアリングに成功しました。");
}
else
{
    Console.WriteLine("ペアリングに失敗しました。");
}

ペアリング時のイベント

private void PairingRequestedHandler(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
    switch (args.PairingKind)
    {
        case DevicePairingKinds.ConfirmOnly:
            // PIN入力が不要なケース
            args.Accept();
            break;

        case DevicePairingKinds.ProvidePin:
            // PIN入力が必要なケース
            var collectPinDeferral = args.GetDeferral();
            args.Accept("000000");
            collectPinDeferral.Complete();
            break;
    }
}

ペアリング成功後、対象のデバイスとの通信を行うため、Characteristicとの疎通を行います。

var deviceInfo = this.devices[selectedIndex];
var device = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);

// デバイスのUUID
Guid vendorSpecificServiceUuid = new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1");
Guid characteristic1Uuid = new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx2");
Guid characteristic2Uuid = new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx3");

// サービスの検索
var result = await device.GetGattServicesForUuidAsync(vendorSpecificServiceUuid);
if (result.Status != GattCommunicationStatus.Success)
{
    return;
}

var services = result.Services;

// 書き込み用のCharacteristic
var characteristicsWriteWithoutResponse = await services[0].GetCharacteristicsForUuidAsync(characteristic1Uuid);
this.WriteWithoutResponseCharacteristic = characteristicsWriteWithoutResponse.Characteristics[0];

// レスポンス用のCharacteristic
var characteristicsWriteAndNotify = await services[0].GetCharacteristicsForUuidAsync(characteristic2Uuid);
this.WriteAndNotifyCharacteristic = characteristicsWriteAndNotify.Characteristics[0];

// Notifyの通知
var status = await this.WriteAndNotifyCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
if (status != GattCommunicationStatus.Success)
{
    return;
}

// BLE側で値の変更が発生した場合に実行するイベント
this.WriteAndNotifyCharacteristic.ValueChanged += this.Characteristic_ValueChanged;

// 電文を作成してBLEへ書き込む
var messageData = new byte[] { 0x01, 0x02 };
var writer = new DataWriter();
writer.ByteOrder = ByteOrder.BigEndian;
writer.WriteBytes(messageData);
var writeResult = await this.WriteWithoutResponseCharacteristic.WriteValueAsync(writer.DetachBuffer());

if (writeResult != GattCommunicationStatus.Success)
{
    throw new Exception();
}

デバイス側の値に変更が発生したことを検知するイベント

private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
    // 値を読み込み
    var reader = DataReader.FromBuffer(args.CharacteristicValue);
    byte[] input = new byte[reader.UnconsumedBufferLength];
    reader.ReadBytes(input);
    
    // 画面への出力
}

実装前の前提知識が不足していましたが、なんとか疎通までこぎつけることができました 🎉