Skip to main content

Cyberwave is in Private Beta.

Request early access to get access to the Cyberwave dashboard.

How to command twins in SDK 0.5.x: grouped capability handles, asset-driven MQTT command catalogs, and separate simulation vs live state buckets.
For articulated joint control and saved poses, see Joints & Poses.

Prerequisites

export CYBERWAVE_API_KEY=your_key   # or: cyberwave login --token …
pip install cyberwave
Use asset slugs (e.g. the-robot-studio/so101, unitree/go2) with cw.twin(slug). Call cw.affect("simulation") or cw.affect("live") before locomotion and joint MQTT so commands and cached state target the right runtime.

Managing assets and twins

To instantiate a twin, query the available assets from the catalog. The search returns both the public assets at cyberwave.com/catalog and the private assets available to your organization.
assets = cw.assets.search("so101")

# registry_id is the canonical asset identifier, e.g. the-robot-studio/so101
robot = cw.twin(assets[0].registry_id)

# Standard assets can also expose a shorter alias for SDK ergonomics
camera = cw.twin("camera")
Move an existing twin to another environment:
robot = cw.twin("the-robot-studio/so101")
robot.add_to_environment("TARGET_ENVIRONMENT_UUID")
add_to_environment() creates a deep copy of the twin in the target environment, marks the original as deleted, and deletes the source environment if it has no twins left.
move() and rotate() are deprecated. Use move_forward(), move_backward(), turn_left(), and turn_right() for locomotion, or edit_rotation() to set orientation directly.

Scene layout

Editor placement does not go over MQTT. Use edit_position / edit_rotation to move the twin in the environment scene.
arm = cw.twin("the-robot-studio/so101")
arm.edit_position(x=1, y=0, z=0.5)
arm.edit_rotation(yaw=90)
arm.edit_scale(x=1.5, y=1.5, z=1.5)

Locomotion

Locomotion twins (Go2, UGV, …) accept velocity-style commands. The SDK publishes a burst of MQTT messages plus an explicit stop so edge drivers with velocity watchdogs stay alive.
cw.affect("simulation")  # or "live"
robot = cw.twin("unitree/go2")
robot.locomotion.move_forward(0.3, duration=0.5, rate_hz=10)
robot.turn_left(0.5, duration=0.3)
# Top-level shortcuts: robot.move_forward(0.3, duration=0.5)
Speed is m/s (forward/back) or rad/s (turns), not travel distance. Do not use the deprecated move() / rotate() for locomotion — use edit_rotation() for editor layout only.

Command catalog

Each asset can declare MQTT commands in cw-driver.yml (metadata["mqtt"]["commands"]). The SDK binds twin.commands.<name>(**kwargs) at runtime. Locomotion names delegate to twin.locomotion (burst); other names publish once.
dog = cw.twin("unitree/go2")
print(dog.commands.get_schema()["commands"]["supported"])
dog.commands.move_forward(linear_x=0.3, duration=0.5, rate_hz=10)
# dog.commands.sit_down()  # catalog-only, single publish
Introspection: twin.describe(), twin.commands.get_schema().

Inbound MQTT (listen)

Subscribe to catalog inbound topics with twin.listen(filters=[…]). It returns a session; call session.stop() when done. Cached state is read with get_joints(), pose.get(), etc.
session = arm.listen(filters=["joints", "pose"])
time.sleep(2)
print(arm.get_joints())
session.stop()
Filter slugs match the asset driver manifest (e.g. joints, pose, power). This replaces the deprecated subscribe_position() / subscribe_rotation().

Runtime mode (affect)

cw.affect("simulation") and cw.affect("live") set config.runtime_mode and the default control source_type. Inbound MQTT updates land in separate buckets; get_joints() reads the bucket for the active mode.
cw.affect("simulation")
arm.set_joints({joint_names[0]: -0.2})
cw.affect("live")
arm.set_joints({joint_names[-1]: 0.2})
affect() is chainable: cw.affect("simulation").twin("unitree/go2"). "real-world" is accepted as an alias for "live".

Simulation vs. live

Use mode="simulation" for a simulator-first client, or cw.affect() to switch an existing client between sim and live.
cw = Cyberwave(mode="simulation")

rover = cw.twin("unitree/go2")
rover.move_forward(1.0)   # moves the digital twin in Cyberwave

cw.affect("live")
rover.move_forward(1.0)   # moves the physical robot

# Per-call override when needed
rover.move_forward(1.0, source_type="tele")
Runtime-mode defaults:
  • Live publishes state updates as edge.
  • Simulation publishes state updates as sim.
  • Locomotion and other control helpers target tele in live mode and sim_tele in simulation.
For lower-level integrations, pass source_type explicitly or set CYBERWAVE_SOURCE_TYPE. Accepted raw values: "sim", "sim_tele", "tele", "edge", "edit". This same mode selection drives twin.get_frame(source="cloud") — see Frames & Cameras.

Teleop policy

In live mode, attach a workspace controller policy before joint/locomotion MQTT when the platform expects teleop routing.
cw.affect("live")
arm.policy.ensure_attached()
# arm.policy.assign(arm.policy.list()[0])
arm.set_joints({joint_names[-1]: 0.2})

Live Teleoperation

How teleoperation and runtime mode work in the Environment Editor.

Pose reads

Twin kindget_pose() meaningPreferred handle
Manipulator (arm)Joint-space (alias for get_joints())twin.get_joints() / twin.joints.get()
Locomote (Go2, …)World pose from MQTTtwin.pose.get()
print(arm.get_pose())   # joint-space on SO101
print(dog.pose.get())   # Cartesian on Go2

Discovery

print(twin.describe())  # handles, catalog commands, routing (via, continuous)

MQTT contract

The MQTT command envelope ({source_type, command, data, timestamp}) and the outbound telemetry rate limits are documented on the contract home:

MQTT API Reference

Command envelopes, source types, and outbound rate limits.

Per-hardware guides

Robotic Arms

Per-arm joints and calibration.

Robotic Dogs

Locomotion and gaits for quadrupeds.