DIM-1842 drone simulator using MuJoCo simulator and Crazyflie 2#2046
DIM-1842 drone simulator using MuJoCo simulator and Crazyflie 2#2046Shreyas0812 wants to merge 18 commits into
Conversation
…s ReplanningAStar (Same as g1) for planning -- yet to be tested
…drone_rerun_blueprint instead of g1 -- Inspired from unitree_g1_primitive_no_nav
…eded, will decided to remove later
…ing the file, updating imports, continuing mujoco_connection for drone
…l -- will create one later if required
…g now. Ran test all blueprints for populating all_blueprints. Added the bitcraze_crazyflie_2 in model and a new class in policy
…e.start(self) -- need to verify if this is temporary solution, refactoring might be needed
…ode -- added drone to mujoco_process -- fixed Drone Controller policy
…merge (same as g1)
Greptile SummaryThis PR adds a MuJoCo-backed drone simulator for the Bitcraze Crazyflie 2, mirroring the existing Unitree sim path without MAVLink, so the drone software stack can be tested without physical hardware.
Confidence Score: 4/5Safe to merge with one fix: stop() will crash with AssertionError if called before start() completes. DroneSimConnection.stop() unconditionally asserts self.connection is not None, but self.connection is initialized to None and only set inside start(). Any cleanup or error-handling path that calls stop() before start() finishes will raise AssertionError and leave teardown incomplete. The rest of the changes are correct. dimos/robot/drone/sim.py — the stop() method needs a defensive null-check on self.connection before calling self.connection.stop(). Important Files Changed
Sequence DiagramsequenceDiagram
participant User as Keyboard / WASD
participant DSC as DroneSimConnection
participant MC as MujocoConnection
participant MP as mujoco_process (_run_simulation)
participant DC as DroneController
participant SHM as Shared Memory
User->>DSC: Twist (cmd_vel)
DSC->>MC: move(twist)
MC->>SHM: write_command(linear, angular)
MP->>SHM: read_command()
SHM-->>MP: [forward, lateral, yaw]
MP->>DC: get_control(model, data)
DC->>DC: "data.ctrl[0]=hover_thrust, ctrl[1-3]=moments"
MP->>SHM: write_odom / write_video / write_lidar
MC-->>DSC: odom_stream / video_stream / lidar_stream
DSC-->>DSC: _publish_sim_odom / publish video / lidar
Reviews (2): Last reviewed commit: "camera and lidar camera is not available..." | Re-trigger Greptile |
| from dimos.robot.unitree.mujoco_connection import MujocoConnection | ||
|
|
||
| self.connection = MujocoConnection(self.config.g) |
There was a problem hiding this comment.
The
MujocoConnection is imported at the module level (line 23) and again with a redundant local import inside start(). The local import serves no purpose since the top-level import is unconditional, and having two imports of the same name in the same scope can silently shadow the outer binding if the inner one ever changes. Remove the duplicate.
| from dimos.robot.unitree.mujoco_connection import MujocoConnection | |
| self.connection = MujocoConnection(self.config.g) | |
| self.connection = MujocoConnection(self.config.g) |
| 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) No newline at end of file |
There was a problem hiding this comment.
DroneController does not inherit from OnnxController, but load_model annotates the policy variable as OnnxController. The case "bitcraze_crazyflie_2" branch then assigns a DroneController instance to that annotated variable, which is a static type error. A strict type checker (mypy/pyright) will flag this. Consider having DroneController inherit from an abstract base that both OnnxController and DroneController satisfy, or introduce a Protocol for get_control/get_obs.
| ReplanningAStarPlanner.blueprint(), | ||
| ) | ||
|
|
||
| __all__ = ["drone_basic_sim"] No newline at end of file |
| ) | ||
| ) | ||
|
|
||
| __all__ = ["drone_primitive_no_nav"] No newline at end of file |
| ip: str = Field(default_factory=lambda m: m["g"].robot_ip) | ||
|
|
There was a problem hiding this comment.
DroneSimConfig.ip is declared but never read anywhere in DroneSimConnection — the sim connection passes self.config.g (the GlobalConfig) directly to MujocoConnection, and robot_ip is only used by MAVLink-based hardware connections. If the field is intentionally reserved for future use, a comment would help; otherwise it can be removed to avoid confusion.
| 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) No newline at end of file |
There was a problem hiding this comment.
Only
data.ctrl[0] receives the full hover thrust, while data.ctrl[1:3] are assigned the raw moment-scale values (lateral * 0.005, -forward * 0.005, -yaw * 0.005). If the bitcraze_crazyflie_2 MuJoCo model exposes four independent per-rotor thrust actuators (which is the default layout in the MuJoCo Menagerie), then all four ctrl entries need the base hover thrust, and the moments are applied as differential offsets on top. With the current code, rotors 1–3 receive near-zero thrust at idle, causing the drone to flip immediately.
… ut by addind to to xml -- using original xml which comes with mujoco playground and adding additional checks instead
| @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) |
There was a problem hiding this comment.
stop() crashes when called before start()
self.connection is initialized to None in DroneConnectionModule.__init__ and is only assigned in start(). If stop() is invoked during an error-handling path before start() completes, the assert self.connection is not None on line 70 raises AssertionError and prevents cleanup from finishing. The parent class DroneConnectionModule.stop() uses a defensive if self.connection: guard for exactly this reason — the pattern should be consistent here.
Problem
No way to test drone software stack without physical hardware. Adds a simulation connection for the Bitcraze Crazyflie 2 using MuJoCo, mirroring the existing Unitree Go1/G1 sim path.
Closes DIM-1842
Solution
How to Test
dimos --simulation run drone-basic-sim
Use WASD to move the drone. W/S = forward/back, A/D = left/right, Q/E = yaw.
Closes DIM-1842