Skip to content

Krishna/issue 2463 aes#2476

Open
KrishnaH96 wants to merge 8 commits into
mainfrom
krishna/issue-2463-aes
Open

Krishna/issue 2463 aes#2476
KrishnaH96 wants to merge 8 commits into
mainfrom
krishna/issue-2463-aes

Conversation

@KrishnaH96

@KrishnaH96 KrishnaH96 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Closes the firmware gap from #2463. Re-applies #2117 onto current main plus the upstream-lib switch agreed in that thread.

New Unitree firmware (G1 ≥1.5.1, Go2 ≥1.1.15) uses a data2=3 WebRTC handshake requiring a per-device AES-128 key; without it dimos cannot connect.

Changes

  1. Forward the key — thread an optional aes_128_key (or UNITREE_AES_128_KEY env var) from the Go2/G1 connection configs down to UnitreeWebRTCConnection, passed to the driver only when set (byte-identical for robots that don't need it).
  2. Bump the driverunitree-webrtc-connect-leshy (caps at 2.0.7, no data2=3) → upstream unitree-webrtc-connect 2.1.2, which has the handshake + aes_128_key. Per @leshy in feat(unitree): forward aes_128_key to WebRTC driver for G1 firmware >=1.5.1 #2117 the fork's lidar-parser changes have landed upstream; same import name → no code changes beyond the pin.
  3. Fail fast on connect errorsconnect() swallowed driver exceptions in a fire-and-forget task and hung forever (no error shown). It now uses the same run_coroutine_threadsafe(...).result() bridge as the rest of the class, so failures raise with the driver's actionable message, and the loop thread is cleaned up on failure. Adds 2 unit tests.

Validation (real hardware)

Latest firmware — Go2 updated to current firmware:

  • main, no key: silent infinite hang (reproduced — motivates change 3).
  • This branch, no key: fails in seconds with AesKeyRequiredError: This robot speaks data2=3 … Pass aes_128_key=….
  • This branch, key set (fetched via unitree-fetch-aes-key --email <you> --sn <serial>): ✅ connects, camera/map/teleop work — exercises the forwarding end-to-end.

No-regression — older firmware, no key:

  • Go2: ✅ connects identically to main (also re-verified on 2.1.2, clean con_notify signaling).
  • Older G1: ⏳ testing.

Pending: new-firmware G1 validation (will try to upgrade firmware of g1 here in the office, meantime @natcl offered, cc @natcl).

Refs #2463

Contributor License Agreement

  • I have read and approved the CLA.

Unitree G1 firmware >=1.5.1 (and Go2 >=1.1.15) use a data2=3 WebRTC
handshake requiring a per-device AES-128 key; without it the driver
fails with "RSA key format is not supported". Thread an optional
aes_128_key from the Go2/G1 connection configs (falling back to the
UNITREE_AES_128_KEY env var) down to UnitreeWebRTCConnection, forwarding
it to the driver only when set so robots that don't need a key are
unaffected.

Re-applies the source changes from #2117 onto current main.

Refs #2463
The unitree-webrtc-connect-leshy fork caps at 2.0.7, which lacks the
data2=3 WebRTC handshake and aes_128_key support required by the latest
Go2/G1 firmware. Upstream unitree-webrtc-connect 2.1.2 has both, and the
leshy fork's lidar-parser changes have since landed upstream (per #2117
discussion). Same import name, so no code changes - only the pin + lock.

Validated: Go2 (AP mode) connects with camera/map/teleop on 2.1.2, no
regression; uses the new con_notify LAN signaling path cleanly.

Refs #2463
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
dimos/robot/unitree/g1/connection.py 50.00% 1 Missing ⚠️
dimos/robot/unitree/go2/connection.py 75.00% 1 Missing ⚠️
dimos/robot/unitree/go2/fleet_connection.py 0.00% 1 Missing ⚠️
Flag Coverage Δ
OS-ubuntu-24.04-arm 63.93% <96.42%> (+0.08%) ⬆️
OS-ubuntu-latest 64.78% <96.42%> (+0.08%) ⬆️
Py-3.10 64.77% <96.42%> (+0.07%) ⬆️
Py-3.11 64.78% <96.42%> (+0.08%) ⬆️
Py-3.12 64.77% <96.42%> (+0.08%) ⬆️
Py-3.13 64.77% <96.42%> (+0.08%) ⬆️
Py-3.14 64.78% <96.42%> (+0.07%) ⬆️
Py-3.14t 64.77% <96.42%> (+0.08%) ⬆️
SelfHosted-Large 30.36% <38.27%> (+<0.01%) ⬆️
SelfHosted-Linux 38.28% <38.27%> (+<0.01%) ⬆️
SelfHosted-macOS 37.00% <38.27%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
dimos/core/global_config.py 81.15% <100.00%> (+0.27%) ⬆️
dimos/robot/unitree/connection.py 46.97% <100.00%> (+12.29%) ⬆️
dimos/robot/unitree/go2/test_connection.py 100.00% <100.00%> (ø)
dimos/robot/unitree/test_connection.py 100.00% <100.00%> (ø)
dimos/robot/unitree/g1/connection.py 58.46% <50.00%> (+0.64%) ⬆️
dimos/robot/unitree/go2/connection.py 56.43% <75.00%> (+1.70%) ⬆️
dimos/robot/unitree/go2/fleet_connection.py 34.44% <0.00%> (ø)

... and 4 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.

KrishnaH96 and others added 3 commits June 12, 2026 13:33
connect() ran the driver handshake in a fire-and-forget task and blocked
on an event only set on success, so failures (e.g. AesKeyRequiredError on
firmware requiring the per-device key) were swallowed and the deploy hung
with no error. Use the same run_coroutine_threadsafe(...).result() bridge
as the rest of the class, which propagates the exception to the caller,
and stop the loop thread on failure. Removes the unused connected_event
and the keepalive task.

Verified on a Go2 on latest firmware: without a key the run now fails in
seconds showing the driver's actionable error; with UNITREE_AES_128_KEY
set it connects as before.

Refs #2463
Pure-Python unit tests for the aes_128_key kwarg / UNITREE_AES_128_KEY
env-var precedence in UnitreeWebRTCConnection, and for make_connection
forwarding the kwarg in the webrtc branch. Ported from #2117.

Refs #2463
@KrishnaH96 KrishnaH96 marked this pull request as ready for review June 13, 2026 02:27
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes a firmware compatibility gap for newer Unitree robots (G1 ≥1.5.1, Go2 ≥1.1.15) that require an AES-128 key for the data2=3 WebRTC handshake, while simultaneously fixing a silent hang in the connection error path.

  • AES key forwarding: Threads an optional aes_128_key (or UNITREE_AES_128_KEY env var via GlobalConfig) from Go2/G1 config classes all the way down to UnitreeWebRTCConnection, passed to LegionConnection only when set so older robots see an identical call.
  • Library bump: Replaces the fork unitree-webrtc-connect-leshy (capped at 2.0.7) with upstream unitree-webrtc-connect==2.1.2, which includes the data2=3 handshake and AES support; the fork's lidar-parser changes are confirmed upstream, so the import name is unchanged.
  • Fast-fail on connect errors: Replaces a fire-and-forget task that hung silently on driver exceptions with a run_coroutine_threadsafe(...).result() call that propagates the driver's error message immediately; cleans up the loop thread on failure; backed by two new unit tests.

Confidence Score: 5/5

Safe to merge. The AES key forwarding is additive and gated behind a falsy check, so robots that don't need a key receive a byte-identical call to before. The connect() rewrite correctly propagates driver exceptions and cleans up the background thread on failure, replacing a silent infinite hang.

All three changes are well-scoped: the key-forwarding chain is consistent across Go2, G1, and fleet paths; the library swap preserves the import name so no downstream code changes were required; and the connect() error-propagation fix is backed by unit tests that exercise both the happy path and the failure path. No correctness defects were identified in the new code paths.

No files require special attention. The fleet_connection.py comment honestly documents the per-device key limitation for heterogeneous fleets as a known future improvement.

Important Files Changed

Filename Overview
dimos/robot/unitree/connection.py Core change: adds aes_128_key param, restructures connect() to use run_coroutine_threadsafe(...).result() for synchronous error propagation. Background loop correctly stays alive via run_forever() after the setup coroutine completes. Failure path stops and joins the loop thread before re-raising. Dead code (self.task, self.connected_event, self.connection_ready) removed cleanly.
dimos/robot/unitree/go2/connection.py Adds aes_128_key field to ConnectionConfig (defaults from GlobalConfig.unitree_aes_128_key) and threads it through make_connectionUnitreeWebRTCConnection. All non-WebRTC branches are unaffected.
dimos/robot/unitree/go2/fleet_connection.py Forwards aes_128_key to follower robots in start(). The heterogeneous-fleet limitation (one key for all robots) is clearly documented in a comment as a known future improvement, so the behaviour is intentional.
dimos/robot/unitree/test_connection.py New unit tests covering: connect failure propagation, successful setup, key omission, key forwarding, empty-string key behaviour, and GlobalConfig env-var resolution. Tests mock at the right boundary (LegionConnection) and properly shut down the background loop on success.
dimos/robot/unitree/go2/test_connection.py New unit tests pinning the Go2 routing layer: key forwarding through make_connection, ConnectionConfig default from GlobalConfig, and explicit override. Stubs UnitreeWebRTCConnection at the module level to avoid network calls.
dimos/core/global_config.py Adds `unitree_aes_128_key: str
pyproject.toml Replaces unitree-webrtc-connect-leshy>=2.0.7 with unitree-webrtc-connect>=2.1.2 in all four dependency groups (unitree, unitree-dds, tests, tests-self-hosted). The import name is unchanged so no code changes are needed elsewhere.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller (main thread)
    participant Cfg as Config (G1Config or ConnectionConfig)
    participant GC as GlobalConfig
    participant Conn as UnitreeWebRTCConnection
    participant BG as Background asyncio thread
    participant Driver as LegionConnection 2.1.2

    Caller->>Cfg: construct config
    Cfg->>GC: read unitree_aes_128_key via default_factory
    GC-->>Cfg: key string or None
    Caller->>Conn: __init__(ip, aes_128_key)
    Conn->>Driver: "LegionConnection(LocalSTA, ip, aes_128_key=key if key)"
    Conn->>BG: start thread running run_forever()
    Conn->>BG: run_coroutine_threadsafe(async_connect()).result()
    BG->>Driver: await conn.connect()
    alt connect succeeds
        Driver-->>BG: ok
        BG->>Driver: disableTrafficSaving + set_decoder + publish_request_new
        BG-->>Conn: Future resolved
        Conn-->>Caller: returns (loop stays running for future commands)
    else connect fails
        Driver-->>BG: raises exception
        BG-->>Conn: Future resolved with exception
        Conn->>BG: call_soon_threadsafe(loop.stop) then thread.join()
        Conn-->>Caller: re-raises exception
    end
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into krishna/issue-2..." | Re-trigger Greptile

Comment thread dimos/robot/unitree/connection.py Outdated
Comment thread dimos/robot/unitree/connection.py Outdated
Comment thread dimos/robot/unitree/connection.py
Comment thread dimos/robot/unitree/connection.py Outdated
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 13, 2026
Comment thread dimos/robot/unitree/connection.py Outdated
# Per-device AES-128 key required by G1 firmware >= 1.5.1 (data2=3 WebRTC handshake).
# Fetch with: unitree-fetch-aes-key --email YOU --sn <serial>
if not aes_128_key:
aes_128_key = os.environ.get("UNITREE_AES_128_KEY")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a config system that parses env vars already, shouldn't touch env within modules, just use config

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Moved it to the config system in 392ebb9. Added unitree_aes_128_key to GlobalConfig (reads UNITREE_AES_128_KEY) and the Go2/G1 connection configs default aes_128_key from it via Field(default_factory=lambda m: m["g"].unitree_aes_128_key), same pattern as iprobot_ip. The module no longer touches os.environ.

Per review: dimos already parses env vars through GlobalConfig, so the
connection shouldn't reach into os.environ directly. Add a
unitree_aes_128_key field to GlobalConfig (reads UNITREE_AES_128_KEY) and
default the Go2/G1 connection configs' aes_128_key from it, mirroring how
ip defaults from robot_ip. UnitreeWebRTCConnection now only forwards the
kwarg it is given. Tests retargeted from the leaf env-read to the config
layer.

Refs #2463
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Jun 15, 2026
…ption

Address review on the connect() fast-fail: self.task is no longer assigned
to a live coroutine, so the field and the unreachable if self.task: cancel
guards in stop()/disconnect() are removed. The connect cleanup now catches
Exception rather than BaseException; the loop thread is daemonized, so a
KeyboardInterrupt during connect propagates without needing our teardown.

Refs #2463
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 15, 2026
@github-actions github-actions Bot added ready-to-merge Required CI checks have passed on this PR and removed ready-to-merge Required CI checks have passed on this PR labels Jun 15, 2026
@KrishnaH96 KrishnaH96 requested a review from ruthwikdasyam June 15, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants