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

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 100 spins 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?