Skip to content

DIM-1842 drone simulator using MuJoCo simulator and Crazyflie 2#2046

Open
Shreyas0812 wants to merge 18 commits into
dimensionalOS:mainfrom
Shreyas0812:feature/1842-drone-simulator
Open

DIM-1842 drone simulator using MuJoCo simulator and Crazyflie 2#2046
Shreyas0812 wants to merge 18 commits into
dimensionalOS:mainfrom
Shreyas0812:feature/1842-drone-simulator

Conversation

@Shreyas0812
Copy link
Copy Markdown

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

  • DroneSimConnection - sim connection module that reuses the existing MujocoConnection infrastructure, skipping MAVLink
  • DroneController - MuJoCo control policy mapping Twist commands (forward/lateral/yaw) to hover thrust and roll/pitch/yaw moments using constants (for thrust) from cf2.xml
  • drone_primitive_no_nav blueprint - minimal drone stack with rerun/foxglove visualization
  • drone_basic_sim blueprint - full sim stack connecting drone_primitive_no_nav with DroneSimConnection

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

…s ReplanningAStar (Same as g1) for planning -- yet to be tested
…drone_rerun_blueprint instead of g1 -- Inspired from unitree_g1_primitive_no_nav
…ing the file, updating imports, continuing mujoco_connection for drone
…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
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 11, 2026

Greptile Summary

This 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.

  • DroneSimConnection wraps MujocoConnection, subscribing cmd_vel and forwarding odometry/video/lidar streams; bypasses DroneConnectionModule.start() deliberately to skip MAVLink setup.
  • DroneController applies a fixed hover thrust to data.ctrl[0] and scaled roll/pitch/yaw moments to ctrl[1\u20133], driven by keyboard input via the existing InputController protocol.
  • mujoco_process.py now guards video and lidar rendering on camera existence (camera_id != -1) and zeros stale commands when none are available \u2014 both safe improvements that benefit all robot sims.

Confidence Score: 4/5

Safe 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

Filename Overview
dimos/robot/drone/sim.py New DroneSimConnection module wrapping MujocoConnection; stop() asserts connection is not None before start() can set it, crashing cleanup paths.
dimos/simulation/mujoco/policy.py Adds DroneController with hand-coded hover thrust + moment control; ignores ONNX-specific params via **kwargs; control model correctness depends on cf2.xml actuator layout.
dimos/simulation/mujoco/mujoco_process.py Adds drone name mapping and spawn height; guards video/lidar rendering on camera existence; zeros stale commands when none available — all safe changes.
dimos/simulation/mujoco/model.py Loads Crazyflie 2 assets, selects hover keyframe for cf2, and dispatches DroneController; straightforward extension of the existing robot-switch pattern.
dimos/robot/drone/blueprints/basic/drone_basic_sim.py New blueprint composing drone_primitive_no_nav, DroneSimConnection, and ReplanningAStarPlanner; has two unused imports (TYPE_CHECKING, global_config).
dimos/robot/drone/blueprints/primitive/drone_primitive_no_nav.py New minimal drone stack configuring visualization, mapping, and LCM transports; several commented-out imports indicate work-in-progress but no functional issues.
dimos/robot/all_blueprints.py Registers drone-basic-sim, drone-primitive-no-nav blueprints and drone-sim-connection module; mechanical addition, no issues.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (2): Last reviewed commit: "camera and lidar camera is not available..." | Re-trigger Greptile

Comment thread dimos/robot/drone/sim.py
Comment on lines +50 to +52
from dimos.robot.unitree.mujoco_connection import MujocoConnection

self.connection = MujocoConnection(self.config.g)
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.

P2 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.

Suggested change
from dimos.robot.unitree.mujoco_connection import MujocoConnection
self.connection = MujocoConnection(self.config.g)
self.connection = MujocoConnection(self.config.g)

Comment on lines +161 to +181
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
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.

P2 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
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.

P2 File is missing a trailing newline, which causes issues with some diff tools and linters (e.g., no-newline-at-end-of-file checks).

Suggested change
__all__ = ["drone_basic_sim"]
__all__ = ["drone_basic_sim"]

)
)

__all__ = ["drone_primitive_no_nav"] No newline at end of file
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.

P2 File is missing a trailing newline. The same issue is present in drone_basic_sim.py and sim.py.

Suggested change
__all__ = ["drone_primitive_no_nav"]
__all__ = ["drone_primitive_no_nav"]

Comment thread dimos/robot/drone/sim.py
Comment on lines +30 to +31
ip: str = Field(default_factory=lambda m: m["g"].robot_ip)

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.

P2 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.

Comment on lines +173 to +181
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
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 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
Comment thread dimos/robot/drone/sim.py
Comment on lines +67 to +74
@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)
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 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.

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.

1 participant