Services and configuration

One-shot requests that change the robot rather than drive it, and why the reply is not the result.

What a service is

A service is a zenoh query on vrobots/{sys_id}/z/srv/{segment}: you send one request, the robot sends one reply, and the exchange is over. That is the opposite shape from a command, which is a publication nobody answers (see Commands latch). Services carry configuration, which is quasi-static: mass, noise models, rotor geometry, frames, the skin. Commands carry setpoints, which change every loop iteration.

Two services live outside a robot's namespace. vrobots/manager/z/srv/delete belongs to the scene manager, and vrobots/scene/z/srv/frame answers for the scene as a whole. Everything else is per robot, keyed by sys_id.

The ack is a receipt, not a result

Every reply is an SrvAck, and the simulator packs it the instant the query lands. The change itself is applied in phase 0 of the robot's next physics step, one step later.

sequenceDiagram
    participant P as Your program
    participant S as Simulator
    participant F as Physics step
    P->>S: GET srv/params
    S-->>P: SrvAck ok=true
    Note over S: queued, nothing changed yet
    S->>F: phase 0 of the next step
    Note over F: applied, or dropped with a log line
    F-->>P: state stream, one sample later

So Ok(()) means the robot heard you. It does not mean the robot agreed with you.

Only srv/skin ever says no

srv/skin is the single service in this API that can answer ok = false, and it does so only for a tier refusal. Everything else acks ok whatever you sent. A rotor list of the wrong length, an unknown frame id, a drive_mode that is not 2 or 4, a noise block for a sensor the robot does not carry: each is acked ok and then refused by a log line inside the simulator that no client can read.

The SDK closes part of that gap by refusing what it can before anything reaches the wire. The "Refused client-side" column of the table below is the whole of that protection; past it you are on your own.

Gotcha. A silently dropped request and a successful one are byte-for-byte identical from outside the simulator. Detect the difference by measuring the robot, never by reading the return value.

The state stream is the confirmation

Because of that, the examples in this chapter measure rather than assert. ex27 makes the point deliberately by sending a rotor list one entry short, with a thrust curve that would drop the aircraft out of the sky.

From examples/rust/src/bin/ex27_rotor_config.rs:

#![allow(unused)]
fn main() {
    robot.configure_rotors(&short)?;
    println!("... returned Ok. That is a receipt, and the request was dropped:");
    let dropped = climb(&robot, "after the short list", &collective)?;
}
The same in C++ (examples/cpp/ex27_rotor_config.cpp)
robot.configure_rotors(shortlist);
std::printf("... returned without throwing. That is a receipt, and the request was "
            "dropped:\n");
const Run dropped = climb(robot, "after the short list", collective);
The same in Python (examples/python/ex27_rotor_config.py)
robot.configure_rotors(short)
print("... returned without raising. That is a receipt, and the request was dropped:")
dropped = climb(robot, "after the short list", collective)

Each surface reports failure differently, and that is exactly what makes the point here: the Rust ?, the C++ catch and the Python except all stay quiet, because a dropped request is acked ok on the wire and none of them has anything to raise.

The ? never fires, and the aircraft climbs exactly as it did before, which is the proof that nothing was applied:

configure_rotors with <n-1> entries for <n> rotors ...
... returned Ok. That is a receipt, and the request was dropped:
-- after the short list --
   t=<seconds>s alt=<metres> m  climb=<rate> m/s  measured=[...]

The service map

Every live robot serves the same seven keys, whatever its type:

SegmentMethodPage
activate(inside connect, no public method)Robot lifecycle
resetresetRobot lifecycle
paramsset_physical_paramsMass and inertia
sensorsconfigure_sensorsSensor noise
framesset_framesCoordinate frames
camerasmount_camera, unmount_cameraMount, open and unmount
skinset_skinSkins

One more service belongs to each of four robot types, and to nothing else:

SegmentMethodRobotPage
driveconfigure_driveTruckThe truck drivetrain
rotorsconfigure_rotorsMultirotorRotors and thrust curves
msdconfigure_msdMsdMass spring damper and cart pole
cartpoleconfigure_cartpoleCartPoleMass spring damper and cart pole

HalfDrone and GlobalHawk add nothing: their entire control surface is command-level.

Signatures, with what each refuses before publishing:

MethodSignatureTopicRefused client-sideRobot
reset() -> VrResult<()>srv/resetnothingall
set_physical_params(&PhysicalParams) -> VrResult<()>srv/paramsempty request, mass not positive-finite, any MOI axis not positive-finiteall
set_skin(&str) -> VrResult<()>srv/skinan empty nameall
configure_sensors(&SensorConfig) -> VrResult<()>srv/sensorsempty request, any non-finite valueall
set_frames(Option<&str>, &[DeviceFrame]) -> VrResult<()>srv/framesboth halves empty, an entry with an empty device or frame idall
scene_frame() -> VrResult<SceneFrame>vrobots/scene/z/srv/framenothingscene scope
configure_drive(&DriveConfig) -> VrResult<()>srv/driveempty request, drive_mode not 2 or 4, any non-finite valueTruck
configure_rotors(&[RotorSpec]) -> VrResult<()>srv/rotorsan empty slice, any non-finite valueMultirotor
configure_msd(&MsdConfig) -> VrResult<()>srv/msdempty request, a negative or non-finite valueMsd
configure_cartpole(&CartPoleConfig) -> VrResult<()>srv/cartpoleempty request, a non-positive mass, length or force, any non-finite valueCartPole
delete() -> VrResult<()>manager/z/srv/deletealready deletedall
is_deleted() -> boollocal flagall

Asking for a service the robot does not serve

A robot registers a queryable only for its own type's service, so configure_drive on a multirotor is a query nobody answers. After ConnectOptions::service_timeout (8 seconds by default) it returns VrError::NoResponder.

That is not a failure mode to defend against, it is a capability probe: it is how ex30_hello_halfdrone establishes that a HalfDrone serves the common seven and nothing more. It is also indistinguishable from a simulator that is not running, so confirm with vrobots topic list before drawing a conclusion.

Next: Robot lifecycle

See also: Five rules that explain everything, The topic namespace, Appendix C: Error reference