Your first program
This chapter builds a small robot program step by step: drive forward until the touch sensor is pressed, back off, beep and turn around.
You need a brick with:
- motors on outputs B (left) and C (right), as in the standard driving base,
- a touch sensor on input 1.
No robot at hand? Every snippet below also runs against the simulator.
1. Connect
use nxt_rs::prelude::*;
fn main() -> nxt_rs::Result<()> {
let brick = Brick::usb_first().block()?;
println!("connected to {}", brick.name().block()?);
Ok(())
}
nxt_rs::prelude::* imports what most programs need: Brick, the port names
A–C and S1–S4, the power!/degrees! macros, unit types and the
.block() helper.
2. Take the devices
Before you can use a motor or sensor, you take its port. That configures the port on the brick and gives you a typed handle:
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let left = brick.motor(B)?;
let right = brick.motor(C)?;
let bumper = brick.touch(S1).await?;
Ok(()) });
Each port can only be taken once. A second brick.motor(B) returns
Error::PortTaken until the first motor is dropped. You can also take
several devices in one go. Using the same port twice in one group is a
compile error:
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let (left, right, bumper) = brick.take((B.motor(), C.motor(), S1.touch())).await?;
Ok(()) });
The compiler also knows which ports are which. brick.motor(S1) doesn’t
compile, because S1 is a sensor input. Ports and devices
explains all of this.
3. Drive
The two drive motors should turn in step, so pair them:
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let (left, right) = brick.take((B.motor(), C.motor())).await?;
let mut drive = SyncedPair::new(left, right);
drive.forward(power!(60)).await?; // runs until told otherwise
Ok(()) });
power!(60) is a Power checked at compile time: power!(150) won’t
compile. For values computed at run time, use Power::new(x)?.
4. React to the sensor
nxt_rs::doc_example_with(|brick, sim| async move {
use nxt_rs::prelude::*;
use std::time::Duration;
let bumper = brick.touch(S1).await?;
sim.set_sensor(InputPort::S1, 1, 1);
bumper.wait_for_press(Duration::from_millis(10)).await?;
Ok(()) });
Or poll it yourself: bumper.read().await? returns a bool.
5. Back off, beep, turn
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
use nxt_rs::turn_ratio;
use std::time::Duration;
let (left, right) = brick.take((B.motor(), C.motor())).await?;
let mut drive = SyncedPair::new(left, right);
drive.forward(power!(-50)).degrees(degrees!(360)).brake().wait().await?;
brick.play_tone(Hz::A4, Duration::from_millis(200)).await?;
drive.steer(power!(50), turn_ratio!(100)).degrees(degrees!(460)).brake().wait().await?;
Ok(()) });
.degrees(..)stops the motors after that much wheel rotation..brake()stops them firmly instead of letting them coast..wait()waits until the movement is done. It’s only available after.degrees(..), because waiting for a motor that never stops would hang forever. The compiler enforces this.- A turn ratio of
100spins the robot in place.
The whole program
use std::time::Duration;
use nxt_rs::{prelude::*, turn_ratio};
fn main() -> nxt_rs::Result<()> {
let brick = Brick::usb_first().block()?;
let (left, right, bumper) = brick.take((B.motor(), C.motor(), S1.touch())).block()?;
let mut drive = SyncedPair::new(left, right);
for _ in 0..5 {
drive.forward(power!(60)).block()?;
bumper.wait_for_press(Duration::from_millis(10)).block()?;
drive.forward(power!(-50)).degrees(degrees!(360)).brake().wait().block()?;
brick.play_tone(Hz::A4, Duration::from_millis(200)).block()?;
drive.steer(power!(50), turn_ratio!(100)).degrees(degrees!(460)).brake().wait().block()?;
}
brick.stop_all().block()?;
Ok(())
}
brick.stop_all() stops all motors, silences the speaker and unconfigures
the sensors. Calling it at the end is good manners.
The same program, async
With an async runtime such as tokio, .await instead of .block():
use std::time::Duration;
use nxt_rs::{prelude::*, turn_ratio};
#[tokio::main]
async fn main() -> nxt_rs::Result<()> {
let brick = Brick::usb_first().await?;
let (left, right, bumper) = brick.take((B.motor(), C.motor(), S1.touch())).await?;
let mut drive = SyncedPair::new(left, right);
loop {
drive.forward(power!(60)).await?;
bumper.wait_for_press(Duration::from_millis(10)).await?;
drive.forward(power!(-50)).degrees(degrees!(360)).brake().wait().await?;
drive.steer(power!(50), turn_ratio!(100)).degrees(degrees!(460)).brake().wait().await?;
}
}
The library works with any async runtime: tokio, async-std, smol, or none at all.
Where next?
- Working without hardware: run this program in a test.
- Motors and Sensors: everything the devices can do.
- Cookbook: complete robots.