Skip to content

feat(go2): go2 SDK adapter + nix cyclonedds setup#1885

Open
ruthwikdasyam wants to merge 43 commits intodevfrom
ruthwik/go2sdk
Open

feat(go2): go2 SDK adapter + nix cyclonedds setup#1885
ruthwikdasyam wants to merge 43 commits intodevfrom
ruthwik/go2sdk

Conversation

@ruthwikdasyam
Copy link
Copy Markdown
Contributor

@ruthwikdasyam ruthwikdasyam commented Apr 20, 2026

Problem

Feat: SDK support for Go2 [Fully working]
Cyclone-dds installation support

Closes DIM-XXX

Solution

  • UnitreeGo2TwistAdapter - high-level Twist (vx, vy, wz) via SDK2 SportClient
  • Optional Rage Mode (rage_mode=True, ~2.5 m/s envelope) via synthesized WirelessController_ on rt/wirelesscontroller_unprocessed.
  • Nix-based cyclonedds install: nix build nixpkgs#cyclonedds + venv activate exports. No sudo, version-pinned. Apt path retained as fallback.
  • 19 hardware-free smoke tests for the low-level adapter.

Breaking Changes

None

How to Test

# Install
nix build nixpkgs#cyclonedds
# (bake CYCLONEDDS_HOME + LD_LIBRARY_PATH into .venv/bin/activate per docs)
export CYCLONEDDS_HOME=$(readlink -f ./result)
export LD_LIBRARY_PATH="\$CYCLONEDDS_HOME/lib:\${LD_LIBRARY_PATH:-}"
uv pip install -e ".[unitree-dds]"

# Real robot
export ROBOT_IP=192.168.123.161
dimos run unitree-go2-keyboard-teleop

Contributor License Agreement

  • I have read and approved the CLA.

@ruthwikdasyam ruthwikdasyam changed the title Ruthwik/go2sdk feat(go2): SDK2 Twist adapter + nix cyclonedds setup Apr 24, 2026
@ruthwikdasyam ruthwikdasyam changed the title feat(go2): SDK2 Twist adapter + nix cyclonedds setup feat(go2): go2 SDK adapter + nix cyclonedds setup Apr 24, 2026
@ruthwikdasyam ruthwikdasyam marked this pull request as ready for review April 24, 2026 23:12
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR adds a UnitreeGo2TwistAdapter that drives the Go2 quadruped over DDS via the unitree_sdk2py SDK2 SportClient, with an optional Rage Mode (joystick publisher on rt/wirelesscontroller_unprocessed) that widens the velocity envelope to ~2.5 m/s. It also introduces a nix-based CycloneDDS install path, a keyboard-teleop blueprint, and a new unitree-dds optional dependency group.

  • P1 — silent velocity drop on rage publish error: When pub.Write() raises in _rage_joystick_loop, the loop exits but leaves session.rage_active = True; all subsequent write_velocities calls stash commands into rage_cmd with no consumer, making the robot silently unresponsive.

Confidence Score: 4/5

Safe to merge after addressing the rage joystick publish-error teardown bug.

One P1 found: the rage joystick loop exits on publish errors without resetting rage_active, causing velocity commands to be silently discarded. All previously flagged issues (throttling, publisher Close, SwitchJoystick return check, subscriber leak, ChannelFactoryInitialize double-init) have been addressed in this version. The rest of the changes are well-structured.

dimos/hardware/drive_trains/unitree_go2/adapter.py — specifically the _rage_joystick_loop error return path.

Important Files Changed

Filename Overview
dimos/hardware/drive_trains/unitree_go2/adapter.py New 693-line UnitreeGo2TwistAdapter with DDS connect/disconnect, velocity commands, and Rage Mode joystick publishing; one P1: rage joystick loop exit on publish error leaves rage_active=True, silently dropping all subsequent velocity commands.
dimos/control/coordinator.py Adds **component.adapter_kwargs to adapter construction; HardwareComponent.adapter_kwargs already defaults to {} so existing components are unaffected.
dimos/robot/unitree/go2/blueprints/basic/unitree_go2_keyboard_teleop.py New keyboard teleop blueprint wiring ControlCoordinator to UnitreeGo2TwistAdapter via LCMTransport; straightforward and correctly structured.
pyproject.toml Adds unitree-dds extra pulling in unitree-sdk2py-dimos>=1.0.2 and cyclonedds>=0.10.5; also adds mypy ignore for unitree_sdk2py.*.
.github/workflows/ci.yml Excludes unitree-dds extra from CI builds (requires native DDS lib); correct.
docs/usage/transports/dds.md Adds nix-based CycloneDDS install path with correct heredoc escaping; apt path retained as fallback.

Sequence Diagram

sequenceDiagram
    participant BP as Blueprint
    participant CC as ControlCoordinator
    participant A as UnitreeGo2TwistAdapter
    participant DDS as DDS Layer
    participant Go2 as Go2 Robot

    BP->>CC: connect()
    CC->>A: connect()
    A->>DDS: ChannelFactoryInitialize(0)
    A->>DDS: ChannelSubscriber(rt/sportmodestate)
    A->>Go2: MotionSwitcherClient.Init()
    A->>Go2: SportClient.Init()
    A->>Go2: StandUp() → FreeWalk() → SpeedLevel()
    A-->>CC: True (connected)

    alt rage_mode=True
        A->>Go2: _call_sport_api(2059, enable=True)
        A->>DDS: ChannelPublisher(rt/wirelesscontroller_unprocessed)
        A->>A: spawn rage_joystick_loop thread (100 Hz)
        A->>Go2: SwitchJoystick(True)
    end

    loop Teleop loop
        BP->>CC: write_velocities([vx,vy,wz])
        CC->>A: write_velocities([vx,vy,wz])
        alt rage_active
            A->>A: stash in session.rage_cmd
            A-->>DDS: rage_joystick_loop publishes WirelessController_
            DDS-->>Go2: rt/wirelesscontroller_unprocessed
        else normal
            A->>Go2: SportClient.Move(vx,vy,wz)
        end
    end

    BP->>CC: disconnect()
    CC->>A: disconnect()
    A->>A: _stop_rage_joystick() — set event, join thread, Close publisher
    A->>Go2: StopMove() → StandDown()
    A->>DDS: state_sub.Close()
Loading

Comments Outside Diff (1)

  1. dimos/hardware/drive_trains/unitree_go2/adapter.py, line 724-728 (link)

    P1 Rage joystick loop exits without clearing rage_active, silently dropping velocity commands

    When pub.Write(msg) raises OSError or RuntimeError, the loop returns but leaves session.rage_active = True and session.rage_pub non-None. From that point on, every write_velocities call hits the if session.rage_active: branch (line 453) and stashes the command into session.rage_cmd, but no thread is consuming it. The robot stops responding to velocity commands without surfacing any error to the caller — operators will only see the earlier Rage joystick publish raised warning and then silence.

    _stop_rage_joystick should be called from within the error path to reset all rage state atomically, or at minimum session.rage_active should be cleared before returning:

            except (OSError, RuntimeError) as e:
                logger.warning(f"[Go2] Rage joystick publish raised: {e}")
                session.rage_active = False  # prevent write_velocities from silently routing here
                return

Reviews (4): Last reviewed commit: "fix: pass on exception" | Re-trigger Greptile

Comment thread dimos/hardware/drive_trains/unitree_go2/adapter.py
Comment thread dimos/hardware/drive_trains/unitree_go2/adapter.py
Comment thread dimos/hardware/drive_trains/unitree_go2/adapter.py
Co-authored-by: Mustafa Bhadsorawala <39084056+mustafab0@users.noreply.github.com>
Comment thread dimos/hardware/drive_trains/unitree_go2/adapter.py
Comment thread dimos/hardware/drive_trains/unitree_go2/adapter.py
@ruthwikdasyam ruthwikdasyam enabled auto-merge (squash) April 24, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant