このページでは

Bluetooth Low Energyの概要

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

Bluetooth Low Energyとは

Bluetooth Low Energy(Bluetooth Smartとも呼ばれる)は、2011年に正式に導入された無線コンピュータネットワーク技術です。「従来の」Bluetoothと同じ2.4 GHzの周波数帯で動作します。 主な違いは、その技術名からもわかるように、低消費電力である点です。これにより、Bluetooth Low Energy対応デバイスは、コイン型電池だけで数ヶ月、場合によっては数年もの間動作することが可能になります。この技術はBluetooth v4.0で導入されました。この技術をサポートするデバイスは、「Bluetooth Smart Readyデバイス」と呼ばれます。この技術の主な特徴は以下の通りです:

  • ピーク時、平均時、およびアイドルモードにおける超低消費電力
  • 標準的なコイン型電池で数年間動作可能
  • 低コスト
  • 複数ベンダー間の相互運用性
  • 通信範囲の拡大

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

基本的なサービス構造

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

ATTプロトコル

ATTの基本構成要素は「属性」です。各属性は、次の3つの要素で構成されています:

  • 値 — ペイロード、あるいは必要な情報
  • UUID — 属性のタイプ(GATTで使用される)
  • 16ビットのハンドル — 属性の一意の識別子

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

GATTプロファイル

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

ハンドルUUID値説明
0x00010x2800UUID 0x180D心拍数サービスの開始
0x00020x2800UUID 0x180D心拍数サービスの開始0x2803UUID 0x2A37、値ハンドル:0x0003タイプ「心拍数測定(HRM)」の特性
0x00030x2A3765 bpm心拍数値
0x00040x2803UUID 0x2A08、値ハンドル:0x0005タイプ「Date Time」の特性
0x00050x2A082014年8月18日 11:00測定日時
0x00060x2800UUID xxxxxx次のサービスの開始
............

GATTでは、上記のUUID「0x2800 」がサービス定義の開始を示すものと規定されています。0x2800 に続くすべての属性は、次の0x2800 または終了が検出されるまで、そのサービスの一部となります。同様に、よく知られたUUID0x2803 は、特性が存在することを示しており、各特性には値の性質を定義するタイプがあります。上記の例では、UUID0x2A08 (日時)および0x2A37 (心拍数測定)が使用されています。 上記の各UUIDはBluetooth Special Interest Groupによって定義されており、GATT仕様書に記載されています。利用可能な場合は事前定義済みのUUIDを使用することが推奨されますが、特性やサービスタイプに対して、新規かつ未使用のUUIDを使用することも十分に可能です。

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

QtにおけるBLE周辺機器の構造

Qt Bluetooth のLow Energy APIの使用

このセクションでは、Qt Bluetoothが提供する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);

ここではLow Energyデバイスにのみ関心があるため、受信スロット内でデバイスタイプをフィルタリングします。デバイスタイプは、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);
}

一般的に、特性の値は一連のバイトで構成されます。これらのバイトの正確な解釈は、特性のタイプと値の構造によって異なります。その多くはBluetooth SIGによって標準化されていますが、一部は独自のプロトコルに従っている場合もあります。上記のコードスニペットは、標準化されたHeartRate値を読み取る方法を示しています。

アドバタイジングサービス

周辺機器上でGATTサーバーアプリケーションを実装する場合、セントラルデバイスに提供したいサービスを定義し、それらをアドバタイズする必要があります:

QLowEnergyAdvertisingData advertisingData;
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("HeartRateServer");
advertisingData.setServices(QList<QBluetoothUuid>()<<QBluetoothUuid::ServiceClassUuid::HeartRate);
boolerrorOccurred= false;
conststd::unique_ptr<QLowEnergyController>leController(QLowEnergyController::createPeripheral());
autoerrorHandler= [&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のこの部分については、すでに前述のセクションで説明しました。

周辺デバイスでのサービスの実装

最初のステップは、サービス、その特性、および記述子を定義することです。これは、QLowEnergyServiceData 、QLowEnergyCharacteristicData 、およびQLowEnergyDescriptorData クラスを使用して行います。これらのクラスは、定義される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 ` オブジェクトは、前述の「サービスの広告」セクションで説明されているように公開できます。QLowEnergyServiceData とQLowEnergyAdvertisingData でラップされる情報には一部重複する部分がありますが、これら 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 (セントラルおよびペリフェラル両方の役割において)iOS上でBluetoothペリフェラルを使用するには、使用説明を含むInfo.plistファイルを指定する必要があります。CoreBluetoothのドキュメントによると、アプリがアクセスする必要のあるデータタイプに対応する使用説明キーがInfo.plistに含まれていない場合、アプリはクラッシュします。 iOS 13 以降でリンクされたアプリで Core Bluetooth API にアクセスするには、NSBluetoothAlwaysUsageDescription キーを含める必要があります。iOS 12 以前では、Bluetooth 周辺機器のデータにアクセスするために NSBluetoothPeripheralUsageDescription を含める必要があります。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.