「Qtの基礎 - Classic Bluetooth」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
381行目: 381行目:
<br>
<br>
Classic Bluetoothでは、<code>QBluetoothServiceInfo</code>クラスを使用して、単純なサービス情報を管理する。<br>
Classic Bluetoothでは、<code>QBluetoothServiceInfo</code>クラスを使用して、単純なサービス情報を管理する。<br>
<br>
==== Bluetoothサービスディスカバリの準備 ====
<code>QBluetoothServiceDiscoveryAgent</code>クラスのインスタンスを生成する。<br>
<br>
以下に示すシグナルを接続する。
* QBluetoothServiceDiscoveryAgent::serviceDiscovered
*: 発見したサービスを通知する。
* QBluetoothServiceDiscoveryAgent::finished
*: 探索完了を通知する。
* QBluetoothServiceDiscoveryAgent::error
*: エラー発生時の通知
<br>
<br>
<syntaxhighlight lang="c++">
#include <QBluetoothServiceDiscoveryAgent>
#include <QDebug>
QBluetoothServiceDiscoveryAgent discoveryAgent;
// シグナルとスロットの接続
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, [](const QBluetoothServiceInfo &info) {
    qDebug() << "サービス発見";
});
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, []() {
    qDebug() << "探索完了";
});
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::error, [](QBluetoothServiceDiscoveryAgent::Error error) {
    qDebug() << "エラー発生";
});
</syntaxhighlight>
<br>
==== サービスディスカバリの開始 ====
<code>QBluetoothAddress</code>クラスを使用して、対象デバイスのBluetoothアドレスを指定する。<br>
<br>
<code>QBluetoothServiceDiscoveryAgent::setRemoteAddress</code>メソッドを実行して、探索対象のデバイスを設定する。<br>
<code>QBluetoothServiceDiscoveryAgent::start</code>メソッドを実行して、ディスカバリを開始する。<br>
<br>
<syntaxhighlight lang="c++">
#include <QBluetoothAddress>
// デバイスのBluetoothアドレスを設定
QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
discoveryAgent.setRemoteAddress(address);
// ディスカバリ開始
discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
</syntaxhighlight>
<br>
==== サービスの発見時 ====
<code>QBluetoothServiceDiscoveryAgent::serviceDiscovered</code>シグナルで通知された<code>QBluetoothServiceInfo</code>クラスから必要な情報を取得する。<br>
<br>
QBluetoothServiceInfoクラスでは、以下に示す情報が取得できる。<br>
<br>
これらの情報は、Bluetoothデバイスとの接続やサービスの詳細な把握に役立つ。<br>
ただし、全ての情報が必ず使用可能とは限らないため、デバイスやサービスの種類によっては取得できる情報が異なることに注意する。<br>
<br>
* サービスの基本情報
** serviceDescription()
**: サービスの説明文
** serviceName()
**: サービス名
** serviceProvider()
**: サービスプロバイダ名
** serviceUuid()
**: サービスのUUID
** serviceAvailability()
**: サービスの可用性 (0x00~0xFF)
*: <br>
* プロトコル関連
** protocolServiceMultiplexer()
**: プロトコルサービス多重化子 (PSM) 値
** serverChannel()
**: RFCOMMサーバチャンネル番号
** socketProtocol()
**: 使用されているソケットプロトコル (RFCOMM / L2CAP)
*: <br>
* サービスクラス情報
** serviceClassUuids()
**: サービスクラスのUUIDリスト
** serviceProvider()
**: サービスプロバイダ情報
*: <br>
* 接続関連
** device()
**: サービスを提供しているデバイス情報
** isComplete()
**: サービス情報が完全かどうか
** isRegistered()
**: サービスが登録されているかどうか
** isValid()
**: サービス情報が有効かどうか
*: <br>
* 属性関連
** attributes()
**: 全ての属性のリスト
** contains(quint16 attributeId)
**: 特定の属性の存在確認
** attribute(quint16 attributeId)
**: 特定の属性値の取得
*: <br>
* プロファイル関連
** majorServiceClass()
**: メジャーサービスクラス (オーディオ、電話等)
** minorServiceClass()
**: マイナーサービスクラス (詳細なサービス種別)
<br>
<syntaxhighlight lang="c++">
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, [](const QBluetoothServiceInfo &info) {
    // サービス名を取得
    QString name = info.serviceName();
    // UUIDを取得
    QBluetoothUuid uuid = info.serviceUuid();
    // プロトコルを取得
    int protocol = info.protocolServiceMultiplexer();
    qDebug() << "サービス名: " << name;
    qDebug() << "UUID: "    << uuid.toString();
    qDebug() << "プロトコル: " << protocol;
});
</syntaxhighlight>
<br>
==== 探索完了 または エラー時 ====
<code>QBluetoothServiceDiscoveryAgent::finished</code>シグナルで探索完了を検知する。<br>
<br>
<code>QBluetoothServiceDiscoveryAgent::error</code>シグナルでエラーを検知する。<br>
<br>
必要に応じて、<code>QBluetoothServiceDiscoveryAgent::stop</code>メソッドで探索を終了することも可能である。<br>
<br>
<syntaxhighlight lang="c++">
// 完了時の処理
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, []() {
    qDebug() << "探索が完了しました";
});
// エラー時の処理
connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::error, [](QBluetoothServiceDiscoveryAgent::Error error) {
    switch (error) {
      case QBluetoothServiceDiscoveryAgent::NoError:
          qDebug() << "エラーなし";
          break;
      case QBluetoothServiceDiscoveryAgent::PoweredOffError:
          qDebug() << "Bluetoothがオフです";
          break;
      default:
          qDebug() << "その他のエラー";
    }
});
// 探索を途中で停止する場合
discoveryAgent.stop();
</syntaxhighlight>
<br>
==== 組み合わせ ====
  <syntaxhighlight lang="c++">
  <syntaxhighlight lang="c++">
  #include <QBluetoothServiceDiscoveryAgent>
  #include <QBluetoothServiceDiscoveryAgent>
  #include <QBluetoothAddress>
  #include <QBluetoothAddress>
  #include <QDebug>
  #include <QDebug>
#include <memory>
   
   
  class ServiceDiscovery : public QObject
  class ServiceDiscovery : public QObject
393行目: 547行目:
   
   
  private:
  private:
     std::unique_ptr<QBluetoothServiceDiscoveryAgent> discoveryAgent;
     QBluetoothServiceDiscoveryAgent discoveryAgent;
   
   
     // プロトコルタイプを文字列に変換するヘルパー関数
     // プロトコルタイプ
     QString protocolToString(int protocol)
     QString protocolToString(int protocol)
     {
     {
407行目: 561行目:
     }
     }
   
   
     // エラーコードを文字列に変換するヘルパー関数
     // エラーコード
     QString errorToString(QBluetoothServiceDiscoveryAgent::Error error)
     QString errorToString(QBluetoothServiceDiscoveryAgent::Error error)
     {
     {
421行目: 575行目:
   
   
  public:
  public:
     explicit ServiceDiscovery(QObject* parent = nullptr) : QObject(parent)
     explicit ServiceDiscovery(QObject* parent = nullptr) : QObject(parent), discoveryAgent(this)
     {
     {
       try {
       // 各種シグナルとスロットの接続
          // サービス探索エージェントの作成
      connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &ServiceDiscovery::onServiceDiscovered);
          discoveryAgent = std::make_unique<QBluetoothServiceDiscoveryAgent>(this);
      connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, this, &ServiceDiscovery::onScanFinished);
      connect(&discoveryAgent, static_cast<void(QBluetoothServiceDiscoveryAgent::*)(QBluetoothServiceDiscoveryAgent::Error)>(&QBluetoothServiceDiscoveryAgent::error),
          // 各種シグナルとスロットの接続
              this, &ServiceDiscovery::onError);
          connect(discoveryAgent.get(), &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &ServiceDiscovery::onServiceDiscovered);
          connect(discoveryAgent.get(), &QBluetoothServiceDiscoveryAgent::finished, this, &ServiceDiscovery::onScanFinished);
          connect(discoveryAgent.get(), static_cast<void(QBluetoothServiceDiscoveryAgent::*)(QBluetoothServiceDiscoveryAgent::Error)>(&QBluetoothServiceDiscoveryAgent::error),
                  this, &ServiceDiscovery::onError);
      }
      catch (const std::exception &e) {
          qDebug() << "初期化エラー: " << e.what();
          throw;
      }
     }
     }
   
   
442行目: 587行目:
     void startDiscovery(const QBluetoothAddress& address)
     void startDiscovery(const QBluetoothAddress& address)
     {
     {
       try {
       qDebug() << "サービス探索を開始...";
          qDebug() << "サービス探索を開始...";
      discoveryAgent.setRemoteAddress(address);
          discoveryAgent->setRemoteAddress(address);
      discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
          discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
      }
      catch (const std::exception &e) {
          qDebug() << "探索開始エラー:" << e.what();
          throw;
      }
     }
     }
   
   
456行目: 595行目:
     void stopDiscovery()
     void stopDiscovery()
     {
     {
       try {
       qDebug() << "サービス探索を停止...";
          qDebug() << "サービス探索を停止...";
      discoveryAgent.stop();
          discoveryAgent->stop();
      }
      catch (const std::exception &e) {
          qDebug() << "探索停止エラー:" << e.what();
          throw;
      }
     }
     }
   
   
  private slots:
  private slots:
     // サービスの探索に成功した場合のスロット
     // サービスの探索に成功した場合
     void onServiceDiscovered(const QBluetoothServiceInfo& service)
     void onServiceDiscovered(const QBluetoothServiceInfo& service)
     {
     {
476行目: 609行目:
   
   
       // サービスの詳細情報を表示
       // サービスの詳細情報を表示
       if (service.serviceUuid().isNull()) {
       if (service.serviceUuid().isNull()) qDebug() << "  UUID: カスタムUUID";
          qDebug() << "  UUID: カスタムUUID";
       else                               qDebug() << "  UUID: " << service.serviceUuid().toString();
      }
       else {
      // サービスクラスを表示
          qDebug() << "  UUID: " << service.serviceUuid().toString();
      QList<QBluetoothUuid> serviceClasses = service.serviceClassUuids();
      if (!serviceClasses.isEmpty()) {
          qDebug() << "  サービスクラス:";
          for (const QBluetoothUuid& uuid : serviceClasses) {
            qDebug() << "    -" << uuid.toString();
          }
       }
       }
   
   
493行目: 631行目:
     }
     }
   
   
     // 探索完了時のスロット
     // 探索完了時
     void onScanFinished()
     void onScanFinished()
     {
     {
499行目: 637行目:
     }
     }
   
   
     // エラー発生時のスロット
     // エラー発生時
     void onError(QBluetoothServiceDiscoveryAgent::Error error)
     void onError(QBluetoothServiceDiscoveryAgent::Error error)
     {
     {
505行目: 643行目:
     }
     }
  };
  };
</syntaxhighlight>
<br>
<syntaxhighlight lang="c++">
// 使用方法
ServiceDiscovery discovery;
// 例: MACアドレスを指定してサービス探索を開始
QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
discovery.startDiscovery(address);
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>

2024年12月18日 (水) 07:02時点における版

概要

QtのBluetoothサポートは、Qt Bluetoothモジュールを通じて提供されている。
Qt Bluetoothモジュールは、クロスプラットフォームなBluetooth通信機能を実現するための包括的なAPIセットとなっている。

主要なコンポーネントとして、Classic BluetoothとBluetooth Low Energy (BLE) の両方をサポートしている。

  • Classic Bluetooth
    従来型の高帯域幅通信
  • BLE
    省電力デバイスとの通信


デバイスの検出と管理において、Qt Bluetoothモジュールは以下に示す機能を提供している。

  • デバイススキャンと検出
  • サービスディスカバリ
  • ペアリング管理
  • 接続の確立と維持


上記の機能は相互に関連しており、アプリケーションでは以下に示すような流れで使用される。

  1. まず、デバイススキャンを行い、周囲のデバイスを探索する。
  2. 目的のデバイスが存在する場合、サービスディスカバリでそのデバイスの機能を確認する。
  3. 次に、必要に応じてペアリングを実行する。
  4. 最後に、接続を確立してデータ通信を開始する。


Classic Bluetoothでの通信では、RFCOMMプロトコルを使用したシリアルポート型の通信が可能である。
これは QBluetoothSocketクラスを通じて実装されており、TCP/IPソケットに似た使い方ができる。

BLEについては、GATT (Generic Attribute Profile) プロトコルをベースとしたサービスとキャラクタリスティックの概念を使用する。
QLowEnergyControllerクラスがBLE通信の中心的な役割を果たしており、デバイスとの接続やデータのやり取りを管理する。

セキュリティ面においては、ペアリング、セキュアな接続の確立、暗号化等の機能が組み込まれている。
また、プラットフォーム固有のセキュリティ要件にも対応している。

実装する場合の注意点として、OSごとの権限設定や制限事項への対応が必要となる。
特に、モバイルプラットフォームでは適切な権限の設定が重要である。
また、Bluetooth機能の有無やステータスの確認も必要である。

エラーハンドリングについては、接続の切断、タイムアウト、デバイスが見つからない場合等、様々な状況に対応する必要がある。

Qt Bluetoothモジュールは、これらの状況を適切に検出して、シグナル / スロットメカニズムを通じて通知する仕組みを提供している。

※Classic Bluetoothを使用した場合の注意

  • ソケットベースの通信に関する知識が重要である。
  • 大きなデータ転送に適しているが、電力消費も大きい。


ソフトウェアの要件 (データ量、通信頻度、電力消費等) に応じて適切な方式を選択する必要がある。
また、多くのモダンなBluetoothデバイスはBLEを採用する傾向にあり、特に省電力性が重要な場合はBLEが推奨される。


Classic Bluetoothの特徴

Classic Bluetoothは、QBluetoothSocketクラスを使用した直接的なソケット通信である。

RFCOMMプロトコルベースの通信であり、主に、シリアル通信のような双方向データストリームに適している。
Bluetooth Low Energyと比較して、より大きなデータ転送が可能である。


Bluetoothの設定 (接続先)

接続先のLinuxにおいて、Bluetoothの設定および状態を確認する。

bluetoothctl show

# 出力例:
Controller 00:E0:4C:23:99:87 (public) 
       Name: mobian 
       Alias: mobian 
       Class: 0x00000110 
       Powered: yes 
       Discoverable: no 
       DiscoverableTimeout: 0x000000b4 
       Pairable: no 
       UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) 
       UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb) 
       UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb) 
       UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb) 
       UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb) 
       UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb) 
       Modalias: usb:xxxxxxxxxxxxxxx 
       Discovering: no 
       Roles: central 
       Roles: peripheral 
Advertising Features: 
       ActiveInstances: 0x00 (0) 
       SupportedInstances: 0x05 (5) 
       SupportedIncludes: tx-power 
       SupportedIncludes: appearance 
       SupportedIncludes: local-name


接続先デバイスが"Discoverable: no"となっている場合は、一時的にデバイスを検出を有効にする。
または、既知のMACアドレスを使用して直接接続を試みる。

bluetoothctl discoverable on


接続先デバイスが"Pairable: no"となっている場合は、一時的にペアリングを有効にする。

bluetoothctl pairable on


"Roles"が"central""peripheral"の両方をサポートしている場合は、接続モードを明示的に指定することを推奨する。

上記の設定は、システムを再起動すると元の状態に戻る、あるいは、一定時間 (通常は180秒) が経過すると自動的に無効になる。

永続的に設定を変更する場合は、Bluetoothの設定ファイルである/etc/bluetooth/main.confファイルを編集する必要がある。
ただし、セキュリティの観点から、必要な時だけ一時的に有効にすることが推奨される。

sudo vi /etc/bluetooth/main.conf


 # /etc/bluetooth/main.confファイル
 
 [General]
 # デバイスは永続的に検出可能な状態となる
 DiscoverableTimeout = 0
 
 # 常にペアリング可能な状態となる
 AlwaysPairable = true


デバイスを検出を有効にする。

bluetoothctl discoverable on


設定を反映させるため、BlueZサービスを再起動する。

sudo systemctl restart bluetooth


また、セキュリティ要件に応じて適切な認証方法を設定する。


デバイススキャンと検出

デバイススキャンは、周囲のBluetooth対応デバイスを探索するプロセスである。
このプロセスでは、アクティブに電波を送信して応答を待つアクティブスキャン、他のデバイスからのアドバタイズメントを受信するパッシブスキャンの2種類がある。

  • アクティブスキャン
  • パッシブスキャン


スキャン中は、各デバイスの基本情報 (デバイス名、MACアドレス、デバイスクラス、信号強度等) を取得できる。
また、スキャン時間や範囲を設定可能であり、バッテリー消費とスキャン精度のバランスを取ることができる。

Bluetooth Classicでは、QBluetoothDeviceDiscoveryAgentを使用して、全てのBluetoothデバイスを検出する。

Bluetoothアダプタの初期化 / デバイス検出用エージェントの生成

Bluetoothアダプタを初期化する。
デバイス検出用エージェントの生成する。

また、BLEデバイスは検出対象から除外する。

 #include <QBluetoothDeviceDiscoveryAgent>
 #include <QBluetoothDeviceInfo>
 
 // Bluetoothアダプタの初期化とデバイス検出エージェントの生成
 QBluetoothDeviceDiscoveryAgent discoveryAgent;
 
 // Bluetooth Classicのみをスキャン (BLEデバイスを除外)
 discoveryAgent.setDiscoveryModes(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
 
 // 検出時にもBLEデバイスをフィルタリング
 connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, [](const QBluetoothDeviceInfo &device) {
    // BLEデバイスの場合
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
       return;
    }
 
    // Bluetooth Classicデバイスの場合
    // ...略
 });


デバイス検出イベントの設定

デバイスが見つかった時のイベントする。
スキャン完了時のイベント処理を設定する。
エラー発生時のイベント処理を設定する。

 // デバイス発見時のイベント
 connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, [](const QBluetoothDeviceInfo &device) {
    // デバイス発見時の処理
 });
 
 // スキャン完了時のイベント
 connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, []() {
    // スキャン完了時の処理
 });
 
 // エラー発生時のイベント
 connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred, [](QBluetoothDeviceDiscoveryAgent::Error error) {
    // エラー発生時の処理
 });


スキャンの開始

スキャン中の場合は停止する。
新しいスキャンを開始する。

 // アクティブなスキャンがある場合は停止
 if (discoveryAgent.isActive()) {
    discoveryAgent.stop();
 }
 
 // スキャン開始
 discoveryAgent.start();


デバイス検出時

検出したデバイスがBLEかどうかを確認する。 Bluetooth Classicの場合は、デバイス名、MACアドレスを取得する。

 void handleDeviceDiscovered(const QBluetoothDeviceInfo &device) 
 {
    // BLEデバイスをスキップ
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
       return;
    }
 
    // デバイス情報の取得
    QString name    = device.name();
    QString address = device.address().toString();
 
    qDebug() << "Found device : " << name << address;
 }


スキャン完了時

スキャンの完了を通知する。
必要に応じてスキャンを再開または終了する。

 void handleScanFinished() 
 {
    qDebug() << "スキャン完了";
 
    // 必要に応じてスキャンを再開
    // discoveryAgent.start();
 }


エラー発生時

Bluetooth Classicでは、以下に示すようなエラー処理がある。

  • Bluetoothの電源が入っていない。
  • Bluetoothアダプタが見つからない。
  • 入出力エラー
  • その他のエラー


 void handleError(QBluetoothDeviceDiscoveryAgent::Error error) 
 {
    switch (error) {
       case QBluetoothDeviceDiscoveryAgent::PoweredOffError:
          qDebug() << "Bluetoothの電源が入っていません";
          break;
       case QBluetoothDeviceDiscoveryAgent::InputOutputError:
          qDebug() << "I/Oエラー";
          break;
       case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError:
          qDebug() << "Bluetoothアダプタが無効です";
          break;
       default:
          qDebug() << "不明なエラー";
          break;
    }
 }


組み合わせ

 #include <QObject>
 #include <QBluetoothDeviceDiscoveryAgent>
 #include <QBluetoothDeviceInfo>
 #include <stdexcept>
 #include <QDebug>
 
 class BluetoothScanner : public QObject
 {
    Q_OBJECT
 
 private:
    QBluetoothDeviceDiscoveryAgent discoveryAgent;
 
    // デバイスタイプ
    QString deviceTypeToString(QBluetoothDeviceInfo::MajorDeviceClass type)
    {
       switch (type) {
          case QBluetoothDeviceInfo::MiscellaneousDevice: return "その他";
          case QBluetoothDeviceInfo::ComputerDevice:      return "コンピュータ";
          case QBluetoothDeviceInfo::PhoneDevice:         return "電話";
          case QBluetoothDeviceInfo::AudioVideoDevice:    return "オーディオ/ビデオ";
          case QBluetoothDeviceInfo::NetworkDevice:       return "ネットワーク";
          case QBluetoothDeviceInfo::PeripheralDevice:    return "周辺機器";
          case QBluetoothDeviceInfo::ImagingDevice:       return "イメージング";
          case QBluetoothDeviceInfo::WearableDevice:      return "ウェアラブル";
          case QBluetoothDeviceInfo::ToyDevice:           return "おもちゃ";
          case QBluetoothDeviceInfo::HealthDevice:        return "ヘルスケア";
          default: return "不明";
        }
    }
 
    // エラーコード
    QString errorToString(QBluetoothDeviceDiscoveryAgent::Error error)
    {
       switch (error) {
          case QBluetoothDeviceDiscoveryAgent::NoError:                      return "エラーなし";
          case QBluetoothDeviceDiscoveryAgent::InputOutputError:             return "I/Oエラー";
          case QBluetoothDeviceDiscoveryAgent::PoweredOffError:              return "Bluetoothがオフ";
          case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError: return "無効なアダプタ";
          case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError:     return "未対応のプラットフォーム";
          case QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod:   return "未対応の探索方法";
          default: return "不明なエラー";
        }
    }
 
 public:
    explicit BluetoothScanner(QObject *parent = nullptr) : QObject(parent)
    {
       try {
          // デバイス探索エージェントの作成
          discoveryAgent = std::make_unique<QBluetoothDeviceDiscoveryAgent>(this);
 
          // 各種シグナルとスロットの接続
          connect(discoveryAgent.get(), &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothScanner::onDeviceDiscovered);
          connect(discoveryAgent.get(), &QBluetoothDeviceDiscoveryAgent::finished, this, &BluetoothScanner::onScanFinished);
          connect(discoveryAgent.get(), static_cast<void(QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error),
                  this, &BluetoothScanner::onError);
       }
       catch (const std::exception &e) {
          qDebug() << "初期化エラー: " << e.what();
          throw;
       }
    }
 
    // スキャンの開始
    void startScan()
    {
       try {
          qDebug() << "デバイススキャンを開始...";
          discoveryAgent.start();
       }
       catch (const std::exception &e) {
          qDebug() << "スキャン開始エラー: " << e.what();
          throw;
       }
    }
 
    // スキャンの停止
    void stopScan()
    {
       try {
          qDebug() << "デバイススキャンを停止...";
          discoveryAgent.stop();
       }
       catch (const std::exception &e) {
          qDebug() << "スキャン停止エラー: " << e.what();
          throw;
       }
    }
 
 private slots:
    // デバイスが存在する場合のスロット
    void onDeviceDiscovered(const QBluetoothDeviceInfo &device)
    {
       qDebug() << "デバイスの探索に成功:";
       qDebug() << "  名前: "           << device.name();
       qDebug() << "  アドレス: "       << device.address().toString();
       qDebug() << "  RSSI: "           << device.rssi();
       qDebug() << "  デバイスタイプ: " << deviceTypeToString(device.majorDeviceClass());
    }
 
    // スキャン完了時のスロット
    void onScanFinished()
    {
        qDebug() << "デバイススキャンが完了";
    }
 
    // エラー発生時のスロット
    void onError(QBluetoothDeviceDiscoveryAgent::Error error)
    {
        qDebug() << "エラーが発生: " << errorToString(error);
    }
 };



サービスディスカバリ

特定のBluetoothデバイスが提供するサービスを探索するプロセスである。
各Bluetoothデバイスは複数のサービスを提供できるが、
サービスディスカバリによってそのデバイスがどのようなサービス (シリアル通信、オーディオ転送、ファイル転送等) を提供しているかを特定できる。

各サービスはUUIDで識別されており、標準的なサービスには予め定義されたUUIDが割り当てられている。
また、カスタムサービスの場合は独自のUUIDを使用する。

Classic Bluetoothでは、QBluetoothServiceInfoクラスを使用して、単純なサービス情報を管理する。

Bluetoothサービスディスカバリの準備

QBluetoothServiceDiscoveryAgentクラスのインスタンスを生成する。

以下に示すシグナルを接続する。

  • QBluetoothServiceDiscoveryAgent::serviceDiscovered
    発見したサービスを通知する。
  • QBluetoothServiceDiscoveryAgent::finished
    探索完了を通知する。
  • QBluetoothServiceDiscoveryAgent::error
    エラー発生時の通知


 #include <QBluetoothServiceDiscoveryAgent>
 #include <QDebug>
 
 QBluetoothServiceDiscoveryAgent discoveryAgent;
 
 // シグナルとスロットの接続
 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, [](const QBluetoothServiceInfo &info) {
    qDebug() << "サービス発見";
 });
 
 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, []() {
    qDebug() << "探索完了";
 });
 
 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::error, [](QBluetoothServiceDiscoveryAgent::Error error) {
    qDebug() << "エラー発生";
 });


サービスディスカバリの開始

QBluetoothAddressクラスを使用して、対象デバイスのBluetoothアドレスを指定する。

QBluetoothServiceDiscoveryAgent::setRemoteAddressメソッドを実行して、探索対象のデバイスを設定する。
QBluetoothServiceDiscoveryAgent::startメソッドを実行して、ディスカバリを開始する。

 #include <QBluetoothAddress>
 
 // デバイスのBluetoothアドレスを設定
 QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
 discoveryAgent.setRemoteAddress(address);
 
 // ディスカバリ開始
 discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);


サービスの発見時

QBluetoothServiceDiscoveryAgent::serviceDiscoveredシグナルで通知されたQBluetoothServiceInfoクラスから必要な情報を取得する。

QBluetoothServiceInfoクラスでは、以下に示す情報が取得できる。

これらの情報は、Bluetoothデバイスとの接続やサービスの詳細な把握に役立つ。
ただし、全ての情報が必ず使用可能とは限らないため、デバイスやサービスの種類によっては取得できる情報が異なることに注意する。

  • サービスの基本情報
    • serviceDescription()
      サービスの説明文
    • serviceName()
      サービス名
    • serviceProvider()
      サービスプロバイダ名
    • serviceUuid()
      サービスのUUID
    • serviceAvailability()
      サービスの可用性 (0x00~0xFF)

  • プロトコル関連
    • protocolServiceMultiplexer()
      プロトコルサービス多重化子 (PSM) 値
    • serverChannel()
      RFCOMMサーバチャンネル番号
    • socketProtocol()
      使用されているソケットプロトコル (RFCOMM / L2CAP)

  • サービスクラス情報
    • serviceClassUuids()
      サービスクラスのUUIDリスト
    • serviceProvider()
      サービスプロバイダ情報

  • 接続関連
    • device()
      サービスを提供しているデバイス情報
    • isComplete()
      サービス情報が完全かどうか
    • isRegistered()
      サービスが登録されているかどうか
    • isValid()
      サービス情報が有効かどうか

  • 属性関連
    • attributes()
      全ての属性のリスト
    • contains(quint16 attributeId)
      特定の属性の存在確認
    • attribute(quint16 attributeId)
      特定の属性値の取得

  • プロファイル関連
    • majorServiceClass()
      メジャーサービスクラス (オーディオ、電話等)
    • minorServiceClass()
      マイナーサービスクラス (詳細なサービス種別)


 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, [](const QBluetoothServiceInfo &info) {
    // サービス名を取得
    QString name = info.serviceName();
 
    // UUIDを取得
    QBluetoothUuid uuid = info.serviceUuid();
 
    // プロトコルを取得
    int protocol = info.protocolServiceMultiplexer();
 
    qDebug() << "サービス名: " << name;
    qDebug() << "UUID: "     << uuid.toString();
    qDebug() << "プロトコル: " << protocol;
 });


探索完了 または エラー時

QBluetoothServiceDiscoveryAgent::finishedシグナルで探索完了を検知する。

QBluetoothServiceDiscoveryAgent::errorシグナルでエラーを検知する。

必要に応じて、QBluetoothServiceDiscoveryAgent::stopメソッドで探索を終了することも可能である。

 // 完了時の処理
 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, []() {
    qDebug() << "探索が完了しました";
 });
 
 // エラー時の処理
 connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::error, [](QBluetoothServiceDiscoveryAgent::Error error) {
    switch (error) {
       case QBluetoothServiceDiscoveryAgent::NoError:
          qDebug() << "エラーなし";
          break;
       case QBluetoothServiceDiscoveryAgent::PoweredOffError:
          qDebug() << "Bluetoothがオフです";
          break;
       default:
          qDebug() << "その他のエラー";
    }
 });
 
 // 探索を途中で停止する場合
 discoveryAgent.stop();


組み合わせ

 #include <QBluetoothServiceDiscoveryAgent>
 #include <QBluetoothAddress>
 #include <QDebug>
 
 class ServiceDiscovery : public QObject
 {
    Q_OBJECT
 
 private:
    QBluetoothServiceDiscoveryAgent discoveryAgent;
 
    // プロトコルタイプ
    QString protocolToString(int protocol)
    {
       switch (protocol) {
          case 0:  return "不明なプロトコル";
          case 1:  return "SDP";
          case 3:  return "RFCOMM";
          case 15: return "L2CAP";
          default: return QString("その他のプロトコル (%1)").arg(protocol);
       }
    }
 
    // エラーコード
    QString errorToString(QBluetoothServiceDiscoveryAgent::Error error)
    {
       switch (error) {
          case QBluetoothServiceDiscoveryAgent::NoError:                      return "エラーなし";
          case QBluetoothServiceDiscoveryAgent::InputOutputError:             return "I/Oエラー";
          case QBluetoothServiceDiscoveryAgent::PoweredOffError:              return "Bluetoothがオフ";
          case QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError: return "無効なアダプタ";
          case QBluetoothServiceDiscoveryAgent::UnknownError:                 return "不明なエラー";
          default: return "予期せぬエラー";
       }
    }
 
 public:
    explicit ServiceDiscovery(QObject* parent = nullptr) : QObject(parent), discoveryAgent(this)
    {
       // 各種シグナルとスロットの接続
       connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &ServiceDiscovery::onServiceDiscovered);
       connect(&discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, this, &ServiceDiscovery::onScanFinished);
       connect(&discoveryAgent, static_cast<void(QBluetoothServiceDiscoveryAgent::*)(QBluetoothServiceDiscoveryAgent::Error)>(&QBluetoothServiceDiscoveryAgent::error),
               this, &ServiceDiscovery::onError);
    }
 
    // 特定のデバイスのサービス探索を開始
    void startDiscovery(const QBluetoothAddress& address)
    {
       qDebug() << "サービス探索を開始...";
       discoveryAgent.setRemoteAddress(address);
       discoveryAgent.start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
    }
 
    // 探索を停止
    void stopDiscovery()
    {
       qDebug() << "サービス探索を停止...";
       discoveryAgent.stop();
    }
 
 private slots:
    // サービスの探索に成功した場合
    void onServiceDiscovered(const QBluetoothServiceInfo& service)
    {
       qDebug() << "サービスの探索に成功:";
       qDebug() << "  サービス名: "   << service.serviceName();
       qDebug() << "  サービス説明: " << service.serviceDescription();
       qDebug() << "  プロトコル: "   << protocolToString(service.protocolServiceMultiplexer());
 
       // サービスの詳細情報を表示
       if (service.serviceUuid().isNull()) qDebug() << "  UUID: カスタムUUID";
       else                                qDebug() << "  UUID: " << service.serviceUuid().toString();
 
       // サービスクラスを表示
       QList<QBluetoothUuid> serviceClasses = service.serviceClassUuids();
       if (!serviceClasses.isEmpty()) {
          qDebug() << "  サービスクラス:";
          for (const QBluetoothUuid& uuid : serviceClasses) {
             qDebug() << "    -" << uuid.toString();
          }
       }
 
       // サービスクラスを表示
       QList<QBluetoothUuid> serviceClasses = service.serviceClassUuids();
       if (!serviceClasses.isEmpty()) {
          qDebug() << "  サービスクラス:";
          for (const QBluetoothUuid& uuid : serviceClasses) {
             qDebug() << "    -" << uuid.toString();
          }
       }
    }
 
    // 探索完了時
    void onScanFinished()
    {
       qDebug() << "サービス探索が完了";
    }
 
    // エラー発生時
    void onError(QBluetoothServiceDiscoveryAgent::Error error)
    {
       qDebug() << "エラーが発生: " << errorToString(error);
    }
 };


 // 使用方法
 
 ServiceDiscovery discovery;
 
 // 例: MACアドレスを指定してサービス探索を開始
 QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
 discovery.startDiscovery(address);



ペアリング管理

ペアリングは、2つのBluetoothデバイス間で安全な通信を確立するための認証プロセスである。

このプロセスおける重要な要素を以下に示す。

  • 初回のペアリング時において、PINコードや数値の確認による認証を行う。
  • ペアリング情報は両デバイスに保存されて、再接続時に使用する。
  • セキュリティレベルの設定が可能であり、必要に応じて暗号化強度を変更できる。
  • ペアリング状態の管理 (新規ペアリング、ペアリング解除、ペアリング状態の確認等) が含まれる。
  • 1度ペアリングされたデバイスは、特別な設定がない限り、自動的に再接続可能になる。


 #include <QBluetoothLocalDevice>
 #include <QDebug>
 #include <memory>
 
 class PairingManager : public QObject
 {
    Q_OBJECT
 
 private:
    std::unique_ptr<QBluetoothLocalDevice> localDevice;
 
    // ペアリング状態を文字列に変換するヘルパー関数
    QString pairingStatusToString(QBluetoothLocalDevice::Pairing status)
    {
       switch (status) {
          case QBluetoothLocalDevice::Unpaired:         return "未ペアリング";
          case QBluetoothLocalDevice::Paired:           return "ペアリング済み";
          case QBluetoothLocalDevice::AuthorizedPaired: return "認証済みペアリング";
          default:                                      return "不明な状態";
       }
    }
 
 public:
    explicit PairingManager(QObject* parent = nullptr) : QObject(parent)
    {
       try {
          // ローカルBluetoothデバイスの初期化
          localDevice = std::make_unique<QBluetoothLocalDevice>(this);
 
          // 各種シグナルとスロットの接続
          connect(localDevice.get(), &QBluetoothLocalDevice::pairingFinished, this, &PairingManager::onPairingFinished);
          connect(localDevice.get(), &QBluetoothLocalDevice::error, this, &PairingManager::onError);
          connect(localDevice.get(), &QBluetoothLocalDevice::pairingDisplayConfirmation, this, &PairingManager::onPairingConfirmationRequest);
          connect(localDevice.get(), &QBluetoothLocalDevice::pairingDisplayPinCode, this, &PairingManager::onPairingDisplayPinCode);
       }
       catch (const std::exception &e) {
          qDebug() << "初期化エラー:" << e.what();
          throw;
       }
    }
 
    // ペアリングを開始
    void requestPairing(const QBluetoothAddress& address)
    {
       try {
          qDebug() << "ペアリングを開始..." << address.toString();
          localDevice->requestPairing(address, QBluetoothLocalDevice::Paired);
       }
       catch (const std::exception &e) {
          qDebug() << "ペアリング開始エラー: " << e.what();
          throw;
       }
    }
 
    // ペアリングの解除
    void removePairing(const QBluetoothAddress& address)
    {
       try {
          qDebug() << "ペアリングを解除..." << address.toString();
          localDevice->requestPairing(address, QBluetoothLocalDevice::Unpaired);
       }
       catch (const std::exception &e) {
          qDebug() << "ペアリング解除エラー:" << e.what();
          throw;
       }
    }
 
    // ペアリング状態の確認
    QBluetoothLocalDevice::Pairing getPairingStatus(const QBluetoothAddress &address)
    {
       try {
          return localDevice->pairingStatus(address);
       }
       catch (const std::exception &e) {
          qDebug() << "ペアリング状態確認エラー:" << e.what();
          throw;
       }
    }
 
    // ペアリング済みデバイスの一覧を取得
    QList<QBluetoothAddress> getPairedDevices()
    {
       try {
          return localDevice->connectedDevices();
       }
       catch (const std::exception &e) {
          qDebug() << "ペアリング済みデバイス取得エラー: " << e.what();
          throw;
       }
    }
 
 private slots:
    // ペアリング完了時のスロット
    void onPairingFinished(const QBluetoothAddress& address, QBluetoothLocalDevice::Pairing status)
    {
       qDebug() << "ペアリング処理が完了:";
       qDebug() << "  デバイス:" << address.toString();
       qDebug() << "  状態:" << pairingStatusToString(status);
    }
 
    // エラー発生時のスロット
    void onError()
    {
        qDebug() << "ペアリング処理でエラーが発生";
    }
 
    // ペアリング確認要求時のスロット
    void onPairingConfirmationRequest(const QBluetoothAddress& address, QString pin)
    {
       qDebug() << "ペアリング確認要求を受信:";
       qDebug() << "  デバイス:" << address.toString();
       qDebug() << "  確認コード:" << pin;
 
       // ここに、ユーザに確認を求めるUIを表示する
       // 以下の例では、自動的に確認している
       localDevice->pairingConfirmation(true);
    }
 
    // PINコード表示要求時のスロット
    void onPairingDisplayPinCode(const QBluetoothAddress &address, QString pin)
    {
       qDebug() << "PINコード表示要求を受信:";
       qDebug() << "  デバイス:" << address.toString();
       qDebug() << "  PINコード:" << pin;
    }
 };



接続の確立と維持

実際のデータ通信を行うための接続管理プロセスである。

  • 接続の確立
    ペアリング済みデバイスとの接続を開始する。
  • 接続状態の監視
    接続品質、切断検知、エラー検出等を常時監視する。
  • 再接続管理
    予期せぬ切断が発生した場合の自動再接続処理を行う。
  • データ転送の管理
    送受信バッファの管理、フロー制御、エラー訂正等を実施する。
  • 接続パラメータの最適化
    電力消費、通信速度、接続の安定性等のバランスを取る。


Classic Bluetoothでは、QBluetoothSocketクラスを使用して、
connectメソッドでソケット接続、writeメソッド / readメソッドでデータの送受信を行う。

 #include <QBluetoothSocket>
 #include <QDebug>
 #include <memory>
 
 class BluetoothConnection : public QObject
 {
    Q_OBJECT
 
 private:
    std::unique_ptr<QBluetoothSocket> socket;
 
    // エラーコードを文字列に変換するヘルパー関数
    QString errorToString(QBluetoothSocket::SocketError error)
    {
       switch (error) {
          case QBluetoothSocket::NoSocketError:        return "エラーなし";
          case QBluetoothSocket::UnknownSocketError:   return "不明なエラー";
          case QBluetoothSocket::HostNotFoundError:    return "ホストが見つかりません";
          case QBluetoothSocket::ServiceNotFoundError: return "サービスが見つかりません";
          case QBluetoothSocket::NetworkError:         return "ネットワークエラー";
          case QBluetoothSocket::OperationError:       return "操作エラー";
          default:                                     return "予期せぬエラー";
       }
    }
 
    // 接続状態を文字列に変換するヘルパー関数
    QString stateToString(QBluetoothSocket::SocketState state)
    {
       switch (state) {
          case QBluetoothSocket::UnconnectedState:   return "未接続";
          case QBluetoothSocket::ConnectingState:    return "接続中";
          case QBluetoothSocket::ConnectedState:     return "接続済み";
          case QBluetoothSocket::BoundState:         return "バインド済み";
          case QBluetoothSocket::ClosingState:       return "切断中";
          case QBluetoothSocket::ServiceLookupState: return "サービス検索中";
          default:                                   return "不明な状態";
       }
    }
 
 public:
    explicit BluetoothConnection(QObject* parent = nullptr) : QObject(parent)
    {
       try {
          // RFCOMMソケットの作成
          socket = std::make_unique<QBluetoothSocket>(QBluetoothServiceInfo::RfcommProtocol, this);
 
          // 各種シグナルとスロットの接続
          connect(socket.get(), &QBluetoothSocket::connected, this, &BluetoothConnection::onConnected);
          connect(socket.get(), &QBluetoothSocket::disconnected, this, &BluetoothConnection::onDisconnected);
          connect(socket.get(), &QBluetoothSocket::errorOccurred, this, &BluetoothConnection::onError);
          connect(socket.get(), &QBluetoothSocket::readyRead, this, &BluetoothConnection::onDataReceived);
          connect(socket.get(), &QBluetoothSocket::stateChanged, this, &BluetoothConnection::onStateChanged);
       }
       catch (const std::exception &e) {
          qDebug() << "初期化エラー: " << e.what();
          throw;
       }
    }
 
    // デバイスに接続
    void connectToDevice(const QBluetoothAddress &address, quint16 port)
    {
       try {
          if (socket->state() == QBluetoothSocket::ConnectedState) {
             qDebug() << "既に接続済み";
             return;
          }
 
          qDebug() << "デバイスに接続します: " << address.toString();
          qDebug() << "ポート: " << port;
          socket->connectToService(address, port);
       }
       catch (const std::exception &e) {
          qDebug() << "接続エラー: " << e.what();
          throw;
       }
    }
 
    // 接続を切断
    void disconnect()
    {
       try {
          if (socket->state() != QBluetoothSocket::UnconnectedState) {
             qDebug() << "接続を切断...";
             socket->disconnectFromService();
          }
       }
       catch (const std::exception &e) {
          qDebug() << "切断エラー: " << e.what();
          throw;
       }
    }
 
    // データを送信
    bool sendData(const QByteArray &data)
    {
       try {
          if (socket->state() != QBluetoothSocket::ConnectedState) {
             qDebug() << "送信エラー: 接続されていません";
             return false;
          }
 
          qint64 bytesWritten = socket->write(data);
          if (bytesWritten == -1) {
             qDebug() << "送信エラー: データの書き込みに失敗";
             return false;
          }
 
          qDebug() << bytesWritten << "バイトデータの送信完了";
          return true;
       }
       catch (const std::exception &e) {
          qDebug() << "送信エラー: " << e.what();
          throw;
       }
    }
 
 private slots:
    // 接続確立時のスロット
    void onConnected()
    {
       qDebug() << "接続が確立";
       qDebug() << "  ローカルアドレス:" << socket->localAddress().toString();
       qDebug() << "  リモートアドレス:" << socket->peerAddress().toString();
    }
 
    // 切断時のスロット
    void onDisconnected()
    {
       qDebug() << "接続が切断";
    }
 
    // エラー発生時のスロット
    void onError(QBluetoothSocket::SocketError error)
    {
       qDebug() << "エラーが発生: " << errorToString(error);
    }
 
    // データ受信時のスロット
    void onDataReceived()
    {
       QByteArray data = socket->readAll();
       qDebug() << "データを受信: " << data.size() << "バイト";
 
       // ここでデータを処理する
       // ...略
    }
 
    // 接続状態変更時のスロット
    void onStateChanged(QBluetoothSocket::SocketState state)
    {
       qDebug() << "接続状態が変更: " << stateToString(state);
    }
 };