Qtの基礎 - Classic Bluetooth

提供:MochiuWiki : SUSE, EC, PCB
2024年12月18日 (水) 08:29時点におけるWiki (トーク | 投稿記録)による版 (→‎接続の確立と維持)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
ナビゲーションに移動 検索に移動

概要

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度ペアリングされたデバイスは、特別な設定がない限り、自動的に再接続可能になる。


ペアリングの準備

QBluetoothLocalDeviceクラスのインスタンスを生成して、ローカルのBluetoothアダプタを初期化する。

以下に示すペアリング関連のシグナルに対するスロットを接続する。

  • QBluetoothLocalDevice::pairingFinished
  • QBluetoothLocalDevice::pairingDisplayConfirmation
  • QBluetoothLocalDevice::pairingDisplayPinCode
  • QBluetoothLocalDevice::error


これらの処理は非同期で行われており、シグナル / スロットメカニズムを通じて状態の変更が通知される。
そのため、ユーザインターフェースと組み合わせる場合は、各シグナルに応じて適切なUIの更新を行う必要がある。

 #include <QBluetoothLocalDevice>
 #include <QDebug>
 
 QBluetoothLocalDevice localDevice;
 
 // シグナルとスロットの接続
 connect(&localDevice, &QBluetoothLocalDevice::pairingFinished, [](const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing status) {
    qDebug() << "ペアリング完了";
 });
 
 connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation, [](const QBluetoothAddress &address, QString pin) {
    qDebug() << "ペアリング確認:" << pin;
 });
 
 connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayPinCode, [](const QBluetoothAddress &address, QString pin) {
    qDebug() << "PINコード:" << pin;
 });
 
 connect(&localDevice, &QBluetoothLocalDevice::error, []() {
    qDebug() << "エラー発生";
 });


ペアリング要求

リモートデバイスのBluetoothアドレス (QBluetoothAddressクラス) を指定して、QBluetoothLocalDevice::requestPairingメソッドを実行する。
この時、ペアリングモードとしてQBluetoothLocalDevice::Pairedを指定する。

 #include <QBluetoothAddress>
 
 // デバイスのBluetoothアドレスを設定
 QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
 
 // ペアリング開始
 localDevice.requestPairing(address, QBluetoothLocalDevice::Paired);


ペアリング処理中

Bluetoothデバイスによっては、PINコードの表示や確認が必要になる。

QBluetoothLocalDevice::pairingDisplayPinCodeシグナルを受信した場合、ユーザにPINコードを表示する。

QBluetoothLocalDevice::pairingDisplayConfirmationシグナルを受信した場合は、ユーザに確認を求めて、QBluetoothLocalDevice::pairingConfirmationメソッドで応答する。

 // ペアリング確認に応答
 localDevice.pairingConfirmation(true);   // 承認する場合
 localDevice.pairingConfirmation(false);  // 拒否する場合


ペアリング完了

QBluetoothLocalDevice::pairingFinishedシグナルを受信して処理結果を確認する。

エラーが発生した場合は、QBluetoothLocalDevice::errorシグナルを受信する。

 // ペアリング結果の確認
 
 #include <QBluetoothLocalDevice>
 #include <QDebug>
 
 // ペアリング完了シグナルの処理
 connect(&localDevice, &QBluetoothLocalDevice::pairingFinished, [](const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing status) {
    switch (status) {
       case QBluetoothLocalDevice::Paired:
          qDebug() << "ペアリング成功:" << address.toString();
          break;
       case QBluetoothLocalDevice::Unpaired:
          qDebug() << "ペアリング解除:" << address.toString();
          break;
       default:
          qDebug() << "不明な状態:" << address.toString();
          break;
    }
 });


 // エラーの確認
 // QBluetoothLocalDevice::errorシグナルを受信してエラー発生を検知する
 
 // エラーシグナルの処理
 connect(&localDevice, &QBluetoothLocalDevice::error, []() {
    qDebug() << "ペアリングでエラーが発生しました";
 });


ペアリング管理

QBluetoothLocalDevice::connectedDevicesメソッドを実行して、ペアリング済みデバイスの一覧を取得することが可能である。
また、QBluetoothLocalDevice::pairingStatusメソッドを実行して、特定デバイスのペアリング状態を確認できる。

ペアリングを解除する場合は、ペアリングモードをQBluetoothLocalDevice::Unpairedに指定してQBluetoothLocalDevice::requestPairingメソッドを実行する。

 // ペアリング状態の確認
 QBluetoothLocalDevice::Pairing status = localDevice.pairingStatus(address);
 
 // ペアリング済みデバイスの取得
 QList<QBluetoothAddress> devices = localDevice.connectedDevices();


組み合わせ

 #include <QBluetoothLocalDevice>
 #include <QDebug>
 
 class PairingManager : public QObject
 {
    Q_OBJECT
 
 private:
    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), localDevice(this) 
    {
        // 各種シグナルとスロットの接続
        connect(&localDevice, &QBluetoothLocalDevice::pairingFinished, this, &PairingManager::onPairingFinished);
        connect(&localDevice, &QBluetoothLocalDevice::error, this, &PairingManager::onError);
        connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation, this, &PairingManager::onPairingConfirmationRequest);
        connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayPinCode, this, &PairingManager::onPairingDisplayPinCode);
    }
 
    // ペアリングを開始
    void requestPairing(const QBluetoothAddress& address)
    {
       qDebug() << "ペアリングを開始..." << address.toString();
       localDevice.requestPairing(address, QBluetoothLocalDevice::Paired);
    }
 
    // ペアリングの解除
    void removePairing(const QBluetoothAddress& address)
    {
       qDebug() << "ペアリングを解除..." << address.toString();
       localDevice.requestPairing(address, QBluetoothLocalDevice::Unpaired);
    }
 
    // ペアリング状態の確認
    QBluetoothLocalDevice::Pairing getPairingStatus(const QBluetoothAddress &address)
    {
       return localDevice.pairingStatus(address);
    }
 
    // ペアリング済みデバイスの一覧を取得
    QList<QBluetoothAddress> getPairedDevices()
    {
       return localDevice.connectedDevices();
    }
 
 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メソッドでデータの送受信を行う。

接続の確立と維持を行う処理は、全ての操作が非同期で行われる。
そのため、各段階でのシグナル / スロット接続による状態管理が重要になる。

接続の確立

QBluetoothSocketクラスのインスタンスを生成する。
この時、RFCOMMプロトコルを指定する。

デバイスのMACアドレスとポート番号を指定して、QBluetoothSocket::connectToServiceメソッドを実行する。
接続状態の変化を監視するためのシグナル / スロット接続を設定する。

 #include <QBluetoothSocket>
 #include <QDebug>
 
 QBluetoothSocket socket(QBluetoothServiceInfo::RfcommProtocol);
 
 connect(&socket, &QBluetoothSocket::connected, []() {
    qDebug() << "接続成功";
 });
 
 connect(&socket, &QBluetoothSocket::stateChanged, [](QBluetoothSocket::SocketState state) {
    qDebug() << "状態変更:" << state;
 });
 
 connect(&socket, &QBluetoothSocket::errorOccurred, [](QBluetoothSocket::SocketError error) {
    qDebug() << "エラー発生:" << error;
 });


RFCOMM (Radio Frequency Communication) とは、Bluetooth Classicで使用される重要なプロトコルの1つである。
RFCOMMプロトコルの特徴を以下に示す。

  • シリアルポート通信をエミュレートするプロトコル
  • RS-232C通信の代替として設計
  • 信頼性の高い双方向通信を提供
  • 最大60個の同時接続をサポート


Qtで使用可能な他のBluetoothプロトコルを以下に示す。

  • L2CAP (Logical Link Control and Adaptation Protocol)
    低レイヤーのプロトコル
    RFCOMMの基盤となるプロトコル
    より高速なデータ転送が可能
    生のデータパケット送受信に使用

  • RFCOMM
    最も使用される。
    シリアルポートエミュレーション
    多くのBluetooth機器で採用されている。
    多くのBluetooth通信では、使いやすさと互換性の高さからRFCOMMが選択される。


 // RFCOMMプロトコルを指定する場合
 // シリアル通信が必要な場合 あるいは 安定性が重要な場合
 QBluetoothSocket socket(QBluetoothServiceInfo::RfcommProtocol);
 
 // L2CAPプロトコルを指定する場合
 // 高速な通信が必要な場合 あるいは 低レベルの制御が必要な場合
 QBluetoothSocket socket(QBluetoothServiceInfo::L2capProtocol);


接続の維持

QBluetoothSocket::connectedシグナルを受信して、接続成功を確認する。

QBluetoothSocket::stateChangedシグナルを監視して、接続状態の変化を検知する。

QBluetoothSocket::errorOccurredシグナルを監視してエラーを検知する。

また、必要に応じて定期的なキープアライブメッセージを送信する。

 QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
 socket.connectToService(address, <ポート番号  : 1>);


データの送信

QBluetoothSocket::writeメソッドを使用してデータを送信する。

 QByteArray sendData = "Hello";
 socket.write(sendData);


データの受信

  1. QBluetoothSocket::readyReadシグナルを受信してデータの到着を検知する。
  2. QBluetoothSocket::readAllメソッド、あるいは、QBluetoothSocket::readメソッドでデータを読む。


 connect(&socket, &QBluetoothSocket::readyRead, []() {
    QByteArray data = socket.readAll();
    qDebug() << "受信データ: " << data;
 });


切断処理

  1. QBluetoothSocket::disconnectFromServiceメソッドを実行して、接続を終了する。
  2. QBluetoothSocket::disconnectedシグナルを受信して切断完了を確認する。
  3. 必要に応じて、リソースの解放を実施する。


 connect(&socket, &QBluetoothSocket::disconnected, []() {
    qDebug() << "切断完了";
 });
 
 // 切断を実行
 socket.disconnectFromService();


組み合わせ

 #include <QBluetoothSocket>
 #include <QDebug>
 
 class BluetoothConnection : public QObject
 {
    Q_OBJECT
 
 private:
    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), socket(QBluetoothServiceInfo::RfcommProtocol, this)
    {
       // 各種シグナルとスロットの接続
       connect(&socket, &QBluetoothSocket::connected, this, &BluetoothConnection::onConnected);
       connect(&socket, &QBluetoothSocket::disconnected, this, &BluetoothConnection::onDisconnected);
       connect(&socket, &QBluetoothSocket::errorOccurred, this, &BluetoothConnection::onError);
       connect(&socket, &QBluetoothSocket::readyRead, this, &BluetoothConnection::onDataReceived);
       connect(&socket, &QBluetoothSocket::stateChanged, this, &BluetoothConnection::onStateChanged);
    }
 
    ~BluetoothConnection()
    {
       disconnect();
    }
 
    // デバイスに接続
    void connectToDevice(const QBluetoothAddress &address, quint16 port)
    {
       if (socket.state() == QBluetoothSocket::ConnectedState) {
          qDebug() << "既に接続済み";
          return;
       }
 
       qDebug() << "デバイスに接続します: " << address.toString();
       qDebug() << "ポート: " << port;
       socket.connectToService(address, port);
    }
 
    // 接続を切断
    void disconnect()
    {
       if (socket.state() != QBluetoothSocket::UnconnectedState) {
          qDebug() << "接続を切断...";
          socket.disconnectFromService();
       }
    }
 
    // データを送信
    bool sendData(const QByteArray &data)
    {
       if (socket.state() != QBluetoothSocket::ConnectedState) {
          qDebug() << "送信エラー: 接続されていません";
          return false;
       }
 
       qint64 bytesWritten = socket.write(data);
       if (bytesWritten == -1) {
          qDebug() << "送信エラー: データの書き込みに失敗";
          return false;
       }
 
       qDebug() << bytesWritten << "バイトデータの送信完了";
       return true;
    }
 
 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);
    }
 };