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
| Opcode | Method | Reply |
|---|---|---|
0x00 | start_program(&name) | () |
0x01 | stop_program() | (), or Status::NoActiveProgram |
0x02 | play_sound_file(&name, looping) | () |
0x03 | play_tone(hz, duration) | () |
0x04 | set_output_state(port_or_all, state) | () |
0x05 | set_input_mode(port, sensor_type, mode) | () |
0x06 | get_output_state(port) | OutputStatus |
0x07 | get_input_values(port) | InputValues |
0x08 | reset_input_scaled_value(port) | () |
0x09 | message_write(mailbox, message) | () |
0x0A | reset_motor_position(port, relative) | () |
0x0B | get_battery_level() | Millivolts |
0x0C | stop_sound_playback() | () |
0x0D | keep_alive() | Duration (sleep timeout) |
0x0E | ls_get_status(port) | u8 bytes ready |
0x0F | ls_write(port, tx, rx_len) | () |
0x10 | ls_read(port) | LsBytes |
0x11 | get_current_program_name() | FileName |
0x13 | message_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(()) }