A compatible driver connects your hardware’s native API to a Cyberwave asset and twin: it translates the device’s protocol into MQTT topics, command envelopes, and (optionally) edge data channels that the UI, workflows, and SDK understand.This guide is for integrators building a driver from scratch for a custom or third-party asset in your workspace. Cyberwave-maintained drivers use the same rules; their cw-driver.yml files are linked later as examples only.Drivers can run on edge hardware, on the robot, in the cloud, or on a developer laptop. In production they usually run beside the device under Edge Core.Each driver ships as a Docker image. Edge Core pulls and runs it with the environment variables below, so local development matches production deployment.
The fastest way to get started is the Cyberwave Driver skill for Claude Code. It asks you a few questions about your hardware and scaffolds a complete, production-ready driver project — including the Dockerfile, local dev setup, and a working twin connection.Install the skill:
Claude will generate the full project tree and walk you through connecting it to a real twin locally. The skill source is open source at cyberwave-os/driver-skill.
When Edge Core starts a driver container it injects the following environment variables. You can develop your driver assuming these are always set to valid values — no need to handle the case where they are absent.
Variable
Description
CYBERWAVE_TWIN_UUID
UUID of the twin instance this driver manages
CYBERWAVE_API_KEY
API key scoped to this driver for authenticating platform calls
CYBERWAVE_TWIN_JSON_FILE
Absolute path to a writable JSON file containing the twin’s current state (see Twin JSON file)
CYBERWAVE_CHILD_TWIN_UUIDS
(optional) Comma-separated UUIDs of child twins (e.g. cameras) attached to this driver
CYBERWAVE_CHILD_TWIN_UUIDS is set when child twins are attached to the driver twin. Drivers can use this to coordinate child devices (for example, multiple cameras) without additional configuration.
Drivers must exit with a non-zero code when they cannot access required hardware (for example, a missing /dev/video* device or a disconnected peripheral). This allows Edge Core to detect startup failures and trigger restart logic.Edge Core raises the following alerts:
driver_start_failure — raised when a driver container cannot reach a stable running state.
driver_restart_loop — raised when a driver exceeds the restart threshold within the window. The container is stopped and marked as flapping.
CYBERWAVE_TWIN_JSON_FILE points to a JSON file on disk that contains the digital twin instance (including its metadata) and the associated catalog twin data, matching the TwinSchema and AssetSchema API schemas.Drivers may read and modify this file. Edge Core syncs any changes back to the backend when connectivity is available.
Drivers should treat metadata["edge_configs"] as the source of truth for per-device runtime configuration, and metadata["edge_fingerprint"] as the edge identity (not duplicated inside edge_configs).Read edge_configs from CYBERWAVE_TWIN_JSON_FILE at startup to obtain per-device settings without hardcoding them in the image.
To make a custom asset work with Cyberwave, your driver project should include a cw-driver.yml at the repository root (next to your Dockerfile). The file is the contract between your hardware bridge, the platform APIs, the web UI, and the SDK: it lists which MQTT topics you use and which command strings you accept.Cyberwave’s own edge drivers (Go2, SO-101, DJI Mini, and others) use the same format — treat them as reference implementations, not as something you must fork. Copy the patterns that match your robot (locomotion, joint bus, camera stream, etc.) and trim what you do not implement.
Run the image with Edge Core (or locally with the same env vars). New twins spawned from that asset inherit metadata["mqtt"].
If the manifest and the running driver disagree (undeclared command, wrong topic), teleop, agents, and SDK helpers will not line up with what the hardware actually does.
Without a declared interface catalog, the platform cannot know which MQTT topics your driver uses, which command strings are valid on cyberwave/twin/{uuid}/command, or whether a command is continuous (stick-held) vs discrete (one-shot). That metadata drives:
Consumer
What it uses from the catalog
Web UI
Dynamic control surfaces — e.g. the keyboard teleop overlay lists only commands.supported entries and respects commands.specs[name].continuous
Python SDK
twin.commands.get_schema() and catalog methods such as twin.commands.move_forward(...) bound from commands.supported
Your driver
A single source of truth for reviews, tests, and parity with production behavior
Planned — topic entries will also reference dedicated payload schemas (protobuf / robot-native layouts) for typed robot data on the wire. Today, payload_schema_ref names the logical schema; strict validation against those refs is not enforced yet.
Topic entries are nested as mqtt.<namespace>.<leaf>. The leaf key is the MQTT path segment (use hyphens in YAML, e.g. webrtc-offer). Each entry becomes a flat key in metadata["mqtt"]["topics"] by joining a namespace prefix with the leaf:
YAML path
Compiled slug
Example live topic (twin_uuid = abc)
mqtt.joint.update
cyberwave/joint/{twin_uuid}/update
cyberwave/joint/abc/update
mqtt.joint.+
cyberwave/joint/{twin_uuid}/+
cyberwave/joint/abc/+ (wildcard)
mqtt.twin.command
cyberwave/twin/{twin_uuid}/command
cyberwave/twin/abc/command
mqtt.twin.position
cyberwave/twin/{twin_uuid}/position
cyberwave/twin/abc/position
mqtt.twin.telemetry
cyberwave/twin/{twin_uuid}/telemetry
cyberwave/twin/abc/telemetry
mqtt.twin.webrtc-offer
cyberwave/twin/{twin_uuid}/webrtc-offer
cyberwave/twin/abc/webrtc-offer
mqtt.twin.webrtc-answer
cyberwave/twin/{twin_uuid}/webrtc-answer
cyberwave/twin/abc/webrtc-answer
mqtt.twin.webrtc-candidate
cyberwave/twin/{twin_uuid}/webrtc-candidate
cyberwave/twin/abc/webrtc-candidate
mqtt.environment.<leaf>
cyberwave/environment/{environment_uuid}/<leaf>
environment-scoped
Namespace prefixes:
mqtt namespace
MQTT prefix
joint
cyberwave/joint/{twin_uuid}
twin
cyberwave/twin/{twin_uuid}
environment
cyberwave/environment/{environment_uuid}
Each leaf entry sets direction, payload_schema_ref, description, and optional source_types.WebRTC signaling is always on the twin prefix (cyberwave/twin/{uuid}/webrtc-offer, webrtc-answer, webrtc-candidate, and webrtc-command for media-service commands). Declare them under mqtt.twin with hyphenated leaf names — not as a separate top-level namespace:
mqtt: twin: command: # ... webrtc-offer: direction: both payload_schema_ref: WebRTCOfferPayload webrtc-answer: direction: both payload_schema_ref: WebRTCAnswerPayload
Only list WebRTC leaves when this twin participates in signaling (e.g. onboard camera or gimbal stream). Arm-only or joint-bus drivers (such as SO-101) should omit them; camera video uses child camera twins and the media service, not WebRTC topics on the arm twin.
In some first-party Cyberwave profiles you may see a legacy mqtt.webrtc.offer shorthand (offer → webrtc-offer under the twin prefix). For new drivers, use mqtt.twin.webrtc-* only.
After you author cw-driver.yml, register it on a twin you own with the Python SDK. You do not need to hand-build JSON or call low-level asset APIs — point set_schema at your manifest file and the platform updates that twin’s metadata["mqtt"], refreshes the catalog cache, and binds catalog-derived command methods on twin.commands (for example twin.commands.stop(), twin.commands.move_forward(...) when those names appear in commands.supported).
from cyberwave import Cyberwavecw = Cyberwave(api_key="...")twin = cw.twin("your-twin-uuid") # or resolve from environment# Compile cw-driver.yml, persist on this twin, re-bind catalog methodstwin.commands.set_schema("./cw-driver.yml")# Inspect the catalog the UI and SDK now useprint(twin.commands.get_schema()["commands"]["supported"])# Call any command you declared — derived from the manifesttwin.commands.stop()twin.commands.move_forward(linear_x=0.3, duration=0.5, rate_hz=10)
Set registry_ids in the YAML to match the asset your twin was created from. Re-run set_schema whenever you change the manifest so teleop, agents, and SDK callers stay aligned with your driver.
Authorization: Bearer <token> (same as CYBERWAVE_API_KEY)
Request body
{ "metadata": { "mqtt": { "schema_version": 1, "topics": { ... }, "commands": { "supported": [...], "specs": { ... } }, ... } } } — only fields you want to change need to be sent; set_schema sends the full metadata object with mqtt set or merged
Response
TwinSchema including updated metadata
Read back
GET /api/v1/twins/{uuid} → metadata.mqtt; SDK: twin.commands.get_schema()
Full OpenAPI entry: Update Twin.merge=True (default) deep-merges the new metadata.mqtt into existing twin metadata. merge=False replaces the entire mqtt block.Invoke catalog commands after set_schema — they publish over MQTT, not REST:
SDK
Wire
twin.commands.<command>(**kwargs)
Publish to cyberwave/twin/{twin_uuid}/command with envelope { source_type, command, data, timestamp }
Use the Python SDK for day-to-day driver work; use REST when integrating from another language or CI. All REST paths are under /api/v1 and require a bearer token unless noted otherwise. See the REST API reference for request/response schemas.
Declared in cw-driver.yml → compiled into metadata.mqtt.topics. At runtime the driver (or SDK teleop) uses the MQTT broker (WebSocket URL in NEXT_PUBLIC_MQTT_URL for frontends; port 1883 / 9001 locally for edge). Topic prefix may include an environment segment in deployed stacks; slugs below are canonical.
Catalog slug (metadata.mqtt.topics key)
Example topic
Typical driver role
cyberwave/twin/{twin_uuid}/command
cyberwave/twin/abc/command
Subscribe for source_type: tele commands; map command string to hardware
cyberwave/joint/{twin_uuid}/update
cyberwave/joint/abc/update
Subscribe for tele joint targets; publish follower/sim observations
Defines the hardware type; twins usually start with asset metadata.
Your twin
Your workspace
set_schema updates this twin’smetadata["mqtt"] so you can iterate without changing a shared asset definition.
Public Cyberwave assets
Cyberwave
You can spawn twins from them; use set_schema on your twin if you need a custom command surface for a private integration.
First-party Cyberwave drivers ship a cw-driver.yml in their repositories for reference — you follow the same file format, then apply it to your twin with set_schema during bring-up.
If your driver produces sensor data (video frames, depth maps, audio, joint states, etc.), publish it to the edge data bus so worker containers and ML models can consume it locally with zero network overhead.There are two options: the Zenoh data bus (recommended) and the filesystem convention (fallback for constrained environments). Both use the same channel names — a driver can switch between them by changing one env var.
The Zenoh data bus provides zero-copy shared memory between driver and worker containers. Data is consumed directly by worker hooks and cw.data.latest().
The filesystem convention is the fallback for environments where
eclipse-zenoh cannot be installed. For most drivers, use cw.data.publish()
(Zenoh data bus) instead — it provides zero-copy shared memory and is consumed
directly by worker hooks. Both conventions use the same channel names.
Write sensor data to a subfolder of the config directory that Edge Core mounts into your container:
If you publish data over MQTT directly (rather than through the SDK’s cw.data.publish), see the MQTT API Reference for the complete list of topics and payload schemas supported by the platform. That page covers:
Twin transform: position, rotation, scale
Joint state updates (single-joint, flat multi-joint, and aggregated formats)
If your driver currently publishes sensor data over MQTT, you can add Zenoh publishing without removing the MQTT path. The two paths serve different consumers:
You own your driver code. There are two common paths:
Open source — publish your driver as a public repository on GitHub under the Apache 2.0 license. This is our recommended default and makes it easier for the community to contribute and reuse your work.
Closed source — keep your driver proprietary. In this case, we recommend obfuscating your code before distributing the image and including a clear license file that reflects your distribution terms. Interested in writing a closed-source driver? Reach out to us.
Fork or read these repositories when building your own compliant driver — each includes a Dockerfile, Edge Core env contract, and (where applicable) a cw-driver.yml: