Skip to content

fix(unitree): tell users how to fetch the Go2/G1 AES key when the handshake needs it - #4067

Open
spomichter wants to merge 1 commit into
mainfrom
fix/unitree-aes-key-error
Open

fix(unitree): tell users how to fetch the Go2/G1 AES key when the handshake needs it#4067
spomichter wants to merge 1 commit into
mainfrom
fix/unitree-aes-key-error

Conversation

@spomichter

Copy link
Copy Markdown
Contributor

Problem

On Go2 firmware >= 1.1.15 (G1 >= 1.5.1) dimos run unitree-go2 fails deep inside unitree-webrtc-connect with:

AesKeyRequiredError: ... Pass `aes_128_key=...` to UnitreeWebRTCConnection.
Fetch it via `examples/fetch_aes_key.py` or `UnitreeCloud.list_devices()`.

That example script lives in the upstream package repo, not in dimos, and the message names the driver class rather than the dimos flag. Users have no path from the error to a working command.

Fix

UnitreeWebRTCConnection.connect translates AesKeyRequiredError into a RuntimeError (chained, original kept as __cause__) that gives the dimos workflow:

Robot at 192.168.12.1 runs firmware that needs its per-device AES-128 key (Go2 >= 1.1.15, G1 >= 1.5.1).
Fetch the key from the Unitree account the robot is bound to (installed with dimos[unitree]):
    unitree-fetch-aes-key --email <unitree account email> --sn <robot serial>
Then pass it:
    dimos --unitree-aes-128-key <32 hex chars> run unitree-go2
or set unitree_aes_128_key in GlobalConfig.

unitree-fetch-aes-key is the console script unitree-webrtc-connect 2.1.2 installs alongside the driver. Quickstart gets the same two commands under Real robot.

Test covers the translation and the chained cause. The two pre-existing mypy errors in test_connection.py (kwargs splat at the velocity_api call) are untouched.

…dshake needs it

unitree-webrtc-connect raises AesKeyRequiredError pointing at an
examples/fetch_aes_key.py that ships with that package's repo, not with
dimos. Translate it into the dimos workflow: the unitree-fetch-aes-key
console script the extra installs, and the --unitree-aes-128-key flag.
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

Not safe to merge until the Go2 key-fetch command explicitly selects the Go2 device family.

Findings

  1. P1 Select the Go2 family

Summary

  • Summary

  • The real-robot quickstart now documents how to retrieve and supply the Unitree AES key. The Go2 command needs to select the Go2 device family explicitly; otherwise it requests the key using the G1 family header. This must be corrected before merging so the documented Go2 setup path can retrieve the intended key.

Reviews (1) · Last reviewed commit: "fix(unitree): tell users how to fetch th..."

Comment thread docs/quickstart.md
Go2 firmware 1.1.15 and later (G1 1.5.1 and later) encrypts the LAN handshake with a per-device key. Fetch it once from the Unitree account the robot is bound to and pass it on the command line:

```bash
unitree-fetch-aes-key --email <unitree account email> --sn <robot serial>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Select the Go2 family

This Go2 setup command omits --device-type Go2. The key-fetch utility defaults to the G1 device family, so this command sends the device-list request with AppName: G1 rather than AppName: Go2. If Unitree scopes bound devices by that header, the documented workflow queries the G1 family and cannot retrieve the Go2 AES key required by the next command. Add --device-type Go2 to this command, or provide separate Go2 and G1 commands. This must be corrected before merging.

Artifacts

Evidence from the check

  • Shows the exact authored command script that invokes the installed CLI handler and captures its cloud-request construction, ending with the runnable proof implementation.

Command output from the check

  • Captured execution of the documented argument shape shows CLI status 0 and a controlled HTTP 200 OK request with `AppName: G1`, proving omission selects G1.

Command output from the check

  • Captured execution with `--device-type Go2` shows CLI status 0 and a controlled HTTP 200 OK request with `AppName: Go2`, proving the required namespace selection.

View artifacts

T-Rex Ran code and verified through T-Rex

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
5277 2 5275 47
View the top 2 failed test(s) by shortest run time
dimos.imitation.test_datacollection_e2e::test_collection_to_lerobot_roundtrip
Stack Traces | 0.001s run time
tmp_path_factory = TempPathFactory(_given_basetemp=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _trace=<plug...=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _retention_count=3, _retention_policy='all')

    @pytest.fixture(scope="module")
    def recorded_session(
        tmp_path_factory: pytest.TempPathFactory,
    ) -> tuple[Path, dict[float, np.ndarray[Any, Any]]]:
        db_path = tmp_path_factory.mktemp("recorded-session") / "recording.db"
        _record_session(db_path)
        with SqliteStore(path=str(db_path), must_exist=True) as store:
            assert store.stream("color_image").count() == 9
            assert store.stream("coordinator_joint_state").count() == 9
>           assert store.stream("status").count() == 7
E           AssertionError: assert 6 == 7
E            +  where 6 = count()
E            +    where count = <dimos.memory.stream.Stream object at 0x7f178a983080>.count
E            +      where <dimos.memory.stream.Stream object at 0x7f178a983080> = stream('status')
E            +        where stream = <dimos.memory.store.sqlite.SqliteStore object at 0x7f178ac66150>.stream

db_path    = PosixPath('.../pytest-of-runner/pytest-0/popen-gw3/recorded-session0/recording.db')
store      = <dimos.memory.store.sqlite.SqliteStore object at 0x7f178ac66150>
tmp_path_factory = TempPathFactory(_given_basetemp=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _trace=<plug...=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _retention_count=3, _retention_policy='all')

dimos/imitation/test_datacollection_e2e.py:230: AssertionError
dimos.imitation.test_datacollection_e2e::test_collection_to_hdf5_roundtrip
Stack Traces | 0.195s run time
tmp_path_factory = TempPathFactory(_given_basetemp=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _trace=<plug...=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _retention_count=3, _retention_policy='all')

    @pytest.fixture(scope="module")
    def recorded_session(
        tmp_path_factory: pytest.TempPathFactory,
    ) -> tuple[Path, dict[float, np.ndarray[Any, Any]]]:
        db_path = tmp_path_factory.mktemp("recorded-session") / "recording.db"
        _record_session(db_path)
        with SqliteStore(path=str(db_path), must_exist=True) as store:
            assert store.stream("color_image").count() == 9
            assert store.stream("coordinator_joint_state").count() == 9
>           assert store.stream("status").count() == 7
E           AssertionError: assert 6 == 7
E            +  where 6 = count()
E            +    where count = <dimos.memory.stream.Stream object at 0x7f178a983080>.count
E            +      where <dimos.memory.stream.Stream object at 0x7f178a983080> = stream('status')
E            +        where stream = <dimos.memory.store.sqlite.SqliteStore object at 0x7f178ac66150>.stream

db_path    = PosixPath('.../pytest-of-runner/pytest-0/popen-gw3/recorded-session0/recording.db')
store      = <dimos.memory.store.sqlite.SqliteStore object at 0x7f178ac66150>
tmp_path_factory = TempPathFactory(_given_basetemp=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _trace=<plug...=PosixPath('.../pytest-of-runner/pytest-0/popen-gw3'), _retention_count=3, _retention_policy='all')

dimos/imitation/test_datacollection_e2e.py:230: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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.

1 participant