-
Notifications
You must be signed in to change notification settings - Fork 602
DIM-1842 drone simulator using MuJoCo simulator and Crazyflie 2 #2046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a342cd2
10de38f
ba0101c
4f37ced
14c1995
e66cd00
e4e9cf9
2c7b6aa
e5c4ff3
4e6dfb4
8ad9144
3c6264a
43de2a0
3e285b0
ced090d
6c9cdf1
58357f9
c782e76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| """ Basic Drone Sim Blueprint with sim connection and visualization""" | ||
|
|
||
| from typing import TYPE_CHECKING | ||
|
|
||
| from dimos.core.coordination.blueprints import autoconnect | ||
| from dimos.core.global_config import global_config | ||
| from dimos.navigation.replanning_a_star.module import ReplanningAStarPlanner | ||
| from dimos.robot.drone.blueprints.primitive.drone_primitive_no_nav import ( | ||
| drone_primitive_no_nav, | ||
| ) | ||
| from dimos.robot.drone.sim import DroneSimConnection | ||
|
|
||
| drone_basic_sim = autoconnect( | ||
| drone_primitive_no_nav, | ||
| DroneSimConnection.blueprint(), | ||
| ReplanningAStarPlanner.blueprint(), | ||
| ) | ||
|
|
||
| __all__ = ["drone_basic_sim"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| """ Minimal Drone Stack without navigation. An attempt to create a minimal stack""" | ||
|
|
||
| from typing import Any | ||
|
|
||
| from dimos_lcm.sensor_msgs import CameraInfo | ||
|
|
||
| from dimos.core.coordination.blueprints import autoconnect | ||
| from dimos.core.global_config import global_config | ||
| from dimos.core.transport import LCMTransport | ||
| # from dimos.hardware.sensors.camera.module import CameraModule | ||
| # from dimos.hardware.sensors.camera.webcam import Webcam | ||
| # from dimos.hardware.sensors.camera.zed import compat as zed | ||
| from dimos.mapping.costmapper import CostMapper | ||
| from dimos.mapping.voxels import VoxelGridMapper | ||
| from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped | ||
| # from dimos.msgs.geometry_msgs.Quaternion import Quaternion | ||
| # from dimos.msgs.geometry_msgs.Transform import Transform | ||
| from dimos.msgs.geometry_msgs.Twist import Twist | ||
| # from dimos.msgs.geometry_msgs.Vector3 import Vector3 | ||
| # from dimos.msgs.nav_msgs.Odometry import Odometry | ||
| # from dimos.msgs.nav_msgs.Path import Path | ||
| from dimos.msgs.sensor_msgs.Image import Image | ||
| from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 | ||
| # from dimos.msgs.std_msgs.Bool import Bool | ||
| # from dimos.navigation.frontier_exploration.wavefront_frontier_goal_selector import ( | ||
| # WavefrontFrontierExplorer, | ||
| # ) | ||
| from dimos.protocol.pubsub.impl.lcmpubsub import LCM | ||
| from dimos.visualization.vis_module import vis_module | ||
| from dimos.web.websocket_vis.websocket_vis_module import WebsocketVisModule | ||
|
|
||
| def _convert_camera_info(camera_info: Any) -> Any: | ||
| return camera_info.to_rerun( | ||
| image_topic="/world/color_image", | ||
| optical_frame="camera_optical", | ||
| ) | ||
|
|
||
| def _convert_navigation_costmap(grid: Any) -> Any: | ||
| return grid.to_rerun( | ||
| colormap="Accent", | ||
| z_offset=0.015, | ||
| opacity=0.2, | ||
| background="#484981", | ||
| ) | ||
|
|
||
| def _static_base_link(rr: Any) -> list[Any]: | ||
| return [ | ||
| rr.Boxes3D( | ||
| half_sizes=[0.05, 0.05, 0.02], | ||
| colors=[(0, 255, 127)], | ||
| fill_mode="MajorWireframe", | ||
| ), | ||
| rr.Transform3D(parent_frame="tf#/base_link"), | ||
| ] | ||
|
|
||
| def _drone_rerun_blueprint() -> Any: | ||
| """Same as the G1 rerun blueprint but with a different base link visualization.""" | ||
| import rerun as rr | ||
| import rerun.blueprint as rrb | ||
|
|
||
| return rrb.Blueprint( | ||
| rrb.Horizontal( | ||
| rrb.Spatial2DView(origin="world/color_image", name="Camera"), | ||
| rrb.Spatial3DView( | ||
| origin="world", | ||
| name="3D", | ||
| background=rrb.Background(kind="SolidColor", color=[0, 0, 0]), | ||
| line_grid=rrb.LineGrid3D( | ||
| plane=rr.components.Plane3D.XY.with_distance(0.5), | ||
| ), | ||
| ), | ||
| column_shares=[1, 2], | ||
| ), | ||
| ) | ||
|
|
||
| rerun_config = { | ||
| "blueprint": _drone_rerun_blueprint, | ||
| "visual_override": { | ||
| "world/camera_info": _convert_camera_info, | ||
| "world/navigation_costmap": _convert_navigation_costmap, | ||
| }, | ||
| "static": { | ||
| "world/tf/base_link": _static_base_link, | ||
| } | ||
| } | ||
|
|
||
| _with_vis = vis_module(viewer_backend=global_config.viewer, rerun_config=rerun_config) | ||
|
|
||
| drone_primitive_no_nav = ( | ||
| autoconnect( | ||
| _with_vis, | ||
| VoxelGridMapper.blueprint(), | ||
| CostMapper.blueprint(), | ||
| #Visualization | ||
| WebsocketVisModule.blueprint(), | ||
| ) | ||
| .global_config(n_workers=4, robot_model="drone", mujoco_room="empty") | ||
| .transports( | ||
| { | ||
| # Movement command | ||
| ("cmd_vel", Twist): LCMTransport("/cmd_vel", Twist), | ||
| # Odometry output from ROSNavigationModule | ||
| ("odom", PoseStamped): LCMTransport("/odom", PoseStamped), | ||
| ("pointcloud", PointCloud2): LCMTransport("/lidar", PointCloud2), | ||
| ("goal_pose", PoseStamped): LCMTransport("/goal_pose", PoseStamped), | ||
| # Camera topics | ||
| ("color_image", Image): LCMTransport("/color_image", Image), | ||
| ("camera_info", CameraInfo): LCMTransport("/camera_info", CameraInfo), | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| __all__ = ["drone_primitive_no_nav"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||
| import threading | ||||||||||
| from threading import Thread | ||||||||||
| import time | ||||||||||
| from typing import Any | ||||||||||
|
|
||||||||||
| from pydantic import Field | ||||||||||
| from reactivex.disposable import Disposable | ||||||||||
|
|
||||||||||
| from dimos.constants import DEFAULT_THREAD_JOIN_TIMEOUT | ||||||||||
| from dimos.core.core import rpc | ||||||||||
| from dimos.core.module import Module, ModuleConfig | ||||||||||
| from dimos.core.stream import In, Out | ||||||||||
| from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped | ||||||||||
| from dimos.msgs.geometry_msgs.Quaternion import Quaternion | ||||||||||
| from dimos.msgs.geometry_msgs.Transform import Transform | ||||||||||
| from dimos.msgs.geometry_msgs.Twist import Twist | ||||||||||
| from dimos.msgs.geometry_msgs.Vector3 import Vector3 | ||||||||||
| from dimos.msgs.sensor_msgs.CameraInfo import CameraInfo | ||||||||||
| from dimos.msgs.sensor_msgs.Image import Image | ||||||||||
| from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 | ||||||||||
|
|
||||||||||
| from dimos.robot.drone.connection_module import DroneConnectionModule | ||||||||||
| from dimos.robot.unitree.mujoco_connection import MujocoConnection | ||||||||||
| from dimos.robot.unitree.type.odometry import Odometry as SimOdometry | ||||||||||
| from dimos.utils.logging_config import setup_logger | ||||||||||
|
|
||||||||||
| logger = setup_logger() | ||||||||||
|
|
||||||||||
| class DroneSimConfig(ModuleConfig): | ||||||||||
| ip: str = Field(default_factory=lambda m: m["g"].robot_ip) | ||||||||||
|
|
||||||||||
|
Comment on lines
+30
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
| class DroneSimConnection(DroneConnectionModule): | ||||||||||
| config: DroneSimConfig | ||||||||||
| cmd_vel: In[Twist] | ||||||||||
| lidar: Out[PointCloud2] | ||||||||||
| odom: Out[PoseStamped] | ||||||||||
| color_image: Out[Image] | ||||||||||
| camera_info: Out[CameraInfo] | ||||||||||
| connection: MujocoConnection | None = None | ||||||||||
| _camera_info_thread: Thread | None = None | ||||||||||
|
|
||||||||||
| def __init__(self, **kwargs: Any) -> None: | ||||||||||
| super().__init__(**kwargs) | ||||||||||
| self._stop_event = threading.Event() | ||||||||||
|
|
||||||||||
| @rpc | ||||||||||
| def start(self) -> None: | ||||||||||
| Module.start(self) | ||||||||||
|
|
||||||||||
| from dimos.robot.unitree.mujoco_connection import MujocoConnection | ||||||||||
|
|
||||||||||
| self.connection = MujocoConnection(self.config.g) | ||||||||||
|
Comment on lines
+50
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| assert self.connection is not None | ||||||||||
| self.connection.start() | ||||||||||
|
|
||||||||||
| self.register_disposable(Disposable(self.cmd_vel.subscribe(self.move))) | ||||||||||
| self.register_disposable(self.connection.odom_stream().subscribe(self._publish_sim_odom)) | ||||||||||
| self.register_disposable(self.connection.lidar_stream().subscribe(self.lidar.publish)) | ||||||||||
| self.register_disposable(self.connection.video_stream().subscribe(self.color_image.publish)) | ||||||||||
|
|
||||||||||
| self._camera_info_thread = Thread( | ||||||||||
| target=self._publish_camera_info_loop, | ||||||||||
| daemon=True, | ||||||||||
| ) | ||||||||||
| self._camera_info_thread.start() | ||||||||||
|
|
||||||||||
| @rpc | ||||||||||
| def stop(self) -> None: | ||||||||||
| self._stop_event.set() | ||||||||||
| assert self.connection is not None | ||||||||||
| self.connection.stop() | ||||||||||
| if self._camera_info_thread and self._camera_info_thread.is_alive(): | ||||||||||
| self._camera_info_thread.join(timeout=DEFAULT_THREAD_JOIN_TIMEOUT) | ||||||||||
| Module.stop(self) | ||||||||||
|
Comment on lines
+67
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
|
|
||||||||||
| def _publish_camera_info_loop(self) -> None: | ||||||||||
| assert self.connection is not None | ||||||||||
| info = self.connection.camera_info_static | ||||||||||
| while not self._stop_event.is_set(): | ||||||||||
| self.camera_info.publish(info) | ||||||||||
| self._stop_event.wait(1.0) | ||||||||||
|
|
||||||||||
| def _publish_sim_odom(self, msg: SimOdometry) -> None: | ||||||||||
| self._publish_tf( | ||||||||||
| PoseStamped( | ||||||||||
| ts=msg.ts, | ||||||||||
| frame_id=msg.frame_id, | ||||||||||
| position=msg.position, | ||||||||||
| orientation=msg.orientation, | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| @rpc | ||||||||||
| def move(self, twist: Twist, duration: float = 0.0) -> None: | ||||||||||
| assert self.connection is not None | ||||||||||
| self.connection.move(twist, duration) | ||||||||||
|
|
||||||||||
| @rpc | ||||||||||
| def publish_request(self, topic: str, data: dict[str, Any]) -> dict[Any, Any]: | ||||||||||
| logger.info(f"Publishing request to topic: {topic} with data: {data}") | ||||||||||
| assert self.connection is not None | ||||||||||
| return self.connection.publish_request(topic, data) | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,3 +154,28 @@ def get_obs(self, model: mujoco.MjModel, data: mujoco.MjData) -> np.ndarray[Any, | |
| def _post_control_update(self) -> None: | ||
| phase_tp1 = self._phase + self._phase_dt | ||
| self._phase = np.fmod(phase_tp1 + np.pi, 2 * np.pi) - np.pi | ||
|
|
||
| _DRONE_HOVER_THRUST = 0.26487 # From cf2.xml file | ||
| _DRONE_MOMENT_SCALE = 0.005 | ||
|
|
||
| class DroneController(): | ||
| def __init__( | ||
| self, | ||
| input_controller: InputController, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| self._input_controller = input_controller | ||
|
|
||
| def get_obs(self, model: mujoco.MjModel, data: mujoco.MjData) -> np.ndarray[Any, Any]: | ||
| command = self._input_controller.get_command() | ||
| return command.astype(np.float32) | ||
|
|
||
| def get_control(self, model: mujoco.MjModel, data: mujoco.MjData) -> None: | ||
| command = self._input_controller.get_command() | ||
| forward = float(command[0]) | ||
| lateral = float(command[1]) | ||
| yaw = float(command[2]) | ||
| data.ctrl[0] = _DRONE_HOVER_THRUST | ||
| data.ctrl[1] = lateral * _DRONE_MOMENT_SCALE # x_moment (roll) | ||
| data.ctrl[2] = -forward * _DRONE_MOMENT_SCALE # y_moment (pitch) | ||
| data.ctrl[3] = -yaw * _DRONE_MOMENT_SCALE # z_moment (yaw) | ||
|
Comment on lines
+161
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+173
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-newline-at-end-of-filechecks).