Working without hardware
nxt-rs includes a simulated brick. It’s always compiled in and needs no feature flag. It answers every protocol command plausibly:
- Motors with a rotation limit finish instantly and update their counters.
- Sensors report whatever values you set.
- Files, mailboxes, I²C registers and the screen live in memory.
use nxt_rs::{prelude::*, transport::mock::Simulator};
fn main() -> nxt_rs::Result<()> {
let sim = Simulator::new();
let brick = Brick::mock_with(sim.clone()); // keep `sim` to control the brick
let motor = brick.motor(A)?;
motor.run(power!(50)).degrees(degrees!(720)).wait().block()?;
assert_eq!(sim.output(OutputPort::A).rotation_count, 720);
let touch = brick.touch(S1).block()?;
assert!(!touch.read().block()?);
sim.set_sensor(InputPort::S1, 1023, 1); // press the button
assert!(touch.read().block()?);
Ok(()) }
Brick::mock() is shorthand for a simulator you don’t need to control.
Use the simulator to:
- try the library before your brick arrives,
- unit-test robot logic in CI,
- develop on a laptop away from the robot.
Write your functions against a generic Brick<T> so that the same code runs
on the real brick and in tests:
use nxt_rs::{prelude::*, transport::{Transport, mock::Simulator}};
/// Turns in small steps until the ultrasonic sensor sees something close.
async fn face_obstacle<T: Transport>(brick: &Brick<T>) -> nxt_rs::Result<()> {
let (motor, sonar) = brick.take((A.motor(), S4.ultrasonic())).await?;
while sonar.read().await?.is_none_or(|d| d > Centimeters(20)) {
motor.run(power!(30)).degrees(degrees!(10)).wait().await?;
}
Ok(())
}
fn main() -> nxt_rs::Result<()> {
let sim = Simulator::new();
sim.set_ultrasonic_distance(InputPort::S4, 15);
face_obstacle(&Brick::mock_with(sim)).block()?;
Ok(()) }
The Testing chapter covers the simulator in depth, including byte-exact scripted tests.
Run cargo run --example simulator in the nxt-rs repository to see a complete
program running against it.