Appendix B: Command reference

Every command id, its arguments, the robots that act on it, and its status.

Command ids share one numbering space across every robot type, so an id is only ever implemented by some of them, and sending one to a robot that does not implement it is ignored exactly like sending an id that does not exist. Nothing on this wire is acknowledged: the state stream's actuator echo is the only receipt. In the Status column, live means a robot type acts on the id today, not yet means it is on the wire and no robot acts on it, and absent means the robot type it belongs to is not in the simulator. The constants live in vrobots_sdk::cmd, and cmd::name(id) maps a value back to its constant name for logging, returning "" for an unknown id.

Command ids

ConstantValueCmdArgs fieldUnitsSDK methodActed on byStatus
SET_ACC1not documentedm/s²send_cmdnothingnot yet
SET_VEL2not documentedm/ssend_cmdnothingnot yet
SET_POS3not documentedmsend_cmdnothingnot yet
SET_ANGACC50not documentedrad/s²send_cmdnothingnot yet
SET_ANGVEL51vec3rad/s, body rates in your header frame; converted as an axial vector, so it carries the handedness signset_angvel; read back with subscribe_setpointGlobalHawk onboard rate looplive
SET_EULER52not documentedradsend_cmdnothingnot yet
SET_EULER_DOT53not documentedrad/ssend_cmdnothingnot yet
SET_QUAT54not documentedunit quaternion, [x, y, z, w]send_cmdnothingnot yet
SET_MASS100not documentedkgsend_cmd; use set_physical_params insteadnothingnot yet
SET_MOI_3X1101not documentedkg·m²send_cmd; use set_physical_paramsnothingnot yet
SET_MOI_3X3102not documentedkg·m²send_cmd; use set_physical_paramsnothingnot yet
SET_BODY_FORCE200vec3N, header frameset_body_forcenothingnot yet
SET_BODY_TORQUE201vec3N·m, header frameset_body_torquenothingnot yet
SET_BODY_FT202vec3 force, vec3_arr[0] torqueN and N·mset_body_ftnothingnot yet
ADD_BODY_FORCE203not documentedNsend_cmdnothingnot yet
ADD_BODY_TORQUE204not documentedN·msend_cmdnothingnot yet
ADD_BODY_FT205not documentedN and N·msend_cmdnothingnot yet
SET_MR_PWM300int_arr, one per rotorµs, 1100 to 2000, checked client-sideset_mr_pwm, set_mr_pwm_nMultirotor (any rotor count), HalfDrone (exactly 2, [left, right])live
SET_MR_THROTTLE301float_arrnormalisedset_mr_throttlenothingnot yet
SET_OMROVER302not documentednot documentedsend_cmdomnidirectional roverabsent
SET_HELI303not documentednot documentedsend_cmdhelicopterabsent
SET_CAR304int_arr, [steer, throttle] or [steer, throttle, brake]µs, 1100 to 2000 checked client-side; the truck's factory band is 1100/1500/1900set_carTrucklive
SET_MSD305float_valN, clamped simulator-side to max_force (100 N by default)set_msd_forceMsdlive
SET_INVPEN306float_valN along the rail's +x, clamped to CartPoleConfig::max_force (20 N by default)set_cartpole_forceCartPolelive
SET_FW_SURFACES307float_arr, one per panelrad, clamped simulator-side to the airframe limit (20 degrees by default)set_fw_surfacesGlobalHawklive
SET_FW_THRUST308float_valN, clamped to [0, max_thrust] (20 kN on the RQ-4B)set_fw_thrustGlobalHawklive
SET_FW_THRUST_BIAS309float_valN, signed trim; ignored in FW_DIRECT_SURFACEset_fw_thrust_biasGlobalHawklive
SET_FW_CTRL_MODE310int_valFW_ONBOARD_RATE or FW_DIRECT_SURFACEset_fw_ctrl_modeGlobalHawklive
SET_FW_EST_SOURCE311int_valFW_EST_TRUTH or FW_EST_OBSERVERset_fw_est_sourceGlobalHawklive

Where the CmdArgs field says "not documented", the SDK ships no typed method for that id and the source does not name the field it reads, so the payload shape has to come from the simulator before you build on it. The units given for those ids follow from the SDK's everything-is-SI rule and the constant's name rather than from a statement in the source.

The CmdArgs field for SET_ANGVEL is vec3, verifiable from both ends: set_angvel builds CmdArgs::vector(rates), and on the way back subscribe_command yields a Setpoint only for commands carrying a vec3, with subscribe_setpoint as shorthand for subscribe_command(cmd::SET_ANGVEL).

Not yet. Seventeen ids above are carried by the schema and acted on by nothing. They publish without error and the state stream does not change, which is indistinguishable from a wrong sys_id or a wrong array length. Commands nothing acts on shows what that looks like from the outside.

Fixed wing mode constants

These are i32 values carried in CmdArgs::int_val, not command ids. They select the behaviour of SET_FW_CTRL_MODE and SET_FW_EST_SOURCE.

ConstantValueBelongs toMeaning
FW_ONBOARD_RATE0SET_FW_CTRL_MODEthe default; the aircraft flies itself on an onboard rate loop tracking SET_ANGVEL, with airspeed hold
FW_DIRECT_SURFACE1SET_FW_CTRL_MODEthe rate loop is bypassed and the panels take SET_FW_SURFACES verbatim
FW_EST_TRUTH0SET_FW_EST_SOURCEthe default; the onboard loop uses the simulator's true attitude
FW_EST_OBSERVER1SET_FW_EST_SOURCEthe onboard loop uses the attitude published on the robot's z/estimate topic, by publish_estimate or any other peer

reset() returns both settings to their defaults, deliberately: direct surface control with zeroed latches would relaunch the aircraft unflyable. A direct-surface client re-asserts both after every reset.

The CmdArgs shape

Every command carries the same argument struct, and each id reads the one or two fields it cares about.

#![allow(unused)]
fn main() {
#[non_exhaustive]
pub struct CmdArgs {
    pub int_val: i32,
    pub float_val: f64,
    pub int_arr: Vec<i32>,
    pub float_arr: Vec<f64>,
    pub vec3: Option<[f64; 3]>,
    pub vec4: Option<[f64; 4]>,   // [x, y, z, w]
    pub vec3_arr: Vec<[f64; 3]>,
    pub vec4_arr: Vec<[f64; 4]>,
}
}
The same in C++ (crates/vrobots-sdk-capi/include/vrobots_sdk.h)
typedef struct vrsdk_cmd_args_t {
    int32_t int_val;
    double float_val;
    const int32_t *int_arr;
    size_t int_arr_len;
    const double *float_arr;
    size_t float_arr_len;
    const double *vec3;
    const double *vec4;
    const double *vec3_arr;
    size_t vec3_arr_len;
    const double *vec4_arr;
    size_t vec4_arr_len;
} vrsdk_cmd_args_t;
The same in Python (crates/vrobots-sdk-py/python/vrsdk/_vrsdk.pyi)
int_val: int = 0
float_val: float = 0.0
int_arr: Optional[Sequence[int]] = None
float_arr: Optional[Sequence[float]] = None
vec3: Optional[Sequence[float]] = None
vec4: Optional[Sequence[float]] = None
vec3_arr: Optional[Sequence[Sequence[float]]] = None
vec4_arr: Optional[Sequence[Sequence[float]]] = None

The same eight fields carry the same meanings in all three. C++ uses the C struct directly, so every array is a pointer with an explicit _len, and vec3_arr and vec4_arr are flat double arrays whose length counts vectors rather than doubles. Python has no argument type at all: the eight are keyword arguments of send_cmd.

The struct is #[non_exhaustive], so it cannot be built with a struct literal. Build it from CmdArgs::default() plus chained setters, or from a shorthand.

BuilderSetsTypical use
CmdArgs::ints(&[i32])int_arrSET_MR_PWM, SET_CAR
CmdArgs::floats(&[f64])float_arrSET_FW_SURFACES
CmdArgs::vector([f64;3])vec3SET_BODY_FORCE
with_int_val, with_float_valint_val, float_valSET_FW_CTRL_MODE, SET_MSD
with_int_arr, with_float_arrint_arr, float_arr
with_vec3, with_vec4vec3, vec4
with_vec3_arr, with_vec4_arrvec3_arr, vec4_arrSET_BODY_FT's torque half

Each with_* setter consumes self and is #[must_use], so they chain. Empty vectors and None are omitted from the wire entirely rather than sent at zero length. Floats narrow: f64 in the API, f32 on the wire.

VirtualRobot::send_cmd(&self, cmd_id: u32, args: &CmdArgs) -> VrResult<()> is the escape hatch for an id with no typed method. It returns Deleted if the robot has been deleted and Publish if zenoh refuses the put, and nothing else: it cannot tell you whether any robot acted on the id.

Next: Appendix C: Error reference

See also: The generic command, Commands latch