Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
07c18f6
add original files
jca0 Jan 15, 2026
4821255
rebased with dev for new controller orchestrator arch
jca0 Jan 15, 2026
b9adc9b
clean up code + comments
jca0 Jan 16, 2026
ac1c647
tests for testing
jca0 Jan 16, 2026
ca898c5
address race condition
jca0 Jan 16, 2026
8116d1f
fix mypy errors
jca0 Jan 16, 2026
d7fd12e
Merge branch 'jing-sim' of github.com:dimensionalOS/dimos into jing-sim
jca0 Jan 16, 2026
3461348
syntax error :(
jca0 Jan 16, 2026
f46ec17
CI code cleanup
jca0 Jan 16, 2026
97508ee
skeleton for modular sim arch
jca0 Jan 17, 2026
89f462a
mujoco engine/bringup script
jca0 Jan 17, 2026
6073d80
manipulation interface that plugs into different sims
jca0 Jan 17, 2026
328a019
simulation model that supports any sim engine
jca0 Jan 17, 2026
c153c13
add support for xarm in sim
jca0 Jan 17, 2026
6d9e135
clean up code
jca0 Jan 17, 2026
86a3c06
more cleanup
jca0 Jan 17, 2026
4121640
mypy error fix
jca0 Jan 18, 2026
a2b66f7
delete joint limits
jca0 Jan 23, 2026
91cd6a3
remove get_blueprint() and set constant
jca0 Jan 23, 2026
bcbe1be
sim module takes in xml file and parses it for robot properties
jca0 Jan 23, 2026
f392af0
Track xarm7 assets with LFS
jca0 Jan 23, 2026
19f886a
make blueprint runnable
jca0 Jan 23, 2026
c3d0c2b
e2e test and unit tests for sim module
jca0 Jan 24, 2026
90c8297
move xarm7 arm to data folder
jca0 Jan 24, 2026
b61e4db
move xml parser to utils folder
jca0 Jan 24, 2026
460f24a
remove 'mujoco' from sim module
jca0 Jan 24, 2026
ca4afc9
no time.sleeps for clock. monotonic time instead
jca0 Jan 24, 2026
2d118e4
connect() and disconnect() return bool
jca0 Jan 24, 2026
7fec092
consolidate write commands into one and fix velocity control
jca0 Jan 24, 2026
104c4fe
config_path changed to Path type and mandatory
jca0 Jan 24, 2026
e62a249
engine typing
jca0 Jan 24, 2026
22d71ec
typing fix, no default so set kw_only=True
jca0 Jan 24, 2026
f06fb46
add hold_current_position() to base
jca0 Jan 24, 2026
7a6662d
fix locks and make sure array length is as expected
jca0 Jan 24, 2026
bc98dd3
revise e2e tests to spin up blueprint
jca0 Jan 24, 2026
499ede5
fix sim module tests
jca0 Jan 24, 2026
ede93fc
Merge branch 'dev' into jing-sim
jca0 Jan 24, 2026
2c52669
fix mypy errors
jca0 Jan 24, 2026
645a5f0
fetch LFS when module starts
jca0 Jan 24, 2026
6ced566
Merge branch 'jing-sim' of github.com:dimensionalOS/dimos into jing-sim
jca0 Jan 24, 2026
9f5f919
clean up
jca0 Jan 24, 2026
2245752
Update test_simulation_module.py
jca0 Jan 25, 2026
d3962cd
Merge branch 'dev' into jing-sim
jca0 Jan 26, 2026
64f5979
remove duplicated blueprint
jca0 Jan 26, 2026
001a053
run all blueprints generation test
jca0 Jan 26, 2026
5d62c4f
Merge branch 'dev' into jing-sim
spomichter Feb 12, 2026
a527d07
CI code cleanup
spomichter Feb 12, 2026
de58166
fixed all blueprints
spomichter Feb 12, 2026
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
3 changes: 3 additions & 0 deletions data/.lfs/xarm7.tar.gz
Git LFS file not shown
86 changes: 86 additions & 0 deletions dimos/e2e_tests/test_simulation_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright 2025-2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""End-to-end tests for the simulation module."""

import os

import pytest

from dimos.msgs.sensor_msgs import JointCommand, JointState, RobotState


def _positions_within_tolerance(
positions: list[float],
target: list[float],
tolerance: float,
) -> bool:
if len(positions) < len(target):
return False
return all(abs(positions[i] - target[i]) <= tolerance for i in range(len(target)))


@pytest.mark.skipif(bool(os.getenv("CI")), reason="LCM doesn't work in CI.")
@pytest.mark.e2e
class TestSimulationModuleE2E:
def test_xarm7_joint_state_published(self, lcm_spy, start_blueprint) -> None:
joint_state_topic = "/xarm/joint_states#sensor_msgs.JointState"
lcm_spy.save_topic(joint_state_topic)

start_blueprint("simulation-xarm7")
lcm_spy.wait_for_saved_topic(joint_state_topic, timeout=15.0)

with lcm_spy._messages_lock:
raw_joint_state = lcm_spy.messages[joint_state_topic][0]

joint_state = JointState.lcm_decode(raw_joint_state)
assert len(joint_state.name) == 8
assert len(joint_state.position) == 8

def test_xarm7_robot_state_published(self, lcm_spy, start_blueprint) -> None:
robot_state_topic = "/xarm/robot_state#sensor_msgs.RobotState"
lcm_spy.save_topic(robot_state_topic)

start_blueprint("simulation-xarm7")
lcm_spy.wait_for_saved_topic(robot_state_topic, timeout=15.0)

with lcm_spy._messages_lock:
raw_robot_state = lcm_spy.messages[robot_state_topic][0]

robot_state = RobotState.lcm_decode(raw_robot_state)
assert robot_state.mt_able in (0, 1)

def test_xarm7_joint_command_updates_joint_state(self, lcm_spy, start_blueprint) -> None:
joint_state_topic = "/xarm/joint_states#sensor_msgs.JointState"
joint_command_topic = "/xarm/joint_position_command#sensor_msgs.JointCommand"
lcm_spy.save_topic(joint_state_topic)

start_blueprint("simulation-xarm7")
lcm_spy.wait_for_saved_topic(joint_state_topic, timeout=15.0)

target_positions = [0.2, -0.2, 0.1, -0.1, 0.15, -0.15, 0.05]
lcm_spy.publish(joint_command_topic, JointCommand(positions=target_positions))

tolerance = 0.03
lcm_spy.wait_for_message_result(
joint_state_topic,
JointState,
predicate=lambda msg: _positions_within_tolerance(
list(msg.position),
target_positions,
tolerance,
),
fail_message=("joint_state did not reach commanded positions within tolerance"),
timeout=10.0,
)
2 changes: 2 additions & 0 deletions dimos/robot/all_blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"xarm-perception": "dimos.manipulation.manipulation_blueprints:xarm_perception",
"xarm6-planner-only": "dimos.manipulation.manipulation_blueprints:xarm6_planner_only",
"xarm7-planner-coordinator": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator",
"xarm7-trajectory-sim": "dimos.simulation.sim_blueprints:xarm7_trajectory_sim",
}


Expand Down Expand Up @@ -103,6 +104,7 @@
"replanning_a_star_planner": "dimos.navigation.replanning_a_star.module",
"rerun_bridge": "dimos.visualization.rerun.bridge",
"ros_nav": "dimos.navigation.rosnav",
"simulation": "dimos.simulation.manipulators.sim_module",
"spatial_memory": "dimos.perception.spatial_perception",
"speak_skill": "dimos.agents.skills.speak_skill",
"temporal_memory": "dimos.perception.experimental.temporal_memory.temporal_memory",
Expand Down
25 changes: 25 additions & 0 deletions dimos/simulation/engines/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Simulation engines for manipulator backends."""

from __future__ import annotations

from typing import Literal

from dimos.simulation.engines.base import SimulationEngine
from dimos.simulation.engines.mujoco_engine import MujocoEngine

EngineType = Literal["mujoco"]

_ENGINES: dict[EngineType, type[SimulationEngine]] = {
"mujoco": MujocoEngine,
}


def get_engine(engine_name: EngineType) -> type[SimulationEngine]:
return _ENGINES[engine_name]


__all__ = [
"EngineType",
"SimulationEngine",
"get_engine",
]
84 changes: 84 additions & 0 deletions dimos/simulation/engines/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2025-2026 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Base interfaces for simulator engines."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pathlib import Path

from dimos.msgs.sensor_msgs import JointState


class SimulationEngine(ABC):
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

"""Abstract base class for a simulator engine instance."""

def __init__(self, config_path: Path, headless: bool) -> None:
self._config_path = config_path
self._headless = headless

@property
def config_path(self) -> Path:
return self._config_path

@property
def headless(self) -> bool:
return self._headless

@abstractmethod
def connect(self) -> bool:
"""Connect to simulation and start the engine."""

@abstractmethod
def disconnect(self) -> bool:
"""Disconnect from simulation and stop the engine."""

@property
@abstractmethod
def connected(self) -> bool:
"""Whether the engine is connected."""

@property
@abstractmethod
def num_joints(self) -> int:
"""Number of joints for the loaded robot."""

@property
@abstractmethod
def joint_names(self) -> list[str]:
"""Joint names for the loaded robot."""

@abstractmethod
def read_joint_positions(self) -> list[float]:
"""Read joint positions in radians."""

@abstractmethod
def read_joint_velocities(self) -> list[float]:
"""Read joint velocities in rad/s."""

@abstractmethod
def read_joint_efforts(self) -> list[float]:
"""Read joint efforts in Nm."""

@abstractmethod
def write_joint_command(self, command: JointState) -> None:
"""Command joints using a JointState message."""

@abstractmethod
def hold_current_position(self) -> None:
"""Hold current joint positions."""
Loading
Loading