Skip to content

Booster K1 intergation#1434

Open
Kaweees wants to merge 4 commits intodevfrom
miguel/booster
Open

Booster K1 intergation#1434
Kaweees wants to merge 4 commits intodevfrom
miguel/booster

Conversation

@Kaweees
Copy link
Member

@Kaweees Kaweees commented Mar 6, 2026

Problem

This merge request adds support for the Booster K1 robot, along with accompanying blueprints (booster-k1-basic, booster-k1-agentic).

Note

This relies on booster-rpc (GitHub source), a library I wrote that provides high-level control of the Booster K1 by interfacing with the same RPC and WebSocket protocols used by the Booster App. Through booster-rpc, Dimensional will be the first and only way to control the Booster wirelessly with no installation or SSH required. Additionally, the App and Dimensional can control the robot concurrently.
Closes DIM-XXX

Solution

Breaking Changes

None

How to Test

ROBOT_IP can be found on the Booster App
Untitled drawing

ROBOT_IP=10.0.0.15 uv run dimos --viewer-backend rerun-web run booster-k1-agentic

Contributor License Agreement

  • I have read and approved the CLA.

@Kaweees Kaweees self-assigned this Mar 6, 2026
@Kaweees Kaweees marked this pull request as draft March 6, 2026 03:51
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR adds initial integration support for the Booster K1 humanoid robot, introducing a K1Connection module, a BoosterK1SkillContainer, and three layered blueprints (booster-k1-basic, booster-k1-spatial, booster-k1-agentic) that follow the same composition pattern as the existing Unitree integrations.

Key findings:

  • The duration parameter on K1Connection.move() is silently ignored — the method accepts it, the skill docstring documents it, and BoosterK1SkillContainer faithfully passes it through, but no timed hold or stop command is ever issued. Agents instructing the robot to "move for N seconds" will see it move indefinitely or not at all.
  • dimos/robot/booster/__init__.py and dimos/robot/booster/k1/__init__.py are absent; every other comparable package level in this repo has an explicit __init__.py, and their absence can cause import failures under some tooling configurations.
  • _on_frame raises KeyboardInterrupt as a stop signal rather than simply returning, which is an unusual pattern that could interact unexpectedly with real user interrupts.
  • Two logger.warning calls embed values in an f-string and then redundantly pass the same values as positional args, which the logging framework silently ignores.
  • Placeholder camera intrinsics are marked with a TODO comment; these will affect any spatial-perception pipelines that depend on accurate calibration.

Confidence Score: 2/5

  • Not ready to merge — the duration contract is broken and two __init__.py files are missing.
  • The overall structure and blueprint layering is solid and mirrors existing Unitree integrations well. However, the duration parameter being silently ignored in K1Connection.move is a functional bug that will cause incorrect agent behaviour (robot never stops after a timed move command). The missing __init__.py files are a packaging issue that can cause import failures in non-namespace-package tooling. These two issues together prevent a confident merge.
  • dimos/robot/booster/k1/connection.py requires the most attention due to the duration bug and KeyboardInterrupt flow-control pattern.

Important Files Changed

Filename Overview
dimos/robot/booster/k1/connection.py Core K1 connection module; duration parameter on move() is silently ignored, raise KeyboardInterrupt used as flow control in _on_frame, and logger.warning f-strings carry redundant positional args.
dimos/robot/booster/k1/skill_container.py Skill container wiring looks correct; passes duration to K1Connection.move but that callee ignores it, making timed-move skills non-functional.
dimos/robot/booster/k1/blueprints/basic/booster_k1_basic.py Module-level match block bakes viewer backend at import time; otherwise straightforward blueprint composition.
dimos/robot/booster/k1/blueprints/agentic/booster_k1_agentic.py Clean blueprint composition combining spatial, agent, and shared agentic skills; no issues found.
dimos/robot/booster/k1/blueprints/agentic/_common_agentic.py Shared agentic skill assembly; follows the same pattern as unitree equivalents.
dimos/robot/booster/k1/blueprints/smart/booster_k1_spatial.py Spatial blueprint layering spatial_memory and utilization on top of booster_k1; straightforward.
dimos/robot/booster/k1/blueprints/smart/booster_k1.py Intermediate blueprint wrapping booster_k1_basic; not exposed in all_blueprints.py which appears intentional.
dimos/robot/all_blueprints.py Registers three new Booster K1 blueprints; file is auto-generated and has been correctly updated.
pyproject.toml Adds booster optional dependency group with booster-rpc>=0.0.6; straightforward addition following existing patterns.

Sequence Diagram

sequenceDiagram
    participant Agent
    participant BoosterK1SkillContainer
    participant K1Connection
    participant BoosterRPC as booster-rpc SDK
    participant Robot as Booster K1 Robot

    Agent->>BoosterK1SkillContainer: move(x, y, yaw, duration)
    BoosterK1SkillContainer->>K1Connection: RPC move(twist, duration=duration)
    K1Connection->>BoosterRPC: _call(ROBOT_MOVE, RobotMoveRequest)
    BoosterRPC->>Robot: UDP/RPC command
    Note over K1Connection: duration is never used ⚠️
    K1Connection-->>BoosterK1SkillContainer: True
    BoosterK1SkillContainer-->>Agent: "Started moving..."

    Agent->>BoosterK1SkillContainer: standup()
    BoosterK1SkillContainer->>K1Connection: RPC standup()
    K1Connection->>BoosterRPC: _call(GET_ROBOT_STATUS)
    BoosterRPC-->>K1Connection: status
    alt mode == DAMPING
        K1Connection->>BoosterRPC: _call(CHANGE_MODE, PREPARE)
        Note over K1Connection: sleep(3s)
    end
    K1Connection->>BoosterRPC: _call(CHANGE_MODE, WALKING)
    Note over K1Connection: sleep(3s)
    K1Connection-->>BoosterK1SkillContainer: True

    loop Background thread
        K1Connection->>BoosterRPC: WebSocket connect(ws://ip:port)
        BoosterRPC-->>K1Connection: JPEG frame bytes
        K1Connection->>K1Connection: _on_frame(jpeg_bytes)
        K1Connection->>K1Connection: color_image.publish(image)
    end
Loading

Last reviewed commit: 106528b

@Kaweees Kaweees marked this pull request as ready for review March 14, 2026 02:07
@Kaweees Kaweees changed the title [DRAFT] Booster K1 intergation Booster K1 intergation Mar 14, 2026
Comment on lines +187 to +197
def move(self, twist: Twist, duration: float = 0.0) -> bool:
"""Send movement command to robot."""
if not self._conn:
return False
try:
req = RobotMoveRequest(vx=twist.linear.x, vy=twist.linear.y, vyaw=twist.angular.z)
self._conn._call(RpcApiId.ROBOT_MOVE, bytes(req))
return True
except Exception as e:
logger.debug("Move command failed: %s", e)
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

duration parameter is accepted but silently ignored

The move method signature accepts a duration parameter and its docstring promises "How long to move (seconds)", and BoosterK1SkillContainer.move explicitly passes duration=duration through the RPC. However, the body never uses duration — it sends the motion command once and returns immediately.

This means agents that instruct the robot to e.g. "move forward for 2 seconds" will send one velocity packet and stop with no timed hold, causing unintended behaviour. Compare with G1Connection.move, which forwards duration to self.connection.move(twist, duration).

A minimal fix is to add a timed hold (if duration > 0) followed by a stop command:

@rpc
def move(self, twist: Twist, duration: float = 0.0) -> bool:
    """Send movement command to robot."""
    if not self._conn:
        return False
    try:
        req = RobotMoveRequest(vx=twist.linear.x, vy=twist.linear.y, vyaw=twist.angular.z)
        self._conn._call(RpcApiId.ROBOT_MOVE, bytes(req))
        if duration > 0:
            time.sleep(duration)
            stop = RobotMoveRequest(vx=0.0, vy=0.0, vyaw=0.0)
            self._conn._call(RpcApiId.ROBOT_MOVE, bytes(stop))
        return True
    except Exception as e:
        logger.debug("Move command failed: %s", e)
        return False

Comment on lines +171 to +173
def _on_frame(self, jpeg_bytes: bytes) -> None:
if not self._running:
raise KeyboardInterrupt
Copy link
Contributor

Choose a reason for hiding this comment

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

raise KeyboardInterrupt used as flow control

KeyboardInterrupt is a user-interrupt signal (Ctrl+C), not an internal stop signal. Raising it from _on_frame works here only because _stream_video has an explicit except KeyboardInterrupt: break, but it has two drawbacks:

  1. Any code between self._on_frame(frame) and that except block that also catches BaseException (or bare except:) could suppress it unexpectedly.
  2. It obscures intent — a reader seeing raise KeyboardInterrupt in a normal callback will be confused.

A cleaner pattern is to simply return early (or raise a small custom exception), since the outer while self._running loop already handles the stop case:

def _on_frame(self, jpeg_bytes: bytes) -> None:
    if not self._running:
        return
    arr = cv2.imdecode(np.frombuffer(jpeg_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)
    ...

Comment on lines +72 to +88
match global_config.viewer_backend:
case "rerun":
from dimos.visualization.rerun.bridge import rerun_bridge

with_vis = autoconnect(_transports_base, rerun_bridge(**rerun_config))
case "rerun-web":
from dimos.visualization.rerun.bridge import rerun_bridge

with_vis = autoconnect(_transports_base, rerun_bridge(viewer_mode="web", **rerun_config))
case _:
with_vis = autoconnect(_transports_base)

booster_k1_basic = autoconnect(
with_vis,
k1_connection(),
websocket_vis(),
).global_config(n_dask_workers=4, robot_model="booster_k1")
Copy link
Contributor

Choose a reason for hiding this comment

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

Module-level match block executes at import time with no isolation

The match global_config.viewer_backend: block (and the conditional from ... import rerun_bridge calls) run at module import time. This means the blueprint value is baked in at the moment the module is first imported, regardless of when or how booster_k1_basic is actually used.

If global_config.viewer_backend is set after this module is imported (e.g. lazily via another subsystem), or if the module is imported during a test with a different backend, the wrong with_vis branch will be chosen and the result is silently incorrect.

This is the same pattern used in some existing blueprints, but it is worth flagging here as it could cause subtle bugs when unit-testing or when the viewer backend is configured late in the startup sequence.

@@ -0,0 +1,255 @@
#!/usr/bin/env python3
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing __init__.py for dimos/robot/booster/ and dimos/robot/booster/k1/

The directories dimos/robot/booster/ and dimos/robot/booster/k1/ have no __init__.py file. Every other comparable level in this codebase (e.g. dimos/robot/unitree/__init__.py) has one. Without them, Python may treat these as implicit namespace packages, which works in many cases but can fail if the project uses tools or configurations that expect regular packages (e.g. certain editable-install backends, import guards, or __init__-based lazy loaders). All blueprint __init__.py files inside k1/ are present; it is only the two parent packages that are missing.

Please add empty dimos/robot/booster/__init__.py and dimos/robot/booster/k1/__init__.py files to match the project convention.

Copy link
Contributor

Choose a reason for hiding this comment

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

Skill containers are not used anymore

Copy link
Member Author

@Kaweees Kaweees Mar 14, 2026

Choose a reason for hiding this comment

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

I'll rebase against dev, this branch is old and I based it on the unitree-go2 blueprints

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.

2 participants