Values and units
The NXT protocol is full of numbers with restricted ranges: motor power is −100…100, tone frequencies 200…14 000 Hz, file names at most 15+3 characters. nxt-rs wraps each one in a small type that can only hold valid values. You validate once, when you create the value, and the rest of your program can rely on it.
Creating values
Each type has a fallible constructor. Literals also have a macro that checks at compile time:
use nxt_rs::{power, degrees, turn_ratio, units::*};
fn main() -> nxt_rs::Result<()> {
let fast = power!(80); // checked by the compiler
let computed = Power::new(-35)?; // checked at run time
let clamped = Power::saturating(250); // clamped to 100
assert_eq!(clamped, Power::MAX);
let quarter = degrees!(90);
let two_turns = Degrees::from_rotations(2)?;
let spin = turn_ratio!(100);
Ok(()) }
let too_fast = nxt_rs::power!(101); // error: power must be in -100..=100
let nothing = nxt_rs::degrees!(0); // error: degrees must be non-zero
The types
| Type | Range | Notes |
|---|---|---|
Power | −100 … 100 | power!, Power::new, Power::saturating, .reverse(), -p |
TurnRatio | −100 … 100 | turn_ratio!; 0 straight, ±50 one wheel stopped, ±100 spin |
Degrees | 1 … u32::MAX | degrees!, Degrees::from_rotations |
TachoLimit | Unlimited or Degrees(d) | “Run forever” is its own variant, never 0 |
Hz | 200 … 14 000 | Note constants Hz::C4 … Hz::C5 |
Millis16 | 0 … 65 535 ms | From Duration with try_from |
Percent | 0 … 100 | Light and sound readings |
Centimeters | 0 … 254 | Ultrasonic distance |
Millivolts | any u16 | Battery voltage; .volts() |
Protocol strings and payloads work the same way:
| Type | Rule |
|---|---|
FileName | printable ASCII, at most 15 characters + . + 3 (* allowed for searches) |
BrickName | ASCII, 1–15 characters |
Message | at most 58 bytes |
LsBytes | at most 16 bytes (one I²C transfer) |
Mailbox | M1 … M10 |
I2cAddress | even (8-bit form) |
Slope | 0 … 31 |
use nxt_rs::protocol::{FileName, Message};
fn main() -> nxt_rs::Result<()> {
let program: FileName = "robot.rxe".parse()?;
assert_eq!(program.extension(), Some("rxe"));
assert!(FileName::new("a-very-long-file-name.rxe").is_err());
let msg = Message::new("hello")?;
assert_eq!(msg.as_str(), Some("hello"));
Ok(()) }
Errors
A failed constructor returns a ValueError naming the value and its valid
range. ? converts it into nxt_rs::Error::Value:
use nxt_rs::units::Power;
let err = Power::new(101).unwrap_err();
assert_eq!(err.to_string(), "invalid power: must be in -100..=100");