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

Ports and devices

An NXT has three motor outputs (A, B, C) and four sensor inputs (1–4). In nxt-rs each one is a value and a type:

PortValue / typeLow-level enum
Output A, B, CA, B, COutputPort::A, …
Input 1–4S1, S2, S3, S4InputPort::S1, …

The values are zero-sized: they only carry their type. Because each port has its own type, a device’s type records which socket it is on. A motor on output B is a Motor<B, T>, and a light sensor on input 3 is a Light<S3, T, Reflected>. (T is the transport.)

Taking devices

A device claims its port. Take devices one at a time:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;

let left = brick.motor(B)?;
let bumper = brick.touch(S1).await?;
let sonar = brick.ultrasonic(S4).await?;
Ok(()) });

Or several at once with take and a tuple of specs:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;

let (left, right, bumper, sonar) =
    brick.take((B.motor(), C.motor(), S1.touch(), S4.ultrasonic())).await?;
Ok(()) });
DeviceOne at a timeSpec for take
Motorbrick.motor(B)?B.motor()
Touch sensorbrick.touch(S1).await?S1.touch()
Light sensorbrick.light(S3).await?S3.light()
Sound sensorbrick.sound(S2).await?S2.sound()
Colour sensorbrick.color(S3).await?S3.color()
Ultrasonic sensorbrick.ultrasonic(S4).await?S4.ultrasonic()
Any analog sensorbrick.raw_sensor(S2, ty, mode).await?S2.raw(ty, mode)
I²C device Dbrick.i2c::<D, _>(S4).await?S4.i2c::<D>()
Raw I²C busbrick.i2c_bus(S4, address, powered).await?—
Bare portbrick.port(S2)?S2.port()

Sensor getters are async because they configure the port on the brick. Motors need no configuration, so brick.motor is synchronous.

What the compiler checks

Wrong kind of port. Motors only go on outputs and sensors only on inputs:

async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
use nxt_rs::prelude::*;
let motor = brick.motor(S1)?;      // error: `S1` is not a motor output
Ok(()) }
async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
use nxt_rs::prelude::*;
let (m,) = brick.take((S1.motor(),)).await?;   // error: no method `motor` on `S1`
Ok(()) }

A port used twice in one group:

async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
use nxt_rs::prelude::*;
let (a, b) = brick.take((B.motor(), B.motor())).await?;   // error: port `B` is taken twice
Ok(()) }

Using a device after giving it away. Switching a sensor’s mode, or converting a port, consumes the old value:

async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
use nxt_rs::prelude::*;
let light = brick.light(S3).await?;
let ambient = light.ambient().await?;
light.read().await?;               // error: use of moved value `light`
Ok(()) }

What is checked at run time

Taking a port that is already claimed returns Error::PortTaken. That happens when separate calls, or separate parts of your program, ask for the same port:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{prelude::*, Error};

let motor = brick.motor(B)?;
assert!(matches!(brick.motor(B), Err(Error::PortTaken(_))));
Ok(()) });

A take group claims all its ports at once. If any of them is in use, nothing is claimed. If configuring a device fails halfway, the ports claimed so far are released again.

Releasing ports

A claim lasts as long as the device. Dropping a device releases its port:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;

let motor = brick.motor(B)?;
drop(motor);
let motor_again = brick.motor(B)?;   // fine
Ok(()) });

To reuse a port for something else without letting go of it, call release(). It resets the hardware (coasts the motor, or unconfigures the sensor) and gives back the still-claimed Port:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;

let touch = brick.touch(S1).await?;
let port = touch.release().await?;        // still claimed by us
let light = port.into_light().await?;     // same socket, new device
Ok(()) });

Inspecting claims

brick.port_claims() shows which ports are in use. It is public so that abstractions you build on the low-level API can take part in the same claim system:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{prelude::*, port::AnyPort};

let _motor = brick.motor(B)?;
assert_eq!(brick.port_claims().claimed(), [AnyPort::Output(OutputPort::B)]);
Ok(()) });

Claims belong to a Brick and are shared by all its clones. They only apply to this library within this process. They can’t stop another program, or a program running on the brick, from using the same port.