Async and blocking code
Every operation that talks to the brick is a Future. That lets several
tasks share one brick, drive motors concurrently and wait without blocking
threads.
With an async runtime
nxt-rs works with any runtime: tokio, async-std, smol, embassy-style
executors, or a simple block_on. Just .await:
use nxt_rs::prelude::*;
#[tokio::main]
async fn main() -> nxt_rs::Result<()> {
let brick = Brick::usb_first().await?;
println!("battery: {}", brick.battery().await?);
Ok(())
}
The Bluetooth transport is the one exception: its backend (bluer) needs a
tokio runtime.
Without one: .block()
Import nxt_rs::blocking::Block (it’s in the prelude) and add .block() to
any future or builder:
use nxt_rs::prelude::*;
fn main() -> nxt_rs::Result<()> {
let brick = Brick::mock(); // or Brick::usb_first().block()?
let battery = brick.battery().block()?;
let motor = brick.motor(A)?;
motor.run(power!(50)).degrees(degrees!(90)).wait().block()?;
let raw = brick.direct().get_battery_level().block()?;
Ok(()) }
.block() works the same on both API tiers, so there is no separate
“blocking” copy of the API to learn. With the bluetooth feature enabled, it
runs futures on a shared tokio runtime. Otherwise it uses a lightweight
executor.
Don’t call .block() from inside an async task: it blocks that task’s
thread. Use .await there.
nxt_rs::blocking::SyncIo adapts the async file reader and writer to
std::io::Read/Write. See Files.
Concurrency
Brick is Clone + Send + Sync. Each request and its reply happen under an
internal lock, so concurrent tasks never receive each other’s replies:
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
use futures_lite::future::zip;
let (a, b) = brick.take((A.motor(), B.motor())).await?;
// Start both motors concurrently.
let (ra, rb) = zip(a.run(power!(50)).into_future(), b.run(power!(-50)).into_future()).await;
ra?;
rb?;
Ok(()) });
The brick handles one command at a time, so concurrency saves waiting time in your program, not time on the wire.
Background keep-alive
The NXT switches itself off after its sleep timeout. To keep it awake during
a long program, spawn brick.keep_alive_task(interval) on your runtime:
async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Usb>) {
use std::time::Duration;
tokio::spawn(brick.keep_alive_task(Duration::from_secs(60)));
}