📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の25版が非表示)
45行目: 45行目:
<u>ソフトウェアの要件 (データ量、通信頻度、電力消費等) に応じて適切な方式を選択する必要がある。</u><br>
<u>ソフトウェアの要件 (データ量、通信頻度、電力消費等) に応じて適切な方式を選択する必要がある。</u><br>
<u>また、多くのモダンなBluetoothデバイスはBLEを採用する傾向にあり、特に省電力性が重要な場合はBLEが推奨される。</u><br>
<u>また、多くのモダンなBluetoothデバイスはBLEを採用する傾向にあり、特に省電力性が重要な場合はBLEが推奨される。</u><br>
<br>
<u>※開発時の注意</u><br>
<u>BLEはClassic Bluetoothと基本的な手順は同じであるが、BLEは独自のアーキテクチャと概念を持っており、より細かな制御と状態管理が必要となる。</u><br>
<u>特に、省電力性を重視する場合は、以下に示す特徴を理解して実装することが重要となる。</u><br>
* 状態管理
*: BLEは状態遷移が多いため、適切な状態管理が重要となる。
*: 各操作のタイミングと順序に注意が必要である。
* エラーハンドリング
** 接続切断
** タイムアウト
** 権限の問題 (特に、モバイル環境)
* パフォーマンス考慮
** データサイズの制限 (MTU)
** 通信頻度の最適化
** バッテリー消費の管理
* プラットフォーム固有の考慮
** OSごとの権限設定
** バックグラウンド動作の制限
** スキャンフィルタの設定
<br><br>
<br><br>


71行目: 90行目:
* パッシブスキャン
* パッシブスキャン
<br>
<br>
BLEデバイスのスキャン中は、<u>アドバタイズメント</u>と呼ばれる特別なブロードキャストパケットを発信する。<br>
BLEデバイスのスキャン中は、<br>
このアドバタイズメントパケットには、以下に示す情報が含まれる。<br>
<u>BLEペリフェラル (アドバタイザ) デバイスがアドバタイズメントと呼ばれる特別なブロードキャストパケットを定期的に発信して</u>、セントラル (スキャナ) デバイスがそれをスキャンする。<br>
スキャンを行うのはセントラル側、アドバタイズメントパケットを発信するのはペリフェラル側である。<br>
<br>
[[ファイル:Qt BLE 1.png|フレームなし|中央]]
<br>
このアドバタイジングパケットには、以下に示す情報が含まれる。<br>
* デバイス名
* デバイス名
* メーカー固有データ
* メーカー固有データ
* 提供するサービスのUUID
* 提供するサービスのUUID
* 電波強度 (RSSI)
* MACアドレス (デバイスアドレス)
* その他のカスタムデータ
* その他のカスタムデータ
<br>
<br>
また、スキャン時間や範囲を設定可能であり、バッテリー消費とスキャン精度のバランスを取ることができる。<br>
また、スキャン時間や範囲を設定可能であり、バッテリー消費とスキャン精度のバランスを取ることができる。<br>
アドバタイジング間隔は、20ミリ秒~10.24秒の範囲で設定可能である。<br>
<br>
<br>
<u>BLEでは、同様の<code>QBluetoothDeviceDiscoveryAgent</code>を使用するが、<code>LowEnergyDiscoveryTimeout</code>の設定が必要となる。</u><br>
<u>BLEでは、同様の<code>QBluetoothDeviceDiscoveryAgent</code>クラスを使用するが、<code>LowEnergyDiscoveryTimeout</code>の設定が必要となる。</u><br>
また、フィルタリングでBLEデバイスのみを検出する。<br>
また、フィルタリングでBLEデバイスのみを検出する。<br>
<br>
==== アドバタイジングパケットの種類 ====
* ADV_IND
*: 一般的な接続可能なアドバタイジング
* ADV_DIRECT_IND
*: 特定のデバイスのみに向けたアドバタイジング
* ADV_NONCONN_IND
*: 接続不可の通知専用
* ADV_SCAN_IND
*: スキャン応答が可能な通知専用
* ADV_EXT_IND
*: 拡張アドバタイジング (Bluetooth 5.0以降)
<br>
==== データ構造 ====
* PDU Header (2バイト)
** アドバタイジングタイプ
** TxAddressタイプ
** RxAddressタイプ
* アドバタイジングアドレス (6バイト)
* アドバタイジングデータ (最大31バイト)
<br>
[[ファイル:Qt BLE 2.png|フレームなし|中央]]
<br>
==== アドバタイジングデータの主要要素 ====
* Length (1バイト)
* AD Type (1バイト)
** 0x01
**: Flags
** 0x09
**: Complete Local Name
** 0xFF
**: Manufacturer Specific Data
** 0x03
**: Complete List of 16-bit Service UUIDs
* AD Data (可変長)
<br>
[[ファイル:Qt BLE 3.png|フレームなし|中央]]
<br>
==== その他 (通信特性 / セキュリティ等) ====
* 通信特性
** アドバタイジング間隔
**: 20ミリ秒~10.24秒
** チャンネル
**: 37, 38, 39の3チャンネル
** 送信電力
**: -20[dBm]~+4[dBm] (地域規制による)
<br>
* セキュリティ
** プライバシー保護のためのランダムアドレス
** アドバタイジングデータの暗号化オプション
** ホワイトリストによるフィルタリング機能
<br>
* スキャン応答
** SCAN_REQ
**: スキャナからの追加情報要求
** SCAN_RSP
**: アドバタイザーからの応答 (最大31バイト)
<br>
==== 使用例 ====
Qt Bluetoothモジュールを使用したBLEスキャンの処理を以下に示す。<br>
<br>
* QBluetoothDeviceDiscoveryAgentクラス
* QBluetoothDeviceInfoクラス
<br>
===== デバイス探索エージェントの作成 =====
QBluetoothDeviceDiscoveryAgentのインスタンスを生成する。<br>
<br>
<syntaxhighlight lang="c++">
#include <QBluetoothDeviceDiscoveryAgent>
QBluetoothDeviceDiscoveryAgent discoveryAgent;
</syntaxhighlight>
<br>
===== シグナル / スロット接続 =====
* QBluetoothDeviceDiscoveryAgent::deviceDiscoveredシグナル
*: デバイスを発見した時に送信される。
* QBluetoothDeviceDiscoveryAgent::finished
*: スキャンが完了した時に送信される。
* QBluetoothDeviceDiscoveryAgent::error
*: エラーが発生した時に送信される。
<br>
<syntaxhighlight lang="c++">
// デバイス発見時
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &MyClass::onDeviceDiscovered);
// スキャン完了時
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &MyClass::onScanFinished);
// エラー発生時
// エラーハンドリングは必ず実装する
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::error, this, &MyClass::onError);
</syntaxhighlight>
<br>
===== スキャン開始 =====
<code>QBluetoothDeviceDiscoveryAgent::LowEnergyMethod</code>メソッドを指定して、<code>QBluetoothDeviceDiscoveryAgent::start</code>メソッドを実行する。<br>
これによりBLEデバイスのスキャンが開始する。<br>
<br>
<syntaxhighlight lang="c++">
discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
</syntaxhighlight>
<br>
===== デバイス発見時 =====
<code>QBluetoothDeviceDiscoveryAgent::deviceDiscovered</code>シグナルで通知する。<br>
<code>QBluetoothDeviceInfo</code>クラスを使用して、以下に示す情報を取得することが可能である。<br>
* デバイス名
* アドレス
* 信号強度 (RSSI値)
* サービスUUID
* マニファクチャラーデータ
<br>
<u>ただし、これらの情報を取得する前は、必ず存在確認を行う。</u><br>
<br>
<syntaxhighlight lang="c++">
void onDeviceDiscovered(const QBluetoothDeviceInfo &device)
{
    // デバイス名の取得
    QString name = device.name();
    // アドレスの取得
    QString address = device.address().toString();
    // RSSI値の取得
    qint16 rssi = device.rssi();
    // サービスUUIDの取得
    QList<QBluetoothUuid> services = device.serviceUuids();
    // マニファクチャラーデータの取得
    QMap<quint16, QByteArray> manufacturerData = device.manufacturerData();
}
</syntaxhighlight>
<br>
===== スキャン完了時 =====
<code>QBluetoothDeviceDiscoveryAgent::finished</code>シグナルで送信する。<br>
<br>
スキャン完了時の処理を記述する。<br>
また、必要に応じて再スキャンを開始する。<br>
<br>
<syntaxhighlight lang="c++">
void onScanFinished()
{
    // 必要に応じて再スキャン
    discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
}
</syntaxhighlight>
<br>
===== エラー発生時 =====
リソースの解放は適切に行う。<br>
<br>
<syntaxhighlight lang="c++">
void onError(QBluetoothDeviceDiscoveryAgent::Error error)
{
    switch (error) {
      case QBluetoothDeviceDiscoveryAgent::NoError:
            break;
      case QBluetoothDeviceDiscoveryAgent::InputOutputError:
            break;
      case QBluetoothDeviceDiscoveryAgent::PoweredOffError:
            break;
      case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError:
            break;
      case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError:
            break;
      case QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod:
            break;
      default:
            break;
    }
}
</syntaxhighlight>
<br>
===== スキャン停止 =====
<syntaxhighlight lang="c++">
discoveryAgent->stop();
</syntaxhighlight>
<br>
===== 組み合わせ =====
<syntaxhighlight lang="c++">
// BLEデバイスのスキャンを管理するクラス
// BLEデバイスの検出と監視, 継続的なスキャンモード, デバイス情報の重複排除, 自動リカバリを行う
#include <QObject>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothDeviceInfo>
#include <QTimer>
#include <QDebug>
class BLEDeviceScanner : public QObject
{
    Q_OBJECT
private:
    QBluetoothDeviceDiscoveryAgent discoveryAgent;  // BLEデバイス探索用エージェント
    QTimer rescanTimer;                            // 継続的スキャン用タイマ
    bool  isContinuousScan = false;                // 継続的スキャンモードのフラグ
    QMap<QBluetoothAddress, QBluetoothDeviceInfo> knownDevices;  // 既知デバイスのキャッシュ
    // デバイス探索エージェントとタイマーのシグナルを適切なスロットに接続
    // デバイス発見時の処理, スキャン完了時の処理, エラー発生時の処理, 定期的な再スキャンの制御を行う
    void connectSignals()
    {
      // デバイス探索エージェントのシグナル接続
      connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BLEDeviceScanner::onDeviceDiscovered);
      connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BLEDeviceScanner::onScanFinished);
      connect(&discoveryAgent, static_cast<void(QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error),
              this, &BLEDeviceScanner::onError);
      // 再スキャンタイマのシグナル接続
      connect(&rescanTimer, &QTimer::timeout, this, [this]() {
          if (isContinuousScan) {
            discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
          }
      });
    }
    // エラーコード
    QString getErrorMessage(QBluetoothDeviceDiscoveryAgent::Error error)
    {
      switch (error) {
          case QBluetoothDeviceDiscoveryAgent::NoError:                      return "エラーなし";
          case QBluetoothDeviceDiscoveryAgent::InputOutputError:            return "Bluetooth IOエラー";
          case QBluetoothDeviceDiscoveryAgent::PoweredOffError:              return "Bluetoothがオフ";
          case QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError: return "無効なBluetoothアダプタ";
          case QBluetoothDeviceDiscoveryAgent::UnsupportedPlatformError:    return "プラットフォームがサポートされていない";
          case QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod:  return "未対応の探索方法";
          default:                                                          return "不明なエラー";
      }
    }
    // 発見されたデバイスの詳細情報をログに出力
    // デバイス名, MACアドレス, 電波強度 (RSSI), 提供サービスのUUID, メーカー固有データを表示
    void logDeviceInfo(const QBluetoothDeviceInfo& device)
    {
      qDebug() << "BLEデバイスを発見:";
      qDebug() << "  名前:" << device.name();
      qDebug() << "  アドレス:" << device.address().toString();
      qDebug() << "  RSSI:" << device.rssi();
      // サービスUUIDのログ出力
      const QList<QBluetoothUuid> serviceUuids = device.serviceUuids();
      if (!serviceUuids.isEmpty()) {
          qDebug() << "  提供サービス:";
          for (const QBluetoothUuid& uuid : serviceUuids) {
            qDebug() << "    -" << uuid.toString();
          }
      }
      // マニファクチャラーデータのログ出力
      const QMap<quint16, QByteArray> manufacturerData = device.manufacturerData();
      if (!manufacturerData.isEmpty()) {
          qDebug() << "  マニファクチャラーデータ:";
          QMap<quint16, QByteArray>::const_iterator i = manufacturerData.constBegin();
          while (i != manufacturerData.constEnd()) {
            qDebug() << "    ID: " << i.key() << "データ: " << i.value().toHex();
            i++;
          }
      }
    }
public:
    explicit BLEDeviceScanner(QObject* parent = nullptr) : QObject(parent)
    {
      // BLEデバイスのみをスキャンするように設定
      discoveryAgent.setLowEnergyDiscoveryTimeout(10000);  // 10秒のタイムアウト
      // 自動再スキャンタイマの設定
      rescanTimer.setInterval(30000);  // 30秒間隔で再スキャン
      connectSignals();
    }
    // BLEデバイスのスキャンを開始
    void startScan(bool continuous = false)
    {
      try {
          qDebug() << "BLEデバイススキャンを開始...";
          isContinuousScan = continuous;
          discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
          if (continuous) rescanTimer.start();
          emit scanStarted();
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("スキャン開始エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // スキャンを停止
    void stopScan()
    {
      try {
          qDebug() << "BLEデバイススキャンを停止...";
          discoveryAgent.stop();
          rescanTimer.stop();
          isContinuousScan = false;
          emit scanStopped();
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("スキャン停止エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
signals:
    void deviceDiscovered(const QBluetoothDeviceInfo& device);  // 新規デバイス発見時
    void scanStarted();    // スキャン開始時
    void scanStopped();    // スキャン停止時
    void scanFinished();  // スキャン完了時
    void errorOccurred(const QString& error);  // エラー発生時
private slots:
    // デバイス発見時の処理
    // BLEデバイスのフィルタリング, 重複デバイスの検出と更新, デバイス情報のログ出力, 新規 / 更新デバイスの通知
    void onDeviceDiscovered(const QBluetoothDeviceInfo& device)
    {
      // BLEデバイスのみを処理
        if (!(device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)) {
          return;
        }
        // デバイスの重複確認
        const QBluetoothAddress& address = device.address();
        if (!knownDevices.contains(address) || knownDevices[address].rssi() != device.rssi()) {
            knownDevices[address] = device;
            // デバイス情報のログ出力と通知
            logDeviceInfo(device);
            emit deviceDiscovered(device);
        }
    }
    // スキャン完了時の処理
    // 完了通知の発行, 継続的スキャンモードの場合は1秒後に再スキャン
    void onScanFinished()
    {
      qDebug() << "スキャンが完了";
      emit scanFinished();
      // 継続的スキャンの場合は再開
      if (isContinuousScan) {
          QTimer::singleShot(1000, this, [this]() {
            discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
          });
      }
    }
    // エラー発生時の処理 (エラーメッセージの生成とログ出力)
    // 継続的スキャンモードの場合は5秒後に自動再試行
    void onError(QBluetoothDeviceDiscoveryAgent::Error error)
    {
      QString errorMessage = getErrorMessage(error);
      qDebug() << "スキャンエラー: " << errorMessage;
      emit errorOccurred(errorMessage);
      // エラーからの自動復帰を試みる (5秒後にBLEデバイスの検出を開始)
      if (isContinuousScan) {
          QTimer::singleShot(5000, this, [this]() {
            discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
          });
      }
    }
};
</syntaxhighlight>
<br><br>
<br><br>


88行目: 481行目:
特定のBluetoothデバイスが提供するサービスを探索するプロセスである。<br>
特定のBluetoothデバイスが提供するサービスを探索するプロセスである。<br>
<br>
<br>
==== GATT階層構造 ====
BLEでは、GATT (Generic Attribute Profile) という階層構造を持つ。<br>
BLEでは、GATT (Generic Attribute Profile) という階層構造を持つ。<br>
Classic Bluetoothとは異なり、キャラクタリスティックとディスクリプタの概念が追加されている。<br>
<br>
* サービス
* サービス
*: デバイスの機能をグループ化 (例: 心拍計測、温度センサ等)
*: デバイスの機能をグループ化 (例: 心拍計測、温度センサ等)
*: キャラクタリスティックは各サービスに1つ以上必要である。
* キャラクタリスティック
* キャラクタリスティック
*: 実際のデータ値や操作 (例: 心拍値、温度値、設定値等)
*: 実際のデータ値や操作 (例: 心拍値、温度値、設定値等)
*: キャラクタリスティックは、各サービスに1つ以上必要である。
* ディスクリプタ
* ディスクリプタ
*: キャラクタリスティックの追加情報 (単位や説明等)
*: キャラクタリスティックの追加情報 (単位や説明等)
*: ディスクリプタは必須ではなく、オプショナル (0個以上) である。
<br>
# GATT階層構造
Profile
└── Service (1..n)      # Primary Service (メインとなる独立したサービス)
    │                    # デバイスの主要な機能を表現
    │                    # 他のサービスに依存しない
    │                    # 例: 心拍計測、温度センサ等
    ├── Include (0..n)  # Secondary Serviceへの参照
    ├── Characteristic (1..n)
    │  └── Descriptor (0..n)
    ├── Secondary Service 1 (0..n)  # バッテリー監視 (Primaryサービスをサポートする補助的なサービス)
    └── Secondary Service 2 (0..n)  # デバイス診断  (Primaryサービスをサポートする補助的なサービス)
                                    # Secondary Serviceは、単独では意味を持たない
                                    # 必ず1つ以上のPrimary Serviceに関連付けられる
<br>
<br>
サービスディスカバリでは、これらの階層構造を探索して、利用可能な機能を特定する。<br>
サービスディスカバリでは、これらの階層構造を探索して、利用可能な機能を特定する。<br>
標準的なサービスには決められたUUIDが割り当てられている。(例: 心拍サービス、電池残量サービス等)<br>
標準的なサービスには決められたUUIDが割り当てられている。(例: 心拍サービス、電池残量サービス等)<br>
UUIDは16-bit (標準) または 128-bit (カスタム) である。<br>
<br>
==== BLEサービスディスカバリの手順 ====
# GAP (Generic Access Profile) によるデバイススキャン
# デバイスへの接続
# GATTサービスの列挙
# 各サービス内のキャラクタリスティック探索
# 必要に応じてディスクリプタ探索
<br>
Qtでは、<code>QLowEnergyService</code>クラスを使用して、BLEの複雑なサービス階層を管理する。<br>
<br>
Classic Bluetoothとの違いを以下に示す。<br>
* <code>QLowEnergyController::connectToDevice</code>メソッドで接続する。
* <code>QLowEnergyController::discoveryFinished</code>シグナルで探索完了を検知する。
* <code>QLowEnergyController::createServiceObject</code>メソッドを使用して、サービスオブジェクトを生成する。
<br>
==== 使用例 ====
===== QLowEnergyControllerクラスのインスタンスの生成 =====
まず、<code>QBluetoothDeviceDiscoveryAgent</code>クラスを使用して、スキャンを実行する。<br>
<br>
次に、接続するデバイスの<code>QBluetoothDeviceInfo</code>クラスのデバイス情報を取得する。<br>
取得したデバイス情報を元に、<code>QLowEnergyController</code>クラスのインスタンスを生成する。<br>
<br>
===== コントローラのシグナル / スロット接続 =====
最低限必要なシグナルを示す。<br>
* connected
*: デバイスへの接続完了を通知
* disconnected
*: デバイスとの切断を通知 
* serviceDiscovered
*: 新しいサービスの発見を通知
* discoveryFinished
*: サービス探索の完了を通知
<br>
===== BLEデバイスの接続 =====
connectToDeviceメソッドを実行してBLEデバイスに接続する。<br>
connectedシグナルの受信を待つ。<br>
<br>
===== サービスの探索 =====
BLEデバイスへ接続後、discoverServicesメソッドを実行してサービスの探索を開始する。<br>
<br>
* serviceDiscoveredシグナルで個々のサービスが見つかる度に通知される。
* discoveryFinishedシグナルで探索完了が通知される。
<br>
===== サービスの取得 =====
createServiceObjectメソッドをを実行して、探索で発見したサービスのQLowEnergyServiceオブジェクトを生成する。<br>
このオブジェクトを使用して、特性 (Characteristic) や ディスクリプタにアクセスできる。<br>
<br>
===== 組み合わせ =====
<syntaxhighlight lang="c++">
// BLEのサービス探索を管理するクラス
// BLEデバイスへの接続, サービスの探索と監視
#include <QObject>
#include <QLowEnergyController>
#include <QLowEnergyService>
#include <QTimer>
#include <QDebug>
class BLEServiceDiscovery : public QObject
{
    Q_OBJECT
private:
    QLowEnergyController controller;        // BLE接続とサービス探索を制御するコントローラ
    QTimer              discoveryTimeout;  // サービス探索のタイムアウトを管理するタイマ
    // コントローラの各種シグナルを接続
    // 接続 / 切断, サービス探索の進行状況, 状態変更, エラー
    void connectControllerSignals()
    {
      connect(&controller, &QLowEnergyController::connected, this, &BLEServiceDiscovery::onConnected);
      connect(&controller, &QLowEnergyController::disconnected, this, &BLEServiceDiscovery::onDisconnected);
      connect(&controller, &QLowEnergyController::serviceDiscovered, this, &BLEServiceDiscovery::onServiceDiscovered);
      connect(&controller, &QLowEnergyController::discoveryFinished, this, &BLEServiceDiscovery::onDiscoveryFinished);
      connect(&controller, static_cast<void(QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error), this, &BLEServiceDiscovery::onError);
      connect(&controller, &QLowEnergyController::stateChanged, this, &BLEServiceDiscovery::onStateChanged);
    }
    // 個別のBLEサービスに対するシグナル
    // 状態変更とエラーイベントを監視してログを出力する
    void connectServiceSignals(QLowEnergyService *service)
    {
      connect(service, &QLowEnergyService::stateChanged, this, [this, service](QLowEnergyService::ServiceState newState) {
          qDebug() << "サービス状態が変更: " << getServiceStateMessage(newState);
      });
      connect(service, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, [this](QLowEnergyService::ServiceError error) {
          qDebug() << "サービスエラー: " << getServiceErrorMessage(error);
      });
    }
    // エラーコードの変換
    QString getErrorMessage(QLowEnergyController::Error error)
    {
      switch (error) {
          case QLowEnergyController::NoError:                      return "エラーなし";
          case QLowEnergyController::UnknownError:                return "不明なエラー";
          case QLowEnergyController::UnknownRemoteDeviceError:    return "リモートデバイスが見つからない";
          case QLowEnergyController::NetworkError:                return "ネットワークエラー";
          case QLowEnergyController::InvalidBluetoothAdapterError: return "無効なBluetoothアダプタ";
          case QLowEnergyController::ConnectionError:              return "接続エラー";
          default:                                                return "予期せぬエラー";
      }
    }
    QString getStateMessage(QLowEnergyController::ControllerState state)
    {
      switch (state) {
          case QLowEnergyController::UnconnectedState: return "未接続";
          case QLowEnergyController::ConnectingState:  return "接続中";
          case QLowEnergyController::ConnectedState:  return "接続済み";
          case QLowEnergyController::DiscoveringState: return "探索中";
          case QLowEnergyController::DiscoveredState:  return "探索完了";
          case QLowEnergyController::ClosingState:    return "切断中";
          case QLowEnergyController::AdvertisingState: return "アドバタイジング中";
          default:                                    return "不明な状態";
      }
    }
    QString getServiceStateMessage(QLowEnergyService::ServiceState state)
    {
      switch (state) {
          case QLowEnergyService::InvalidService:      return "無効なサービス";
          case QLowEnergyService::DiscoveryRequired:  return "探索が必要";
          case QLowEnergyService::DiscoveringServices: return "サービス探索中";
          case QLowEnergyService::ServiceDiscovered:  return "サービス探索完了";
          default:                                    return "不明なサービス状態";
      }
    }
    QString getServiceErrorMessage(QLowEnergyService::ServiceError error)
    {
      switch (error) {
          case QLowEnergyService::NoError:                  return "エラーなし";
          case QLowEnergyService::OperationError:          return "操作エラー";
          case QLowEnergyService::CharacteristicReadError:  return "キャラクタリスティック読み取りエラー";
          case QLowEnergyService::CharacteristicWriteError: return "キャラクタリスティック書き込みエラー";
          case QLowEnergyService::DescriptorReadError:      return "ディスクリプタ読み取りエラー";
          case QLowEnergyService::DescriptorWriteError:    return "ディスクリプタ書き込みエラー";
          case QLowEnergyService::UnknownError:            return "不明なエラー";
          default:                                          return "予期せぬサービスエラー";
      }
    }
public:
    explicit BLEServiceDiscovery(QObject* parent = nullptr) : QObject(parent)
    {
      // タイムアウトタイマの初期化
      discoveryTimeout.setInterval(10000);  // 10秒のタイムアウト
      discoveryTimeout.setSingleShot(true);
      connect(&discoveryTimeout, &QTimer::timeout, this, &BLEServiceDiscovery::onDiscoveryTimeout);
    }
    // デバイスへの接続とサービス探索の開始
    // コントローラの初期化, シグナルの接続, デバイスへの接続開始, タイムアウトタイマの開始
    void startDiscovery(const QBluetoothDeviceInfo &device)
    {
      try {
          qDebug() << "サービス探索を開始: " << device.name();
          // コントローラの初期化
          connectControllerSignals();
          // 接続開始
          controller.connectToDevice();
          discoveryTimeout.start();
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("探索開始エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 探索を停止 (タイムアウトタイマも停止)
    void stopDiscovery()
    {
      try {
          if (controller) {
            controller.disconnectFromDevice();
            discoveryTimeout.stop();
          }
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("探索停止エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
signals:
    void serviceDiscovered(QLowEnergyService *service);  // 新しいサービスが発見された時に発行
    void discoveryComplete();                            // 全てのサービス探索が完了した時に発行
    void errorOccurred(const QString &error);            // エラーが発生した時に発行
    void connectionStateChanged(QLowEnergyController::ControllerState state);  // 接続状態が変更された時に発行
private slots:
    // デバイスへの接続が完了した時 (タイムアウトタイマを停止して、サービス探索を開始)
    void onConnected()
    {
      qDebug() << "デバイスに接続";
      discoveryTimeout.stop();
      // サービス探索の開始
      controller.discoverServices();
    }
    // デバイスから切断された時 (タイムアウトタイマを停止)
    void onDisconnected()
    {
      qDebug() << "デバイスから切断";
      discoveryTimeout.stop();
    }
    // 新しいサービスが発見された時
    void onServiceDiscovered(const QBluetoothUuid& uuid)
    {
      qDebug() << "サービスを発見:" << uuid.toString();
      // サービスオブジェクトの作成
      QLowEnergyService* service = controller.createServiceObject(uuid, this);
      if (service) {
          connectServiceSignals(service);
          emit serviceDiscovered(service);
      }
    }
    // サービス探索が完了した時
    void onDiscoveryFinished()
    {
      qDebug() << "サービス探索が完了";
      discoveryTimeout.stop();
      emit discoveryComplete();
    }
    // エラーが発生した時
    void onError(QLowEnergyController::Error error)
    {
      QString errorMessage = getErrorMessage(error);
      qDebug() << "エラーが発生: " << errorMessage;
      emit errorOccurred(errorMessage);
    }
    // タイムアウトが発生した時 (探索を停止して、エラーとして通知)
    void onDiscoveryTimeout()
    {
      qDebug() << "サービス探索がタイムアウト";
      stopDiscovery();
      emit errorOccurred("探索タイムアウト");
    }
    // 接続状態が変更された時 (状態の変更をログ出力して、通知)
    void onStateChanged(QLowEnergyController::ControllerState state)
    {
      qDebug() << "接続状態が変更: " << getStateMessage(state);
      emit connectionStateChanged(state);
    }
};
</syntaxhighlight>
<br><br>
<br><br>


== ペアリング管理 (ボンディング) ==
==== セキュリティレベル ====
* Just Works
*: 自動的にペアリングを実行する。
*: Man-in-the-Middle攻撃に対して脆弱である。
*: IoTセンサ等、機密性の低いデバイスに適用される。
*: 最も簡単であるが、セキュリティは低い。
*: <br>
* Passkey Entry
*: PINコードを使用する。
*:* Fixed PIN
*:*: デバイスに事前設定された固定PIN
*:* Dynamic PIN
*:*: 接続時に動的に生成されるPIN
*: キーボード付きデバイスやディスプレイ付きデバイスで使用される。
*: <br>
* Out of Band
*: NFC / QRコード等の別チャネルの通信によりでペアリング情報を交換する。
*: 高いセキュリティレベルを実現しているが、追加のハードウェアが必要となる。
*: <br>
* Numeric Comparison
*: 両デバイスで6桁の数字を表示・確認する。
*: そのため、ディスプレイ付きデバイス間で使用される。
*: Bluetooth 4.2以降で利用可能である。
<br>
==== ボンディング管理の要素 ====
* LTK (Long Term Key)
*: 暗号化に使用する長期キー
*: 不揮発性メモリに保存
*: デバイスペア固有の値
*: <br>
* IRK (Identity Resolving Key)
*: プライバシー保護用の識別子解決キー
*: ランダムアドレスの解決に使用する。
*: デバイスのプライバシー保護に重要である。
*: <br>
* ボンディング情報の管理
*: タイムアウト時の自動削除
*: 手動での削除機能
*: 最大ペアリング数の制限
<br>
==== Classic Bluetoothとの主な違い ====
* 接続パラメータ
*: 接続間隔、レイテンシ、タイムアウト等
*: Connection Interval: 7.5ミリ秒~4秒
*: Slave Latencyが設定可能である。
*: MTUサイズは、BLEは23バイト (デフォルト)
*: <br>
* 省電力モード
*: スリープモード制御
*: 接続パラメータの動的調整
*: バッテリー状態の監視
*: <br>
* セキュリティ面
*: BLEは、SMPプロトコルを使用する。
*: より安全なECDH鍵交換方式採用している。
*: 簡略化されたペアリングプロセス
*: <br>
* 追加のセキュリティ機能
** LEガーディアンタイムアウト
**: 接続維持の時間制限
**: セキュリティリスク軽減
**: 設定可能な時間値
<br>
==== 使用例 ====
===== ローカルデバイスの初期化 =====
BLEデバイスを利用するため、QBluetoothLocalDeviceクラスを使用して初期化する。<br>
初期化時にBLEデバイスが有効かどうかの確認を行う。<br>
<br>
* QBluetoothLocalDeviceクラス
* QBluetoothAddressクラス
<br>
<syntaxhighlight lang="c++">
#include <QBluetoothLocalDevice>
#include <QBluetoothAddress>
QBluetoothLocalDevice localDevice;
if (!localDevice.isValid()) {
    qDebug() << "Bluetoothデバイスが利用できません";
    return;
}
</syntaxhighlight>
<br>
===== ペアリング状態の変化を監視 =====
ペアリングの結果を受信すため、<code>QBluetoothLocalDevice::pairingFinished</code>シグナルに接続する。<br>
これにより、ペアリング成功 / 失敗の通知を受け取ることができる。<br>
<br>
<syntaxhighlight lang="c++">
connect(&localDevice, &QBluetoothLocalDevice::pairingFinished, [](const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) {
    if (pairing == QBluetoothLocalDevice::Paired) {
      qDebug() << "ペアリング成功:" << address.toString();
    }
    else {
      qDebug() << "ペアリング失敗:" << address.toString();
    }
});
</syntaxhighlight>
<br>
===== PINコード確認のシグナル (必要な場合のみ) =====
PINコードが必要なデバイスは、<code>BluetoothLocalDevice::pairingDisplayConfirmation</code>シグナルに接続する。<br>
これにより、PINコードの確認が必要な場合に通知を受け取ることができる。<br>
<br>
<syntaxhighlight lang="c++">
connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation, [](const QBluetoothAddress &address, QString pin) {
    qDebug() << "PINコード確認: " << address.toString() << pin;
});
</syntaxhighlight>
<br>
===== ペアリング要求 =====
ペアリングを開始するため、ペアリングするデバイスのBluetoothアドレスを指定して、<code>QBluetoothLocalDevice::requestPairing</code>メソッドを実行する。<br>
<br>
ペアリングの結果は、<code>QBluetoothLocalDevice::pairingFinished</code>シグナルで受信して、成功 / 失敗に応じた処理を行う。<br>
<br>
<syntaxhighlight lang="c++">
QBluetoothAddress targetAddress("XX:XX:XX:XX:XX:XX"); // 接続先アドレス
localDevice.requestPairing(targetAddress, QBluetoothLocalDevice::Paired);
</syntaxhighlight>
<br>
===== 組み合わせ =====
<syntaxhighlight lang="c++">
// BLEデバイスとのペアリング操作を管理するクラス
// ペアリングの開始と解除, ペアリング状態の監視, PINコード認証
#include <QObject>
#include <QBluetoothLocalDevice>
#include <QBluetoothAddress>
#include <QDebug>
class BLEPairing : public QObject
{
  Q_OBJECT
private:
    QBluetoothLocalDevice localDevice;  // ローカルのBLEデバイスを管理するオブジェクト
    // ペアリング状態を表す文字列
    QString getPairingStatusString(QBluetoothLocalDevice::Pairing status)
    {
      switch (status) {
          case QBluetoothLocalDevice::Unpaired:        return "未ペアリング";
          case QBluetoothLocalDevice::Paired:          return "ペアリング済み";
          case QBluetoothLocalDevice::AuthorizedPaired: return "認証済みペアリング";
          default:                                      return "不明な状態";
      }
    }
    // BLEデバイスが使用可能な状態かどうかを確認
    bool isDeviceReady() const
    {
      if (!localDevice || !localDevice->isValid()) return false;
      return localDevice.hostMode() != QBluetoothLocalDevice::HostPoweredOff;
    }
    // BLEデバイスのホストモードを設定
    void setHostMode(QBluetoothLocalDevice::HostMode mode)
    {
      if (isDeviceReady()) localDevice.setHostMode(mode);
    }
    // ホスト側においてBLEが利用可能かどうかを確認
    bool isBluetoothAvailable() const
    {
      return QBluetoothLocalDevice::allDevices().count() > 0;
    }
public:
    explicit BLEPairing(QObject *parent = nullptr) : QObject(parent)
    {
      try {
          // シグナルの接続
          connect(&localDevice, &QBluetoothLocalDevice::pairingFinished, this, &BLEPairing::onPairingFinished);
          connect(&localDevice, &QBluetoothLocalDevice::error, this, &BLEPairing::onError);
          connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation, this, &BLEPairing::onPairingDisplayConfirmation);
          connect(&localDevice, &QBluetoothLocalDevice::pairingDisplayPinCode, this, &BLEPairing::onPairingDisplayPinCode);
      }
      catch (const std::exception &e) {
          qDebug() << "初期化エラー:" << e.what();
          throw;
      }
    }
    ~BLEPairing()
    {
      if (localDevice) {
          disconnect(&localDevice);  // 全ての接続を解除
      }
    }
    // 指定したデバイスとのペアリングを開始
    // 30秒のタイムアウトを設定 (失敗時はエラーを通知)
    void requestPairing(const QBluetoothAddress &address)
    {
      try {
          if (!isDeviceReady()) {
            emit errorOccurred("ローカルBluetoothデバイスが無効です");
            return;
          }
          // タイムアウトの設定
          QTimer::singleShot(30000, this, [this, address]() {
            if (getPairingStatus(address) != QBluetoothLocalDevice::Paired) {
                emit errorOccurred("ペアリングがタイムアウトしました");
            }
          });
          qDebug() << "ペアリングを開始: " << address.toString();
          localDevice.requestPairing(address, QBluetoothLocalDevice::Paired);
      }
      catch (const std::exception &e) {
          emit errorOccurred("ペアリング開始時に予期せぬエラーが発生しました");
      }
    }
    // 指定したBLEデバイスとのペアリングを解除
    void removePairing(const QBluetoothAddress& address)
    {
      try {
          if (!localDevice.isValid()) {
            emit errorOccurred("ローカルBluetoothデバイスが無効");
            return;
          }
          qDebug() << "ペアリングを解除: " << address.toString();
          localDevice.requestPairing(address, QBluetoothLocalDevice::Unpaired);
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("ペアリング解除エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 指定したBLEデバイスとのペアリング状態を確認
    QBluetoothLocalDevice::Pairing getPairingStatus(const QBluetoothAddress &address)
    {
      try {
          if (!localDevice.isValid()) {
            emit errorOccurred("ローカルBluetoothデバイスが無効");
            return QBluetoothLocalDevice::Unpaired;
          }
          return localDevice.pairingStatus(address);
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("ペアリング状態確認エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
          return QBluetoothLocalDevice::Unpaired;
      }
    }
signals:
    void pairingComplete(const QBluetoothAddress& address, bool success);            // ペアリング完了通知
    void pairingConfirmationRequired(const QBluetoothAddress& address, QString pin);  // PIN確認要求通知
    void errorOccurred(const QString& error);  // エラー通知
private slots:
    // ペアリング完了時
    void onPairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing)
    {
      QString status = getPairingStatusString(pairing);
      qDebug() << "ペアリング完了: " << address.toString() << "-" << status;
      emit pairingComplete(address, pairing == QBluetoothLocalDevice::Paired);
    }
    // BLEエラー発生時
    void onError()
    {
      QBluetoothLocalDevice::Error error = localDevice->error();
      QString errorMsg;
      switch (error) {
          case QBluetoothLocalDevice::NoError:
            errorMsg = "エラーなし";
            break;
          case QBluetoothLocalDevice::PairingError:
            errorMsg = "ペアリングエラー";
            break;
          case QBluetoothLocalDevice::UnknownError:
          default:
            errorMsg = "不明なエラー";
            break;
      }
      qDebug() << "Bluetoothエラー: " << errorMsg;
      emit errorOccurred(errorMsg);
    }
    // ペアリング時のPIN確認要求処理
    void onPairingDisplayConfirmation(const QBluetoothAddress &address, QString pin)
    {
      qDebug() << "ペアリング確認が必要:";
      qDebug() << "  デバイス: " << address.toString();
      qDebug() << "  PIN: " << pin;
      emit pairingConfirmationRequired(address, pin);
    }
    // PINコード表示要求時
    void onPairingDisplayPinCode(const QBluetoothAddress &address, QString pin)
    {
      qDebug() << "PINコードの表示:";
      qDebug() << "  デバイス: " << address.toString();
      qDebug() << "  PIN: " << pin;
    }
};
</syntaxhighlight>
<br><br>
== 接続の確立と維持 ==
BLEでは、<u>Central (セントラル)</u>および<u>Peripheral (ペリフェラル)</u>という役割がある。<br>
* Central
*: 通常は、スマートフォンやPCのことを指す。(接続を開始する側)
* Peripheral
*: 通常は、センサやウェアラブルデバイスのことを指す。(接続を待つ側)
<br>
データ通信の特徴を以下に示す。<br>
* Read
*: データの読み取り
* Write
*: データの書き込み
* Notify
*: データの変更時に自動通知
* Indicate
*: Notifyと同様、確認応答あり
<br>
==== 使用例 ====
<syntaxhighlight lang="c++">
#include <QObject>
#include <QLowEnergyController>
#include <QLowEnergyService>
#include <QLowEnergyCharacteristic>
#include <QTimer>
#include <QDebug>
class BLEConnection : public QObject
{
    Q_OBJECT
private:
    QLowEnergyController controller;
    QTimer              reconnectTimer;
    QBluetoothDeviceInfo currentDevice;
    bool autoReconnect = false;
    void connectControllerSignals()
    {
      connect(&controller, &QLowEnergyController::connected, this, &BLEConnection::onConnected);
      connect(&controller, &QLowEnergyController::disconnected, this, &BLEConnection::onDisconnected);
      connect(&controller, &QLowEnergyController::serviceDiscovered, this, &BLEConnection::onServiceDiscovered);
      connect(&controller, static_cast<void(QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error), this, &BLEConnection::onError);
      connect(&controller, &QLowEnergyController::stateChanged, this, &BLEConnection::onStateChanged);
    }
    void connectServiceSignals(QLowEnergyService *service)
    {
        connect(service, &QLowEnergyService::stateChanged, this, [this, service](QLowEnergyService::ServiceState newState) {
          qDebug() << "サービス状態が変更: " << getServiceStateMessage(newState);
          if (newState == QLowEnergyService::ServiceDiscovered) {
              // キャラクタリスティックの処理
              const QList<QLowEnergyCharacteristic> chars = service->characteristics();
              for (const QLowEnergyCharacteristic& ch : chars) {
                qDebug() << "キャラクタリスティック発見:";
                qDebug() << "  UUID: " << ch.
                qDebug() << "キャラクタリスティック発見:";
                qDebug() << "  UUID: " << ch.uuid().toString();
                qDebug() << "  プロパティ: " << getCharacteristicPropertiesString(ch.properties());
                // Notify対応のキャラクタリスティックの場合
                if (ch.properties() & QLowEnergyCharacteristic::Notify) {
                    QLowEnergyDescriptor notifyDesc = ch.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
                    if (notifyDesc.isValid()) {
                      service->writeDescriptor(notifyDesc, QByteArray::fromHex("0100"));
                    }
                }
              }
          }
        });
        connect(service, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this,
                [this](QLowEnergyService::ServiceError error) {
                  qDebug() << "サービスエラー:" << getServiceErrorMessage(error);
        });
        connect(service, &QLowEnergyService::characteristicChanged, this,
                [](const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
                  qDebug() << "キャラクタリスティック値が変更:";
                  qDebug() << "  UUID:" << characteristic.uuid().toString();
                  qDebug() << "  新しい値:" << newValue.toHex();
        });
    }
    QString getErrorMessage(QLowEnergyController::Error error)
    {
      switch (error) {
          case QLowEnergyController::NoError:                      return "エラーなし";
          case QLowEnergyController::UnknownError:                return "不明なエラー";
          case QLowEnergyController::UnknownRemoteDeviceError:    return "リモートデバイスが見つかりません";
          case QLowEnergyController::NetworkError:                return "ネットワークエラー";
          case QLowEnergyController::InvalidBluetoothAdapterError: return "無効なBluetoothアダプタ";
          case QLowEnergyController::ConnectionError:              return "接続エラー";
          default:                                                return "予期せぬエラー";
      }
    }
    QString getStateMessage(QLowEnergyController::ControllerState state)
    {
      switch (state) {
          case QLowEnergyController::UnconnectedState: return "未接続";
          case QLowEnergyController::ConnectingState:  return "接続中";
          case QLowEnergyController::ConnectedState:  return "接続済み";
          case QLowEnergyController::DiscoveringState: return "探索中";
          case QLowEnergyController::DiscoveredState:  return "探索完了";
          case QLowEnergyController::ClosingState:    return "切断中";
          case QLowEnergyController::AdvertisingState: return "アドバタイジング中";
          default:                                    return "不明な状態";
      }
    }
    QString getServiceStateMessage(QLowEnergyService::ServiceState state)
    {
      switch (state) {
          case QLowEnergyService::InvalidService:      return "無効なサービス";
          case QLowEnergyService::DiscoveryRequired:  return "探索が必要";
          case QLowEnergyService::DiscoveringServices: return "サービス探索中";
          case QLowEnergyService::ServiceDiscovered:  return "サービス探索完了";
          default:                                    return "不明なサービス状態";
      }
    }
    QString getServiceErrorMessage(QLowEnergyService::ServiceError error)
    {
      switch (error) {
          case QLowEnergyService::NoError:                  return "エラーなし";
          case QLowEnergyService::OperationError:          return "操作エラー";
          case QLowEnergyService::CharacteristicReadError:  return "キャラクタリスティック読み取りエラー";
          case QLowEnergyService::CharacteristicWriteError: return "キャラクタリスティック書き込みエラー";
          case QLowEnergyService::DescriptorReadError:      return "ディスクリプタ読み取りエラー";
          case QLowEnergyService::DescriptorWriteError:    return "ディスクリプタ書き込みエラー";
          case QLowEnergyService::UnknownError:            return "不明なエラー";
          default:                                          return "予期せぬサービスエラー";
      }
    }
    QString getCharacteristicPropertiesString(QLowEnergyCharacteristic::PropertyTypes properties)
    {
      QStringList props;
      if (properties & QLowEnergyCharacteristic::Read)            props << "Read";
      if (properties & QLowEnergyCharacteristic::Write)          props << "Write";
      if (properties & QLowEnergyCharacteristic::Notify)          props << "Notify";
      if (properties & QLowEnergyCharacteristic::Indicate)        props << "Indicate";
      if (properties & QLowEnergyCharacteristic::WriteSigned)    props << "WriteSigned";
      if (properties & QLowEnergyCharacteristic::WriteNoResponse) props << "WriteNoResponse";
      return props.join(", ");
    }
public:
    explicit BLEConnection(QObject* parent = nullptr) : QObject(parent)
    {
      // 再接続タイマの初期化
      reconnectTimer.setInterval(5000);  // 5秒間隔で再接続
      reconnectTimer.setSingleShot(true);
      connect(&reconnectTimer &QTimer::timeout, this, &BLEConnection::onReconnectTimeout);
    }
    // デバイスへの接続
    void connectToDevice(const QBluetoothDeviceInfo &device)
    {
      try {
          qDebug() << "デバイスへの接続を開始:" << device.name();
          // コントローラの初期化
          connectControllerSignals();
          // 接続パラメータの設定
          controller.setRemoteAddressType(QLowEnergyController::PublicAddress);
          // 接続開始
          controller.connectToDevice();
          currentDevice = device;
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("接続エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 切断
    void disconnect()
    {
      try {
          if (controller) {
            controller.disconnectFromDevice();
            reconnectTimer.stop();
          }
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("切断エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 自動再接続の設定
    void setAutoReconnect(bool enable)
    {
      autoReconnect = enable;
      if (!enable) {
          reconnectTimer.stop();
      }
    }
signals:
    void connected();
    void disconnected();
    void errorOccurred(const QString& error);
    void connectionStateChanged(QLowEnergyController::ControllerState state);
    void serviceDiscovered(QLowEnergyService* service);
private slots:
    void onConnected()
    {
      qDebug() << "デバイスに接続";
      reconnectTimer.stop();
      emit connected();
      // サービスの探索を開始
      controller.discoverServices();
    }
    void onDisconnected()
    {
      qDebug() << "デバイスから切断";
      emit disconnected();
      // 自動再接続が有効な場合
      if (autoReconnect) {
          qDebug() << "再接続を試行...";
          reconnectTimer.start();
      }
    }
    void onServiceDiscovered(const QBluetoothUuid& uuid)
    {
      qDebug() << "サービスを発見: " << uuid.toString();
      QLowEnergyService* service = controller.createServiceObject(uuid, this);
      if (service) {
          connectServiceSignals(service);
          emit serviceDiscovered(service);
      }
    }
    void onError(QLowEnergyController::Error error)
    {
      QString errorMessage = getErrorMessage(error);
      qDebug() << "エラーが発生: " << errorMessage;
      emit errorOccurred(errorMessage);
      if (autoReconnect && error != QLowEnergyController::InvalidBluetoothAdapterError) reconnectTimer.start();
    }
    void onStateChanged(QLowEnergyController::ControllerState state)
    {
      qDebug() << "接続状態が変更: " << getStateMessage(state);
      emit connectionStateChanged(state);
    }
    void onReconnectTimeout()
    {
      if (autoReconnect && currentDevice.isValid()) {
          qDebug() << "再接続を試行...";
          controller.connectToDevice();
      }
    }
};
</syntaxhighlight>
<br><br>
== データの送受信 ==
==== Classic Bluetoothとの違い ====
* キャラクタリスティックの操作が基本
* 読み取り
*: readCharacteristicメソッド
* 書き込み
*: writeCharacteristicメソッド
* 通知
*: characteristicChangedシグナルで受信
<br>
==== 使用例 ====
以下の例では、BLEを使用してデータの送受信を行っている。<br>
<br>
BLEDataTransferクラスの機能を以下に示す。<br>
* データの受信
*: 特定のキャラクタリスティックから値を受信
*: 非同期の受信完了通知
* データの送信
*: 特定のキャラクタリスティックに値を送信
*: キューを使用した順序付き送信
*: 非同期の送信完了通知
* Notify / Indicate (通知) の管理
*: 特定のキャラクタリスティックの通知を有効化 / 無効化
*: 値変更時の自動通知
<br>
<syntaxhighlight lang="c++">
#include <QObject>
#include <QLowEnergyService>
#include <QLowEnergyCharacteristic>
#include <QLowEnergyDescriptor>
#include <QQueue>
#include <QTimer>
#include <memory>
#include <QDebug>
class BLEDataTransfer : public QObject
{
    Q_OBJECT
private:
    struct WriteRequest {
      QBluetoothUuid uuid;
      QByteArray value;
    };
    QLowEnergyService      *service = nullptr;
    std::unique_ptr<QTimer> writeTimer;
    QQueue<WriteRequest>    writeQueue;
 
    void connectServiceSignals()
    {
      // キャラクタリスティック受信完了時
      connect(service, &QLowEnergyService::characteristicRead, this, [this](const QLowEnergyCharacteristic &c, const QByteArray &value) {
          qDebug() << "キャラクタリスティック受信完了:";
          qDebug() << "  UUID:" << c.uuid().toString();
          qDebug() << "  値:" << value.toHex();
          emit characteristicRead(c.uuid(), value);
      });
      // キャラクタリスティック送信完了時
      connect(service, &QLowEnergyService::characteristicWritten, this, [this](const QLowEnergyCharacteristic &c, const QByteArray &value) {
          qDebug() << "キャラクタリスティック送信完了:";
          qDebug() << "  UUID:" << c.uuid().toString();
          qDebug() << "  値:" << value.toHex();
          emit characteristicWritten(c.uuid(), value);
      });
      // キャラクタリスティック値変更時 (Notify / Indicate)
      connect(service, &QLowEnergyService::characteristicChanged, this, [this](const QLowEnergyCharacteristic &c, const QByteArray &value) {
          qDebug() << "キャラクタリスティック値変更:";
          qDebug() << "  UUID:" << c.uuid().toString();
          qDebug() << "  新しい値:" << value.toHex();
          emit characteristicChanged(c.uuid(), value);
      });
      // サービスエラー発生時
      connect(service, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error), this, [this](QLowEnergyService::ServiceError error) {
          QString errorMsg = getServiceErrorMessage(error);
          qDebug() << "サービスエラー: " << errorMsg;
          emit errorOccurred(errorMsg);
      });
    }
    QString getServiceErrorMessage(QLowEnergyService::ServiceError error)
    {
      switch (error) {
          case QLowEnergyService::NoError:
            return "エラーなし";
          case QLowEnergyService::OperationError:
            return "操作エラー";
          case QLowEnergyService::CharacteristicReadError:
            return "キャラクタリスティック読み取りエラー";
          case QLowEnergyService::CharacteristicWriteError:
            return "キャラクタリスティック書き込みエラー";
          case QLowEnergyService::DescriptorReadError:
            return "ディスクリプタ読み取りエラー";
          case QLowEnergyService::DescriptorWriteError:
            return "ディスクリプタ書き込みエラー";
          case QLowEnergyService::UnknownError:
            return "不明なエラー";
          default:
            return "予期せぬサービスエラー";
      }
    }
public:
    explicit BLEDataTransfer(QObject* parent = nullptr) : QObject(parent)
    {
      // 送信キュー処理用タイマ
      writeTimer = std::make_unique<QTimer>(this);
      writeTimer->setInterval(100);  // 100[ms]間隔
      connect(writeTimer.get(), &QTimer::timeout, this, &BLEDataTransfer::processWriteQueue);
    }
    // サービスの設定
    void setService(QLowEnergyService* service)
    {
      try {
          if (!service) {
            throw std::runtime_error("無効なサービス");
          }
          this->service = service;
          connectServiceSignals();
          qDebug() << "サービスの設定完了: " << service->serviceUuid().toString();
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("サービス設定エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 特定のキャラクタリスティックの値を受信
    void readCharacteristic(const QBluetoothUuid& uuid)
    {
      try {
          if (!service) {
            throw std::runtime_error("サービスが未設定");
          }
          QLowEnergyCharacteristic characteristic = service->characteristic(uuid);
          if (!characteristic.isValid()) throw std::runtime_error("無効なキャラクタリスティック");
          qDebug() << "キャラクタリスティックの読み取りを開始:" << uuid.toString();
          service->readCharacteristic(characteristic);
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("読み取りエラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // 特定のキャラクタリスティックに値を送信
    void writeCharacteristic(const QBluetoothUuid& uuid, const QByteArray& value, bool useQueue = true)
    {
      try {
          if (!service) throw std::runtime_error("サービスが未設定");
          QLowEnergyCharacteristic characteristic = service->characteristic(uuid);
          if (!characteristic.isValid()) throw std::runtime_error("無効なキャラクタリスティックです");
          // 書き込みプロパティの確認
          if (!(characteristic.properties() & (QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::WriteNoResponse))) {
            throw std::runtime_error("書き込みが許可されていない");
          }
          if (useQueue) {
            // キューに追加
            WriteRequest request;
            request.uuid = uuid;
            request.value = value;
            writeQueue.enqueue(request);
            if (!writeTimer->isActive()) writeTimer->start();
          }
          else {
            // 即時書き込み
            qDebug() << "キャラクタリスティックに送信: " << uuid.toString() << "値: " << value.toHex();
            service->writeCharacteristic(characteristic, value);
          }
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("書き込みエラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
    // Notify / Indicateの有効化
    void enableNotifications(const QBluetoothUuid& uuid, bool enable = true)
    {
      try {
          if (!service) throw std::runtime_error("サービスが未設定");
          QLowEnergyCharacteristic characteristic = service->characteristic(uuid);
          if (!characteristic.isValid()) throw std::runtime_error("無効なキャラクタリスティック");
          // Notify/Indicateプロパティのチェック
          if (!(characteristic.properties() & (QLowEnergyCharacteristic::Notify | QLowEnergyCharacteristic::Indicate))) {
            throw std::runtime_error("通知が許可されていない");
          }
          // Client Characteristic Configuration Descriptorを取得
          QLowEnergyDescriptor cccd = characteristic.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
          if (!cccd.isValid()) throw std::runtime_error("CCCDが見つからない");
          // 通知の有効化 / 無効化
          QByteArray value = enable ? QByteArray::fromHex("0100") : QByteArray::fromHex("0000");
          qDebug() << "通知を" << (enable ? "有効" : "無効") << "にします:" << uuid.toString();
          service->writeDescriptor(cccd, value);
      }
      catch (const std::exception &e) {
          QString errorMsg = QString("通知設定エラー: %1").arg(e.what());
          qDebug() << errorMsg;
          emit errorOccurred(errorMsg);
      }
    }
signals:
    void characteristicRead(const QBluetoothUuid& uuid, const QByteArray& value);
    void characteristicWritten(const QBluetoothUuid& uuid, const QByteArray& value);
    void characteristicChanged(const QBluetoothUuid& uuid, const QByteArray& value);
    void errorOccurred(const QString& error);
private slots:
    void processWriteQueue()
    {
      if (writeQueue.isEmpty()) {
          writeTimer->stop();
          return;
      }
      if (!service) {
          writeQueue.clear();
          writeTimer->stop();
          return;
      }
      WriteRequest request = writeQueue.dequeue();
      writeCharacteristic(request.uuid, request.value, false);
    }
};
</syntaxhighlight>
<br><br>
== BLE通信の使用例 ==
<syntaxhighlight lang="c++">
// BLEManager.hファイル
#include <QCoreApplication>
#include <QTimer>
#include <memory>
#include "bledevicescanner.h"
#include "bleservicediscovery.h"
#include "blepairing.h"
#include "bleconnection.h"
#include "bledatatransfer.h"
class BLEManager : public QObject
{
    Q_OBJECT
private:
    std::unique_ptr<BLEDeviceScanner>    scanner;
    std::unique_ptr<BLEServiceDiscovery> serviceDiscovery;
    std::unique_ptr<BLEPairing>          pairing;
    std::unique_ptr<BLEConnection>      connection;
    std::unique_ptr<BLEDataTransfer>    dataTransfer;
    QBluetoothDeviceInfo                targetDevice;
    std::unique_ptr<QTimer>              transferTimer;
    void connectSignals()
    {
      // スキャナのシグナル接続
        connect(scanner.get(), &BLEDeviceScanner::deviceDiscovered, this, &BLEManager::onDeviceDiscovered);
        connect(scanner.get(), &BLEDeviceScanner::errorOccurred, this, &BLEManager::onErrorOccurred);
        // ペアリングのシグナル接続
        connect(pairing.get(), &BLEPairing::pairingComplete, this, &BLEManager::onPairingComplete);
        connect(pairing.get(), &BLEPairing::errorOccurred, this, &BLEManager::onErrorOccurred);
        // 接続管理のシグナル接続
        connect(connection.get(), &BLEConnection::connected, this, &BLEManager::onConnected);
        connect(connection.get(), &BLEConnection::serviceDiscovered, this, &BLEManager::onServiceDiscovered);
        connect(connection.get(), &BLEConnection::errorOccurred, this, &BLEManager::onErrorOccurred);
        // データ転送のシグナル接続
        connect(dataTransfer.get(), &BLEDataTransfer::characteristicChanged, this, &BLEManager::onCharacteristicChanged);
        connect(dataTransfer.get(), &BLEDataTransfer::errorOccurred, this, &BLEManager::onErrorOccurred);
    }
    void startPeriodicDataTransfer()
    {
      transferTimer = std::make_unique<QTimer>(this);
      transferTimer->setInterval(1000);  // 1秒間隔
      connect(transferTimer.get(), &QTimer::timeout, this, [this]() {
          // サンプルデータの送信
          QByteArray data = generateSampleData();
          dataTransfer->writeCharacteristic(QBluetoothUuid(QString("YOUR_CHARACTERISTIC_UUID")), data );
      });
      transferTimer->start();
    }
    QByteArray generateSampleData()
    {
      // サンプルデータの生成 (実務では、適切なデータを生成)
      QByteArray data;
      data.append(0x01);  // コマンド
      data.append(0x02);  // データ長
      data.append(0x03);  // データ1
      data.append(0x04);  // データ2
      return data;
    }
    void processReceivedData(const QByteArray &data)
    {
      // 受信データの処理 (実務では、適切な処理を実装)
      if (data.isEmpty()) return;
      // データの解析例
      quint8 command = data.at(0);
      switch (command) {
          case 0x01:
            qDebug() << "コマンド1を受信";
            break;
          case 0x02:
            qDebug() << "コマンド2を受信";
            break;
          default:
            qDebug() << "不明なコマンド: " << command;
            break;
      }
    }
public:
    explicit BLEManager(QObject* parent = nullptr) : QObject(parent)
    {
      // 各コンポーネントの初期化
      scanner = std::make_unique<BLEDeviceScanner>(this);
      serviceDiscovery = std::make_unique<BLEServiceDiscovery>(this);
      pairing = std::make_unique<BLEPairing>(this);
      connection = std::make_unique<BLEConnection>(this);
      dataTransfer = std::make_unique<BLEDataTransfer>(this);
      connectSignals();
    }
    // デバイススキャンを開始
    void startDeviceScan()
    {
      qDebug() << "BLEデバイスのスキャンを開始...";
      scanner->startScan();
    }
private slots:
    void onDeviceDiscovered(const QBluetoothDeviceInfo& device)
    {
      // ターゲットデバイスかどうかを確認 (実務では、適切なフィルタリングが必要)
      if (device.name().contains("YourDeviceName", Qt::CaseInsensitive))
      {
          scanner->stopScan();
          targetDevice = device;
          qDebug() << "ターゲットデバイスを発見: " << device.name();
          // ペアリングを開始
          pairing->requestPairing(device.address());
      }
    }
    void onPairingComplete(const QBluetoothAddress &address, bool success)
    {
      if (success) {
          qDebug() << "ペアリング完了: 接続を開始...";
          connection->connectToDevice(targetDevice);
      }
      else {
          qDebug() << "ペアリングに失敗";
      }
    }
    void onConnected()
    {
      qDebug() << "デバイスに接続完了";
      connection->setAutoReconnect(true);  // 自動再接続を有効化
    }
    void onServiceDiscovered(QLowEnergyService *service)
    {
      qDebug() << "サービスを発見: " << service->serviceUuid().toString();
      // 対象のサービスかどうかを確認
      if (service->serviceUuid() == QBluetoothUuid(QString("YOUR_SERVICE_UUID"))) {
          dataTransfer->setService(service);
          // Notifyを有効化
          dataTransfer->enableNotifications(QBluetoothUuid(QString("YOUR_CHARACTERISTIC_UUID")));
          // 定期的なデータ送信を開始
          startPeriodicDataTransfer();
      }
    }
    void onCharacteristicChanged(const QBluetoothUuid &uuid, const QByteArray &value)
    {
      qDebug() << "データを受信:";
      qDebug() << "  UUID: " << uuid.toString();
      qDebug() << "  値: " << value.toHex();
      // 受信データの処理
      processReceivedData(value);
    }
    void onErrorOccurred(const QString &error)
    {
      qDebug() << "エラーが発生: " << error;
      // エラーに応じた適切な処理を実装
      // ...略
    }
};
</syntaxhighlight>
<br>
<syntaxhighlight lang="c++">
// main.cppファイル
#include <QCoreApplication>
#include "BLEManager.h"
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    try {
      // BLEマネージャーオブジェクトの生成と開始
      auto bleManager = std::make_unique<BLEManager>();
      bleManager->startDeviceScan();
      return app.exec();
    }
    catch (const std::exception &e) {
      qDebug() << "致命的なエラーが発生: " << e.what();
      return -1;
    }
}
</syntaxhighlight>
<br><br>


{{#seo:
{{#seo: