Bluetooth Low Energy の概要

Qt Bluetooth Low Energy API はペリフェラル/サーバーとセントラル/クライアントの役割をサポートします。すべての主要な Qt プラットフォームでサポートされています。唯一の例外は Windows でペリフェラルの役割がサポートされていないことです。

Bluetooth Low Energyとは

Bluetooth Smartとしても知られるBluetooth Low Energyは、2011年に正式に発表されたワイヤレス・コンピュータ・ネットワーク技術です。従来の」Bluetoothと同じ2.4GHzの周波数で動作する。主な違いは、技術名にもあるように、エネルギー消費が少ないことだ。この技術により、Bluetooth Low Energyデバイスは、コイン電池で数ヶ月、あるいは数年間動作する機会を得ることができる。この技術はBluetooth v4.0で導入された。この技術をサポートする機器はBluetooth Smart Ready機器と呼ばれる。この技術の主な特徴は以下の通り:

  • 超低ピーク、平均、アイドルモード消費電力
  • 標準的なコイン電池で数年間動作可能
  • 低コスト
  • マルチベンダーの相互運用性
  • 拡張された通信距離

Bluetooth Low Energyはクライアント・サーバー・アーキテクチャを採用しています。サーバー(周辺機器とも呼ばれる)は体温や心拍数などのサービスを提供し、それらを広告します。クライアント(中央デバイスとして知られる)はサーバーに接続し、サーバーが広告した値を読み取ります。例えば、サーモスタット、湿度センサー、圧力センサーなどのBluetooth Smart Readyセンサーが設置されたアパートがある。これらのセンサーは、アパートの環境値を広告する周辺機器である。同時に、携帯電話やコンピューターがこれらのセンサーに接続し、その値を取得し、より大きな環境制御アプリケーションの一部としてユーザーに提示するかもしれない。

基本的なサービス構造

Bluetooth Low Energyは2つのプロトコルに基づいています:ATT(Attribute Protocol)とGATT(Generic Attribute Profile)です。これらは、すべてのBluetooth Smart Readyデバイスで使用される通信レイヤーを指定します。

ATTプロトコル

ATTの基本的な構成要素は属性です。各属性は3つの要素で構成されます:

  • 値 - ペイロードまたは望ましい情報の一部
  • UUID - 属性のタイプ(GATTによって使用される)
  • 16ビットのハンドル - 属性の一意な識別子

サーバーは属性を保存し、クライアントはATTプロトコルを使用してサーバー上の値を読み書きする。

GATTプロファイル

GATTは、事前定義されたUUIDに意味を適用することにより、一連の属性のグループ化を定義する。下の表は、特定の日の心拍数を公開するサービスの例を示している。実際の値は、2つの特性の中に格納されます:

ハンドルUUID説明
0x00010x2800UUID 0x180D心拍数サービス開始
0x00020x2803UUID 0x2A37、値ハンドル:0x0003心拍計測(HRM)タイプの特性
0x00030x2A3765 bpm心拍数値
0x00040x2803UUID 0x2A08、値ハンドル:0x0005タイプ特性 日付 時間
0x00050x2A0818/08/2014 11:00測定日時
0x00060x2800UUID xxxxxx次のサービスを開始する
............

GATTは、上記で使用されたUUID0x2800 がサービス定義の開始を示すと規定している。0x2800 に続くすべての属性は、次の0x2800 または終了に出会うまで、サービスの一部です。よく知られたUUID0x2803 は、同様の方法で、特性が見つかることを示し、特性の各々は、値の性質を定義する型を持つ。上記の例では、0x2A08 (Date Time)と0x2A37 (Heart Rate Measurement)というUUIDを使用している。上記の各UUIDはBluetooth Special Interest Groupによって定義されており、GATT仕様に記載されています。利用可能な場合は事前に定義されたUUIDを使用することが望ましいですが、特性やサービスタイプに新しいUUIDやまだ使用されていないUUIDを使用することも十分に可能です。

一般に、各サービスは1つ以上の特性から構成される。特性はデータを含み、追加情報または特性を操作する手段を提供する記述子によってさらに記述することができる。すべてのサービス、特性、および記述子は、128ビットのUUIDによって認識される。最後に、サービスの中にサービスを含めることも可能です(下図参照)。

Qt Bluetooth Low Energy API の使用

このセクションでは、Qt が提供する Bluetooth Low Energy API の使用方法について説明します。クライアント側では、API は周辺機器との接続を作成し、周辺機器のサービスを検出し、機器に保存されたデータを読み書きすることができます。サーバー側では、サービスのセットアップ、広告、クライアントが特性を書き込んだときの通知を受けることができます。以下のコード例は、Heart Rate Gameと Heart Rate Serverの例から引用しています。

接続の確立

Bluetooth Low Energy周辺デバイスの特性を読み書きできるようにするには、デバイスを見つけて接続する必要があります。これには周辺デバイスがその存在とサービスをアドバタイズする必要があります。QBluetoothDeviceDiscoveryAgent クラスの助けを借りてデバイス検出を開始します。QBluetoothDeviceDiscoveryAgent::deviceDiscovered() シグナルに接続し、start() で検索を開始します:

m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(15000);

connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
        this, &DeviceFinder::addDevice);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
        this, &DeviceFinder::scanError);

connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
        this, &DeviceFinder::scanFinished);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled,
        this, &DeviceFinder::scanFinished);
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);

低エネルギー・デバイスにしか興味がないので、受信スロット内のデバイス・タイプをフィルタリングする。デバイス・タイプは、QBluetoothDeviceInfo::coreConfigurations ()フラグで確認できる。deviceDiscovered() シグナルは、詳細が発見されると、同じデバイスに対して複数回発せられることがある。ここでは、ユーザが個々のデバイスだけを見ることができるように、これらのデバイス発見を照合します:

void DeviceFinder::addDevice(const QBluetoothDeviceInfo &device)
{
    // If device is LowEnergy-device, add it to the list
    if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
        auto devInfo = new DeviceInfo(device);
        auto it = std::find_if(m_devices.begin(), m_devices.end(),
                               [devInfo](DeviceInfo *dev) {
                                   return devInfo->getAddress() == dev->getAddress();
                               });
        if (it == m_devices.end()) {
            m_devices.append(devInfo);
        } else {
            auto oldDev = *it;
            *it = devInfo;
            delete oldDev;
        }
        setInfo(tr("Low Energy device found. Scanning more..."));
        setIcon(IconProgress);
    }
    //...
}

周辺デバイスのアドレスがわかったら、QLowEnergyController クラスを使用します。このクラスはすべてのBluetooth Low Energy開発のエントリーポイントです。このクラスのコンストラクタは、リモート・デバイスのQBluetoothAddress を受け取ります。最後に、恒例のスロットを設定し、connectToDevice ()を使用してデバイスに直接接続します:

m_control = QLowEnergyController::createCentral(m_currentDevice->getDevice(), this);
connect(m_control, &QLowEnergyController::serviceDiscovered,
        this, &DeviceHandler::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished,
        this, &DeviceHandler::serviceScanDone);

connect(m_control, &QLowEnergyController::errorOccurred, this,
        [this](QLowEnergyController::Error error) {
            Q_UNUSED(error);
            setError("Cannot connect to remote device.");
            setIcon(IconError);
        });
connect(m_control, &QLowEnergyController::connected, this, [this]() {
    setInfo("Controller connected. Search services...");
    setIcon(IconProgress);
    m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
    setError("LowEnergy controller disconnected");
    setIcon(IconError);
});

// Connect
m_control->connectToDevice();

上記のコード・スニペットは、接続が確立された後、アプリケーションがどのようにサービス探索を開始するかを示しています。

以下のserviceDiscovered() スロットは、QLowEnergyController::serviceDiscovered() シグナルの結果としてトリガーされ、断続的な進捗報告を提供します。ここでは、近辺のHeartRateデバイスを監視するハートリスナーアプリについて話しているので、QBluetoothUuid::ServiceClassUuid::HeartRate タイプでないサービスは無視する。

void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
{
    if (gatt == QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate)) {
        setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
        setIcon(IconProgress);
        m_foundHeartRateService = true;
    }
}

最終的に、QLowEnergyController::discoveryFinished ()シグナルが発行され、サービス発見が正常に完了したことを示します。HeartRateサービスが見つかった場合、そのサービスを表すQLowEnergyService インスタンスが作成されます。返されたサービスオブジェクトは、更新通知に必要なシグナルを提供し、QLowEnergyService::discoverDetails ()を使用してサービス詳細の発見がトリガーされる:

    // If heartRateService found, create new service
    if (m_foundHeartRateService)
        m_service = m_control->createServiceObject(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate), this);

    if (m_service) {
        connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged);
        connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateHeartRateValue);
        connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite);
        m_service->discoverDetails();
    } else {
        setError("Heart Rate Service not found.");
        setIcon(IconError);
    }

詳細検索中、サービスのstate() はRemoteService からRemoteServiceDiscovering へと遷移し、最終的にRemoteServiceDiscovered で終了する:

void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
{
    switch (s) {
    case QLowEnergyService::RemoteServiceDiscovering:
        setInfo(tr("Discovering services..."));
        setIcon(IconProgress);
        break;
    case QLowEnergyService::RemoteServiceDiscovered:
    {
        setInfo(tr("Service discovered."));
        setIcon(IconBluetooth);

        const QLowEnergyCharacteristic hrChar =
                m_service->characteristic(QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement));
        if (!hrChar.isValid()) {
            setError("HR Data not found.");
            setIcon(IconError);
            break;
        }

        m_notificationDesc = hrChar.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
        if (m_notificationDesc.isValid())
            m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));

        break;
    }
    default:
        //nothing for now
        break;
    }

    emit aliveChanged();
}

周辺機器とのインタラクション

上記のコード例では、希望する特性はHeartRateMeasurement 型です。アプリケーションは心拍数の変化を測定するため、特性の変更通知を有効にする必要があります。すべての特性が変更通知を提供するわけではないことに注意してください。HeartRate特性は標準化されているので、通知を受け取ることができると仮定することができる。最終的にQLowEnergyCharacteristic::properties()は、QLowEnergyCharacteristic::Notify フラグが設定されていなければならず、適切な通知の利用可能性を確認するためにQBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration 型の記述子が存在しなければならない。

最後に、Bluetooth Low Energy規格に従って、HeartRate特性の値を処理する:

void DeviceHandler::updateHeartRateValue(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
    // ignore any other characteristic change -> shouldn't really happen though
    if (c.uuid() != QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement))
        return;

    auto data = reinterpret_cast<const quint8 *>(value.constData());
    quint8 flags = *data;

    //Heart Rate
    int hrvalue = 0;
    if (flags & 0x1) // HR 16 bit? otherwise 8 bit
        hrvalue = static_cast<int>(qFromLittleEndian<quint16>(data[1]));
    else
        hrvalue = static_cast<int>(data[1]);

    addMeasurement(hrvalue);
}

一般的に、特性値は一連のバイトである。これらのバイトの正確な解釈は、特性タイプと値構造によって異なります。一般的に、特性値は一連のバイトです。これらのバイトの正確な解釈は、特性のタイプと値の構造によって異なります上記のコード・スニペットは、標準化されたHeartRate値を読み取る方法を示しています。

広告サービス

周辺機器にGATTサーバー・アプリケーションを実装する場合、中央機器に提供したいサービスを定義し、それを宣伝する必要があります:

QLowEnergyAdvertisingData advertisingData;
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("HeartRateServer");
advertisingData.setServices(QList<QBluetoothUuid>() << QBluetoothUuid::ServiceClassUuid::HeartRate);
bool errorOccurred = false;
const std::unique_ptr<QLowEnergyController> leController(QLowEnergyController::createPeripheral());
auto errorHandler = [&leController, &errorOccurred](QLowEnergyController::Error errorCode) {
        qWarning().noquote().nospace() << errorCode << " occurred: "
            << leController->errorString();
        if (errorCode != QLowEnergyController::RemoteHostClosedError) {
            qWarning("Heartrate-server quitting due to the error.");
            errorOccurred = true;
            QCoreApplication::quit();
        }
};
QObject::connect(leController.get(), &QLowEnergyController::errorOccurred, errorHandler);

std::unique_ptr<QLowEnergyService> service(leController->addService(serviceData));
leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,
                               advertisingData);
if (errorOccurred)
    return -1;

これで潜在的なクライアントは我々のデバイスに接続し、提供されるサービスを発見し、特性値の変更を通知してもらうために自分自身を登録することができる。APIのこの部分は、上記のセクションですでにカバーされています。

周辺機器へのサービスの実装

最初のステップは、サービス、その特性、記述子を定義することである。これは、QLowEnergyServiceDataQLowEnergyCharacteristicDataQLowEnergyDescriptorData クラスを使用して実現します。これらのクラスは、定義するBluetooth Low Energyサービスを構成する重要な情報のコンテナまたはビルディング・ブロックとして機能します。以下のコード・スニペットは、測定された1分間の拍動を公表する単純なHeartRateサービスを定義しています。このようなサービスが使用される例としては、腕時計があります。

QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
charData.setValue(QByteArray(2, 0));
charData.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
                                            QByteArray(2, 0));
charData.addDescriptor(clientConfig);

QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(QBluetoothUuid::ServiceClassUuid::HeartRate);
serviceData.addCharacteristic(charData);

結果のserviceData オブジェクトは、上記の広告サービスのセクションで説明したように公開することができます。QLowEnergyServiceDataQLowEnergyAdvertisingData によってラップされた情報の間に部分的な情報の重複があるにもかかわらず、2つのクラスは2つの非常に異なるタスクに対応する。広告データは近隣のデバイスにパブリッシュされ、29バイトのサイズ制限のため、しばしば範囲が制限される。そのため、常に100%完全というわけではない。それに比べて、QLowEnergyServiceData の中に含まれるサービスデータは、サービスデータの完全なセットを提供し、アクティブなサービス発見との接続が実行されたときにのみ、接続クライアントに見えるようになります。

次のセクションでは、サービスが心拍数の値を更新する方法を示します。サービスの性質によっては、https://www.bluetooth.org で定義されている公式サービス定義に準拠する必要があります他のサービスは完全にカスタムかもしれない。心拍数サービスは採用され、その仕様はhttps://www.bluetooth.com/specifications/adopted-specifications。

QTimer heartbeatTimer;
quint8 currentHeartRate = 60;
enum ValueChange { ValueUp, ValueDown } valueChange = ValueUp;
const auto heartbeatProvider = [&service, &currentHeartRate, &valueChange]() {
    QByteArray value;
    value.append(char(0)); // Flags that specify the format of the value.
    value.append(char(currentHeartRate)); // Actual value.
    QLowEnergyCharacteristic characteristic
            = service->characteristic(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
    Q_ASSERT(characteristic.isValid());
    service->writeCharacteristic(characteristic, value); // Potentially causes notification.
    if (currentHeartRate == 60)
        valueChange = ValueUp;
    else if (currentHeartRate == 100)
        valueChange = ValueDown;
    if (valueChange == ValueUp)
        ++currentHeartRate;
    else
        --currentHeartRate;
};
QObject::connect(&heartbeatTimer, &QTimer::timeout, heartbeatProvider);
heartbeatTimer.start(1000);

一般的に、周辺デバイスの特性と記述値の更新は、Bluetooth Low Energy デバイスの接続と同じ方法を使用します。

注意: iOS でQt Bluetooth を(セントラルとペリフェラルの両方の役割で)使用するには、使用説明を含む Info.plist ファイルを提供する必要があります。CoreBluetoothのドキュメントによると:CoreBluetoothのドキュメントによると、Info.plistにアクセスする必要があるデータタイプの使用説明キーが含まれていないと、アプリはクラッシュします。iOS 13以降にリンクされたアプリでCore Bluetooth APIにアクセスするには、NSBluetoothAlwaysUsageDescriptionキーをインクルードしてください。iOS 12 以前では、Bluetooth 周辺機器データにアクセスするために NSBluetoothPeripheralUsageDescription をインクルードします。

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。