📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)
| (同じ利用者による、間の21版が非表示) | |||
| 93行目: | 93行目: | ||
<u>BLEペリフェラル (アドバタイザ) デバイスがアドバタイズメントと呼ばれる特別なブロードキャストパケットを定期的に発信して</u>、セントラル (スキャナ) デバイスがそれをスキャンする。<br> | <u>BLEペリフェラル (アドバタイザ) デバイスがアドバタイズメントと呼ばれる特別なブロードキャストパケットを定期的に発信して</u>、セントラル (スキャナ) デバイスがそれをスキャンする。<br> | ||
スキャンを行うのはセントラル側、アドバタイズメントパケットを発信するのはペリフェラル側である。<br> | スキャンを行うのはセントラル側、アドバタイズメントパケットを発信するのはペリフェラル側である。<br> | ||
<br> | |||
[[ファイル:Qt BLE 1.png|フレームなし|中央]] | |||
<br> | <br> | ||
このアドバタイジングパケットには、以下に示す情報が含まれる。<br> | このアドバタイジングパケットには、以下に示す情報が含まれる。<br> | ||
| 126行目: | 128行目: | ||
* アドバタイジングアドレス (6バイト) | * アドバタイジングアドレス (6バイト) | ||
* アドバタイジングデータ (最大31バイト) | * アドバタイジングデータ (最大31バイト) | ||
<br> | |||
[[ファイル:Qt BLE 2.png|フレームなし|中央]] | |||
<br> | <br> | ||
==== アドバタイジングデータの主要要素 ==== | ==== アドバタイジングデータの主要要素 ==== | ||
| 140行目: | 144行目: | ||
* AD Data (可変長) | * AD Data (可変長) | ||
<br> | <br> | ||
[[ファイル:Qt BLE 3.png|フレームなし|中央]] | |||
<br> | |||
==== その他 (通信特性 / セキュリティ等) ==== | ==== その他 (通信特性 / セキュリティ等) ==== | ||
* 通信特性 | * 通信特性 | ||
| 161行目: | 168行目: | ||
<br> | <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++"> | <syntaxhighlight lang="c++"> | ||
discoveryAgent->stop(); | |||
</syntaxhighlight> | |||
<br> | |||
===== 組み合わせ ===== | |||
<syntaxhighlight lang="c++"> | |||
// BLEデバイスのスキャンを管理するクラス | |||
// BLEデバイスの検出と監視, 継続的なスキャンモード, デバイス情報の重複排除, 自動リカバリを行う | |||
#include <QObject> | #include <QObject> | ||
#include <QBluetoothDeviceDiscoveryAgent> | #include <QBluetoothDeviceDiscoveryAgent> | ||
#include <QBluetoothDeviceInfo> | #include <QBluetoothDeviceInfo> | ||
#include <QTimer> | #include <QTimer> | ||
#include <QDebug> | #include <QDebug> | ||
| 174行目: | 301行目: | ||
private: | private: | ||
QBluetoothDeviceDiscoveryAgent discoveryAgent; // BLEデバイス探索用エージェント | |||
QTimer rescanTimer; // 継続的スキャン用タイマ | |||
bool isContinuousScan = false; | bool isContinuousScan = false; // 継続的スキャンモードのフラグ | ||
QMap<QBluetoothAddress, QBluetoothDeviceInfo> knownDevices; // 既知デバイスのキャッシュ | |||
// デバイス探索エージェントとタイマーのシグナルを適切なスロットに接続 | |||
// デバイス発見時の処理, スキャン完了時の処理, エラー発生時の処理, 定期的な再スキャンの制御を行う | |||
void connectSignals() | void connectSignals() | ||
{ | { | ||
// デバイス探索エージェントのシグナル接続 | // デバイス探索エージェントのシグナル接続 | ||
connect(discoveryAgent | connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BLEDeviceScanner::onDeviceDiscovered); | ||
connect(discoveryAgent | connect(&discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BLEDeviceScanner::onScanFinished); | ||
connect(discoveryAgent | connect(&discoveryAgent, static_cast<void(QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error), | ||
this, &BLEDeviceScanner::onError); | this, &BLEDeviceScanner::onError); | ||
// 再スキャンタイマのシグナル接続 | // 再スキャンタイマのシグナル接続 | ||
connect(rescanTimer | connect(&rescanTimer, &QTimer::timeout, this, [this]() { | ||
if (isContinuousScan) { | if (isContinuousScan) { | ||
discoveryAgent | discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); | ||
} | } | ||
}); | }); | ||
} | } | ||
// エラーコード | |||
QString getErrorMessage(QBluetoothDeviceDiscoveryAgent::Error error) | QString getErrorMessage(QBluetoothDeviceDiscoveryAgent::Error error) | ||
{ | { | ||
| 204行目: | 335行目: | ||
case QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod: return "未対応の探索方法"; | case QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod: return "未対応の探索方法"; | ||
default: 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++; | |||
} | |||
} | } | ||
} | } | ||
| 210行目: | 371行目: | ||
explicit BLEDeviceScanner(QObject* parent = nullptr) : QObject(parent) | explicit BLEDeviceScanner(QObject* parent = nullptr) : QObject(parent) | ||
{ | { | ||
// BLEデバイスのみをスキャンするように設定 | // BLEデバイスのみをスキャンするように設定 | ||
discoveryAgent | discoveryAgent.setLowEnergyDiscoveryTimeout(10000); // 10秒のタイムアウト | ||
// 自動再スキャンタイマの設定 | // 自動再スキャンタイマの設定 | ||
rescanTimer | rescanTimer.setInterval(30000); // 30秒間隔で再スキャン | ||
connectSignals(); | connectSignals(); | ||
} | } | ||
// | // BLEデバイスのスキャンを開始 | ||
void startScan(bool continuous = false) | void startScan(bool continuous = false) | ||
{ | { | ||
| 229行目: | 386行目: | ||
qDebug() << "BLEデバイススキャンを開始..."; | qDebug() << "BLEデバイススキャンを開始..."; | ||
isContinuousScan = continuous; | isContinuousScan = continuous; | ||
discoveryAgent | discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); | ||
if (continuous) rescanTimer | if (continuous) rescanTimer.start(); | ||
emit scanStarted(); | emit scanStarted(); | ||
| 247行目: | 404行目: | ||
try { | try { | ||
qDebug() << "BLEデバイススキャンを停止..."; | qDebug() << "BLEデバイススキャンを停止..."; | ||
discoveryAgent | discoveryAgent.stop(); | ||
rescanTimer | rescanTimer.stop(); | ||
isContinuousScan = false; | isContinuousScan = false; | ||
emit scanStopped(); | emit scanStopped(); | ||
| 260行目: | 417行目: | ||
signals: | signals: | ||
void deviceDiscovered(const QBluetoothDeviceInfo& device); | void deviceDiscovered(const QBluetoothDeviceInfo& device); // 新規デバイス発見時 | ||
void scanStarted(); | void scanStarted(); // スキャン開始時 | ||
void scanStopped(); | void scanStopped(); // スキャン停止時 | ||
void scanFinished(); | void scanFinished(); // スキャン完了時 | ||
void errorOccurred(const QString& error); | void errorOccurred(const QString& error); // エラー発生時 | ||
private slots: | private slots: | ||
// デバイス発見時の処理 | |||
// BLEデバイスのフィルタリング, 重複デバイスの検出と更新, デバイス情報のログ出力, 新規 / 更新デバイスの通知 | |||
void onDeviceDiscovered(const QBluetoothDeviceInfo& device) | void onDeviceDiscovered(const QBluetoothDeviceInfo& device) | ||
{ | { | ||
// BLEデバイスのみを処理 | // 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() | void onScanFinished() | ||
{ | { | ||
| 308行目: | 454行目: | ||
if (isContinuousScan) { | if (isContinuousScan) { | ||
QTimer::singleShot(1000, this, [this]() { | QTimer::singleShot(1000, this, [this]() { | ||
discoveryAgent | discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); | ||
}); | }); | ||
} | } | ||
} | } | ||
// エラー発生時の処理 (エラーメッセージの生成とログ出力) | |||
// 継続的スキャンモードの場合は5秒後に自動再試行 | |||
void onError(QBluetoothDeviceDiscoveryAgent::Error error) | void onError(QBluetoothDeviceDiscoveryAgent::Error error) | ||
{ | { | ||
| 322行目: | 470行目: | ||
if (isContinuousScan) { | if (isContinuousScan) { | ||
QTimer::singleShot(5000, this, [this]() { | QTimer::singleShot(5000, this, [this]() { | ||
discoveryAgent | discoveryAgent.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); | ||
}); | }); | ||
} | } | ||
| 333行目: | 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> | <br> | ||
Qtでは、<code>QLowEnergyService</code>クラスを使用して、BLEの複雑なサービス階層を管理する。<br> | |||
<br> | <br> | ||
Classic Bluetoothとの違いを以下に示す。<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 | * Just Works | ||
*: 自動的にペアリングを実行する。 | |||
*: Man-in-the-Middle攻撃に対して脆弱である。 | |||
*: IoTセンサ等、機密性の低いデバイスに適用される。 | |||
*: 最も簡単であるが、セキュリティは低い。 | *: 最も簡単であるが、セキュリティは低い。 | ||
*: <br> | |||
* Passkey Entry | * Passkey Entry | ||
*: PINコードを使用する。 | *: PINコードを使用する。 | ||
*:* Fixed PIN | |||
*:*: デバイスに事前設定された固定PIN | |||
*:* Dynamic PIN | |||
*:*: 接続時に動的に生成されるPIN | |||
*: キーボード付きデバイスやディスプレイ付きデバイスで使用される。 | |||
*: <br> | |||
* Out of Band | * Out of Band | ||
*: | *: NFC / QRコード等の別チャネルの通信によりでペアリング情報を交換する。 | ||
*: 高いセキュリティレベルを実現しているが、追加のハードウェアが必要となる。 | |||
*: <br> | |||
* Numeric Comparison | * Numeric Comparison | ||
*: | *: 両デバイスで6桁の数字を表示・確認する。 | ||
*: そのため、ディスプレイ付きデバイス間で使用される。 | |||
*: Bluetooth 4.2以降で利用可能である。 | |||
<br> | <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> | <br><br> | ||
| 389行目: | 1,100行目: | ||
*: Notifyと同様、確認応答あり | *: Notifyと同様、確認応答あり | ||
<br> | <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との違い ==== | |||
* キャラクタリスティックの操作が基本 | * キャラクタリスティックの操作が基本 | ||
* 読み取り | * 読み取り | ||
| 397行目: | 1,366行目: | ||
* 通知 | * 通知 | ||
*: characteristicChangedシグナルで受信 | *: characteristicChangedシグナルで受信 | ||
<br> | |||
==== 使用例 ==== | |||
== | |||
以下の例では、BLEを使用してデータの送受信を行っている。<br> | 以下の例では、BLEを使用してデータの送受信を行っている。<br> | ||
<br> | <br> | ||
| 637行目: | 1,605行目: | ||
<br><br> | <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: | ||