Skip to content

feat(go2): command the robot with calibrated velocity (SPORT Move) ov…#2567

Open
mustafab0 wants to merge 2 commits into
mainfrom
mustafa/go2-velocity-move
Open

feat(go2): command the robot with calibrated velocity (SPORT Move) ov…#2567
mustafab0 wants to merge 2 commits into
mainfrom
mustafa/go2-velocity-move

Conversation

@mustafab0

Copy link
Copy Markdown
Contributor

Problem

UnitreeWebRTCConnection.move() sent the body twist as raw normalized joystick stick deflections (lx/ly/rx) on WIRELESS_CONTROLLER, so the commanded wz/vx were never calibrated velocities

Solution

move() now sends SPORT Move (api_id 1008) with real m/s & rad/s (x forward, y left, z yaw CCW),

Contributor License Agreement

  • [ x] I have read and approved the CLA.

…er WebRTC

UnitreeWebRTCConnection.move() sent the body twist as raw normalized
joystick stick deflections (lx/ly/rx) on WIRELESS_CONTROLLER, so the
commanded wz/vx were never calibrated velocities — the firmware's
nonlinear, speed-coupled gait mixer was the de-facto plant.

move() now sends SPORT Move (api_id 1008) with real m/s & rad/s
(x forward, y left, z yaw CCW), fire-and-forget at tick rate, via a
shared _publish_move() helper that stop() also uses for a zero-velocity
halt. Gait stays the caller's responsibility (GO2Connection already runs
standup()/balance_stand() at start-up; Move works from BalanceStand).

On hardware this removed a ~2.2x yaw over-turn: 0.8 rad/s commanded went
from a 3.5s circle to ~10s (~224% -> ~78% of commanded yaw) — a clean
linear gain characterization can now fit. Re-characterize K/tau/L on this
command path.
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Switches the Go2 movement command path from raw WIRELESS_CONTROLLER joystick deflections (lx/ly/rx) to the calibrated SPORT Move API (api_id 1008), so move() now accepts and sends real m/s and rad/s values rather than normalised stick positions.

  • Adds _publish_move(vx, vy, vyaw) which assembles the SPORT Move payload (header + JSON-stringified parameter, monotonic _move_seq id) and publishes it to rt/api/sport/request with msg_type=REQUEST.
  • Updates move() to forward twist.linear.x/y and twist.angular.z directly to _publish_move, removing the old axis remapping (lx=-y, ly=x, rx=-yaw).
  • The zero-velocity halt on disconnect now uses the same _publish_move(0, 0, 0) path instead of a separate WIRELESS_CONTROLLER publish.

Confidence Score: 4/5

The core change is well-scoped and the payload format matches the Unitree SPORT Move contract; the main thing to keep in mind is that the client-side auto-stop timer no longer sends an explicit zero-velocity command, relying entirely on the firmware watchdog.

The payload construction (JSON-stringified parameter, monotonic id, correct topic and msg_type) looks correct. One stale docstring and the implicit firmware-watchdog dependency for auto-stop are worth addressing but don't represent a functional regression in the command path itself.

dimos/robot/unitree/connection.py — specifically stop_movement() and the balance_stand() docstring.

Important Files Changed

Filename Overview
dimos/robot/unitree/connection.py Replaces raw WIRELESS_CONTROLLER joystick deflections with calibrated SPORT Move (api_id 1008) commands; introduces _publish_move helper and a monotonic sequence counter. The payload format and topic look correct; minor stale docstring and an implicit stop-on-command-loss assumption for the auto-stop timer.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant UnitreeWebRTCConnection
    participant EventLoop
    participant Firmware as Go2 Firmware

    Caller->>UnitreeWebRTCConnection: "move(twist, duration=0)"
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: extract x, y, yaw from twist
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: reset cmd_vel_timeout timer (0.2s)
    UnitreeWebRTCConnection->>EventLoop: run_coroutine_threadsafe(async_move)
    EventLoop->>UnitreeWebRTCConnection: _publish_move(x, y, yaw)
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: "_move_seq += 1"
    UnitreeWebRTCConnection->>Firmware: "SPORT_MOD publish_without_callback api_id=1008, parameter={x,y,z}"
    UnitreeWebRTCConnection-->>Caller: True

    Note over UnitreeWebRTCConnection,Firmware: After 0.2s of no new move() calls...
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: stop_movement() cancels timer only
    Note over Firmware: Robot stops via firmware watchdog
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant UnitreeWebRTCConnection
    participant EventLoop
    participant Firmware as Go2 Firmware

    Caller->>UnitreeWebRTCConnection: "move(twist, duration=0)"
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: extract x, y, yaw from twist
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: reset cmd_vel_timeout timer (0.2s)
    UnitreeWebRTCConnection->>EventLoop: run_coroutine_threadsafe(async_move)
    EventLoop->>UnitreeWebRTCConnection: _publish_move(x, y, yaw)
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: "_move_seq += 1"
    UnitreeWebRTCConnection->>Firmware: "SPORT_MOD publish_without_callback api_id=1008, parameter={x,y,z}"
    UnitreeWebRTCConnection-->>Caller: True

    Note over UnitreeWebRTCConnection,Firmware: After 0.2s of no new move() calls...
    UnitreeWebRTCConnection->>UnitreeWebRTCConnection: stop_movement() cancels timer only
    Note over Firmware: Robot stops via firmware watchdog
Loading

Comments Outside Diff (2)

  1. dimos/robot/unitree/connection.py, line 320-321 (link)

    P2 The balance_stand() docstring still references WIRELESS_CONTROLLER, but the codebase no longer uses that topic for movement — it now sends SPORT Move commands. A developer reading this comment after calling balance_stand() before issuing move() commands will be misled about how the robot is controlled.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. dimos/robot/unitree/connection.py, line 431-435 (link)

    P2 stop_movement() doesn't send a zero-velocity command

    With the old WIRELESS_CONTROLLER transport the robot stopped naturally when the stream of joystick values ceased. The new SPORT Move API is fire-and-forget: if the firmware persists the last commanded velocity (or if its own watchdog timeout is longer than the 0.2 s client-side timer), calling stop_movement() only cancels the Python timer but leaves the robot at whatever velocity it last received. Sending self._publish_move(0.0, 0.0, 0.0) here (or scheduling it on the event loop) would make the client-side stop deterministic and not reliant on firmware watchdog behaviour alone.

Reviews (1): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
dimos/robot/unitree/connection.py 37.50% 5 Missing ⚠️
@@            Coverage Diff             @@
##             main    #2567      +/-   ##
==========================================
+ Coverage   69.59%   69.61%   +0.01%     
==========================================
  Files         878      878              
  Lines       79311    79317       +6     
  Branches     7126     7126              
==========================================
+ Hits        55200    55215      +15     
+ Misses      22310    22297      -13     
- Partials     1801     1805       +4     
Flag Coverage Δ
OS-ubuntu-24.04-arm 62.99% <37.50%> (-0.01%) ⬇️
OS-ubuntu-latest 65.82% <37.50%> (+<0.01%) ⬆️
Py-3.10 65.82% <37.50%> (-0.01%) ⬇️
Py-3.11 65.81% <37.50%> (+<0.01%) ⬆️
Py-3.12 65.82% <37.50%> (+<0.01%) ⬆️
Py-3.13 65.81% <37.50%> (+<0.01%) ⬆️
Py-3.14 65.83% <37.50%> (+<0.01%) ⬆️
Py-3.14t 65.81% <37.50%> (+<0.01%) ⬆️
SelfHosted-Linux 37.81% <25.00%> (-0.01%) ⬇️
SelfHosted-macOS 36.60% <25.00%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
dimos/robot/unitree/connection.py 50.45% <37.50%> (+3.72%) ⬆️

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

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 23, 2026
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.

1 participant