The generic command

send_cmd reaches the whole command id space, including the ids the SDK has no typed method for.

cargo run -p vrobots-examples --bin ex08_generic_cmd
./target/cpp-build/ex08_generic_cmd
python examples/python/ex08_generic_cmd.py

One message, and cmd_id decides what it means

#![allow(unused)]
fn main() {
pub fn send_cmd(&self, cmd_id: u32, args: &CmdArgs) -> VrResult<()>
}
The same in C++ (cpp/include/vrobots_sdk.hpp)
/// Publish any command by id -- the escape hatch for the whole
/// `VROBOTS_CMDS` space. Fill `args` with `vrsdk_cmd_args_default` first.
void send_cmd(std::uint32_t cmd_id, const vrsdk_cmd_args_t* args = nullptr)
The same in Python (crates/vrobots-sdk-py/python/vrsdk/_vrsdk.pyi)
def send_cmd(
    self,
    cmd_id: int,
    *,
    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,
) -> None: ...

Python has no CmdArgs type at all: the eight payload fields are keyword arguments on the call, and anything you omit stays off the wire. C++ passes the plain C struct by pointer, and every field in it is a borrowed pointer, so a null means "absent" and nothing is retained after the call returns.

Command is a union by convention: one message shape, and the id decides which payload fields mean anything. SET_MR_PWM reads int_arr and ignores the rest, SET_BODY_FORCE reads vec3. Fill the fields the target id reads and leave the others alone. Empty vectors and None vectors are omitted from the wire entirely rather than sent as zero length, so the receiver's "field absent" default applies.

Errors are VrError::Deleted if the robot was deleted and VrError::Publish if zenoh refuses the put. There is no validation here, which is the trade: every typed method validates its arguments, and send_cmd publishes whatever you build.

Sending an implemented id and an ignored one, side by side

examples/rust/src/bin/ex08_generic_cmd.rs sends two commands per iteration to the truck, on purpose:

#![allow(unused)]
fn main() {
    loop {
        // (1) An implemented id, built by hand. CmdArgs::ints fills int_arr,
        //     which is the field SET_CAR reads; everything else stays off the
        //     wire.
        let drive = CmdArgs::ints(&[STEER_US, THROTTLE_US, BRAKE_US]);
        robot.send_cmd(cmd::SET_CAR, &drive)?;

        // (2) An id nothing acts on, whose payload rides vec3. Same call, same
        //     Ok(()), no effect. `cmd::name` turns an id back into its schema
        //     name, which is what makes a log line readable.
        let gust = CmdArgs::default().with_vec3(GUST_N);
        robot.send_cmd(cmd::ADD_BODY_FORCE, &gust)?;
}
The same in C++ (examples/cpp/ex08_generic_cmd.cpp)
// ===== loop =====
for (;;) {
    // (1) An implemented id, built by hand. int_arr is the field SET_CAR
    //     reads; everything else stays NULL and off the wire.
    vrsdk_cmd_args_t drive;
    vrsdk_cmd_args_default(&drive);
    drive.int_arr = channels;
    drive.int_arr_len = 3;
    robot.send_cmd(SET_CAR, &drive);

    // (2) An id nothing acts on, whose payload rides vec3. Same call,
    //     no exception, no effect. Every payload field is a borrowed
    //     pointer: NULL means "absent" and nothing is retained after
    //     the call returns, so a stack array is fine.
    vrsdk_cmd_args_t force;
    vrsdk_cmd_args_default(&force);
    force.vec3 = gust;  // exactly 3 doubles
    robot.send_cmd(ADD_BODY_FORCE, &force);
The same in Python (examples/python/ex08_generic_cmd.py)
# ===== loop =====
while True:
    # (1) An implemented id, built by hand. int_arr is the field SET_CAR
    #     reads; everything else stays off the wire.
    car.send_cmd(cmd.SET_CAR, int_arr=[STEER_US, THROTTLE_US, BRAKE_US])

    # (2) An id nothing acts on, whose payload rides vec3. Same call, no
    #     exception, no effect. `cmd.name` turns an id back into its schema
    #     name, which is what makes a log line readable.
    car.send_cmd(cmd.ADD_BODY_FORCE, vec3=GUST_N)

Python is the shortest because the payload is keyword arguments. C++ is the longest because each call needs a vrsdk_cmd_args_default first, and it also declares the two ids as its own constants: the C surface ships no cmd namespace, so SET_CAR = 304 and ADD_BODY_FORCE = 203 are written out from the schema and there is no cmd::name to turn one back into a label.

Both calls return Ok(()). Only one of them changes anything, and the printed lines say which:

sent SET_CAR(304) + ADD_BODY_FORCE(203) -> echo=[1500, 1600, 1100]
      SET_CAR landed (the echo is the receipt); ADD_BODY_FORCE was ignored -- wrench=(<fx>,<fy>,<fz>) N unchanged

cmd::name(cmd_id) -> &'static str maps an id back to its constant name for logging, and returns "" for an id that is not in the schema.

CmdArgs

#![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]>,
    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 eight fields in the table below are the same eight in all three. The C struct pairs every array with an explicit _len, and it flattens vec3_arr and vec4_arr into one double array of three or four values per entry, so vec3_arr_len counts vectors, not doubles. Python's are the keyword arguments of send_cmd rather than a type of their own.

FieldTypeUnitsDefaultNotes
int_vali32per command0mode selectors live here (SET_FW_CTRL_MODE, SET_FW_EST_SOURCE)
float_valf64per command0.0scalar forces (SET_MSD, SET_INVPEN, SET_FW_THRUST); narrows to f32
int_arrVec<i32>microsecondsemptypulse widths (SET_MR_PWM, SET_CAR)
float_arrVec<f64>per commandemptyper-panel radians (SET_FW_SURFACES); narrows to f32
vec3Option<[f64; 3]>per commandNoneframe-tagged and converted by the robot
vec4Option<[f64; 4]>per commandNoneordered [x, y, z, w], matching the wire
vec3_arrVec<[f64; 3]>per commandemptySET_BODY_FT puts the torque half here
vec4_arrVec<[f64; 4]>per commandempty

The struct is #[non_exhaustive], so it cannot be built with a struct literal: a future schema field would otherwise be a breaking change across three languages. Build it with a shorthand or with CmdArgs::default() plus chained setters.

ShorthandSetsThe shape used by
CmdArgs::ints(&[i32])int_arrSET_MR_PWM, SET_CAR
CmdArgs::floats(&[f64])float_arrSET_FW_SURFACES
CmdArgs::vector([f64; 3])vec3SET_BODY_FORCE, SET_ANGVEL

The builders are with_int_val, with_float_val, with_int_arr, with_float_arr, with_vec3, with_vec4, with_vec3_arr and with_vec4_arr. Each consumes self, returns Self and is #[must_use], so they chain and a dropped result is a compiler warning rather than a silent no-op.

Every command id

Live means a robot type acts on the id today. Not yet means it is defined on the wire and no robot type acts on it. Absent means the robot type it belongs to does not exist in the simulator.

ConstantValueStatusActed on by
SET_ACC1not yet
SET_VEL2not yet
SET_POS3not yet
SET_ANGACC50not yet
SET_ANGVEL51liveGlobalHawk onboard rate loop; the in-game IMU panel publishes it at 50 Hz
SET_EULER52not yet
SET_EULER_DOT53not yet
SET_QUAT54not yet
SET_MASS100not yetuse srv/params instead
SET_MOI_3X1101not yetuse srv/params
SET_MOI_3X3102not yetuse srv/params
SET_BODY_FORCE200not yet
SET_BODY_TORQUE201not yet
SET_BODY_FT202not yet
ADD_BODY_FORCE203not yet
ADD_BODY_TORQUE204not yet
ADD_BODY_FT205not yet
SET_MR_PWM300liveMultirotor, HalfDrone
SET_MR_THROTTLE301not yet
SET_OMROVER302absentrobot type not in the simulator
SET_HELI303absentrobot type not in the simulator
SET_CAR304liveTruck
SET_MSD305liveMsd
SET_INVPEN306liveCartPole
SET_FW_SURFACES307liveGlobalHawk
SET_FW_THRUST308liveGlobalHawk
SET_FW_THRUST_BIAS309liveGlobalHawk
SET_FW_CTRL_MODE310liveGlobalHawk
SET_FW_EST_SOURCE311liveGlobalHawk

The mode constants are argument values rather than ids: FW_ONBOARD_RATE (0) and FW_DIRECT_SURFACE (1) for SET_FW_CTRL_MODE, FW_EST_TRUTH (0) and FW_EST_OBSERVER (1) for SET_FW_EST_SOURCE.

When to reach for it

Use send_cmd for ids the SDK has no method for, SET_ANGVEL being the one that comes up in practice. Prefer the typed methods everywhere they exist: they fill the right field for you and they validate, and a pulse width outside 1100 to 2000 microseconds is refused before anything is sent, where send_cmd publishes it.

Next: Commands nothing acts on

See also: Sending commands, Fixed wing control, Appendix B: Command reference