Qtの基礎 - Classic Bluetooth

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2024年11月17日 (日) 09:37時点における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モジュールは、これらの状況を適切に検出して、シグナル / スロットメカニズムを通じて通知する仕組みを提供している。


デバイススキャンと検出

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

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


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

 #include <QObject>
 #include <QBluetoothDeviceDiscoveryAgent>
 #include <memory>
 #include <stdexcept>
 #include <QDebug>
 
 class BluetoothScanner : public QObject
 {
    Q_OBJECT
 
 private:
    std::unique_ptr<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を使用する。

 #include <QBluetoothServiceDiscoveryAgent>
 #include <QBluetoothAddress>
 #include <QDebug>
 #include <memory>
 
 class ServiceDiscovery : public QObject
 {
    Q_OBJECT
 
 private:
    std::unique_ptr<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)
    {
       try {
          // サービス探索エージェントの作成
          discoveryAgent = std::make_unique<QBluetoothServiceDiscoveryAgent>(this);
 
          // 各種シグナルとスロットの接続
          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;
       }
    }
 
    // 特定のデバイスのサービス探索を開始
    void startDiscovery(const QBluetoothAddress& address)
    {
       try {
          qDebug() << "サービス探索を開始...";
          discoveryAgent->setRemoteAddress(address);
          discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
       }
       catch (const std::exception &e) {
          qDebug() << "探索開始エラー:" << e.what();
          throw;
       }
    }
 
    // 探索を停止
    void stopDiscovery()
    {
       try {
          qDebug() << "サービス探索を停止...";
          discoveryAgent->stop();
       }
       catch (const std::exception &e) {
          qDebug() << "探索停止エラー:" << e.what();
          throw;
       }
    }
 
 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();
          }
       }
    }
 
    // 探索完了時のスロット
    void onScanFinished()
    {
       qDebug() << "サービス探索が完了";
    }
 
    // エラー発生時のスロット
    void onError(QBluetoothServiceDiscoveryAgent::Error error)
    {
       qDebug() << "エラーが発生: " << errorToString(error);
    }
 };



ペアリング管理

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

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

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



接続の確立と維持

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

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