LoginSignup
31
31

More than 1 year has passed since last update.

SPP(Serial Port Profile)によるBluetooth通信を行う際に デバイス名からCOMポート番号を取得する

Last updated at Posted at 2019-02-07

はじめに

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デバイスとすぐにシリアル通信ができるようになりました。
お疲れ様でした。

31
31
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
31