「ソフテックだより」では、ソフトウェア開発に関する情報や開発現場における社員の取り組みなどを定期的にお知らせしています。
さまざまなテーマを取り上げていますので、他のソフテックだよりも、ぜひご覧下さい。
ソフテックだより(発行日順)のページへ
ソフテックだより 技術レポート(技術分野別)のページへ
ソフテックだより 現場の声(シーン別)のページへ
私は入社2年目の社員で、主にWindowsアプリケーションソフト開発に携わっています。担当した案件の中に、Windows上でBluetoothを用いてファイル転送を行うアプリケーションの開発がありました。
近年、スマートフォンやタブレットといった持ち運びが容易な通信機器(いわゆるモバイル端末)が普及してきたことから、無線通信に対応したアプリケーションの需要が高まってきています。無線通信の実現には様々な手段がありますが、「低消費電力」「障害物に強い」「1対1の通信」といった利点からBluetooth通信が用いられることがあります。今回は、WindowsでのBluetooth通信を用いたアプリケーション開発について紹介させていただきます。
Bluetoothとは、一言で言えば無線通信規格の1種です。2.4GHz帯を使用しており、通信距離は一般的に使用されているClass2では10m程度、最大でもClass1の100mほどしかありません。一般的には近距離で低速度な通信に用いられます。詳しい特徴は、同じくBluetooth通信アプリケーションについて述べている2013年6月5日発行のソフテックだより第187号をご覧ください。
iOSやAndroidと比べてWindowsでは需要が少ないためBluetoothがあまり使用されていません。マウスやキーボードなどのPC周辺機器をコードレス化するために用いられる程度です。BluetoothがWindowsであまり使用されない理由は、USBポートが関係していると思われます。PCはUSB接続で給電しながら機器を使用できます。対してスマートフォンやタブレットは、USBポートがない機種も多く、給電しながら機器を使用できないことがあります。PCを使用する場合は電源のことを考えると、機器を充電して無線通信するよりもUSBから給電しつつ使用する方が良いという人が多いのだと思います。また開発者目線では、Microsoftが用意しているBluetoothAPIでできることが、デバイス認識・デバイス検索・ペアリングまでで、データ通信部分は自作しなければならないという点も一因のように思います。
また、近年はノートPCの軽量化やWindowsタブレットなどでWindowsを携帯できるようになってきましたが、モバイル端末の市場はiPhone(iPad)・Mac・Androidが優勢ということも理由の一つです。「Bluetooth プログラミング」などとWeb検索していただければ分かっていただけるかと思いますが、ほとんどiOSかAndroid用の情報です。WindowsでBluetoothを扱っている記事は多くありません。特に、ペアリング機器名を取得しCOM番号と関連付ける方法については情報が少なく、私も開発時に苦労しました。
そこで今回は、C#でのBluetooth通信アプリケーション開発例を基にして、ペアリング機器名とCOM番号を取得し関連付ける方法について紹介させていただきます。
図1. システム構成図
今回は例としてWindowsのデスクトップPCとWindowsのノートPC(タブレット)同士でBluetooth通信を使用しデータを送受信するシステムを作成します。
BluetoothにはSPP(Serial Port Profile)というシリアル通信を無線化するプロファイル(※1)が策定されており、SPPを用いることで通常のシリアル通信と同じように通信できます。これを用いて、ペアリング機器名とCOM番号を取得してシリアル通信にてデータを送受信するシステムとします。
今回の開発環境は以下の通りです。
前述の通り、SPPを用いるとC#での開発はシリアル通信とほぼ同様ですので難しくはありません。SPPで問題となるのは接続している機器名とCOM番号を同時に取得できないということです。通常のシリアルポート通信では、デバイスマネージャーに記載されている情報を取得することでシリアル通信の接続相手の機器名を取得できますが、Bluetoothを経由したSPPではPCの機種などで文言の違いがあるものの「Bluetoothのシリアルポート」という旨が表示されます(図2左側)。これでは、複数台の機器を接続した場合にどの機器がどのCOM番号を使用しているのか分かりません。また、1台しか接続していなくても本当にペアリングした機器かどうか確証が得られません。
Bluetooth経由でも機器名を取得するためには、レジストリを使用します。今回の開発を例にデスクトップPCからノートPC(機器名「NotePC」)へデータを送信する手順を紹介します。
(1) プログラムからデバイスマネージャーの情報を取得する
まずはデバイスマネージャーからデバイスID(またはデバイスアドレス)を取得します。デバイスIDはプロパティの「デバイス インスタンス パス」の値(図2右側)から取得します。この値は以下のように構成されています。
ハードウェアID\***&***&***&デバイスID_***
※「***」は数値・英字交じりの文字列
ハードウェアID、デバイスIDはそれぞれ図2のプロパティから「ハードウェアID」「Bluetoothデバイスアドレス」を選択することでも確認できます。
C#のプログラムからはWMI(Windows Management Instrumentation)を使用して値を取得します。ハードウェアID、デバイスIDを個々に取得する方法もあるようですが、デバイスインスタンスパスから抜き出す方が簡単なように思います。図2では、「34C73185126E」がデバイスIDになります。
プログラムでは、ここですべてのポートのデバイスIDを配列などで確保しておきます。また、COM番号も取得しておきます。COM番号の取得方法はいくつかありますが、どの方法でも特に問題はありません。
図2. デバイスマネージャーとプロパティ
ソースコード例は以下の通りです。
// COM番号を抜き出すための準備
Regex regexPortName = new Regex(@"(COM\d+)");
ManagementObjectSearcher searchSerial = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity"); // デバイスマネージャーから情報を取得するためのオブジェクト
// デバイスマネージャーの情報を列挙する
foreach (ManagementObject obj in searchSerial.Get())
{
string name = obj["Name"] as string; // デバイスマネージャーに表示されている機器名
string classGuid = obj["ClassGuid"] as string; // GUID
string devicePass = obj["DeviceID"] as string; // デバイスインスタンスパス
if (classGuid != null && devicePass != null)
{
// デバイスインスタンスパスからBluetooth接続機器のみを抽出
// {4d36e978-e325-11ce-bfc1-08002be10318}はBluetooth接続機器を示す固定値
if (String.Equals(classGuid, "{4d36e978-e325-11ce-bfc1-08002be10318}",
StringComparison.InvariantCulture))
{
// デバイスインスタンスパスからデバイスIDを2段階で抜き出す
string[] tokens = devicePass.Split('&');
string[] addressToken = tokens[4].Split('_');
string bluetoothAddress = addressToken[0];
Match m = regexPortName.Match(name);
string comPortNumber = "";
if (m.Success)
{
// COM番号を抜き出す
comPortNumber = m.Groups[1].ToString();
}
if (Convert.ToUInt64(bluetoothAddress, 16) > 0)
{
string bluetoothName = GetBluetoothRegistryName(bluetoothAddress);
}
// bluetoothNameが接続機器名
// comPortNumberが接続機器名のCOM番号
}
}
}
(2) レジストリからペアリング機器名を取得する
次に、レジストリエディターの以下の場所を開きます。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BTHPORT\Parameters\Devices
開くと数値と英字が混ざったフォルダがあるはずです(図3左側)。このフォルダ名はデバイスIDですので、(1)で取得したデバイスIDと同じ名前のフォルダを探します(フォルダ名の英字は大文字・小文字を区別しません)。探し当てたら、そのフォルダの「NAME」(図3右側)のデータを確認してください。
このデータには接続機器名がASCIIコードで書かれています。なお、末尾は必ずNULL(00)が入るようです。図3では「NotePC」となります。
図3.
レジストリエディター
ここで通信したい機器の名称を検索します。C#ではRegistryKeyクラスを使ってレジストリの内容を読み書きすることができます。このクラスを使って、(1)で準備したデバイスIDの配列からフォルダ名(今回は「34c73185126e」)を検索し、見つかった場合はその機器名をASCII変換(今回は「NotePC」)して取得します。
以下にソースコード例を載せます。
/// <summary>機器名称取得</summary>
/// <param name="address">[in] アドレス</param>
/// <returns>[out] 機器名称</returns>
private string GetBluetoothRegistryName(string address)
{
string deviceName = "";
// 以下のレジストリパスはどのPCでも共通
string registryPath = @"SYSTEM\CurrentControlSet\Services\BTHPORT\Parameters\Devices";
string devicePath = String.Format(@"{0}\{1}", registryPath, address);
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(devicePath))
{
if (key != null)
{
Object o = key.GetValue("Name");
byte[] raw = o as byte[];
if (raw != null)
{
// ASCII変換
deviceName = Encoding.ASCII.GetString(raw);
}
}
}
// NULL文字をトリミングしてリターン
return deviceName.TrimEnd('\0');
}
(1)で取得した機器名と(2)で取得したCOM番号をデバイスIDで紐付けしたことによって、接続機器名とCOM番号を両方取得することができました。実際に紐づけた結果を出力してみます。機器名と結びつけない場合ではCOM番号のみを参照できるため図4のようになり、どれが通信したい機器「NotePC」であるのか分かりません。しかし、(2)で取得した機器名を紐付けることで、図5のように狙った機器(NotePC)だけを表示してCOM番号を1つに絞り込むことも出来ます。もちろん絞り込まないことも可能です。
図4. レジストリエディター未使用で接続機器を表示した例
図5. レジストリエディターを使用し接続機器を絞り込んだ例
前述の通りSPPを使用すればBluetooth経由でもシリアル通信と同じように通信することができます。C#では.NET Framework2.0からSerialPortクラスが標準サポートされています。COM番号さえ分かれば、SerialPortクラスを使って簡単にシリアル通信を行うことができます。レジストリを使用して接続機器がどれか、COM番号は何番かということを把握できたので、あとはシリアル通信を行うことでBluetoothを用いた通信が可能となります。
今回は、WindowsにてBluetooth通信を使用したアプリケーションの開発例をご紹介させていただきました。Bluetooth通信を用いたアプリケーションはWindowsで使用されることはあまり多くありません。本稿が少しでも、WindowsでBluetooth通信を用いた開発を行おうとしている皆様の参考となれば幸いです。
(T.H.)
関連ページへのリンク
関連するソフテックだより