12,925
回編集
(→概要) |
編集の要約なし |
||
100行目: | 100行目: | ||
また、スキャン時間や範囲を設定可能であり、バッテリー消費とスキャン精度のバランスを取ることができる。<br> | また、スキャン時間や範囲を設定可能であり、バッテリー消費とスキャン精度のバランスを取ることができる。<br> | ||
<br> | <br> | ||
<u>BLEでは、同様の<code>QBluetoothDeviceDiscoveryAgent</code> | <u>BLEでは、同様の<code>QBluetoothDeviceDiscoveryAgent</code>クラスを使用するが、<code>LowEnergyDiscoveryTimeout</code>の設定が必要となる。</u><br> | ||
また、フィルタリングでBLEデバイスのみを検出する。<br> | また、フィルタリングでBLEデバイスのみを検出する。<br> | ||
<br><br> | <br><br> | ||
120行目: | 120行目: | ||
BLEでは、<code>QLowEnergyService</code>クラスでより複雑なサービス階層を管理する。<br> | BLEでは、<code>QLowEnergyService</code>クラスでより複雑なサービス階層を管理する。<br> | ||
キャラクタリスティックとディスクリプタの概念が追加されている。<br> | キャラクタリスティックとディスクリプタの概念が追加されている。<br> | ||
<br> | |||
Classic Bluetoothとの違いを以下に示す。<br> | |||
* QLowEnergyControllerクラスを使用 | |||
* connectToDeviceメソッドで接続する | |||
* discoveryFinishedシグナルで探索完了を検知する | |||
* createServiceObjectメソッドを使用して、サービスオブジェクトを生成する | |||
<br><br> | |||
== ペアリング管理 (ボンディング) == | |||
以下に示すように、セキュリティレベルが定義されている。<br> | |||
* Just Works | |||
*: 最も簡単であるが、セキュリティは低い。 | |||
* Passkey Entry | |||
*: PINコードを使用する。 | |||
* Out of Band | |||
*: NFC等の別の通信手段を使用する。 | |||
* Numeric Comparison | |||
*: 両デバイスで同じ数字を確認する。 | |||
<br> | |||
Classic Bluetoothとの違いを以下に示す。<br> | |||
* 接続パラメータの設定が可能 | |||
*: 接続間隔、レイテンシ、タイムアウト等 | |||
* 省電力モードの制御 | |||
* 接続状態の監視 | |||
<br><br> | |||
== 接続の確立と維持 == | |||
BLEでは、<u>Central (セントラル)</u>および<u>Peripheral (ペリフェラル)</u>という役割がある。<br> | |||
* Central | |||
*: 通常は、スマートフォンやPCのことを指す。(接続を開始する側) | |||
* Peripheral | |||
*: 通常は、センサやウェアラブルデバイスのことを指す。(接続を待つ側) | |||
<br> | |||
データ通信の特徴を以下に示す。<br> | |||
* Read | |||
*: データの読み取り | |||
* Write | |||
*: データの書き込み | |||
* Notify | |||
*: データの変更時に自動通知 | |||
* Indicate | |||
*: Notifyと同様、確認応答あり | |||
<br> | |||
Classic Bluetoothとの違いを以下に示す。<br> | |||
* キャラクタリスティックの操作が基本 | |||
* 読み取り | |||
*: readCharacteristicメソッド | |||
* 書き込み | |||
*: writeCharacteristicメソッド | |||
* 通知 | |||
*: characteristicChangedシグナルで受信 | |||
<br><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> | <br><br> | ||