Skip to content

feat(drone): add RoboMaster TT/Tello integration#1667

Open
jlelong-berkeley wants to merge 1 commit intodimensionalOS:devfrom
jlelong-berkeley:feat/drone-tello-tt-integration-rebased
Open

feat(drone): add RoboMaster TT/Tello integration#1667
jlelong-berkeley wants to merge 1 commit intodimensionalOS:devfrom
jlelong-berkeley:feat/drone-tello-tt-integration-rebased

Conversation

@jlelong-berkeley
Copy link
Copy Markdown

Problem

DimOS had no native RoboMaster TT / DJI Tello embodiment layer. Existing drone support is MAVLink/RosettaDrone-oriented, while TT uses a Wi-Fi UDP text SDK (8889 commands, 8890 state, 11111 video).
This made TT workflows (teleop, agentic commands, tracking/follow) unavailable without custom integration.

Closes DIM-XXX

Solution

Implemented a full TT/Tello integration path and documented operational setup.

  1. Added low-level TT SDK adapter:
  • dimos/robot/drone/tello_sdk.py
  • Manages UDP command/state sockets and H264 video stream decode/reconnect.
  1. Added DimOS connection module for TT:
  • dimos/robot/drone/tello_connection_module.py
  • Publishes odom/status/telemetry/video
  • Bridges command streams
  • Exposes skills for TT control:
    • takeoff, land, move, move_relative, yaw, rc, send_ext
    • follow_object, center_person_by_yaw, orbit_object, observe
  1. Added new blueprints:
  • drone-tello-tt-basic
  • drone-tello-tt-agentic
  • Registered in dimos/robot/all_blueprints.py
  1. Extended tracking for TT use cases in:
  • dimos/robot/drone/drone_tracking_module.py
  • Added optional local YOLO person detection fallback
  • Added passive detection overlay stream
  • Added detector-loop tracking mode and yaw-only centering mode
  • Added Tello-oriented follow policy (yaw_forward_constant) and yaw controller tuning
  1. Added documentation:
  • docs/usage/drone_tello_tt.md
  • docs/usage/README.md link
  • dimos/robot/drone/README.md updates
  • Included TT hardware/network guidance (Tello AP + separate internet interface for cloud APIs)

Design notes/tradeoffs:

  • TT behavior is enabled via TT blueprints; non-TT stacks are intended to keep legacy behavior unless explicitly opted in.
  • Person follow uses detector-driven control and with no distance commanding to abide by TT constraints.

Breaking Changes

None intended.

How to Test

  1. Verify blueprints are discoverable:
dimos list | rg "drone-tello-tt"
#Run TT basic:
dimos --robot-ip 192.168.10.1 run drone-tello-tt-basic

Expected:

Tello SDK connected to 192.168.10.1:8889
video stream starts and camera feed appears in viewer.

Run TT agentic:

export OPENAI_API_KEY=...
dimos --robot-ip 192.168.10.1 run drone-tello-tt-agentic --disable web-input

Then in another terminal:

dimos humancli

Try:

takeoff
relative movement commands
center on person and keep me in frame
follow person meter
land
Validate local detector fallback (no Alibaba key):

unset ALIBABA_API_KEY

Run agentic again and confirm tracking module logs fallback to local detector (no startup crash).

Sanity check non-TT drone stack still starts:

dimos --replay run drone-agentic

Optional static checks:

uv run ruff check dimos/robot/drone/
uv run mypy dimos/robot/drone/

Contributor License Agreement
I have read and approved the CLA.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR adds a complete RoboMaster TT / Tello integration to DimOS, covering the UDP SDK adapter (TelloSdkClient), a DimOS module wrapper (TelloConnectionModule), two new blueprints (drone-tello-tt-basic / drone-tello-tt-agentic), and extended tracking/follow capabilities in DroneTrackingModule. The implementation is well-scoped: all Tello-specific behavior (YOLO detection, passive overlay, yaw-only centering) is gated behind opt-in flags that default to the legacy path, so existing non-Tello stacks remain unaffected.

Key additions and concerns:

  • tello_sdk.py – Two issues found: (1) self._video_capture is not updated after stream reconnect, leaking the new cv2.VideoCapture object; (2) _command_lock is held for the full blocking recvfrom timeout, which can starve real-time rc control commands from the visual servoing loop during worst-case network conditions (up to 21 s with default retries).
  • drone_tracking_module.py – The new YOLO-backed detector loop, yaw-only centering, and yaw_forward_constant follow policy are cleanly integrated. The PR also fixes a pre-existing bug where BGR frames were passed to get_bbox_from_qwen_frame without converting to RGB first.
  • Blueprints / registration – New blueprints and module registrations are minimal and correct. Documentation is thorough, including hardware/network split guidance.

Confidence Score: 3/5

  • Two concrete bugs in the core SDK layer need to be addressed before this is safe on a live drone.
  • The overall architecture is solid and the non-regression design is well-executed. However, two issues in tello_sdk.py are worth resolving before merge: the stale self._video_capture reference after reconnect (resource leak) and the shared _command_lock blocking high-rate rc control during slow-command timeouts (up to 21 s stall in worst case on a flying drone). Fixing the capture leak is a one-liner; the lock contention is a more nuanced design trade-off but at minimum deserves acknowledgment and a plan.
  • dimos/robot/drone/tello_sdk.py — stale video capture reference and command lock contention

Important Files Changed

Filename Overview
dimos/robot/drone/tello_sdk.py New low-level Tello UDP adapter. Two issues: (1) self._video_capture is not updated after stream reconnect, leaking the new capture object; (2) _command_lock is held for the full blocking recvfrom timeout, which can starve high-rate rc control commands from the visual servoing loop.
dimos/robot/drone/tello_connection_module.py New DimOS module bridging TelloSdkClient to streams and skills. Follows the existing connection-module pattern (no super().start(), consistent with MAVLink module). Skills are well-structured with good guard clauses. The orbit_object skill correctly uses _manual_override_until to block the twist callback during maneuvering. Minor: movecmd is subscribed unconditionally while other inputs check .transport first.
dimos/robot/drone/drone_tracking_module.py Significantly extended with YOLO-based local person detection, a detector-driven servoing loop, passive overlay, yaw-only centering mode, and a new yaw_forward_constant follow policy. New features are cleanly gated behind opt-in flags (all defaulting to the legacy path). The Qwen color-space fix (BGR→RGB conversion) corrects a pre-existing bug for all callers. Overall well-structured with appropriate fallback logic.
dimos/robot/drone/blueprints/basic/drone_tello_tt_basic.py New basic Tello blueprint. Module-level viewer/IP config is evaluated at import time, consistent with existing drone_basic.py pattern. Blueprint composition and remappings look correct.
dimos/robot/drone/blueprints/agentic/drone_tello_tt_agentic.py New agentic Tello blueprint composing tracking and LLM agent on top of the basic blueprint. System prompt is detailed and provides clear behavioral guidance. Stream remappings for video and cmd_vel look correct.
dimos/robot/all_blueprints.py Two new blueprints and one new module registered in alphabetical order. No issues.

Sequence Diagram

sequenceDiagram
    participant Agent as LLM Agent
    participant TCM as TelloConnectionModule
    participant SDK as TelloSdkClient
    participant DTM as DroneTrackingModule
    participant Drone as Tello Drone (UDP)

    Agent->>TCM: takeoff()
    TCM->>SDK: connection.takeoff()
    SDK->>Drone: UDP "takeoff"
    Drone-->>SDK: "ok"
    SDK-->>TCM: True
    TCM-->>Agent: "Takeoff command sent"

    Agent->>TCM: follow_object("person")
    TCM->>TCM: _wait_until_airborne()
    TCM->>TCM: follow_object_cmd.publish(JSON)
    TCM->>DTM: follow_object_cmd stream

    DTM->>DTM: _detect_initial_bbox(frame, "person")
    Note over DTM: YOLO local fallback if no ALIBABA_API_KEY
    DTM->>DTM: _detection_servoing_loop()

    loop 20 Hz tracking loop
        DTM->>DTM: _detect_person_local(frame)
        DTM->>DTM: _compute_person_follow_command()
        DTM->>TCM: cmd_vel (Twist) via movecmd_twist
        TCM->>SDK: connection.move_twist(twist)
        SDK->>Drone: UDP "rc lr fb ud yw" (no-wait)
        DTM->>DTM: tracking_overlay.publish(frame)
    end

    Agent->>TCM: land()
    TCM->>SDK: connection.land()
    SDK->>Drone: UDP "land"
    Drone-->>SDK: "ok"
    SDK-->>TCM: True
    TCM-->>Agent: "Land command sent"
Loading

Reviews (1): Last reviewed commit: "feat(drone): add RoboMaster TT/Tello int..." | Re-trigger Greptile

Comment on lines +415 to +420
logger.warning("Tello video stalled; reopening capture")
capture.release()
capture = cv2.VideoCapture(uri, cv2.CAP_FFMPEG)
if capture.isOpened():
capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
no_frame_count = 0
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 Stale self._video_capture after stream reconnect

When the video stream stalls and is reopened, the local variable capture is reassigned to the new cv2.VideoCapture object, but self._video_capture is never updated to point to it. As a result:

  1. The disconnect() method calls self._video_capture.release() on the old, already-released capture object (the one released at line 416) — the new active capture is never properly released.
  2. The new cv2.VideoCapture leaks until the thread exits naturally.
Suggested change
logger.warning("Tello video stalled; reopening capture")
capture.release()
capture = cv2.VideoCapture(uri, cv2.CAP_FFMPEG)
if capture.isOpened():
capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
no_frame_count = 0
capture.release()
capture = cv2.VideoCapture(uri, cv2.CAP_FFMPEG)
if capture.isOpened():
capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self._video_capture = capture
no_frame_count = 0

Comment on lines +218 to +231
while attempt <= retries:
try:
with self._command_lock:
self._command_socket.sendto(command.encode("utf-8"), self._tello_addr)
data, _ = self._command_socket.recvfrom(2048)
response = data.decode("utf-8", errors="ignore").strip().lower()
return response
except TimeoutError:
attempt += 1
logger.warning(f"Tello command timeout [{command}] attempt {attempt}/{retries + 1}")
except OSError as exc:
logger.error(f"Tello command socket error [{command}]: {exc}")
break
return "error:timeout"
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 _command_lock held during blocking recvfrom starves real-time rc control

Both send_command and send_command_no_wait share the same _command_lock. send_command acquires the lock for the entire sendto + recvfrom cycle — up to command_timeout (default 7 s) per attempt, and up to 3 attempts (21 s total) when the drone is slow to ack.

send_command_no_wait (used by every rc() call in the visual servoing and orbit loops) must wait to acquire that same lock. So if a blocking SDK command (yaw, move_relative, land, takeoff) times out and retries while the tracking loop is pushing 20 Hz rc updates, the control channel is frozen for the full retry window.

A common fix is to split the socket layer into a fast path (fire-and-forget rc commands, no lock contention) and a slow path (blocking commands with their own lock/socket or a command queue). Alternatively, use a non-blocking send in send_command that releases the lock between sendto and recvfrom.


# Inputs
movecmd: In[Vector3]
movecmd_twist: In[Twist]
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.

if you name this cmd_vel keyboard controls in rerun will work


# Outputs
odom: Out[PoseStamped]
status: Out[Any]
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.

should add types for this stuff

follow_object_cmd: Out[Any]

# Parameters
tello_ip: str
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.

we have standard ways for configuring modules (it auto-allows you to use CLI, config files)

https://github.com/dimensionalOS/dimos/blob/dev/docs/usage/configuration.md

@@ -0,0 +1,557 @@
#!/usr/bin/env python3
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.

would be nice to move this to dimos/robot/drone/tello/ dir

@@ -0,0 +1,136 @@
# RoboMaster TT / Tello Integration
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.

this is very tello specific doc so can move it to dimos/robot/drone/tello/readme.md

odom: Out[PoseStamped]
status: Out[Any]
telemetry: Out[Any]
video: Out[Image]
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.

name it color_image pls (makes it compatible with some stuff)

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