Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

接続

ここからメインループに入ります。各ループでは、BLE 接続を受け付けるためにアドバタイズを行います。接続が確立されると、GATT サーバーとバッテリー通知タスクを並行して開始します。

ここでは複数のことが同時に行われているので、それぞれを順に説明します。

#![allow(unused)]
fn main() {
loop {
        let config = peripheral::Config::default();

        let adv = ble::adv::get_adv();

        let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
        info!("advertising done! I have a connection.");

        let battery_fut = server.notify_battery_value(&conn);
        let gatt_fut = gatt_server::run(&conn, &server, |e| match e {
            ServerEvent::Bas(e) => match e {
                BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => {
                    info!("battery notifications: {}", notifications)
                }
            },
        });

        pin_mut!(battery_fut);
        pin_mut!(gatt_fut);

        match select(battery_fut, gatt_fut).await {
            Either::Left((_, _)) => {
                info!("Battery Notification encountered an error and stopped!")
            }
            Either::Right((e, _)) => {
                info!("gatt_server run exited with error: {:?}", e);
            }
        };
    }
}

アドバタイズ

まず、"peripheral::Config::default()" を使ってアドバタイズ用のデフォルト設定を初期化します。次に、先ほど定義した get_adv() を呼び出してアドバタイズメントペイロードを取得します。その後、アドバタイズデータと設定を指定して "advertise_connectable" を呼び出します。

#![allow(unused)]
fn main() {
let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
}

この行はアドバタイズパケットのブロードキャストを開始し、スマートフォンのようなセントラルデバイスが接続するまで待機します。接続が確立されると、接続されたクライアントとやり取りするために使用する Connection オブジェクトが返されます。

GATT 接続

接続を取得したら、"notify_battery_value""gatt_server::run" を呼び出します。どちらも async 関数です。ただし、見てわかるように、ここではまだ ".await" を使っていません。代わりに、future を変数に格納して、それらに対して pin_mut! を呼び出しています。

#![allow(unused)]
fn main() {
let battery_fut = server.notify_battery_value(&conn);
let gatt_fut = gatt_server::run(&conn, &server, |e| match e {
    ServerEvent::Bas(e) => match e {
        BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => {
            info!("battery notifications: {}", notifications)
        }
    },
});
}

gatt_server::run 関数は GATT サーバーを実行し、こちらが渡したコールバックを使ってイベントを受け取ります。これらのイベントは ServerEvent という enum で表され、この enum は nrf_softdevice クレートの #[gatt_server] proc macro によって自動生成されます(service.rs ファイル内の Server 構造体にこのマクロを付与しました)。

その後、2 つの pin 済み future に対して select を使用します。これにより、どちらか一方が完了するまで待機し、もう一方は自動的にキャンセルされます。

  • バッテリー通知タスクは無限ループで動作し、更新を継続的に送信し、エラーが発生した場合にのみ終了します。

  • GATT サーバーも、エラーが発生するか、クライアントが切断するまで(たとえば DisconnectError の場合など)継続して動作します。

この構成により、どちらか一方が終了したときにクリーンに抜けて、ループを再開し、新しい接続を受け付けられるようになります。

main.rs の全内容

#![no_std]
#![no_main]
mod ble;
mod service;
use crate::ble::get_soft_device;
use crate::service::*;

use {defmt_rtt as _, panic_probe as _};

use defmt::{info, unwrap};
use embassy_executor::Spawner;
use embassy_nrf::interrupt;
use futures::future::{Either, select};
use futures::pin_mut;
use nrf_softdevice::Softdevice;
use nrf_softdevice::ble::{gatt_server, peripheral};

// アプリケーションは softdevice より低い優先度で実行しなければなりません
fn nrf_config() -> embassy_nrf::config::Config {
    let mut config = embassy_nrf::config::Config::default();
    config.gpiote_interrupt_priority = interrupt::Priority::P2;
    config.time_interrupt_priority = interrupt::Priority::P2;
    config
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // まず peripherals access crate を取得します。
    let _ = embassy_nrf::init(nrf_config());

    let sd = get_soft_device();
    let server = unwrap!(Server::new(sd));
    unwrap!(spawner.spawn(softdevice_task(sd)));

    loop {
        let config = peripheral::Config::default();

        let adv = ble::adv::get_adv();

        let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await);
        info!("advertising done! I have a connection.");

        let battery_fut = server.notify_battery_value(&conn);
        let gatt_fut = gatt_server::run(&conn, &server, |e| match e {
            ServerEvent::Bas(e) => match e {
                BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => {
                    info!("battery notifications: {}", notifications)
                }
            },
        });

        pin_mut!(battery_fut);
        pin_mut!(gatt_fut);

        match select(battery_fut, gatt_fut).await {
            Either::Left((_, _)) => {
                info!("Battery Notification encountered an error and stopped!")
            }
            Either::Right((e, _)) => {
                info!("gatt_server run exited with error: {:?}", e);
            }
        };
    }
}

#[embassy_executor::task]
pub async fn softdevice_task(sd: &'static Softdevice) -> ! {
    sd.run().await
}