Skip to content

feat(hosted-teleop): Go2 teleoperation over Cloudflare/LiveKit broker#2798

Draft
ruthwikdasyam wants to merge 209 commits into
mainfrom
ruthwik/hostedteleop/2
Draft

feat(hosted-teleop): Go2 teleoperation over Cloudflare/LiveKit broker#2798
ruthwikdasyam wants to merge 209 commits into
mainfrom
ruthwik/hostedteleop/2

Conversation

@ruthwikdasyam

@ruthwikdasyam ruthwikdasyam commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Teleoperating a robot over the internet (not just LAN) needs a dial-out architecture: robots behind NAT/cellular can't accept inbound connections, so they must connect out to a broker, and browser/VR operators reach them through it. dimos had no such path — only the LAN-only quest WebSocket teleop.

Closes DIM-XXX

Solution

Robot-side hosted teleoperation over WebRTC, via the dimensional-teleop broker (Cloudflare Realtime or LiveKit SFU). One session per robot carries video, commands, telemetry and safety.

Transport layer - CloudflareTransport / LiveKitTransport (+ *VideoTransport) bind directly to blueprint streams; a per-process BrokerProvider (CF) / LiveKitBrokerProvider owns the single session. Commands, state and video all ride it - no separate teleop module owning its own PeerConnection.

Go2HostedConnection - colocates every broker-bound stream on one module (the driver is dedicated_worker, so they must share a process to share a session). Adds:

  • Drive with stale/out-of-order cmd_vel drop + firmware deadman
  • Command plane: allow-listed sport commands, rage mode, obstacle avoidance, head LED — on a single-worker executor with nonce dedup, bounded backlog, and an urgent bypass so E-STOP never queues behind a slow command
  • E-STOP latch + stop-on-operator-lost
  • Operator minimap: occupancy grid + robot pose on a dedicated map_unreliable channel (coarsened + PNG under the 16 KB datachannel ceiling)
  • Click-to-navigate: map click → planner goal, live operator steering overrides autonomy
  • Operator→robot audio: recvonly track → the Go2's speaker
  • Glass-to-glass latency benchmark (frame-embedded capture-time stamp)
  • Shared plane (HostedConnectionMixin + CameraMuxMixin) — state dispatch, cmd_ack, telemetry, and an operator-selectable camera mux; a new robot implements a handful of hooks.

Clock-sync ping/pong is answered inline in the provider (no module hop, so RTT/offset stay jitter-free). Legacy HostedTeleopModule is deprecated. Arm/manipulation hosted teleop was split out to a follow-up branch to keep this Go2-focused.

Coverage: unit tests for the command plane, camera mux, clock-sync ping, stream/video stats, speaker track, and the LiveKit frame packing. The live WebRTC session paths are exercised by the opt-in broker e2e/benchmark cases (they need a real SFU + operator), not unit tests.

How to Test

# Cloudflare transport (keyboard/VR operator at https://teleop.dimensionalos.com)
dimos run teleop-hosted-go2-transport -o transports.broker.api_key=dtk_live_...

# LiveKit backend, or the dual-RealSense multicam / minimap variant:
dimos run teleop-hosted-go2-livekit   -o transports.broker.api_key=dtk_live_...
dimos run teleop-hosted-go2-multicam  -o transports.broker.api_key=dtk_live_...
Glass-to-glass latency benchmark: add -o go2hostedconnection.latency_stamp=true and read the operator HUD.

Contributor License Agreement

  • I have read and approved the CLA.

ruthwikdasyam and others added 30 commits May 7, 2026 15:28
…e SFU

Implements a new pubsub transport backed by WebRTC DataChannels over
Cloudflare's Realtime SFU. Two new classes in
dimos/protocol/pubsub/impl/webrtcpubsub.py:

- CloudflareSession: manages the WebRTC PeerConnection lifecycle.
  Opens two CF sessions (publisher + subscriber) so a single process
  can do loopback pubsub. Runs aiortc on a dedicated background
  asyncio thread with its own ThreadPoolExecutor (so we don't leak
  asyncio_N worker threads). Uses negotiated=True placeholder DCs
  with id=100 during transport establishment to avoid stream-id
  collisions with CF-assigned ids.

- WebRTCPubSub: bytes-on-the-wire pubsub facade matching the
  LCMPubSubBase / BytesSharedMemory interface (string topics, bytes
  payloads). Lazily creates pub/sub DataChannel pairs on first
  publish/subscribe per topic.

Also adds:
- WebRTCTransport in dimos/core/transport.py (mirrors LCMTransport
  pattern, no encoding - bytes only).
- WebRTC benchmark testcase in
  dimos/protocol/pubsub/benchmark/testdata.py, gated on aiortc +
  CF_TELEOP_APP_ID / CF_TELEOP_APP_SECRET env vars.
- Integration test in
  dimos/protocol/pubsub/impl/test_webrtcpubsub.py covering basic
  pub/sub, latency, and throughput (all live tests skip without CF
  credentials).
- aiortc + httpx as new 'webrtc' optional extra in pyproject.toml.

Live benchmark (us-east-2 -> CF edge):
- 64-256B:  ~10K msgs/s, 0% loss
- 1KiB:     ~7K msgs/s, 0% loss
- >= 64KiB: dropped (above SCTP message size)
- Median single-RTT: ~2.5 ms
…sport

- Add BrokerProvider: DataChannelProvider that works through the hosted
  teleop broker (dimensional-teleop) instead of directly with CF credentials.
  Handles session registration, heartbeat loop, and DataChannel creation
  when an operator joins via the broker's bridge-datachannel API.

- Extend WebRTCTransport with optional msg_type parameter for typed LCM
  encode/decode with fingerprint-based filtering. Multiple transports can
  share a single multiplexed DataChannel and each receives only its type.

- Add hosted teleop blueprints (dimos/teleop/hosted/) demonstrating the
  module-free architecture: make_teleop_hosted_go2() uses pure transport
  (zero modules), make_teleop_hosted_go2_scaled() adds a thin
  TeleopScalerModule for speed scaling only.

- Add unit tests for typed mode, fingerprint filtering, multiplexed
  dispatch, and BrokerProvider credential validation.
- Rebase on main and regenerate uv.lock (resolve conflict)
- Add _LoopbackProvider (in-process, no network) to benchmark testdata
- Enables local WebRTC transport benchmarking without CF credentials
- All 12 message sizes pass locally (2.78s total)
The previous lock regen dropped the `exclude-newer-span` marker, leaving
only the frozen `exclude-newer` timestamp. uv then treats every resolve as
"cooldown was newly added" and forces a re-resolve against today minus 7
days — which currently excludes md-babel-py 1.2.0 (published 2026-05-15)
and breaks `uv sync --extra all` / `uv lock`.

Re-adding the span line tells uv the lock was generated with P7D
semantics, so the existing pinned versions are honored.
- Remove __init__.py files (project policy: no init files)
- Remove section markers from test_webrtcpubsub.py
- Regenerate all_blueprints.py (adds TeleopScalerModule)
- Fix WebRTCTransport.__reduce__ to preserve msg_type across pickle
- Fix CloudflareProvider.publish() race: snapshot loop ref before use
- Fix CloudflareProvider.subscribe() race: check sub_channels inside lock
- Add comment clarifying TwistStamped→Twist type safety in blueprint
- Add return type annotations to CloudflareProvider event handlers
- Fix type: ignore codes to match actual mypy errors (attr-defined)
- Add type annotation to __setstate__ dict parameter
- Add type: ignore[arg-type] for WebRTCPubSub→PubSub duck typing in benchmark
- Remove TwistStamped subclass comment from blueprint
spomichter and others added 17 commits July 7, 2026 04:03
If createDataChannel raised inside the heartbeat, the id was already
recorded, so every later heartbeat skipped the open and the channel
stayed dead until the operator id changed. Extract _reconcile_channels
and store the id only after a successful open so the next beat retries.
Also drop needless forward refs in Blueprint.transports (review nit).
…pping

Per Paul's review: a dev or CI env without the webrtc deps used to skip
the entire suite invisibly. The guard asserts WEBRTC_AVAILABLE when CI
is set; downstream/no-network installs (Sam's case) keep skipping.
Per review: the repo convention moved dev-only/live tests to tool_*.py
filenames (never collected; run by explicit path) and removed the tool
marker codebase-wide. Cred-gating skipifs stay for keyless path runs.
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.47047% with 590 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...col/pubsub/impl/webrtc/providers/livekit_broker.py 26.01% 180 Missing and 2 partials ⚠️
dimos/teleop/quest_hosted/go2_hosted_connection.py 68.86% 133 Missing and 18 partials ⚠️
...os/protocol/pubsub/impl/webrtc/providers/broker.py 19.87% 123 Missing and 2 partials ⚠️
dimos/teleop/quest_hosted/hosted_base.py 52.00% 31 Missing and 5 partials ⚠️
dimos/teleop/utils/camera_mux.py 75.00% 18 Missing and 6 partials ⚠️
.../teleop/quest_hosted/test_go2_hosted_connection.py 97.78% 5 Missing and 8 partials ⚠️
dimos/robot/unitree/go2/connection.py 20.00% 12 Missing ⚠️
...imos/protocol/pubsub/impl/webrtc/providers/spec.py 52.63% 9 Missing ⚠️
dimos/robot/unitree/connection.py 50.00% 7 Missing ⚠️
dimos/teleop/quest/quest_teleop_module.py 12.50% 7 Missing ⚠️
... and 6 more
@@            Coverage Diff             @@
##             main    #2798      +/-   ##
==========================================
+ Coverage   71.91%   71.97%   +0.05%     
==========================================
  Files         979      987       +8     
  Lines       87346    89117    +1771     
  Branches     7982     8133     +151     
==========================================
+ Hits        62819    64140    +1321     
- Misses      22387    22794     +407     
- Partials     2140     2183      +43     
Flag Coverage Δ
OS-ubuntu-24.04-arm 64.74% <70.46%> (+0.16%) ⬆️
OS-ubuntu-latest 67.16% <70.46%> (+0.10%) ⬆️
Py-3.10 67.15% <70.46%> (+0.10%) ⬆️
Py-3.11 67.15% <70.46%> (+0.11%) ⬆️
Py-3.12 67.16% <70.46%> (+0.11%) ⬆️
Py-3.13 67.15% <70.46%> (+0.10%) ⬆️
Py-3.14 67.16% <70.46%> (+0.10%) ⬆️
Py-3.14t 67.14% <70.46%> (+0.10%) ⬆️
SelfHosted-Large 29.83% <19.05%> (-0.27%) ⬇️
SelfHosted-Linux 37.22% <19.47%> (-0.45%) ⬇️
SelfHosted-macOS 35.61% <19.47%> (-0.41%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/core/coordination/module_coordinator.py 84.22% <100.00%> (+0.06%) ⬆️
dimos/core/transport.py 67.46% <100.00%> (+0.49%) ⬆️
...os/protocol/pubsub/impl/webrtc/test_broker_ping.py 100.00% <100.00%> (ø)
...imos/protocol/pubsub/impl/webrtc/test_transport.py 93.40% <100.00%> (+0.83%) ⬆️
dimos/robot/all_blueprints.py 100.00% <ø> (ø)
dimos/robot/test_all_blueprints.py 87.50% <ø> (ø)
dimos/robot/unitree/go2/test_speaker.py 100.00% <100.00%> (ø)
dimos/teleop/quest/quest_extensions.py 51.72% <100.00%> (ø)
dimos/teleop/quest/quest_types.py 58.62% <100.00%> (+1.99%) ⬆️
dimos/teleop/quest_hosted/blueprints.py 100.00% <100.00%> (ø)
... and 20 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ruthwikdasyam ruthwikdasyam force-pushed the ruthwik/hostedteleop/2 branch from f3e5502 to 57d37ef Compare July 8, 2026 18:43
@ruthwikdasyam ruthwikdasyam changed the title Ruthwik/hostedteleop/2 feat(hosted-teleop): Go2 teleoperation over Cloudflare/LiveKit broker Jul 8, 2026
@mintlify

mintlify Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 9, 2026, 1:33 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants