Reading state
How one state snapshot is put together, and which page of this chapter describes each part of it.
The snapshot model
The simulator publishes one complete state sample per robot at 25 Hz. The SDK subscribes in the background, decodes each sample into an owned snapshot and stores it. You never poll a queue and you never register a callback: you read the latest snapshot whenever your loop wants it.
That read is the narrowest API in the SDK, one call with no arguments:
crates/vrobots-sdk/src/robot.rs:
#![allow(unused)]
fn main() {
#[must_use]
pub fn states(&self) -> State {
let mut raw = sys::vrsdk_state_t::default();
// SAFETY: `self.raw` is live and connected; `raw` is writable storage
// for one `vrsdk_state_t`.
let code = unsafe { sys::vrsdk_robot_states(self.raw.as_ptr(), &mut raw) };
expect_ok(code, "vrsdk_robot_states");
State::from_raw(&raw)
}
}
The three differ only in how the copy is made and named. Rust and C++ both have the C
library fill a vrsdk_state_t and convert it into a value of their own, an owned State in
Rust and a vrsdk::State in C++, which you can store and pass to another thread. Python
builds a State object. In Python it is a property, so it is mr.states with no
parentheses; forgetting that is the most common transcription error when porting a loop from
one of the other two.
The call returns immediately with the most recent decoded sample, hands you a copy of it,
and cannot fail. A State is a plain owned struct of fixed-size arrays, strings and Vecs,
with no borrows into the receive buffer, so it outlives the sample it came from.
Three consequences follow, and the rest of the chapter is mostly their detail:
- A snapshot is never torn. You either see the previous sample in full or the next one in full, never half of each.
- A snapshot is never absent.
connectblocks for the first sample before it returns, sostates()is valid immediately afterwards. - A snapshot is never an error. If the simulator stops,
states()keeps returning the last sample it had, unchanged, forever. Detecting that requireswait_new_state, not an error check.
What one snapshot contains
Every sample carries a header, then four blocks that differ in what a real robot could know about itself.
flowchart TD
S["State (one sample)"]
S --> H["header: t_ns, elapsed, seq, sys_id, coord_frame_id"]
S --> T["truth: simulator-exact"]
S --> M["measured: robot-observable"]
S --> B["believed: the robot's filter"]
S --> A["actuator: none of the three"]
T --> K["kin: pose, twist, accel"]
T --> W["wrench: force, torque"]
T --> E["env: gravity, air, geo, agl"]
M --> SN["sensors: accel, gyro, mag, baro, gnss, flow"]
B --> ES["estimate: kin, valid, timestamp"]
A --> AC["pwm, normalized, measured"]
The split is the schema’s whole purpose. kin, wrench and env are values no
physical vehicle could measure. sensors is the noisy view of the same instant.
estimate is what the robot’s own filter believes. actuator belongs to none of
them, because it is the command going out and the realised motion coming back.
The minimal read loop
The smallest useful program takes two fields out of the snapshot and paces itself.
examples/rust/src/bin/ex01_hello_states.rs:
#![allow(unused)]
fn main() {
loop {
let s = robot.states(); // immutable latest snapshot, never torn
let [x, y, z] = s.kin.lin_pos;
println!("State t={:.3} pos=({x:.3},{y:.2},{z:.2})", s.elapsed);
robot.rate(HZ); // drift-compensated pacing, Hz
}
}
Rust and Python destructure the position into three names; C++ takes a pointer to the
three-element array, because lin_pos is a plain C double[3] there.
At the example’s 50 Hz against a 25 Hz stream, consecutive lines repeat the same sample about half the time, which is correct and costs nothing:
State t=12.480 pos=(0.031,0.85,-1.20)
State t=12.480 pos=(0.031,0.85,-1.20)
State t=12.520 pos=(0.032,0.85,-1.21)
Note. Reading the same snapshot twice is free and is not a bug. If duplicate processing would be a bug, for example when you differentiate or log, pace on the data instead: see Pacing your loop.
The rest of this chapter
| Page | Answers |
|---|---|
| Truth, measured and believed | Which block may a real robot use, and which differences are the experiment |
| Kinematics | Pose, twist, acceleration, their frames, and the net wrench |
| Sensors | Every device, its fields, its units, its own clock |
| The environment block | The truth the sensors were noised from |
| Actuators | The command echo, and the only proof a command landed |
| Timestamps and sequence numbers | Which of the four clocks to use for what |
| Pacing your loop | Free-running against sample-paced loops |
| Stream health | Counters, last_error, stalls and restarts |
| A tour of the whole snapshot | One program that prints all of it at once |
Next: Truth, measured and believed
See also: Hello states, The shape of a program, Five rules that explain everything