Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2b2ba02
standalone unitree initial sketch
leshy May 6, 2025
7e47d88
unitree standalone sample
leshy May 6, 2025
12ae99a
standalone test
leshy May 6, 2025
551aa8f
kicked out go2_webrtc_connect submodule
leshy May 6, 2025
c2d6d32
added mapping
leshy May 6, 2025
e596a6a
global planner example implementation
leshy May 6, 2025
d6ce829
renamed unitree_standalone to unitree_webrtc
leshy May 6, 2025
8d5b5ea
hooking up unitree driver
leshy May 6, 2025
860202c
implemented motion, cleanup
leshy May 6, 2025
2b02398
reactive callback conversion test, color changing
leshy May 6, 2025
9fe1cfc
motion, video, lidar
leshy May 6, 2025
2c46a40
odometry stream, low level state stream
leshy May 6, 2025
dfea736
odometry stream
leshy May 6, 2025
fb37867
removed old entry file
leshy May 6, 2025
95f6e39
handstand function
leshy May 6, 2025
fedec2b
auto standup/standdown
leshy May 6, 2025
01b2b09
planner integration
leshy May 6, 2025
6f8e41c
pathing fixes
leshy May 6, 2025
014515a
lru cache for video
leshy May 6, 2025
bcc7dae
Changed Connection to WebRTCRobot and added required methods, Changes…
spomichter May 7, 2025
e50e199
Added helper AbstractRobot
spomichter May 7, 2025
c9b5d11
Temporary implementation of continuous movement commands to webrtc
spomichter May 7, 2025
71d7a49
Merge remote-tracking branch 'origin/dev' into test_dev
spomichter May 7, 2025
ba0484f
Temporary asyncio continuous vector move() implementation
spomichter May 7, 2025
318b469
Re-implemented Unitree_go2 to be WebRTC only
spomichter May 7, 2025
757c156
New webrtc only UnitreeGo2 run.py
spomichter May 7, 2025
f5bd631
tooling improvements for webrtc, multi message storage & replay
leshy May 13, 2025
31fdfe7
webrtc cleanup, coordinate transforms, message reply helpers
leshy May 13, 2025
737ef36
removed mock images
leshy May 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
[submodule "dimos/external/openMVS"]
path = dimos/external/openMVS
url = https://github.com/cdcseacave/openMVS.git

[submodule "dimos/external/vcpkg"]
path = dimos/external/vcpkg
url = https://github.com/microsoft/vcpkg.git
[submodule "dimos/robot/unitree/external/go2_webrtc_connect"]
path = dimos/robot/unitree/external/go2_webrtc_connect
url = https://github.com/spomichter/go2_webrtc_connect
[submodule "dimos/robot/unitree/external/go2_ros2_sdk"]
path = dimos/robot/unitree/external/go2_ros2_sdk
url = https://github.com/dimensionalOS/go2_ros2_sdk
66 changes: 66 additions & 0 deletions dimos/robot/abstract_robot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Abstract base class for all DIMOS robot implementations.

This module defines the AbstractRobot class which serves as the foundation for
all robot implementations in DIMOS, establishing a common interface regardless
of the underlying hardware or communication protocol (ROS, WebRTC, etc).
"""

from abc import ABC, abstractmethod
from typing import Any, Union, Optional
from reactivex.observable import Observable
import numpy as np


class AbstractRobot(ABC):
"""Abstract base class for all robot implementations.

This class defines the minimal interface that all robot implementations
must provide, regardless of whether they use ROS, WebRTC, or other
communication protocols.
"""

@abstractmethod
def connect(self) -> bool:
"""Establish a connection to the robot.

This method should handle all necessary setup to establish
communication with the robot hardware.

Returns:
bool: True if connection was successful, False otherwise.
"""
pass

@abstractmethod
def move(self, *args, **kwargs) -> bool:
"""Move the robot.

This is a generic movement interface that should be implemented
by all robot classes. The exact parameters will depend on the
specific robot implementation.

Returns:
bool: True if movement command was successfully sent.
"""
pass

@abstractmethod
def get_video_stream(self, fps: int = 30) -> Observable:
"""Get a video stream from the robot's camera.

Args:
fps: Frames per second for the video stream. Defaults to 30.

Returns:
Observable: An observable stream of video frames.
"""
pass

@abstractmethod
def stop(self) -> None:
"""Clean up resources and stop the robot.

This method should handle all necessary cleanup when shutting down
the robot connection, including stopping any ongoing movements.
"""
pass
8 changes: 5 additions & 3 deletions dimos/robot/global_planner/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ def plan(self, goal: VectorLike) -> Path: ...
def set_goal(
self, goal: VectorLike, goal_theta: Optional[float] = None, stop_event: Optional[threading.Event] = None
):
goal = to_vector(goal).to_2d()
path = self.plan(goal)
if not path:
logger.warning("No path found to the goal.")
return False
print("pathing success", path)
return self.set_local_nav(path, stop_event=stop_event, goal_theta=goal_theta)


Expand All @@ -53,12 +53,14 @@ class AstarPlanner(Planner):
conservativism: int = 8

def plan(self, goal: VectorLike) -> Path:
pos = self.get_robot_pos()
goal = to_vector(goal).to_2d()
pos = self.get_robot_pos().to_2d()
costmap = self.get_costmap().smudge(preserve_unknown=False)

self.vis("planner_costmap", costmap)
# self.vis("costmap", costmap)
self.vis("target", goal)

print("ASTAR ", costmap, goal, pos)
path = astar(costmap, goal, pos)

if path:
Expand Down
5 changes: 2 additions & 3 deletions dimos/robot/unitree/unitree_go2.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
from dimos.utils.logging_config import setup_logger
from dimos.perception.person_tracker import PersonTrackingStream
from dimos.perception.object_tracker import ObjectTrackingStream
from dimos.robot.local_planner.vfh_local_planner import VFHPurePursuitPlanner
from dimos.robot.local_planner.local_planner import navigate_path_local
from dimos.robot.local_planner import VFHPurePursuitPlanner, navigate_path_local
from dimos.robot.global_planner.planner import AstarPlanner
from dimos.types.path import Path
from dimos.types.costmap import Costmap
Expand Down Expand Up @@ -170,7 +169,7 @@ def __init__(
robot_width=0.36, # Unitree Go2 width in meters
robot_length=0.6, # Unitree Go2 length in meters
max_linear_vel=0.5,
lookahead_distance=1.0,
lookahead_distance=2.0,
visualization_size=500, # 500x500 pixel visualization
)

Expand Down
93 changes: 88 additions & 5 deletions dimos/robot/unitree/unitree_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ def __call__(self):
# region Class-based Skills

class Move(AbstractRobotSkill):
"""Move the robot using direct velocity commands."""
"""Move the robot using direct velocity commands.

This skill works with both ROS and WebRTC robot implementations.
"""

x: float = Field(..., description="Forward velocity (m/s).")
y: float = Field(default=0.0, description="Left/right velocity (m/s)")
Expand All @@ -246,10 +249,52 @@ class Move(AbstractRobotSkill):

def __call__(self):
super().__call__()
return self._robot.move_vel(x=self.x, y=self.y, yaw=self.yaw, duration=self.duration)

from dimos.types.vector import Vector
Copy link
Contributor

Choose a reason for hiding this comment

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

This was my test implementation for continuous move velocity control and needs to be cleaned up. TODO

vector = Vector(self.x, self.y, self.yaw)

# Handle duration for continuous movement
if self.duration > 0:
import time
import threading
import asyncio

# Create a stop event
stop_event = threading.Event()

# Function to continuously send movement commands
async def continuous_move():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
start_time = time.time()
try:
while not stop_event.is_set() and (time.time() - start_time) < self.duration:
self._robot.move(vector)
await asyncio.sleep(0.001) # Send commands at 1000Hz
# Always stop at the end
self._robot.move(Vector(0, 0, 0))
finally:
loop.close()

# Run movement in a separate thread with asyncio event loop
move_thread = threading.Thread(target=lambda: asyncio.run(continuous_move()))
move_thread.daemon = True
move_thread.start()

# Wait for the full duration
time.sleep(self.duration)
stop_event.set()
move_thread.join(timeout=0.5) # Wait for thread to finish with timeout
else:
# Just execute the move command once for continuous movement
self._robot.move(vector)
return True

class Reverse(AbstractRobotSkill):
"""Reverse the robot using direct velocity commands."""
"""Reverse the robot using direct velocity commands.

This skill works with both ROS and WebRTC robot implementations.
"""

x: float = Field(..., description="Backward velocity (m/s). Positive values move backward.")
y: float = Field(default=0.0, description="Left/right velocity (m/s)")
Expand All @@ -258,8 +303,46 @@ class Reverse(AbstractRobotSkill):

def __call__(self):
super().__call__()
# Use move_vel with negative x for backward movement
return self._robot.move_vel(x=-self.x, y=self.y, yaw=self.yaw, duration=self.duration)
from dimos.types.vector import Vector
# Use negative x for backward movement
vector = Vector(-self.x, self.y, self.yaw)

# Handle duration for continuous movement
if self.duration > 0:
import time
import threading
import asyncio

# Create a stop event
stop_event = threading.Event()

# Function to continuously send movement commands
async def continuous_move():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
start_time = time.time()
try:
while not stop_event.is_set() and (time.time() - start_time) < self.duration:
self._robot.move(vector)
await asyncio.sleep(0.001) # Send commands at 1000Hz
# Always stop at the end
self._robot.move(Vector(0, 0, 0))
finally:
loop.close()

# Run movement in a separate thread with asyncio event loop
move_thread = threading.Thread(target=lambda: asyncio.run(continuous_move()))
move_thread.daemon = True
move_thread.start()

# Wait for the full duration
time.sleep(self.duration)
stop_event.set()
move_thread.join(timeout=0.5) # Wait for thread to finish with timeout
else:
# Just execute the move command once for continuous movement
self._robot.move(vector)
return True

class SpinLeft(AbstractRobotSkill):
"""Spin the robot left using degree commands."""
Expand Down
Empty file.
Loading