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

Direct commands

brick.direct() has one method per direct command, the protocol commands for motors, sensors, sound, programs and messages (Bluetooth Developer Kit, appendix 2). The methods are stateless and map 1:1 onto the protocol: no port claims, no hidden state. Each one waits for the brick’s reply and returns it decoded.

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{port::*, protocol::*, units::*};

let d = brick.direct();
d.set_input_mode(InputPort::S1, SensorType::Switch, SensorMode::Boolean).await?;
let pressed = d.get_input_values(InputPort::S1).await?.scaled != 0;
let battery: Millivolts = d.get_battery_level().await?;
Ok(()) });

Commands

OpcodeMethodReply
0x00start_program(&name)()
0x01stop_program()(), or Status::NoActiveProgram
0x02play_sound_file(&name, looping)()
0x03play_tone(hz, duration)()
0x04set_output_state(port_or_all, state)()
0x05set_input_mode(port, sensor_type, mode)()
0x06get_output_state(port)OutputStatus
0x07get_input_values(port)InputValues
0x08reset_input_scaled_value(port)()
0x09message_write(mailbox, message)()
0x0Areset_motor_position(port, relative)()
0x0Bget_battery_level()Millivolts
0x0Cstop_sound_playback()()
0x0Dkeep_alive()Duration (sleep timeout)
0x0Els_get_status(port)u8 bytes ready
0x0Fls_write(port, tx, rx_len)()
0x10ls_read(port)LsBytes
0x11get_current_program_name()FileName
0x13message_read(remote, local, remove)ReceivedMessage
—ls_transfer(port, tx, rx_len)LsBytes: write, poll, read in one call

Motors

set_output_state takes the complete output state. OutputState::COAST and OutputState::BRAKE are ready-made states, and ..OutputState::default() fills in the rest:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{power, degrees, turn_ratio, port::OutputPort, protocol::*, units::TachoLimit};

let d = brick.direct();

// Motor A: 75 % with speed regulation, stop after one turn.
d.set_output_state(OutputPort::A, OutputState {
    power: power!(75),
    mode: OutputMode::MOTOR_ON | OutputMode::REGULATED,
    regulation: RegulationMode::Speed,
    run_state: RunState::Running,
    tacho_limit: TachoLimit::Degrees(degrees!(360)),
    ..OutputState::default()
}).await?;

// B and C synchronised, curving right.
let synced = OutputState {
    power: power!(60),
    mode: OutputMode::MOTOR_ON | OutputMode::REGULATED,
    regulation: RegulationMode::Sync,
    turn_ratio: turn_ratio!(30),
    run_state: RunState::Running,
    ..OutputState::default()
};
d.set_output_state(OutputPort::B, synced).await?;
d.set_output_state(OutputPort::C, synced).await?;

// Everything off.
d.set_output_state(Outputs::All, OutputState::COAST).await?;
Ok(()) });

Only set_output_state accepts “all outputs” (Outputs::All). The other commands take a single OutputPort, so passing “all” to them doesn’t compile.

Sensors

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{port::InputPort, protocol::*};

let d = brick.direct();
d.set_input_mode(InputPort::S3, SensorType::LightActive, SensorMode::PercentFullScale).await?;
let v = d.get_input_values(InputPort::S3).await?;
println!("valid={} raw={} normalized={} scaled={}", v.valid, v.raw, v.normalized, v.scaled);

// Count presses of a touch sensor.
d.set_input_mode(InputPort::S1, SensorType::Switch, SensorMode::TransitionCount).await?;
d.reset_input_scaled_value(InputPort::S1).await?;
Ok(()) });

See Sensor types and modes for all combinations.

I²C

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{port::InputPort, protocol::*};

let d = brick.direct();
d.set_input_mode(InputPort::S4, SensorType::LowSpeed9V, SensorMode::Raw).await?;

// The whole transaction:
let id = d.ls_transfer(InputPort::S4, &[0x02, 0x08], 4).await?;   // "LEGO"

// Or step by step:
d.ls_write(InputPort::S4, &[0x02, 0x42], 1).await?;
while d.ls_get_status(InputPort::S4).await? < 1 {}
let distance = d.ls_read(InputPort::S4).await?;
Ok(()) });

While a transfer is still running, ls_get_status may fail with Status::PendingCommunication. ls_transfer handles that for you.

Messages, sound, programs

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{protocol::*, units::Hz};
use std::time::Duration;

let d = brick.direct();
d.message_write(Mailbox::M1, Message::new("hi")?).await?;
match d.message_read(RemoteMailbox::Outbox(Mailbox::M1), Mailbox::M1, true).await {
    Ok(reply) => println!("{:?}", reply.message),
    Err(e) if e.status() == Some(nxt_rs::Status::MailboxEmpty) => {}
    Err(e) => return Err(e.into()),
}
d.play_tone(Hz::A4, Duration::from_millis(100)).await?;
let sleep = d.keep_alive().await?;
Ok(()) });

Skipping replies

Each method waits for the brick’s reply. Over Bluetooth, that wait is most of the cost of a command. Commands that only return a status can be sent without asking for a reply. Send the command struct with brick.send_no_reply:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::protocol::{Outputs, OutputState, direct::SetOutputState};

brick.send_no_reply(SetOutputState { port: Outputs::All, state: OutputState::COAST }).await?;
Ok(()) });

Errors on the brick then go unnoticed. Queries can’t be sent this way, because the reply is their whole point. That’s checked at compile time:

async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
brick.send_no_reply(nxt_rs::protocol::direct::GetBatteryLevel).await?;   // error
Ok(()) }