Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello service

Create a robot from code and delete it again, which is what explicit lifecycle means.

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

The whole program

Lifecycle and configuration are one-shot request and response, so this is the first example with no loop in it.

examples/rust/src/bin/ex04_hello_service.rs:

use vrobots_sdk::{RobotType, VirtualRobot, VrError};

const ROBOT_TYPE: RobotType = RobotType::Multirotor;

fn main() -> Result<(), VrError> {
    vrobots_sdk::init_logging("info");

    // Create a NEW robot in the sim (no sys_id -> manager create; reply carries the id).
    let robot = VirtualRobot::connect(ROBOT_TYPE, None)?;
    let sys_id = robot.sys_id();
    println!("created sys_id = {sys_id}");

    // The create reply is a receipt; the robot *exists* once its state topic
    // publishes, which connect() already waited for -- so this is real data.
    let s = robot.states();
    println!(
        "first state: t={:.3} seq={} name={:?}",
        s.elapsed, s.seq, s.name
    );
    println!("its state topic: {}", vrobots_sdk::topics::state(sys_id));

    // Deletion is explicit and never implicit. delete() waits for the state topic
    // to fall silent: the manager's ack is only a receipt, absence is the proof.
    robot.delete()?;
    println!(
        "deleted sys_id = {sys_id} (is_deleted={})",
        robot.is_deleted()
    );

    // The handle is spent. Commands do not silently do nothing -- they say why.
    match robot.set_mr_pwm([1500.0; 4]) {
        Ok(()) => println!("unexpected: a deleted robot accepted a command"),
        Err(e) => println!("the handle is spent, as expected: [{}] {e}", e.code()),
    }
    Ok(())
}

Three differences worth naming. C++ spells creation VirtualRobot::create rather than a one-argument constructor, because a literal 0 would be ambiguous between “attach to sys_id 0” and a null options pointer. C++ spells deletion remove(), because delete is a keyword. And C++ has no topic-name builder, so it composes vrobots/<id>/z/state inline where Rust calls topics::state and Python calls vrsdk.topics.

The Python script is shorter in one respect: it stops after the delete, where Rust and C++ go on to send one command on the spent handle.

The run takes a second or two, most of it spent waiting for the new robot’s state topic to start and then to stop:

created sys_id = 7
first state: t=0.000 seq=0 name="multirotor"
its state topic: vrobots/7/z/state
deleted sys_id = 7 (is_deleted=true)
the handle is spent, as expected: [8] deleted: sys_id 7 was deleted from the sim; this handle is spent

That is the Rust run. C++ prints removed=true where Rust prints is_deleted=true, and the Python script prints the first four lines in Python’s own spelling, name='multirotor' and is_deleted=True.

Create versus attach

The second argument to connect decides which of two quite different things happens.

ArgumentWhat it doesTouches srv/create
Some(id)attaches to a robot the scene already containsno
Noneasks the manager to spawn a new one; the reply carries the idyes

Every example so far attached by id, which is Some(SYS_ID) in Rust. This one passes None, which in Python means leaving sys_id out, so the manager allocates the id and robot.sys_id() is the only way to learn it.

Creating is limited by the scene’s catalog, not the SDK’s. The sandbox scene registers multirotor, truck and msd; any other key is refused with a message naming the ones it does know, which is also the only live way to enumerate the catalog. Robot types that are scene-authored only, such as the cart pole and the Global Hawk, can be attached to but never created.

Note. Create is the one non-idempotent service in the system. The SDK sends it exactly once and never retries, because every retry that reaches the manager reserves another id and spawns another robot. What connect actually does walks the four steps.

Robots outlive the process

Dropping a VirtualRobot closes its zenoh session. It does not delete the robot. The robot keeps flying its last latched command, keeps publishing state, and is still there after your program exits, after you rebuild, and after you run something else.

This is why the example calls delete(): without it, every run would leave another multirotor in the scene. Comment the delete() line out and run it twice to see exactly that, then attach ex01_hello_states to one of the ids it printed. Create in one process, attach from another, is the normal shape of a multi-program session.

The way back from a littered scene is to reload it. Ids are allocated at scene load and keep incrementing, so the reloaded scene numbers its robots differently.

Absence is the proof, both ways

connect(type, None) returns only once the new robot’s state topic has published, and delete() returns only once that topic has fallen silent. Neither waits on the service acknowledgement, because an acknowledgement is packed the instant the request arrives and says nothing about whether the work happened.

That principle runs through the whole services chapter: an ack is a receipt, not a result, and the state stream is the confirmation. It is rule two of Five rules that explain everything.

After delete() the handle is spent, and it says so rather than failing quietly. Every command on it returns VrError::Deleted, error code 8, with a message naming the id; the last line of the Rust and C++ runs shows it.

Next: When nothing happens

See also: Robot lifecycle, System ids, and the two kinds of robot, Appendix C: Error reference