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

Remote control with an on-brick program

Commands sent over Bluetooth take about 60 ms each way, which is too slow for tight control. The usual solution is to split the work:

  • an NXC program on the brick runs the fast loop: motors and sensors,
  • your Rust program sends high-level commands and receives status through mailboxes.

On the brick

// remote.nxc — compile with: nbc -O=remote.rxe remote.nxc
task main() {
    string cmd;
    while (true) {
        if (ReceiveRemoteString(MAILBOX1, true, cmd) == NO_ERR) {
            if (cmd == "fwd")   OnFwdSync(OUT_BC, 75, 0);
            if (cmd == "back")  OnRevSync(OUT_BC, 75, 0);
            if (cmd == "left")  OnFwdSync(OUT_BC, 50, -100);
            if (cmd == "right") OnFwdSync(OUT_BC, 50, 100);
            if (cmd == "stop")  Off(OUT_BC);
            SendResponseString(MAILBOX1, cmd);
        }
        if (SensorUS(IN_4) < 20) {
            Off(OUT_BC);
            SendResponseString(MAILBOX2, "obstacle");
        }
        Wait(10);
    }
}

The program needs the ultrasonic sensor configured on port 4. Add SetSensorLowspeed(IN_4); at the start of main in real code.

On the computer

use std::{io::BufRead, time::Duration};

use nxt_rs::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let brick = Brick::usb_first().block()?;   // or Brick::bluetooth(..)

    // Upload and start the program (see the upload recipe).
    let program = FileName::new("remote.rxe")?;
    brick.stop_program().block()?;
    brick.files().upload(&program, &std::fs::read("remote.rxe")?).block()?;
    brick.start_program(&program).block()?;

    let messages = brick.messages();
    println!("commands: fwd, back, left, right, stop");
    for line in std::io::stdin().lock().lines() {
        let cmd = line?;
        messages.send(Mailbox::M1, cmd.trim()).block()?;
        let ack = messages.wait(Mailbox::M1, Duration::from_millis(20)).block()?;
        println!("brick: {}", ack.as_str().unwrap_or("?"));

        if let Some(alert) = messages.recv(Mailbox::M2).block()? {
            println!("brick: {}", alert.as_str().unwrap_or("?"));
        }
    }
    brick.stop_program().block()?;
    Ok(())
}

Why this works well

  • The brick reacts to obstacles within 10 ms, whatever the Bluetooth latency.
  • The computer only sends a command when something changes.
  • Mailbox 1 carries commands and acknowledgements; mailbox 2 carries events. Use separate mailboxes for separate streams.