概要
Bluetoothのデバイス検索やペアリングを.NETプログラミングで制御する。
UWPにはBluetooth関連のライブラリがそろっているが、それ以前のWindowsFormやWPFではマネージドなライブラリがそろっていない。
そこで、32feetというBluetoothや赤外線通信という近距離無線通信関連のWin32APIをマネージドで使用できるライブラリを使って簡単なBluetooth制御を行う。
(2020年7月時点)今更WindowsFormでのBluetooth操作が必要か? いや単なる自己満足さ。もしかしたら必要な人がいるのかもしれないし。
詳細
以下の環境でBluetooth機器との接続関連処理を実装していきます。
- Windows10 pro
- Visual Studio 2017 CommutyEdition
- WindowsFormプロジェクト(C#)
- .NET framework 4.6.1
- Logicool M557 マウス のペアリング
32feetのインストール
32feetのライブラリ、ソースコード自体はgithubにあるが、VisualStdioで開発する際はNuGetからインストールするのが最も簡単。
プロジェクトを作成後、「ツール」→「NuGetパッケージマネージャー」→「ソリューションのNuGetパッケージの管理」を選択。
「32feet」で検索し、上記画像のライブラリをインストールする。
デバイスの検索
まずは、Bluetoothデバイスを検索する機能を実装する。使用するクラスはInTheHand.Net.Sockets.BluetoothClient
// BluetoothClient オブジェクトの生成
BluetoothClient bc = new BluetoothClient();
// 未ペアリングデバイスを探索する際の所要時間を設定。デフォルトは10秒。
bc.InquiryLength = new TimeSpan(0, 0, 5); // この場合は5秒
// Bluetoothデバイスの探索を行う。
// 引数:
// int maxDevices : 探索するデバイスの最大数。指定の数で探索を打ち切る。
// bool authenticated: trueだと認証済み(ペアリング済み)のデバイス、falseだと認証していないデバイスを結果に含める。
// bool remembered : trueだとホストに記録済みのデバイスのみを結果に含める(認証したことのあるデバイス以外無視すると同じ?)
// bool unknown : falseだと詳細のわからないデバイスを結果に含めない。
// 未ペアリングのデバイスを探索し、結果を返す
BluetoothDeviceInfo[] devices_nonpaired = bc.DiscoverDevices(32, false, false, true);
// ペアリング済みのデバイスを探索し、結果を返す
BluetoothDeviceInfo[] devices_paired = bc.DiscoverDevices(32, true, false, false);
ペアリング済み、未ペアリングのデバイス両方とも同じDiscoverDevices
メソッドで取得する。引数により取得対象を変更できる。
ペアリング済みのデバイス検索は一瞬で終わるが、未ペアリングのデバイス探索は周囲にペアリングリクエストをしているデバイスの信号を受信するので時間がかかる。普通にデバイスをペアリングするとき、デバイス一覧に表示されるまで少し時間がかかるのと同じ。
探索する時間はInquiryLength
で設定できる。authenticated=true
で探索するときっちり設定した時間ブロックされる。
戻り値はBluetoothDeviceInfo
の配列。
ペアリング
前項で取得した未ペアリングデバイスをペアリングする方法を考える。ペアリングはInTheHand.Net.Bluetooth.BluetoothSecurityクラスを使用する。
// とりあえず、1つ以上発見できたものとして。
BluetoothDeviceInfo deviceInfo = devices_nonpaired[0];
// ペアリングのリクエストを行う
bool paired = BluetoothSecurity.PairRequest(deviceInfo.DeviceAddress, null);
すると、Bluetoothデバイスを通常追加する際におなじみのポップアップが表示される。
このポップアップをクリックすると以降は通常のペアリングと同じようにペアリング処理が進む。
この段階では単にペアリングのリクエストを送るだけで、プログラム側でペアリング処理を完了させることができない。
プログラム側でペアリング処理を完了させるには
PairRequest
メソッドでペアリングの強制了承を行うことはできないが、ペアリングのリクエストが発生したことをハンドルする方法がある。
InTheHand.Net.Bluetooth.BluetoothWin32Authenticationを利用する。
// ペアリングリクエストが発生した場合、引数のメソッドをコールバックする。
// PairRequestする前に1度設定すればOK.
BluetoothWin32Authentication authenticator = new BluetoothWin32Authentication(Win32AuthCallbackHandler);
/// <summary>
/// ペアリングリクエストが発生した際に呼び出される。
/// </summary>
private void Win32AuthCallbackHandler(Object sender, BluetoothWin32AuthenticationEventArgs e)
{
// pinの設定。デバイスによっては必須。
e.Pin = "0000";
// ペアリングの了承・拒否を設定できる。trueにしてメソッドを返すとペアリング了承にできる。
e.Confirm = true;
}
ペアリングリクエストのポップアップは表示されるが、ポップアップをクリックしなくてもペアリングは完了する。
ただし「ペアリング了承」しただけで、そのデバイスが使用できるようにはなっていない。デバイスを使えるようにするためには後述のサービスインストールを行う必要がある。
この方法でペアリングしたデバイスは「その他のデバイス」となっていて、マウスやキーボードの扱いになっていないのがわかる。
サービス一覧取得
まずは、対象のBluetoothデバイスから「あなたはどんなサービスが使えるの?」と問い合わせする必要がある。
以下の方法で対応サービス一覧の内容を取得できる。
// L2CapProtocolにてGetServiceRecordsを実行するとデバイスが利用可能なサービス一覧を取得できる。
// この作業はペアリング前でも実施可能。
var serviceinfo = remoteEP.GetServiceRecords(BluetoothService.L2CapProtocol);
// レコードの内容をテキストでダンプしてみる。
foreach(var record in serviceinfo){
ServiceRecordUtilities.Dump(Console.Error, record);
}
サービス一覧をテキストでダンプすると、以下のようなデータが取得できる。レコードは3つ取得でき、その1番目の内容がこちら。
AttrId: 0x0000 -- ServiceRecordHandle
UInt32: 0x0
AttrId: 0x0001 -- ServiceClassIdList
ElementSequence
Uuid16: 0x1000 -- ServiceDiscoveryServer
AttrId: 0x0004 -- ProtocolDescriptorList
ElementSequence
ElementSequence
Uuid16: 0x100 -- L2CapProtocol
UInt16: 0x1
ElementSequence
Uuid16: 0x1 -- SdpProtocol
( ( L2Cap, PSM=Sdp ), ( Sdp ) )
AttrId: 0x0005 -- BrowseGroupList
ElementSequence
Uuid16: 0x1002 -- PublicBrowseGroup
AttrId: 0x0006 -- LanguageBaseAttributeIdList
ElementSequence
UInt16: 0x656E
UInt16: 0x6A
UInt16: 0x100
AttrId: 0x0009 -- BluetoothProfileDescriptorList
ElementSequence
ElementSequence
Uuid16: 0x100 -- L2CapProtocol
UInt16: 0x100
AttrId: 0x0100 -- ServiceName
TextString: [en] 'Logitech Bluetooth Wireless Mouse SDP Server'
AttrId: 0x0101 -- ServiceDescription
TextString: [en] 'Bluetooth Mouse'
AttrId: 0x0200 -- VersionNumberList
ElementSequence
UInt16: 0x100
AttrId: 0x0000 -- ServiceRecordHandle
UInt32: 0x10000
このうち、AttrId: 0x0001
の内容がそのデバイスが対応しているサービスのGuidである。
AttrId: 0x0001 -- ServiceClassIdList
ElementSequence
Uuid16: 0x1000 -- ServiceDiscoveryServer
どのGuidがどのサービスを表しているかはこちらのサイトの仕様:サービスディスカバリーを参考にしてほしい。
サービス一覧の2番目のレコードはこちら。
AttrId: 0x0001 -- ServiceClassIdList
ElementSequence
Uuid16: 0x1124 -- HumanInterfaceDevice
AttrId: 0x0004 -- ProtocolDescriptorList
ElementSequence
ElementSequence
Uuid16: 0x100 -- L2CapProtocol
UInt16: 0x11
ElementSequence
Uuid16: 0x11 -- HidpProtocol
( ( L2Cap, PSM=HidControl ), ( Hidp ) )
AttrId: 0x0005 -- BrowseGroupList
ElementSequence
Uuid16: 0x1002 -- PublicBrowseGroup
AttrId: 0x0006 -- LanguageBaseAttributeIdList
ElementSequence
UInt16: 0x656E
UInt16: 0x6A
UInt16: 0x100
AttrId: 0x0009 -- BluetoothProfileDescriptorList
ElementSequence
ElementSequence
Uuid16: 0x1124 -- HumanInterfaceDevice
UInt16: 0x101
AttrId: 0x000D -- AdditionalProtocolDescriptorLists
ElementSequence
ElementSequence
ElementSequence
Uuid16: 0x100 -- L2CapProtocol
UInt16: 0x13
ElementSequence
Uuid16: 0x11 -- HidpProtocol
( ( L2Cap, PSM=HidInterrupt ), ( Hidp ) )
AttrId: 0x0100 -- ServiceName
TextString: [en] 'Logitech Bluetooth Wireless Mouse'
AttrId: 0x0101 -- ServiceDescription
TextString: [en] 'Bluetooth Mouse'
AttrId: 0x0102 -- ProviderName
TextString: [en] 'Logitech'
AttrId: 0x0201 -- ParserVersion
UInt16: 0x111
AttrId: 0x0202 -- DeviceSubclass
UInt8: 0x80
AttrId: 0x0203 -- CountryCode
UInt8: 0x21
AttrId: 0x0204 -- VirtualCable
Boolean: True
AttrId: 0x0205 -- ReconnectInitiate
Boolean: True
AttrId: 0x0206 -- DescriptorList
ElementSequence
ElementSequence
UInt8: 0x22
AttrId: 0x0207 -- LangIdBaseList
ElementSequence
ElementSequence
UInt16: 0x409
UInt16: 0x100
AttrId: 0x0209 -- BatteryPower
Boolean: True
AttrId: 0x020A -- RemoteWake
Boolean: True
AttrId: 0x020C -- SupervisionTimeout
UInt16: 0x1F40
AttrId: 0x020D -- NormallyConnectable
Boolean: False
AttrId: 0x020E -- BootDevice
Boolean: True
AttrId: 0x020F
UInt16: 0x12
AttrId: 0x0210
UInt16: 0x0
AttrId: 0x0000 -- ServiceRecordHandle
UInt32: 0x10001
先頭にそれっぽいのがある。
AttrId: 0x0001 -- ServiceClassIdList
ElementSequence
Uuid16: 0x1124 -- HumanInterfaceDevice
このデバイスはマウスなので、まさにHumanInterfaceDevice(HID)である。
レコードごとに、以下のコードでサービスのGuidを取得できる。
// 取得したサービスレコードのうち、ServiceDescription のレコードを取得する。
ServiceAttribute sdpRecord = record.GetAttributeById(InTheHand.Net.Bluetooth.AttributeIds.UniversalAttributeId.ServiceDescription);
if (sdpRecord != null)
{
// サービスのGuidを取得
Guid guid = sdpRecord.Value.GetValueAsElementArray()[0].GetValueAsUuid();
}
サービスのインストール
前項で取得できたサービスのGuidをもとにサービスをインストールする。BluetoothDeviceInfo.SetServiceStateで行う。
取得したGuidを一旦Listに保存し、サービス一覧取得が終わってからまとめてサービスのインストールを行う。
// ペアリングしたデバイスが対応しているサービス一覧を取得し、リストに記憶する。
List<Guid> serviceGuidList = new List<Guid>();
// L2CapProtocolにてGetServiceRecordsを実行するとデバイスが利用可能なサービス一覧を取得できる。
ServiceRecord[] serviceinfo = deviceInfo.GetServiceRecords(BluetoothService.L2CapProtocol);
foreach (var record in serviceinfo)
{
// サービスレコードをテキストでダンプ
//ServiceRecordUtilities.Dump(Console.Error, record);
// 取得したサービスレコードのうち、ServiceDescription のレコードを取得する。
ServiceAttribute sdpRecord = record.GetAttributeById(InTheHand.Net.Bluetooth.AttributeIds.UniversalAttributeId.ServiceDescription);
if (sdpRecord != null)
{
// サービスのGuidを取得
serviceGuidList.Add(sdpRecord.Value.GetValueAsElementArray()[0].GetValueAsUuid());
}
}
// サービスのインストール
foreach(Guid service in serviceGuidList)
{
// 個々のServiceに対し、有効を設定する。
deviceInfo.SetServiceState(service, true);
}
これで実際にマウスが動かせるようになる。
ペアリング解除
ペアリング解除もInTheHand.Net.Bluetooth.BluetoothSecurityクラスを使用する。
BluetoothSecurity.RemoveDevice(deviceInfo.DeviceAddress);
終わりに
32feetのライブラリを用いてBluetoothのペアリング、サービスインストール、ペアリング解除までを実装した。