Skip to main content
The Virtual Controller (virtual_controller) resolves a command string against the keyboard controller policy assigned to a digital twin and publishes the matching actuation on cyberwave/twin/{uuid}/command. The MQTT payload matches keyboard teleoperation (KeyboardTeleoperationController / useMQTTTwin.publishCommand) — nothing is hardcoded per robot; bindings come from the twin’s policy at runtime.
Palette category: Actuation. Runs on cloud (Celery MQTT) and edge (worker client.mqtt.publish). Locomotion uses the MQTT command topic, not WebRTC; media streaming stays on separate channels.

Voice-to-actuation pipeline

Inputs

Precedence: wired input mapping → node parameters.

Command matching

The node accepts either form (normalization is shared with Fuzzy Matcher): Resolution order:
  1. Exact actuation (case / punctuation insensitive)
  2. Exact human label
  3. Keyboard key
  4. Substring containment (label inside longer STT phrase)
Bindings are collected from:
  • metadata.keyboard_bindings (actuation, label, key, optional playground data)
  • metadata.display_config.widgets (button command, slider command)
  • Universal keyboard policies with supports_locomotion and no bindings → default WASD locomotion actuations (move_forward, turn_left, …)

Inspector

  • Digital Twin — combobox; policy is never hardcoded in the node.
  • command — wire from upstream (typically Fuzzy Matcher match_string).

Inspector preview

When a twin is selected, the panel shows: Use this list to verify what voice commands the pipeline can dispatch before running the workflow.

MQTT payload

Published to cyberwave/twin/{twin_uuid}/command:
Same shape as manual keyboard teleop so edge drivers and simulators consume workflow commands without special cases.

Outputs

On resolution failure the node raises an error (unknown command, missing policy, missing twin).

Execution targets

Edge workers save the resolved controller snapshot (UUID, name, metadata, parsed entries) in process memory so repeat commands on the same twin do not re-fetch the policy every frame. Only run Virtual Controller when Fuzzy Matcher succeeded:
Pass fuzzy_matcher.match_string into virtual_controller.command on the true branch.

Dependencies

rapidfuzz is only required on the Fuzzy Matcher node upstream, not on Virtual Controller. Full voice-pipeline matrix: Edge workflow dependencies.

Controller policy examples

Command-mode UGV policies (control_mode: "command") define bindings like:
Virtual Controller sends move_forward on MQTT when the pipeline passes "Forward", "move_forward", or fuzzy-matched variants. See seeded policies in seed_controllers.py (e.g. controller:ugv-beast:v1). Universal keyboard (controller:keyboard-locomotion:v1) has no per-asset bindings in seed data; the node uses built-in locomotion defaults when supports_locomotion is true.
  • Twin — supplies Fuzzy Matcher candidates from control_actuations / control_labels
  • Fuzzy Matcher — maps noisy STT (uncertain string) → match_string from a source-of-truth string or array
  • Call Model — STT text → Fuzzy Matcher query
  • Conditional — gate on matched
  • Send MQTT — generic publish; Virtual Controller is purpose-built for controller actuations