接続
ここからメインループに入ります。各ループでは、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 }