はじめに
PCと自作デバイス間などで、Bluetoothを使った無線通信をする場合は、SPPを利用して
従来の有線のような感覚でシリアル通信を行うのが簡単です。
SPPはBluetooth搭載のデバイスをWindowsとペアリングすると、仮想COMポートが割り振られ、このCOMポートに対してプログラムやターミナルからアクセスできるというものです。
しかし実際にやってみると、対象のCOMポートがどれなのかすぐにはわかりません。
デバイスマネージャーで見ると「BluetoothデバイスのCOMポート」みたいなざっくりとした名前で複数個並んでいたりします。
これらのどれかが目的のポートなので、接続できるまで試してみればいいのですが、めんどうくさいですし
ユーザーにそんなことをさせてはいけません。開発者自身もやりたくありません。
そこで、設定したデバイス名から、このCOMポート番号を求める方法について書きます。
デバイス名さえ決まっていれば、実行時動的にCOMポートが判明するので、手動での設定が必要なくなります。
ちなみに、C#での実現方法は下記リンク先が詳しいです。
https://www.softech.co.jp/mm_170705_tr.htm
ここでは、C++での実現方法をご紹介します。
手順
レジストリから、デバイス名をキーにしてフォルダ名を取得
レジストリを検索します。COMデバイスが登録されているところは、次の
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BTHPORT\Parameters\Devices
になります。ここのサブキーのパラメータNameに、デバイス名が入っているので、目的のデバイス名を探します。
見つかったら、ここのフォルダパスの一部が次のCOMポートを探すためのキー(デバイスインスタンスID)となるので保持しておきます。
デバイスインスタンスIDをキーとしてデバイス情報を探す
SetupDiGetClassDevs()にGUID_DEVINTERFACE_COMPORTを指定してハンドルを取得し、
SetupDiEnumDeviceInfo()で、デバイス情報の列挙ができます。
SetupDiGetDeviceInstanceId()で、デバイスインスタンスIDが得られるので、先程のものを探します。
見つかったら、SetupDiEnumDeviceInfo()のときに第二引数にセットされてくる deviceInfoData をおぼえておきます。
レジストリからCOMポートを取得
先程のdeviceInfoDataを、SetupDiOpenDevRegKey()に食わせてあげるとレジストリのキーを返してくれます。
この場所のパラメータ"PortName"に、目的のCOM番号があります。やっと見つけました!
説明はいいからコードを
サンプルコードです。
SearchPort( const std::string& searchname, std::string& portname )
第一引数に、デバイス名を入れてあげると、第二引数でCOMポート名を返します。
※一部、安全でないコードも含まれるので各自修正してください。
#include <setupapi.h> // Setupapi.lib
// 指定した名前のポート名を見つける
bool SearchPort( const std::string& searchname, std::string& portname )
{
// COMデバイスが登録されているところ
const std::string findKey = "SYSTEM\\CurrentControlSet\\services\\BTHPORT\\Parameters\\Devices";
DWORD dwResult;
std::string strFolderID;
////// レジストリから指定した名前を持つポートのフォルダ名を取得する
// フォルダキー(サブキー)を開く
HKEY hFolderKey;
dwResult = RegOpenKeyExA( HKEY_LOCAL_MACHINE, findKey.c_str(), 0, KEY_READ, &hFolderKey );
if ( dwResult != ERROR_SUCCESS ) {
return false;
}
FILETIME ftLastWriteTime;
char folder[1024];
// フォルダーの列挙
for ( DWORD dwIndex = 0; ; dwIndex++ ) {
DWORD folderSz = sizeof( folder ) / sizeof( char );
// 列挙
dwResult = RegEnumKeyExA( hFolderKey, dwIndex, folder, &folderSz, NULL, NULL, NULL, &ftLastWriteTime );
if ( dwResult == ERROR_NO_MORE_ITEMS ) break;
if ( dwResult != ERROR_SUCCESS ) continue;
// 取得された→folder
// その下のNameエンティティを調べる
char namePath[1024];
DWORD namePathSz = sizeof( namePath ) / sizeof( char );
strcpy_s( namePath, namePathSz, folder );
HKEY hNameEntKey;
dwResult = RegOpenKeyExA( hFolderKey, namePath, 0, KEY_READ, &hNameEntKey );
if ( dwResult != ERROR_SUCCESS ) {
continue;
}
DWORD dwType;
char lpData[1024];
DWORD dwDataSize = sizeof( lpData ) / sizeof( char );
dwResult = RegQueryValueEx( hNameEntKey, TEXT( "Name" ), 0, &dwType, (LPBYTE)lpData, &dwDataSize );
RegCloseKey( hNameEntKey );
if ( dwResult != ERROR_SUCCESS ) {
continue;
}
lpData[dwDataSize] = '\0';
// 値のチェック
if ( strcmp( lpData, searchname.c_str() ) == 0 ) { /// ここ文字数見たほうがいいかも TODO:
char searchID[512];
sprintf_s( searchID, 512, "&%s_", folder );
strFolderID = std::string( searchID );
std::transform( strFolderID.begin(), strFolderID.end(), strFolderID.begin(), std::toupper );
break;
}
}
RegCloseKey( hFolderKey );
if ( strFolderID == "" ) return false;
////// デバイス情報から、フォルダ名をキーにCOMポート名をひっぱる
// デバイス情報取得 COMPORT
HDEVINFO hDevInfo = SetupDiGetClassDevs( &GUID_DEVINTERFACE_COMPORT, NULL, NULL, ( DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ) );
SP_DEVINFO_DATA deviceInfoData = { sizeof( SP_DEVINFO_DATA ) };
for ( DWORD i = 0; SetupDiEnumDeviceInfo( hDevInfo, i, &deviceInfoData ); ++i ) {
char buffer[512];
DWORD buflen = 0;
SetupDiGetDeviceInstanceIdA( hDevInfo, &deviceInfoData, (char*)buffer, sizeof( buffer ), &buflen );
std::string strBuffer = std::string( buffer );
if ( strBuffer.find( strFolderID ) == std::string::npos ) continue;
// 発見
//// COMポート番号を取得
HKEY tmp_key = SetupDiOpenDevRegKey( hDevInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE );
if ( !tmp_key ) {
return false;
}
char name[256];
DWORD type = 0;
DWORD size = sizeof( name );
RegQueryValueExA( tmp_key, "PortName", NULL, &type, (LPBYTE)name, &size );
portname = std::string( name );
return true;
}
return false;
}
おわりに
これで、ペアリングされたBluetoothデバイスとすぐにシリアル通信ができるようになりました。
お疲れ様でした。