From e08a31100b0ea46ece0734a236a97d23b41d3b4c Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 13:24:17 -0800 Subject: [PATCH 01/45] first shot at breaking up the g1 blueprints --- .../unitree_g1_blueprints/__init__.py | 47 ++++++++ .../unitree_g1_blueprints/_agentic_skills.py | 31 +++++ .../_perception_and_memory.py | 29 +++++ .../uintree_g1_basic_no_nav.py | 91 ++++++++++++++ .../unitree_g1_blueprints/unitree_g1.py | 28 +++++ .../unitree_g1_agentic.py | 28 +++++ .../unitree_g1_agentic_sim.py | 28 +++++ .../unitree_g1_blueprints/unitree_g1_basic.py | 30 +++++ .../unitree_g1_basic_sim.py | 30 +++++ .../unitree_g1_detection.py | 112 ++++++++++++++++++ .../unitree_g1_blueprints/unitree_g1_full.py | 30 +++++ .../unitree_g1_joystick.py | 28 +++++ .../unitree_g1_blueprints/unitree_g1_shm.py | 41 +++++++ .../unitree_g1_blueprints/unitree_g1_sim.py | 28 +++++ 14 files changed, 581 insertions(+) create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py create mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py new file mode 100644 index 0000000000..5a3e7b8f84 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# 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. + +"""Cascaded G1 blueprints split into focused modules.""" + +from ._agentic_skills import _agentic_skills +from ._perception_and_memory import _perception_and_memory +from .uintree_g1_basic_no_nav import basic_no_nav +from .unitree_g1 import unitree_g1 +from .unitree_g1_agentic import unitree_g1_agentic +from .unitree_g1_agentic_sim import unitree_g1_agentic_sim +from .unitree_g1_basic import unitree_g1_basic +from .unitree_g1_basic_sim import unitree_g1_basic_sim +from .unitree_g1_detection import unitree_g1_detection +from .unitree_g1_full import unitree_g1_full +from .unitree_g1_joystick import unitree_g1_joystick +from .unitree_g1_shm import unitree_g1_shm +from .unitree_g1_sim import unitree_g1_sim + +__all__ = [ + "_agentic_skills", + "_perception_and_memory", + "basic_no_nav", + "uintree_g1_basic_no_nav", + "unitree_g1", + "unitree_g1_agentic", + "unitree_g1_agentic_sim", + "unitree_g1_basic", + "unitree_g1_basic_sim", + "unitree_g1_detection", + "unitree_g1_full", + "unitree_g1_joystick", + "unitree_g1_shm", + "unitree_g1_sim", +] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py new file mode 100644 index 0000000000..0436294bf6 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# 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. + +"""Agentic skills used by higher-level G1 blueprints.""" + +from dimos.agents.agent import llm_agent +from dimos.agents.cli.human import human_input +from dimos.agents.skills.navigation import navigation_skill +from dimos.core.blueprints import autoconnect +from dimos.robot.unitree_webrtc.unitree_g1_skill_container import g1_skills + +agentic_skills = autoconnect( + llm_agent(), + human_input(), + navigation_skill(), + g1_skills(), +) + +__all__ = ["agentic_skills"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py new file mode 100644 index 0000000000..47dc2588b9 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# 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. + +"""Perception and memory modules used by higher-level G1 blueprints.""" + +from dimos.core.blueprints import autoconnect +from dimos.perception.object_tracker import object_tracking +from dimos.perception.spatial_perception import spatial_memory +from dimos.utils.monitoring import utilization + +_perception_and_memory = autoconnect( + spatial_memory(), + object_tracking(frame_id="camera_link"), + utilization(), +) + +__all__ = ["_perception_and_memory"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py new file mode 100644 index 0000000000..fe90e0ca84 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# 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. + +"""Minimal G1 stack without navigation, used as a base for larger blueprints.""" + +from dimos_lcm.sensor_msgs import CameraInfo + +from dimos.core.blueprints import autoconnect +from dimos.core.transport import LCMTransport +from dimos.dashboard.tf_rerun_module import tf_rerun +from dimos.hardware.sensors.camera import zed +from dimos.hardware.sensors.camera.module import camera_module # type: ignore[attr-defined] +from dimos.hardware.sensors.camera.webcam import Webcam +from dimos.mapping.costmapper import cost_mapper +from dimos.mapping.voxels import voxel_mapper +from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Transform, Twist, Vector3 +from dimos.msgs.nav_msgs import Odometry, Path +from dimos.msgs.sensor_msgs import Image, PointCloud2 +from dimos.msgs.std_msgs import Bool +from dimos.navigation.frontier_exploration import wavefront_frontier_explorer +from dimos.robot.foxglove_bridge import foxglove_bridge +from dimos.web.websocket_vis.websocket_vis_module import websocket_vis + +uintree_g1_basic_no_nav = ( + autoconnect( + camera_module( + transform=Transform( + translation=Vector3(0.05, 0.0, 0.6), # height of camera on G1 robot + rotation=Quaternion.from_euler(Vector3(0.0, 0.2, 0.0)), + frame_id="sensor", + child_frame_id="camera_link", + ), + hardware=lambda: Webcam( + camera_index=0, + fps=15, + stereo_slice="left", + camera_info=zed.CameraInfo.SingleWebcam, + ), + ), + voxel_mapper(voxel_size=0.1), + cost_mapper(), + wavefront_frontier_explorer(), + # Visualization + websocket_vis(), + foxglove_bridge(), + tf_rerun( + robot_frame="base_link", + cameras=[ + ("world/robot/camera", "camera_optical", zed.CameraInfo.SingleWebcam), + ], + ), + ) + .global_config(n_dask_workers=4, robot_model="unitree_g1") + .transports( + { + # G1 uses Twist for movement commands + ("cmd_vel", Twist): LCMTransport("/cmd_vel", Twist), + # State estimation from ROS + ("state_estimation", Odometry): LCMTransport("/state_estimation", Odometry), + # Odometry output from ROSNavigationModule + ("odom", PoseStamped): LCMTransport("/odom", PoseStamped), + # Navigation module topics from nav_bot + ("goal_req", PoseStamped): LCMTransport("/goal_req", PoseStamped), + ("goal_active", PoseStamped): LCMTransport("/goal_active", PoseStamped), + ("path_active", Path): LCMTransport("/path_active", Path), + ("pointcloud", PointCloud2): LCMTransport("/lidar", PointCloud2), + ("global_pointcloud", PointCloud2): LCMTransport("/map", PointCloud2), + # Original navigation topics for backwards compatibility + ("goal_pose", PoseStamped): LCMTransport("/goal_pose", PoseStamped), + ("goal_reached", Bool): LCMTransport("/goal_reached", Bool), + ("cancel_goal", Bool): LCMTransport("/cancel_goal", Bool), + # Camera topics (if camera module is added) + ("color_image", Image): LCMTransport("/g1/color_image", Image), + ("camera_info", CameraInfo): LCMTransport("/g1/camera_info", CameraInfo), + } + ) +) + +__all__ = ["uintree_g1_basic_no_nav"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py new file mode 100644 index 0000000000..b02df3be42 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +"""G1 stack with perception and memory.""" + +from dimos.core.blueprints import autoconnect + +from ._perception_and_memory import perception_and_memory +from .unitree_g1_basic import unitree_g1_basic + +unitree_g1 = autoconnect( + unitree_g1_basic, + perception_and_memory, +).global_config(n_dask_workers=8) + +__all__ = ["unitree_g1"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py new file mode 100644 index 0000000000..200191f256 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +"""Full G1 stack with agentic skills.""" + +from dimos.core.blueprints import autoconnect + +from ._agentic_skills import agentic_skills +from .unitree_g1 import unitree_g1 + +unitree_g1_agentic = autoconnect( + unitree_g1, + agentic_skills, +) + +__all__ = ["unitree_g1_agentic"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py new file mode 100644 index 0000000000..f2a0c68334 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +"""Agentic G1 sim stack.""" + +from dimos.core.blueprints import autoconnect + +from ._agentic_skills import agentic_skills +from .unitree_g1_sim import unitree_g1_sim + +unitree_g1_agentic_sim = autoconnect( + unitree_g1_sim, + agentic_skills, +) + +__all__ = ["unitree_g1_agentic_sim"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py new file mode 100644 index 0000000000..6f227fb8e1 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# 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. + +"""Basic G1 stack: base sensors plus real robot connection and ROS nav.""" + +from dimos.core.blueprints import autoconnect +from dimos.navigation.rosnav import ros_nav +from dimos.robot.unitree.connection.g1 import g1_connection + +from .uintree_g1_basic_no_nav import uintree_g1_basic_no_nav + +unitree_g1_basic = autoconnect( + uintree_g1_basic_no_nav, + g1_connection(), + ros_nav(), +) + +__all__ = ["unitree_g1_basic"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py new file mode 100644 index 0000000000..87f9d24d8e --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# 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. + +"""Basic G1 sim stack: base sensors plus sim connection and planner.""" + +from dimos.core.blueprints import autoconnect +from dimos.navigation.replanning_a_star.module import replanning_a_star_planner +from dimos.robot.unitree.connection.g1sim import g1_sim_connection + +from .unitree_g1_basic import unitree_g1_basic + +unitree_g1_basic_sim = autoconnect( + unitree_g1_basic.uintree_g1_basic_no_nav, + g1_sim_connection(), + replanning_a_star_planner(), +) + +__all__ = ["unitree_g1_basic_sim"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py new file mode 100644 index 0000000000..3efa3dd44d --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# 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. + +"""G1 stack with person tracking and 3D detection.""" + +from dimos_lcm.foxglove_msgs import SceneUpdate +from dimos_lcm.foxglove_msgs.ImageAnnotations import ImageAnnotations + +from dimos.core.blueprints import autoconnect +from dimos.core.transport import LCMTransport +from dimos.hardware.sensors.camera import zed +from dimos.msgs.geometry_msgs import PoseStamped +from dimos.msgs.sensor_msgs import Image, PointCloud2 +from dimos.msgs.vision_msgs import Detection2DArray +from dimos.perception.detection.detectors.person.yolo import YoloPersonDetector +from dimos.perception.detection.module3D import Detection3DModule, detection3d_module +from dimos.perception.detection.moduleDB import ObjectDBModule, detection_db_module +from dimos.perception.detection.person_tracker import PersonTracker, person_tracker_module + +from .unitree_g1_basic import unitree_g1_basic + +unitree_g1_detection = ( + autoconnect( + unitree_g1_basic, + # Person detection modules with YOLO + detection3d_module( + camera_info=zed.CameraInfo.SingleWebcam, + detector=YoloPersonDetector, + ), + detection_db_module( + camera_info=zed.CameraInfo.SingleWebcam, + filter=lambda det: det.class_id == 0, # Filter for person class only + ), + person_tracker_module( + cameraInfo=zed.CameraInfo.SingleWebcam, + ), + ) + .global_config(n_dask_workers=8) + .remappings( + [ + # Connect detection modules to camera and lidar + (Detection3DModule, "image", "color_image"), + (Detection3DModule, "pointcloud", "pointcloud"), + (ObjectDBModule, "image", "color_image"), + (ObjectDBModule, "pointcloud", "pointcloud"), + (PersonTracker, "image", "color_image"), + (PersonTracker, "detections", "detections_2d"), + ] + ) + .transports( + { + # Detection 3D module outputs + ("detections", Detection3DModule): LCMTransport( + "/detector3d/detections", Detection2DArray + ), + ("annotations", Detection3DModule): LCMTransport( + "/detector3d/annotations", ImageAnnotations + ), + ("scene_update", Detection3DModule): LCMTransport( + "/detector3d/scene_update", SceneUpdate + ), + ("detected_pointcloud_0", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/0", PointCloud2 + ), + ("detected_pointcloud_1", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/1", PointCloud2 + ), + ("detected_pointcloud_2", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/2", PointCloud2 + ), + ("detected_image_0", Detection3DModule): LCMTransport("/detector3d/image/0", Image), + ("detected_image_1", Detection3DModule): LCMTransport("/detector3d/image/1", Image), + ("detected_image_2", Detection3DModule): LCMTransport("/detector3d/image/2", Image), + # Detection DB module outputs + ("detections", ObjectDBModule): LCMTransport( + "/detectorDB/detections", Detection2DArray + ), + ("annotations", ObjectDBModule): LCMTransport( + "/detectorDB/annotations", ImageAnnotations + ), + ("scene_update", ObjectDBModule): LCMTransport("/detectorDB/scene_update", SceneUpdate), + ("detected_pointcloud_0", ObjectDBModule): LCMTransport( + "/detectorDB/pointcloud/0", PointCloud2 + ), + ("detected_pointcloud_1", ObjectDBModule): LCMTransport( + "/detectorDB/pointcloud/1", PointCloud2 + ), + ("detected_pointcloud_2", ObjectDBModule): LCMTransport( + "/detectorDB/pointcloud/2", PointCloud2 + ), + ("detected_image_0", ObjectDBModule): LCMTransport("/detectorDB/image/0", Image), + ("detected_image_1", ObjectDBModule): LCMTransport("/detectorDB/image/1", Image), + ("detected_image_2", ObjectDBModule): LCMTransport("/detectorDB/image/2", Image), + # Person tracker outputs + ("target", PersonTracker): LCMTransport("/person_tracker/target", PoseStamped), + } + ) +) + +__all__ = ["unitree_g1_detection"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py new file mode 100644 index 0000000000..30e3c9ba80 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# 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. + +"""Full featured G1 stack with agentic skills and teleop.""" + +from dimos.core.blueprints import autoconnect +from dimos.robot.unitree_webrtc.keyboard_teleop import keyboard_teleop + +from ._agentic_skills import agentic_skills +from .unitree_g1_shm import unitree_g1_shm + +unitree_g1_full = autoconnect( + unitree_g1_shm, + agentic_skills, + keyboard_teleop(), +) + +__all__ = ["unitree_g1_full"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py new file mode 100644 index 0000000000..23550b89cc --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +"""G1 stack with keyboard teleop.""" + +from dimos.core.blueprints import autoconnect +from dimos.robot.unitree_webrtc.keyboard_teleop import keyboard_teleop + +from .unitree_g1_basic import unitree_g1_basic + +unitree_g1_joystick = autoconnect( + unitree_g1_basic, + keyboard_teleop(), # Pygame-based joystick control +) + +__all__ = ["unitree_g1_joystick"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py new file mode 100644 index 0000000000..668c6eaf49 --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# 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. + +"""G1 stack with shared memory image transport.""" + +from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE +from dimos.core.blueprints import autoconnect +from dimos.core.transport import pSHMTransport +from dimos.msgs.sensor_msgs import Image +from dimos.robot.foxglove_bridge import foxglove_bridge + +from .unitree_g1 import unitree_g1 + +unitree_g1_shm = autoconnect( + unitree_g1.transports( + { + ("color_image", Image): pSHMTransport( + "/g1/color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE + ), + } + ), + foxglove_bridge( + shm_channels=[ + "/g1/color_image#sensor_msgs.Image", + ] + ), +) + +__all__ = ["unitree_g1_shm"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py new file mode 100644 index 0000000000..bf3383eb9e --- /dev/null +++ b/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +"""G1 sim stack with perception and memory.""" + +from dimos.core.blueprints import autoconnect + +from ._perception_and_memory import _perception_and_memory +from .unitree_g1_basic_sim import unitree_g1_basic_sim + +unitree_g1_sim = autoconnect( + unitree_g1_basic_sim, + _perception_and_memory, +).global_config(n_dask_workers=8) + +__all__ = ["unitree_g1_sim"] From 4f38e8f3673f05cfe9e5c223e7c9559cb65abf31 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 16:06:02 -0800 Subject: [PATCH 02/45] the great unitree renaming --- dimos/agents/skills/conftest.py | 2 +- dimos/agents/skills/person_follow.py | 2 +- dimos/agents/test_mock_agent.py | 2 +- dimos/control/tasks/__init__.py | 24 +- dimos/core/test_core.py | 2 +- dimos/core/test_stream.py | 2 +- dimos/core/testing.py | 4 +- dimos/msgs/sensor_msgs/test_PointCloud2.py | 2 +- dimos/perception/detection/conftest.py | 12 +- dimos/perception/detection/test_moduleDB.py | 6 +- .../perception/test_spatial_memory_module.py | 2 +- dimos/protocol/mcp/test_mcp_module.py | 2 +- dimos/robot/all_blueprints.py | 61 +-- .../testing => unitree}/__init__.py | 0 .../unitree_b1 => unitree/b1}/README.md | 4 +- .../unitree_b1 => unitree/b1}/__init__.py | 0 .../unitree_b1 => unitree/b1}/b1_command.py | 0 .../unitree_b1 => unitree/b1}/connection.py | 0 .../b1}/joystick_module.py | 0 .../b1}/joystick_server_udp.cpp | 0 .../b1}/test_connection.py | 0 .../unitree_b1 => unitree/b1}/unitree_b1.py | 4 +- .../unitree/{connection => }/connection.py | 6 +- dimos/robot/unitree/connection/__init__.py | 4 - .../demo_error_on_name_conflicts.py | 0 .../depth_module.py | 0 .../g1/blueprints}/__init__.py | 29 +- .../g1/blueprints/agentic}/_agentic_skills.py | 6 +- .../blueprints/agentic}/unitree_g1_agentic.py | 6 +- .../agentic}/unitree_g1_agentic_sim.py | 6 +- .../g1/blueprints/agentic}/unitree_g1_full.py | 8 +- .../g1/blueprints/basic}/unitree_g1_basic.py | 4 +- .../blueprints/basic}/unitree_g1_basic_sim.py | 2 +- .../blueprints/basic}/unitree_g1_joystick.py | 2 +- .../perceptive}/_perception_and_memory.py | 0 .../g1/blueprints/perceptive}/unitree_g1.py | 6 +- .../perceptive}/unitree_g1_detection.py | 2 +- .../blueprints/perceptive}/unitree_g1_shm.py | 0 .../blueprints/perceptive}/unitree_g1_sim.py | 2 +- .../primitive/uintree_g1_primitive_no_nav.py} | 0 .../{connection/g1.py => g1/connection.py} | 2 +- dimos/robot/unitree/g1/g1agent.py | 48 -- dimos/robot/unitree/g1/g1detector.py | 41 -- dimos/robot/unitree/g1/g1zed.py | 90 ---- .../{connection/g1sim.py => g1/sim.py} | 6 +- .../g1/skill_container.py} | 0 .../go2/all_blueprints.py} | 6 +- .../{connection/go2.py => go2/connection.py} | 4 +- dimos/robot/unitree/go2/go2.py | 37 -- .../keyboard_teleop.py | 0 .../modular/detect.py | 6 +- .../mujoco_connection.py | 2 +- .../params/front_camera_720.yaml | 0 .../params/sim_camera.yaml | 0 .../{unitree_webrtc => unitree}/rosnav.py | 0 dimos/robot/unitree/run.py | 115 ----- dimos/robot/unitree/testing/__init__.py | 0 .../testing/helpers.py | 0 .../testing/mock.py | 2 +- .../testing/test_actors.py | 2 +- .../testing/test_tooling.py | 4 +- dimos/robot/unitree/type/__init__.py | 0 dimos/robot/unitree/type/lidar.py | 74 +++ dimos/robot/unitree/type/lowstate.py | 93 ++++ dimos/robot/unitree/type/map.py | 128 +++++ dimos/robot/unitree/type/odometry.py | 102 ++++ .../type/test_lidar.py | 2 +- .../type/test_odometry.py | 2 +- .../type/test_timeseries.py | 2 +- dimos/robot/unitree/type/timeseries.py | 149 ++++++ dimos/robot/unitree/type/vector.py | 442 ++++++++++++++++++ .../unitree_skill_container.py | 2 +- .../unitree_skills.py | 0 dimos/robot/unitree_webrtc/__init__.py | 33 ++ dimos/robot/unitree_webrtc/type/__init__.py | 18 + dimos/robot/unitree_webrtc/type/lidar.py | 62 +-- dimos/robot/unitree_webrtc/type/lowstate.py | 81 +--- dimos/robot/unitree_webrtc/type/map.py | 116 +---- dimos/robot/unitree_webrtc/type/odometry.py | 90 +--- dimos/robot/unitree_webrtc/type/timeseries.py | 137 +----- dimos/robot/unitree_webrtc/type/vector.py | 430 +---------------- .../unitree_webrtc/unitree_g1_blueprints.py | 275 ----------- dimos/utils/testing/test_moment.py | 6 +- dimos/utils/testing/test_replay.py | 4 +- 84 files changed, 1196 insertions(+), 1631 deletions(-) rename dimos/robot/{unitree_webrtc/testing => unitree}/__init__.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/README.md (98%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/__init__.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/b1_command.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/connection.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/joystick_module.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/joystick_server_udp.cpp (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/test_connection.py (100%) rename dimos/robot/{unitree_webrtc/unitree_b1 => unitree/b1}/unitree_b1.py (98%) rename dimos/robot/unitree/{connection => }/connection.py (98%) delete mode 100644 dimos/robot/unitree/connection/__init__.py rename dimos/robot/{unitree_webrtc => unitree}/demo_error_on_name_conflicts.py (100%) rename dimos/robot/{unitree_webrtc => unitree}/depth_module.py (100%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints}/__init__.py (56%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/agentic}/_agentic_skills.py (87%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/agentic}/unitree_g1_agentic.py (87%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/agentic}/unitree_g1_agentic_sim.py (87%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/agentic}/unitree_g1_full.py (82%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/basic}/unitree_g1_basic.py (86%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/basic}/unitree_g1_basic_sim.py (93%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/basic}/unitree_g1_joystick.py (92%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/perceptive}/_perception_and_memory.py (100%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/perceptive}/unitree_g1.py (85%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/perceptive}/unitree_g1_detection.py (98%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/perceptive}/unitree_g1_shm.py (100%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints => unitree/g1/blueprints/perceptive}/unitree_g1_sim.py (93%) rename dimos/robot/{unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py => unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py} (100%) rename dimos/robot/unitree/{connection/g1.py => g1/connection.py} (97%) delete mode 100644 dimos/robot/unitree/g1/g1agent.py delete mode 100644 dimos/robot/unitree/g1/g1detector.py delete mode 100644 dimos/robot/unitree/g1/g1zed.py rename dimos/robot/unitree/{connection/g1sim.py => g1/sim.py} (94%) rename dimos/robot/{unitree_webrtc/unitree_g1_skill_container.py => unitree/g1/skill_container.py} (100%) rename dimos/robot/{unitree_webrtc/unitree_go2_blueprints.py => unitree/go2/all_blueprints.py} (97%) rename dimos/robot/unitree/{connection/go2.py => go2/connection.py} (98%) delete mode 100644 dimos/robot/unitree/go2/go2.py rename dimos/robot/{unitree_webrtc => unitree}/keyboard_teleop.py (100%) rename dimos/robot/{unitree_webrtc => unitree}/modular/detect.py (96%) rename dimos/robot/{unitree_webrtc => unitree}/mujoco_connection.py (99%) rename dimos/robot/{unitree_webrtc => unitree}/params/front_camera_720.yaml (100%) rename dimos/robot/{unitree_webrtc => unitree}/params/sim_camera.yaml (100%) rename dimos/robot/{unitree_webrtc => unitree}/rosnav.py (100%) delete mode 100644 dimos/robot/unitree/run.py create mode 100644 dimos/robot/unitree/testing/__init__.py rename dimos/robot/{unitree_webrtc => unitree}/testing/helpers.py (100%) rename dimos/robot/{unitree_webrtc => unitree}/testing/mock.py (97%) rename dimos/robot/{unitree_webrtc => unitree}/testing/test_actors.py (97%) rename dimos/robot/{unitree_webrtc => unitree}/testing/test_tooling.py (89%) create mode 100644 dimos/robot/unitree/type/__init__.py create mode 100644 dimos/robot/unitree/type/lidar.py create mode 100644 dimos/robot/unitree/type/lowstate.py create mode 100644 dimos/robot/unitree/type/map.py create mode 100644 dimos/robot/unitree/type/odometry.py rename dimos/robot/{unitree_webrtc => unitree}/type/test_lidar.py (92%) rename dimos/robot/{unitree_webrtc => unitree}/type/test_odometry.py (97%) rename dimos/robot/{unitree_webrtc => unitree}/type/test_timeseries.py (95%) create mode 100644 dimos/robot/unitree/type/timeseries.py create mode 100644 dimos/robot/unitree/type/vector.py rename dimos/robot/{unitree_webrtc => unitree}/unitree_skill_container.py (98%) rename dimos/robot/{unitree_webrtc => unitree}/unitree_skills.py (100%) delete mode 100644 dimos/robot/unitree_webrtc/unitree_g1_blueprints.py diff --git a/dimos/agents/skills/conftest.py b/dimos/agents/skills/conftest.py index 0e2e3e0636..ae5566b9f9 100644 --- a/dimos/agents/skills/conftest.py +++ b/dimos/agents/skills/conftest.py @@ -21,7 +21,7 @@ from dimos.agents.skills.gps_nav_skill import GpsNavSkillContainer from dimos.agents.skills.navigation import NavigationSkillContainer from dimos.agents.system_prompt import SYSTEM_PROMPT -from dimos.robot.unitree_webrtc.unitree_skill_container import UnitreeSkillContainer +from dimos.robot.unitree.unitree_skill_container import UnitreeSkillContainer @pytest.fixture(autouse=True) diff --git a/dimos/agents/skills/person_follow.py b/dimos/agents/skills/person_follow.py index 0d4420632c..047fc5778f 100644 --- a/dimos/agents/skills/person_follow.py +++ b/dimos/agents/skills/person_follow.py @@ -75,7 +75,7 @@ def __init__( # Use MuJoCo camera intrinsics in simulation mode if self._global_config.simulation: - from dimos.robot.unitree_webrtc.mujoco_connection import MujocoConnection + from dimos.robot.unitree.mujoco_connection import MujocoConnection camera_info = MujocoConnection.camera_info_static diff --git a/dimos/agents/test_mock_agent.py b/dimos/agents/test_mock_agent.py index 4f449e973a..37eac2bf31 100644 --- a/dimos/agents/test_mock_agent.py +++ b/dimos/agents/test_mock_agent.py @@ -26,7 +26,7 @@ from dimos.msgs.geometry_msgs import PoseStamped, Vector3 from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.protocol.skill.test_coordinator import SkillContainerTest -from dimos.robot.unitree.connection.go2 import GO2Connection +from dimos.robot.unitree.go2.connection import GO2Connection @pytest.mark.integration diff --git a/dimos/control/tasks/__init__.py b/dimos/control/tasks/__init__.py index bbb77dc129..c4b386e151 100644 --- a/dimos/control/tasks/__init__.py +++ b/dimos/control/tasks/__init__.py @@ -14,10 +14,16 @@ """Task implementations for the ControlCoordinator.""" -from dimos.control.tasks.cartesian_ik_task import ( - CartesianIKTask, - CartesianIKTaskConfig, -) +_HAS_PINOCCHIO = True +try: + from dimos.control.tasks.cartesian_ik_task import ( + CartesianIKTask, + CartesianIKTaskConfig, + ) +except ModuleNotFoundError as exc: + if exc.name != "pinocchio": + raise + _HAS_PINOCCHIO = False from dimos.control.tasks.servo_task import ( JointServoTask, JointServoTaskConfig, @@ -32,8 +38,6 @@ ) __all__ = [ - "CartesianIKTask", - "CartesianIKTaskConfig", "JointServoTask", "JointServoTaskConfig", "JointTrajectoryTask", @@ -41,3 +45,11 @@ "JointVelocityTask", "JointVelocityTaskConfig", ] + +if _HAS_PINOCCHIO: + __all__.extend( + [ + "CartesianIKTask", + "CartesianIKTaskConfig", + ] + ) diff --git a/dimos/core/test_core.py b/dimos/core/test_core.py index acc64b63dd..50bc1dd4f2 100644 --- a/dimos/core/test_core.py +++ b/dimos/core/test_core.py @@ -29,7 +29,7 @@ from dimos.core.testing import MockRobotClient, dimos from dimos.msgs.geometry_msgs import Vector3 from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.odometry import Odometry assert dimos diff --git a/dimos/core/test_stream.py b/dimos/core/test_stream.py index b963022c50..836f879b67 100644 --- a/dimos/core/test_stream.py +++ b/dimos/core/test_stream.py @@ -25,7 +25,7 @@ ) from dimos.core.testing import MockRobotClient, dimos from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.odometry import Odometry assert dimos diff --git a/dimos/core/testing.py b/dimos/core/testing.py index 38774ef327..88598e74b8 100644 --- a/dimos/core/testing.py +++ b/dimos/core/testing.py @@ -20,8 +20,8 @@ from dimos.core import In, Module, Out, rpc, start from dimos.msgs.geometry_msgs import Vector3 from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.testing import SensorReplay diff --git a/dimos/msgs/sensor_msgs/test_PointCloud2.py b/dimos/msgs/sensor_msgs/test_PointCloud2.py index 652ff08921..437ac65e4d 100644 --- a/dimos/msgs/sensor_msgs/test_PointCloud2.py +++ b/dimos/msgs/sensor_msgs/test_PointCloud2.py @@ -26,7 +26,7 @@ ROSHeader = None from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar from dimos.utils.testing import SensorReplay # Try to import ROS types for testing diff --git a/dimos/perception/detection/conftest.py b/dimos/perception/detection/conftest.py index 8c6953e410..3b24422c47 100644 --- a/dimos/perception/detection/conftest.py +++ b/dimos/perception/detection/conftest.py @@ -35,8 +35,8 @@ ImageDetections3DPC, ) from dimos.protocol.tf import TF -from dimos.robot.unitree.connection import go2 -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.go2 import connection +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.data import get_data from dimos.utils.testing import TimedSensorReplay @@ -100,7 +100,7 @@ def moment_provider(**kwargs) -> Moment: if odom_frame is None: raise ValueError("No odom frame found") - transforms = go2.GO2Connection._odom_to_tf(odom_frame) + transforms = connection.GO2Connection._odom_to_tf(odom_frame) tf.receive_transform(*transforms) @@ -108,7 +108,7 @@ def moment_provider(**kwargs) -> Moment: "odom_frame": odom_frame, "lidar_frame": lidar_frame, "image_frame": image_frame, - "camera_info": go2._camera_info_static(), + "camera_info": connection._camera_info_static(), "transforms": transforms, "tf": tf, } @@ -260,8 +260,8 @@ def object_db_module(get_moment): from dimos.perception.detection.detectors import Yolo2DDetector module2d = Detection2DModule(detector=lambda: Yolo2DDetector(device="cpu")) - module3d = Detection3DModule(camera_info=go2._camera_info_static()) - moduleDB = ObjectDBModule(camera_info=go2._camera_info_static()) + module3d = Detection3DModule(camera_info=connection._camera_info_static()) + moduleDB = ObjectDBModule(camera_info=connection._camera_info_static()) # Process 5 frames to build up object history for i in range(5): diff --git a/dimos/perception/detection/test_moduleDB.py b/dimos/perception/detection/test_moduleDB.py index e9815f1f3e..8445372bf6 100644 --- a/dimos/perception/detection/test_moduleDB.py +++ b/dimos/perception/detection/test_moduleDB.py @@ -22,16 +22,16 @@ from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.moduleDB import ObjectDBModule -from dimos.robot.unitree.connection import go2 +from dimos.robot.unitree.go2 import connection @pytest.mark.module def test_moduleDB(dimos_cluster) -> None: - connection = go2.deploy(dimos_cluster, "fake") + connection = connection.deploy(dimos_cluster, "fake") moduleDB = dimos_cluster.deploy( ObjectDBModule, - camera_info=go2._camera_info_static(), + camera_info=connection._camera_info_static(), goto=lambda obj_id: print(f"Going to {obj_id}"), ) moduleDB.image.connect(connection.video) diff --git a/dimos/perception/test_spatial_memory_module.py b/dimos/perception/test_spatial_memory_module.py index 47518b889b..11b8eec562 100644 --- a/dimos/perception/test_spatial_memory_module.py +++ b/dimos/perception/test_spatial_memory_module.py @@ -25,7 +25,7 @@ from dimos.msgs.sensor_msgs import Image from dimos.perception.spatial_perception import SpatialMemory from dimos.protocol import pubsub -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.data import get_data from dimos.utils.logging_config import setup_logger from dimos.utils.testing import TimedSensorReplay diff --git a/dimos/protocol/mcp/test_mcp_module.py b/dimos/protocol/mcp/test_mcp_module.py index 2a247e6ff0..8ae587221e 100644 --- a/dimos/protocol/mcp/test_mcp_module.py +++ b/dimos/protocol/mcp/test_mcp_module.py @@ -30,7 +30,7 @@ def test_unitree_blueprint_has_mcp() -> None: - contents = Path("dimos/robot/unitree_webrtc/unitree_go2_blueprints.py").read_text() + contents = Path("dimos/robot/unitree/go2/all_blueprints.py").read_text() assert "agentic_mcp" in contents assert "MCPModule.blueprint()" in contents diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index db100cfca0..d5c60afda0 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -30,34 +30,35 @@ "coordinator-xarm6": "dimos.control.blueprints:coordinator_xarm6", "coordinator-xarm7": "dimos.control.blueprints:coordinator_xarm7", "demo-camera": "dimos.hardware.sensors.camera.module:demo_camera", - "demo-error-on-name-conflicts": "dimos.robot.unitree_webrtc.demo_error_on_name_conflicts:demo_error_on_name_conflicts", + "demo-error-on-name-conflicts": "dimos.robot.unitree.demo_error_on_name_conflicts:demo_error_on_name_conflicts", "demo-google-maps-skill": "dimos.agents.skills.demo_google_maps_skill:demo_google_maps_skill", "demo-gps-nav": "dimos.agents.skills.demo_gps_nav:demo_gps_nav", "demo-object-scene-registration": "dimos.perception.demo_object_scene_registration:demo_object_scene_registration", "demo-osm": "dimos.mapping.osm.demo_osm:demo_osm", "demo-skill": "dimos.agents.skills.demo_skill:demo_skill", "dual-xarm6-planner": "dimos.manipulation.manipulation_blueprints:dual_xarm6_planner", - "unitree-g1": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1", - "unitree-g1-agentic": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_agentic", - "unitree-g1-agentic-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_agentic_sim", - "unitree-g1-basic": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_basic", - "unitree-g1-basic-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_basic_sim", - "unitree-g1-detection": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_detection", - "unitree-g1-full": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_full", - "unitree-g1-joystick": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_joystick", - "unitree-g1-shm": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_shm", - "unitree-g1-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_sim", - "unitree-go2": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2", - "unitree-go2-agentic": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic", - "unitree-go2-agentic-huggingface": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_huggingface", - "unitree-go2-agentic-mcp": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_mcp", - "unitree-go2-agentic-ollama": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_ollama", - "unitree-go2-basic": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_basic", - "unitree-go2-detection": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_detection", - "unitree-go2-ros": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_ros", - "unitree-go2-spatial": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_spatial", - "unitree-go2-temporal-memory": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_temporal_memory", - "unitree-go2-vlm-stream-test": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_vlm_stream_test", + "uintree-g1-basic-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_basic_no_nav", + "unitree-g1": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1:unitree_g1", + "unitree-g1-agentic": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic:unitree_g1_agentic", + "unitree-g1-agentic-sim": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim:unitree_g1_agentic_sim", + "unitree-g1-basic": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic:unitree_g1_basic", + "unitree-g1-basic-sim": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim:unitree_g1_basic_sim", + "unitree-g1-detection": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_detection:unitree_g1_detection", + "unitree-g1-full": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_full:unitree_g1_full", + "unitree-g1-joystick": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick:unitree_g1_joystick", + "unitree-g1-shm": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm:unitree_g1_shm", + "unitree-g1-sim": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim:unitree_g1_sim", + "unitree-go2": "dimos.robot.unitree.go2.all_blueprints:unitree_go2", + "unitree-go2-agentic": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic", + "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_huggingface", + "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_mcp", + "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_ollama", + "unitree-go2-basic": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_basic", + "unitree-go2-detection": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_detection", + "unitree-go2-ros": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_ros", + "unitree-go2-spatial": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_spatial", + "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_temporal_memory", + "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_vlm_stream_test", "xarm6-planner-only": "dimos.manipulation.manipulation_blueprints:xarm6_planner_only", "xarm7-planner-coordinator": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator", } @@ -70,22 +71,22 @@ "cost_mapper": "dimos.mapping.costmapper", "demo_calculator_skill": "dimos.agents.skills.demo_calculator_skill", "demo_robot": "dimos.agents.skills.demo_robot", - "depth_module": "dimos.robot.unitree_webrtc.depth_module", + "depth_module": "dimos.robot.unitree.depth_module", "detection3d_module": "dimos.perception.detection.module3D", "detection_db_module": "dimos.perception.detection.moduleDB", "foxglove_bridge": "dimos.robot.foxglove_bridge", - "g1_connection": "dimos.robot.unitree.connection.g1", - "g1_sim_connection": "dimos.robot.unitree.connection.g1sim", - "g1_skills": "dimos.robot.unitree_webrtc.unitree_g1_skill_container", - "go2_connection": "dimos.robot.unitree.connection.go2", + "g1_connection": "dimos.robot.unitree.g1.connection", + "g1_sim_connection": "dimos.robot.unitree.g1.sim", + "g1_skills": "dimos.robot.unitree.g1.skill_container", + "go2_connection": "dimos.robot.unitree.go2.connection", "google_maps_skill": "dimos.agents.skills.google_maps_skill_container", "gps_nav_skill": "dimos.agents.skills.gps_nav_skill", "human_input": "dimos.agents.cli.human", "joint_trajectory_controller": "dimos.manipulation.control.trajectory_controller.joint_trajectory_controller", - "keyboard_teleop": "dimos.robot.unitree_webrtc.keyboard_teleop", + "keyboard_teleop": "dimos.robot.unitree.keyboard_teleop", "llm_agent": "dimos.agents.agent", "manipulation_module": "dimos.manipulation.manipulation_module", - "mapper": "dimos.robot.unitree_webrtc.type.map", + "mapper": "dimos.robot.unitree.type.map", "navigation_skill": "dimos.agents.skills.navigation", "object_scene_registration_module": "dimos.perception.object_scene_registration", "object_tracking": "dimos.perception.object_tracker", @@ -99,7 +100,7 @@ "spatial_memory": "dimos.perception.spatial_perception", "speak_skill": "dimos.agents.skills.speak_skill", "temporal_memory": "dimos.perception.experimental.temporal_memory.temporal_memory", - "unitree_skills": "dimos.robot.unitree_webrtc.unitree_skill_container", + "unitree_skills": "dimos.robot.unitree.unitree_skill_container", "utilization": "dimos.utils.monitoring", "vlm_agent": "dimos.agents.vlm_agent", "vlm_stream_tester": "dimos.agents.vlm_stream_tester", diff --git a/dimos/robot/unitree_webrtc/testing/__init__.py b/dimos/robot/unitree/__init__.py similarity index 100% rename from dimos/robot/unitree_webrtc/testing/__init__.py rename to dimos/robot/unitree/__init__.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/README.md b/dimos/robot/unitree/b1/README.md similarity index 98% rename from dimos/robot/unitree_webrtc/unitree_b1/README.md rename to dimos/robot/unitree/b1/README.md index f59e6a57ae..1443067a2a 100644 --- a/dimos/robot/unitree_webrtc/unitree_b1/README.md +++ b/dimos/robot/unitree/b1/README.md @@ -112,7 +112,7 @@ pip install -e .[cpu,sim] #### With Joystick Control (Recommended for Testing) ```bash -python -m dimos.robot.unitree_webrtc.unitree_b1.unitree_b1 \ +python -m dimos.robot.unitree.b1.unitree_b1 \ --ip 192.168.12.1 \ --port 9090 \ --joystick @@ -129,7 +129,7 @@ python -m dimos.robot.unitree_webrtc.unitree_b1.unitree_b1 \ #### Test Mode (No Robot Required) ```bash -python -m dimos.robot.unitree_webrtc.unitree_b1.unitree_b1 \ +python -m dimos.robot.unitree.b1.unitree_b1 \ --test \ --joystick ``` diff --git a/dimos/robot/unitree_webrtc/unitree_b1/__init__.py b/dimos/robot/unitree/b1/__init__.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/__init__.py rename to dimos/robot/unitree/b1/__init__.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/b1_command.py b/dimos/robot/unitree/b1/b1_command.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/b1_command.py rename to dimos/robot/unitree/b1/b1_command.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/connection.py b/dimos/robot/unitree/b1/connection.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/connection.py rename to dimos/robot/unitree/b1/connection.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/joystick_module.py b/dimos/robot/unitree/b1/joystick_module.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/joystick_module.py rename to dimos/robot/unitree/b1/joystick_module.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/joystick_server_udp.cpp b/dimos/robot/unitree/b1/joystick_server_udp.cpp similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/joystick_server_udp.cpp rename to dimos/robot/unitree/b1/joystick_server_udp.cpp diff --git a/dimos/robot/unitree_webrtc/unitree_b1/test_connection.py b/dimos/robot/unitree/b1/test_connection.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_b1/test_connection.py rename to dimos/robot/unitree/b1/test_connection.py diff --git a/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py b/dimos/robot/unitree/b1/unitree_b1.py similarity index 98% rename from dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py rename to dimos/robot/unitree/b1/unitree_b1.py index ff608c2b1f..447aacf428 100644 --- a/dimos/robot/unitree_webrtc/unitree_b1/unitree_b1.py +++ b/dimos/robot/unitree/b1/unitree_b1.py @@ -32,7 +32,7 @@ from dimos.msgs.tf2_msgs.TFMessage import TFMessage from dimos.robot.robot import Robot from dimos.robot.ros_bridge import BridgeDirection, ROSBridge -from dimos.robot.unitree_webrtc.unitree_b1.connection import ( +from dimos.robot.unitree.b1.connection import ( B1ConnectionModule, MockB1ConnectionModule, ) @@ -124,7 +124,7 @@ def start(self) -> None: # Deploy joystick move_vel control if self.enable_joystick: - from dimos.robot.unitree_webrtc.unitree_b1.joystick_module import JoystickModule + from dimos.robot.unitree.b1.joystick_module import JoystickModule self.joystick = self._dimos.deploy(JoystickModule) # type: ignore[assignment] self.joystick.twist_out.transport = core.LCMTransport("/cmd_vel", TwistStamped) # type: ignore[attr-defined] diff --git a/dimos/robot/unitree/connection/connection.py b/dimos/robot/unitree/connection.py similarity index 98% rename from dimos/robot/unitree/connection/connection.py rename to dimos/robot/unitree/connection.py index fd365aca33..217c7e72b0 100644 --- a/dimos/robot/unitree/connection/connection.py +++ b/dimos/robot/unitree/connection.py @@ -39,9 +39,9 @@ from dimos.msgs.geometry_msgs import Pose, Transform, Twist from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.sensor_msgs.Image import ImageFormat -from dimos.robot.unitree_webrtc.type.lidar import RawLidarMsg, pointcloud2_from_webrtc_lidar -from dimos.robot.unitree_webrtc.type.lowstate import LowStateMsg -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.lidar import RawLidarMsg, pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.lowstate import LowStateMsg +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.decorators.decorators import simple_mcache from dimos.utils.reactive import backpressure, callback_to_observable diff --git a/dimos/robot/unitree/connection/__init__.py b/dimos/robot/unitree/connection/__init__.py deleted file mode 100644 index 5c1dff1922..0000000000 --- a/dimos/robot/unitree/connection/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import dimos.robot.unitree.connection.g1 as g1 -import dimos.robot.unitree.connection.go2 as go2 - -__all__ = ["g1", "go2"] diff --git a/dimos/robot/unitree_webrtc/demo_error_on_name_conflicts.py b/dimos/robot/unitree/demo_error_on_name_conflicts.py similarity index 100% rename from dimos/robot/unitree_webrtc/demo_error_on_name_conflicts.py rename to dimos/robot/unitree/demo_error_on_name_conflicts.py diff --git a/dimos/robot/unitree_webrtc/depth_module.py b/dimos/robot/unitree/depth_module.py similarity index 100% rename from dimos/robot/unitree_webrtc/depth_module.py rename to dimos/robot/unitree/depth_module.py diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py b/dimos/robot/unitree/g1/blueprints/__init__.py similarity index 56% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py rename to dimos/robot/unitree/g1/blueprints/__init__.py index 5a3e7b8f84..08efb8c82b 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/__init__.py +++ b/dimos/robot/unitree/g1/blueprints/__init__.py @@ -15,19 +15,22 @@ """Cascaded G1 blueprints split into focused modules.""" -from ._agentic_skills import _agentic_skills -from ._perception_and_memory import _perception_and_memory -from .uintree_g1_basic_no_nav import basic_no_nav -from .unitree_g1 import unitree_g1 -from .unitree_g1_agentic import unitree_g1_agentic -from .unitree_g1_agentic_sim import unitree_g1_agentic_sim -from .unitree_g1_basic import unitree_g1_basic -from .unitree_g1_basic_sim import unitree_g1_basic_sim -from .unitree_g1_detection import unitree_g1_detection -from .unitree_g1_full import unitree_g1_full -from .unitree_g1_joystick import unitree_g1_joystick -from .unitree_g1_shm import unitree_g1_shm -from .unitree_g1_sim import unitree_g1_sim +from .agentic._agentic_skills import _agentic_skills +from .agentic.unitree_g1_agentic import unitree_g1_agentic +from .agentic.unitree_g1_agentic_sim import unitree_g1_agentic_sim +from .agentic.unitree_g1_full import unitree_g1_full +from .basic.unitree_g1_basic import unitree_g1_basic +from .basic.unitree_g1_basic_sim import unitree_g1_basic_sim +from .basic.unitree_g1_joystick import unitree_g1_joystick +from .primitive.uintree_g1_primitive_no_nav import ( + uintree_g1_basic_no_nav, + uintree_g1_basic_no_nav as basic_no_nav, +) +from .smart._perception_and_memory import _perception_and_memory +from .smart.unitree_g1 import unitree_g1 +from .smart.unitree_g1_detection import unitree_g1_detection +from .smart.unitree_g1_shm import unitree_g1_shm +from .smart.unitree_g1_sim import unitree_g1_sim __all__ = [ "_agentic_skills", diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py b/dimos/robot/unitree/g1/blueprints/agentic/_agentic_skills.py similarity index 87% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py rename to dimos/robot/unitree/g1/blueprints/agentic/_agentic_skills.py index 0436294bf6..5ca139f968 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_agentic_skills.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/_agentic_skills.py @@ -19,13 +19,13 @@ from dimos.agents.cli.human import human_input from dimos.agents.skills.navigation import navigation_skill from dimos.core.blueprints import autoconnect -from dimos.robot.unitree_webrtc.unitree_g1_skill_container import g1_skills +from dimos.robot.unitree.g1.skill_container import g1_skills -agentic_skills = autoconnect( +_agentic_skills = autoconnect( llm_agent(), human_input(), navigation_skill(), g1_skills(), ) -__all__ = ["agentic_skills"] +__all__ = ["_agentic_skills"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py similarity index 87% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py rename to dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py index 200191f256..9f4167e326 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py @@ -17,12 +17,12 @@ from dimos.core.blueprints import autoconnect -from ._agentic_skills import agentic_skills -from .unitree_g1 import unitree_g1 +from ..smart.unitree_g1 import unitree_g1 +from ._agentic_skills import _agentic_skills unitree_g1_agentic = autoconnect( unitree_g1, - agentic_skills, + _agentic_skills, ) __all__ = ["unitree_g1_agentic"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py similarity index 87% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py rename to dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py index f2a0c68334..4cf27eae89 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_agentic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py @@ -17,12 +17,12 @@ from dimos.core.blueprints import autoconnect -from ._agentic_skills import agentic_skills -from .unitree_g1_sim import unitree_g1_sim +from ..smart.unitree_g1_sim import unitree_g1_sim +from ._agentic_skills import _agentic_skills unitree_g1_agentic_sim = autoconnect( unitree_g1_sim, - agentic_skills, + _agentic_skills, ) __all__ = ["unitree_g1_agentic_sim"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py similarity index 82% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py rename to dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py index 30e3c9ba80..951a44c813 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_full.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py @@ -16,14 +16,14 @@ """Full featured G1 stack with agentic skills and teleop.""" from dimos.core.blueprints import autoconnect -from dimos.robot.unitree_webrtc.keyboard_teleop import keyboard_teleop +from dimos.robot.unitree.keyboard_teleop import keyboard_teleop -from ._agentic_skills import agentic_skills -from .unitree_g1_shm import unitree_g1_shm +from ..smart.unitree_g1_shm import unitree_g1_shm +from ._agentic_skills import _agentic_skills unitree_g1_full = autoconnect( unitree_g1_shm, - agentic_skills, + _agentic_skills, keyboard_teleop(), ) diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py similarity index 86% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py rename to dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py index 6f227fb8e1..5310918a25 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py @@ -17,9 +17,9 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.rosnav import ros_nav -from dimos.robot.unitree.connection.g1 import g1_connection +from dimos.robot.unitree.g1.connection import g1_connection -from .uintree_g1_basic_no_nav import uintree_g1_basic_no_nav +from ..primitive.uintree_g1_primitive_no_nav import uintree_g1_basic_no_nav unitree_g1_basic = autoconnect( uintree_g1_basic_no_nav, diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py similarity index 93% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py rename to dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py index 87f9d24d8e..c740ea7fe6 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_basic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py @@ -17,7 +17,7 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.replanning_a_star.module import replanning_a_star_planner -from dimos.robot.unitree.connection.g1sim import g1_sim_connection +from dimos.robot.unitree.g1.sim import g1_sim_connection from .unitree_g1_basic import unitree_g1_basic diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py similarity index 92% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py rename to dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py index 23550b89cc..fab068fa7e 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_joystick.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py @@ -16,7 +16,7 @@ """G1 stack with keyboard teleop.""" from dimos.core.blueprints import autoconnect -from dimos.robot.unitree_webrtc.keyboard_teleop import keyboard_teleop +from dimos.robot.unitree.keyboard_teleop import keyboard_teleop from .unitree_g1_basic import unitree_g1_basic diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py b/dimos/robot/unitree/g1/blueprints/perceptive/_perception_and_memory.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/_perception_and_memory.py rename to dimos/robot/unitree/g1/blueprints/perceptive/_perception_and_memory.py diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py similarity index 85% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py rename to dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py index b02df3be42..f185eac164 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py @@ -17,12 +17,12 @@ from dimos.core.blueprints import autoconnect -from ._perception_and_memory import perception_and_memory -from .unitree_g1_basic import unitree_g1_basic +from ..basic.unitree_g1_basic import unitree_g1_basic +from ._perception_and_memory import _perception_and_memory unitree_g1 = autoconnect( unitree_g1_basic, - perception_and_memory, + _perception_and_memory, ).global_config(n_dask_workers=8) __all__ = ["unitree_g1"] diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py similarity index 98% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py rename to dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py index 3efa3dd44d..cb2119cbf8 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_detection.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py @@ -29,7 +29,7 @@ from dimos.perception.detection.moduleDB import ObjectDBModule, detection_db_module from dimos.perception.detection.person_tracker import PersonTracker, person_tracker_module -from .unitree_g1_basic import unitree_g1_basic +from ..basic.unitree_g1_basic import unitree_g1_basic unitree_g1_detection = ( autoconnect( diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_shm.py rename to dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py similarity index 93% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py rename to dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py index bf3383eb9e..713dc35ce5 100644 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/unitree_g1_sim.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py @@ -17,8 +17,8 @@ from dimos.core.blueprints import autoconnect +from ..basic.unitree_g1_basic_sim import unitree_g1_basic_sim from ._perception_and_memory import _perception_and_memory -from .unitree_g1_basic_sim import unitree_g1_basic_sim unitree_g1_sim = autoconnect( unitree_g1_basic_sim, diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py b/dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_g1_blueprints/uintree_g1_basic_no_nav.py rename to dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py diff --git a/dimos/robot/unitree/connection/g1.py b/dimos/robot/unitree/g1/connection.py similarity index 97% rename from dimos/robot/unitree/connection/g1.py rename to dimos/robot/unitree/g1/connection.py index 1e15809146..8c94d279c9 100644 --- a/dimos/robot/unitree/connection/g1.py +++ b/dimos/robot/unitree/g1/connection.py @@ -21,7 +21,7 @@ from dimos.core import DimosCluster, In, Module, rpc from dimos.core.global_config import GlobalConfig from dimos.msgs.geometry_msgs import Twist -from dimos.robot.unitree.connection.connection import UnitreeWebRTCConnection +from dimos.robot.unitree.connection import UnitreeWebRTCConnection from dimos.utils.logging_config import setup_logger logger = setup_logger() diff --git a/dimos/robot/unitree/g1/g1agent.py b/dimos/robot/unitree/g1/g1agent.py deleted file mode 100644 index a95a905b7d..0000000000 --- a/dimos/robot/unitree/g1/g1agent.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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. - -from dimos import agents -from dimos.agents.skills.navigation import NavigationSkillContainer -from dimos.core import DimosCluster -from dimos.perception import spatial_perception -from dimos.robot.unitree.g1 import g1detector - - -def deploy(dimos: DimosCluster, ip: str): # type: ignore[no-untyped-def] - g1 = g1detector.deploy(dimos, ip) - - nav = g1.get("nav") - camera = g1.get("camera") - detector3d = g1.get("detector3d") - connection = g1.get("connection") - - spatialmem = spatial_perception.deploy(dimos, camera) - - navskills = dimos.deploy( # type: ignore[attr-defined] - NavigationSkillContainer, - spatialmem, - nav, - detector3d, - ) - navskills.start() - - agent = agents.deploy( # type: ignore[attr-defined] - dimos, - "You are controling a humanoid robot", - skill_containers=[connection, nav, camera, spatialmem, navskills], - ) - agent.run_implicit_skill("current_position") - agent.run_implicit_skill("video_stream") - - return {"agent": agent, "spatialmem": spatialmem, **g1} diff --git a/dimos/robot/unitree/g1/g1detector.py b/dimos/robot/unitree/g1/g1detector.py deleted file mode 100644 index 55986eb087..0000000000 --- a/dimos/robot/unitree/g1/g1detector.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. - -from dimos.core import DimosCluster -from dimos.perception.detection import module3D, moduleDB -from dimos.perception.detection.detectors.person.yolo import YoloPersonDetector -from dimos.robot.unitree.g1 import g1zed - - -def deploy(dimos: DimosCluster, ip: str): # type: ignore[no-untyped-def] - g1 = g1zed.deploy(dimos, ip) - - nav = g1.get("nav") - camera = g1.get("camera") - - person_detector = module3D.deploy( - dimos, - camera=camera, - lidar=nav, - detector=YoloPersonDetector, - ) - - detector3d = moduleDB.deploy( # type: ignore[attr-defined] - dimos, - camera=camera, - lidar=nav, - filter=lambda det: det.class_id != 0, - ) - - return {"person_detector": person_detector, "detector3d": detector3d, **g1} diff --git a/dimos/robot/unitree/g1/g1zed.py b/dimos/robot/unitree/g1/g1zed.py deleted file mode 100644 index cafcbec909..0000000000 --- a/dimos/robot/unitree/g1/g1zed.py +++ /dev/null @@ -1,90 +0,0 @@ -# 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. - -from typing import TypedDict, cast - -from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE -from dimos.core import DimosCluster, LCMTransport, pSHMTransport -from dimos.hardware.sensors.camera import zed -from dimos.hardware.sensors.camera.module import CameraModule -from dimos.hardware.sensors.camera.webcam import Webcam -from dimos.msgs.geometry_msgs import ( - Quaternion, - Transform, - Vector3, -) -from dimos.msgs.sensor_msgs import CameraInfo -from dimos.navigation import rosnav -from dimos.navigation.rosnav import ROSNav -from dimos.robot import foxglove_bridge -from dimos.robot.unitree.connection import g1 -from dimos.robot.unitree.connection.g1 import G1Connection -from dimos.utils.logging_config import setup_logger - -logger = setup_logger() - - -class G1ZedDeployResult(TypedDict): - nav: ROSNav - connection: G1Connection - camera: CameraModule - camerainfo: CameraInfo - - -def deploy_g1_monozed(dimos: DimosCluster) -> CameraModule: - camera = cast( - "CameraModule", - dimos.deploy( # type: ignore[attr-defined] - CameraModule, - frequency=4.0, - transform=Transform( - translation=Vector3(0.05, 0.0, 0.0), - rotation=Quaternion.from_euler(Vector3(0.0, 0.0, 0.0)), - frame_id="sensor", - child_frame_id="camera_link", - ), - hardware=lambda: Webcam( - camera_index=0, - fps=5, - stereo_slice="left", - camera_info=zed.CameraInfo.SingleWebcam, - ), - ), - ) - - camera.color_image.transport = pSHMTransport( - "/image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE - ) - camera.camera_info.transport = LCMTransport("/camera_info", CameraInfo) - camera.start() - return camera - - -def deploy(dimos: DimosCluster, ip: str): # type: ignore[no-untyped-def] - nav = rosnav.deploy( # type: ignore[call-arg] - dimos, - sensor_to_base_link_transform=Transform( - frame_id="sensor", child_frame_id="base_link", translation=Vector3(0.0, 0.0, 1.5) - ), - ) - connection = g1.deploy(dimos, ip, nav) - zedcam = deploy_g1_monozed(dimos) - - foxglove_bridge.deploy(dimos) - - return { - "nav": nav, - "connection": connection, - "camera": zedcam, - } diff --git a/dimos/robot/unitree/connection/g1sim.py b/dimos/robot/unitree/g1/sim.py similarity index 94% rename from dimos/robot/unitree/connection/g1sim.py rename to dimos/robot/unitree/g1/sim.py index 196b11f214..e8a68e7cb2 100644 --- a/dimos/robot/unitree/connection/g1sim.py +++ b/dimos/robot/unitree/g1/sim.py @@ -28,11 +28,11 @@ Vector3, ) from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.odometry import Odometry as SimOdometry +from dimos.robot.unitree.type.odometry import Odometry as SimOdometry from dimos.utils.logging_config import setup_logger if TYPE_CHECKING: - from dimos.robot.unitree_webrtc.mujoco_connection import MujocoConnection + from dimos.robot.unitree.mujoco_connection import MujocoConnection logger = setup_logger() @@ -60,7 +60,7 @@ def __init__( def start(self) -> None: super().start() - from dimos.robot.unitree_webrtc.mujoco_connection import MujocoConnection + from dimos.robot.unitree.mujoco_connection import MujocoConnection self.connection = MujocoConnection(self._global_config) assert self.connection is not None diff --git a/dimos/robot/unitree_webrtc/unitree_g1_skill_container.py b/dimos/robot/unitree/g1/skill_container.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_g1_skill_container.py rename to dimos/robot/unitree/g1/skill_container.py diff --git a/dimos/robot/unitree_webrtc/unitree_go2_blueprints.py b/dimos/robot/unitree/go2/all_blueprints.py similarity index 97% rename from dimos/robot/unitree_webrtc/unitree_go2_blueprints.py rename to dimos/robot/unitree/go2/all_blueprints.py index 6dda182c15..ecb72cdac2 100644 --- a/dimos/robot/unitree_webrtc/unitree_go2_blueprints.py +++ b/dimos/robot/unitree/go2/all_blueprints.py @@ -58,9 +58,9 @@ from dimos.perception.spatial_perception import spatial_memory from dimos.protocol.mcp.mcp import MCPModule from dimos.robot.foxglove_bridge import foxglove_bridge -import dimos.robot.unitree.connection.go2 as _go2_mod -from dimos.robot.unitree.connection.go2 import GO2Connection, go2_connection -from dimos.robot.unitree_webrtc.unitree_skill_container import unitree_skills +import dimos.robot.unitree.go2.connection as _go2_mod +from dimos.robot.unitree.go2.connection import GO2Connection, go2_connection +from dimos.robot.unitree.unitree_skill_container import unitree_skills from dimos.utils.monitoring import utilization from dimos.web.websocket_vis.websocket_vis_module import websocket_vis diff --git a/dimos/robot/unitree/connection/go2.py b/dimos/robot/unitree/go2/connection.py similarity index 98% rename from dimos/robot/unitree/connection/go2.py rename to dimos/robot/unitree/go2/connection.py index c971161964..c017755773 100644 --- a/dimos/robot/unitree/connection/go2.py +++ b/dimos/robot/unitree/go2/connection.py @@ -38,7 +38,7 @@ from dimos.msgs.sensor_msgs.Image import ImageFormat from dimos.protocol.skill.skill import skill from dimos.protocol.skill.type import Output -from dimos.robot.unitree.connection.connection import UnitreeWebRTCConnection +from dimos.robot.unitree.connection import UnitreeWebRTCConnection from dimos.utils.data import get_data from dimos.utils.decorators.decorators import simple_mcache from dimos.utils.testing import TimedSensorReplay, TimedSensorStorage @@ -184,7 +184,7 @@ def __init__( # type: ignore[no-untyped-def] if ip in ["fake", "mock", "replay"] or connection_type == "replay": self.connection = ReplayConnection() elif ip == "mujoco" or connection_type == "mujoco": - from dimos.robot.unitree_webrtc.mujoco_connection import MujocoConnection + from dimos.robot.unitree.mujoco_connection import MujocoConnection self.connection = MujocoConnection(self._global_config) else: diff --git a/dimos/robot/unitree/go2/go2.py b/dimos/robot/unitree/go2/go2.py deleted file mode 100644 index d2e7e74674..0000000000 --- a/dimos/robot/unitree/go2/go2.py +++ /dev/null @@ -1,37 +0,0 @@ -# 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. - -import logging - -from dimos.core import DimosCluster -from dimos.robot import foxglove_bridge -from dimos.robot.unitree.connection import go2 -from dimos.utils.logging_config import setup_logger - -logger = setup_logger(level=logging.INFO) - - -def deploy(dimos: DimosCluster, ip: str): # type: ignore[no-untyped-def] - connection = go2.deploy(dimos, ip) - foxglove_bridge.deploy(dimos) - - # detector = moduleDB.deploy( - # dimos, - # camera=connection, - # lidar=connection, - # ) - - # agent = agents.deploy(dimos) - # agent.register_skills(detector) - return connection diff --git a/dimos/robot/unitree_webrtc/keyboard_teleop.py b/dimos/robot/unitree/keyboard_teleop.py similarity index 100% rename from dimos/robot/unitree_webrtc/keyboard_teleop.py rename to dimos/robot/unitree/keyboard_teleop.py diff --git a/dimos/robot/unitree_webrtc/modular/detect.py b/dimos/robot/unitree/modular/detect.py similarity index 96% rename from dimos/robot/unitree_webrtc/modular/detect.py rename to dimos/robot/unitree/modular/detect.py index 8f92d15e81..e5999e9fd8 100644 --- a/dimos/robot/unitree_webrtc/modular/detect.py +++ b/dimos/robot/unitree/modular/detect.py @@ -18,8 +18,8 @@ from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.std_msgs import Header -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.odometry import Odometry image_resize_factor = 1 originalwidth, originalheight = (1280, 720) @@ -141,7 +141,7 @@ def process_data(): # type: ignore[no-untyped-def] Detection2DModule, build_imageannotations, ) - from dimos.robot.unitree_webrtc.type.odometry import Odometry + from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.data import get_data from dimos.utils.testing import TimedSensorReplay diff --git a/dimos/robot/unitree_webrtc/mujoco_connection.py b/dimos/robot/unitree/mujoco_connection.py similarity index 99% rename from dimos/robot/unitree_webrtc/mujoco_connection.py rename to dimos/robot/unitree/mujoco_connection.py index 1bfdef2e3c..f998ae1dd9 100644 --- a/dimos/robot/unitree_webrtc/mujoco_connection.py +++ b/dimos/robot/unitree/mujoco_connection.py @@ -37,7 +37,7 @@ from dimos.core.global_config import GlobalConfig from dimos.msgs.geometry_msgs import Quaternion, Twist, Vector3 from dimos.msgs.sensor_msgs import CameraInfo, Image, ImageFormat, PointCloud2 -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.odometry import Odometry from dimos.simulation.mujoco.constants import ( LAUNCHER_PATH, LIDAR_FPS, diff --git a/dimos/robot/unitree_webrtc/params/front_camera_720.yaml b/dimos/robot/unitree/params/front_camera_720.yaml similarity index 100% rename from dimos/robot/unitree_webrtc/params/front_camera_720.yaml rename to dimos/robot/unitree/params/front_camera_720.yaml diff --git a/dimos/robot/unitree_webrtc/params/sim_camera.yaml b/dimos/robot/unitree/params/sim_camera.yaml similarity index 100% rename from dimos/robot/unitree_webrtc/params/sim_camera.yaml rename to dimos/robot/unitree/params/sim_camera.yaml diff --git a/dimos/robot/unitree_webrtc/rosnav.py b/dimos/robot/unitree/rosnav.py similarity index 100% rename from dimos/robot/unitree_webrtc/rosnav.py rename to dimos/robot/unitree/rosnav.py diff --git a/dimos/robot/unitree/run.py b/dimos/robot/unitree/run.py deleted file mode 100644 index 5b17ad7a9d..0000000000 --- a/dimos/robot/unitree/run.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -# 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. - -""" -Centralized runner for modular Unitree robot deployment scripts. - -Usage: - python run.py g1agent --ip 192.168.1.100 - python run.py g1/g1zed --ip $ROBOT_IP - python run.py go2/go2.py --ip $ROBOT_IP - python run.py connection/g1.py --ip $ROBOT_IP -""" - -import argparse -import importlib -import os -import sys - -from dotenv import load_dotenv - -from dimos.core import start, wait_exit - - -def main() -> None: - load_dotenv() - - parser = argparse.ArgumentParser(description="Unitree Robot Modular Deployment Runner") - parser.add_argument( - "module", - help="Module name/path to run (e.g., g1agent, g1/g1zed, go2/go2.py)", - ) - parser.add_argument( - "--ip", - default=os.getenv("ROBOT_IP"), - help="Robot IP address (default: ROBOT_IP from .env)", - ) - parser.add_argument( - "--workers", - type=int, - default=8, - help="Number of worker threads for DimosCluster (default: 8)", - ) - - args = parser.parse_args() - - # Validate IP address - if not args.ip: - print("ERROR: Robot IP address not provided") - print("Please provide --ip or set ROBOT_IP in .env") - sys.exit(1) - - # Parse the module path - module_path = args.module - - # Remove .py extension if present - if module_path.endswith(".py"): - module_path = module_path[:-3] - - # Convert path separators to dots for import - module_path = module_path.replace("/", ".") - - # Import the module - try: - # Build the full import path - full_module_path = f"dimos.robot.unitree.{module_path}" - print(f"Importing module: {full_module_path}") - module = importlib.import_module(full_module_path) - except ImportError: - # Try as a relative import from the unitree package - try: - module = importlib.import_module(f".{module_path}", package="dimos.robot.unitree") - except ImportError as e2: - import traceback - - traceback.print_exc() - - print(f"\nERROR: Could not import module '{args.module}'") - print("Tried importing as:") - print(f" 1. {full_module_path}") - print(" 2. Relative import from dimos.robot.unitree") - print("Make sure the module exists in dimos/robot/unitree/") - print(f"Import error: {e2}") - - sys.exit(1) - - # Verify deploy function exists - if not hasattr(module, "deploy"): - print(f"ERROR: Module '{args.module}' does not have a 'deploy' function") - sys.exit(1) - - print(f"Running {args.module}.deploy() with IP {args.ip}") - - # Run the standard deployment pattern - dimos = start(args.workers) - try: - module.deploy(dimos, args.ip) - wait_exit() - finally: - dimos.close_all() # type: ignore[attr-defined] - - -if __name__ == "__main__": - main() diff --git a/dimos/robot/unitree/testing/__init__.py b/dimos/robot/unitree/testing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dimos/robot/unitree_webrtc/testing/helpers.py b/dimos/robot/unitree/testing/helpers.py similarity index 100% rename from dimos/robot/unitree_webrtc/testing/helpers.py rename to dimos/robot/unitree/testing/helpers.py diff --git a/dimos/robot/unitree_webrtc/testing/mock.py b/dimos/robot/unitree/testing/mock.py similarity index 97% rename from dimos/robot/unitree_webrtc/testing/mock.py rename to dimos/robot/unitree/testing/mock.py index 2af1754cb4..26e6a90018 100644 --- a/dimos/robot/unitree_webrtc/testing/mock.py +++ b/dimos/robot/unitree/testing/mock.py @@ -22,7 +22,7 @@ from reactivex.observable import Observable from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.lidar import RawLidarMsg, pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.lidar import RawLidarMsg, pointcloud2_from_webrtc_lidar class Mock: diff --git a/dimos/robot/unitree_webrtc/testing/test_actors.py b/dimos/robot/unitree/testing/test_actors.py similarity index 97% rename from dimos/robot/unitree_webrtc/testing/test_actors.py rename to dimos/robot/unitree/testing/test_actors.py index e02f292c8b..9366092eb6 100644 --- a/dimos/robot/unitree_webrtc/testing/test_actors.py +++ b/dimos/robot/unitree/testing/test_actors.py @@ -20,7 +20,7 @@ from dimos import core from dimos.core import Module, rpc from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.map import Map as Mapper +from dimos.robot.unitree.type.map import Map as Mapper @pytest.fixture diff --git a/dimos/robot/unitree_webrtc/testing/test_tooling.py b/dimos/robot/unitree/testing/test_tooling.py similarity index 89% rename from dimos/robot/unitree_webrtc/testing/test_tooling.py rename to dimos/robot/unitree/testing/test_tooling.py index 50b689931e..d1f2eeb169 100644 --- a/dimos/robot/unitree_webrtc/testing/test_tooling.py +++ b/dimos/robot/unitree/testing/test_tooling.py @@ -16,8 +16,8 @@ import pytest -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.reactive import backpressure from dimos.utils.testing import TimedSensorReplay diff --git a/dimos/robot/unitree/type/__init__.py b/dimos/robot/unitree/type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dimos/robot/unitree/type/lidar.py b/dimos/robot/unitree/type/lidar.py new file mode 100644 index 0000000000..df2909dc38 --- /dev/null +++ b/dimos/robot/unitree/type/lidar.py @@ -0,0 +1,74 @@ +# 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. + +"""Unitree WebRTC lidar message parsing utilities.""" + +import time +from typing import TypedDict + +import numpy as np +import open3d as o3d # type: ignore[import-untyped] + +from dimos.msgs.sensor_msgs import PointCloud2 + +# Backwards compatibility alias for pickled data +LidarMessage = PointCloud2 + + +class RawLidarPoints(TypedDict): + points: np.ndarray # type: ignore[type-arg] # Shape (N, 3) array of 3D points [x, y, z] + + +class RawLidarData(TypedDict): + """Data portion of the LIDAR message""" + + frame_id: str + origin: list[float] + resolution: float + src_size: int + stamp: float + width: list[int] + data: RawLidarPoints + + +class RawLidarMsg(TypedDict): + """Static type definition for raw LIDAR message from Unitree WebRTC.""" + + type: str + topic: str + data: RawLidarData + + +def pointcloud2_from_webrtc_lidar(raw_message: RawLidarMsg, ts: float | None = None) -> PointCloud2: + """Convert a raw Unitree WebRTC lidar message to PointCloud2. + + Args: + raw_message: Raw lidar message from Unitree WebRTC API + ts: Optional timestamp override. If None, uses current time. + + Returns: + PointCloud2 message with the lidar points + """ + data = raw_message["data"] + points = data["data"]["points"] + + pointcloud = o3d.geometry.PointCloud() + pointcloud.points = o3d.utility.Vector3dVector(points) + + return PointCloud2( + pointcloud=pointcloud, + # webrtc stamp is broken (e.g., "stamp": 1.758148e+09), use current time + ts=ts if ts is not None else time.time(), + frame_id="world", + ) diff --git a/dimos/robot/unitree/type/lowstate.py b/dimos/robot/unitree/type/lowstate.py new file mode 100644 index 0000000000..3e7926424a --- /dev/null +++ b/dimos/robot/unitree/type/lowstate.py @@ -0,0 +1,93 @@ +# 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. + +from typing import Literal, TypedDict + +raw_odom_msg_sample = { + "type": "msg", + "topic": "rt/lf/lowstate", + "data": { + "imu_state": {"rpy": [0.008086, -0.007515, 2.981771]}, + "motor_state": [ + {"q": 0.098092, "temperature": 40, "lost": 0, "reserve": [0, 674]}, + {"q": 0.757921, "temperature": 32, "lost": 0, "reserve": [0, 674]}, + {"q": -1.490911, "temperature": 38, "lost": 6, "reserve": [0, 674]}, + {"q": -0.072477, "temperature": 42, "lost": 0, "reserve": [0, 674]}, + {"q": 1.020276, "temperature": 32, "lost": 5, "reserve": [0, 674]}, + {"q": -2.007172, "temperature": 38, "lost": 5, "reserve": [0, 674]}, + {"q": 0.071382, "temperature": 50, "lost": 5, "reserve": [0, 674]}, + {"q": 0.963379, "temperature": 36, "lost": 6, "reserve": [0, 674]}, + {"q": -1.978311, "temperature": 40, "lost": 5, "reserve": [0, 674]}, + {"q": -0.051066, "temperature": 48, "lost": 0, "reserve": [0, 674]}, + {"q": 0.73103, "temperature": 34, "lost": 10, "reserve": [0, 674]}, + {"q": -1.466473, "temperature": 38, "lost": 6, "reserve": [0, 674]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, + ], + "bms_state": { + "version_high": 1, + "version_low": 18, + "soc": 55, + "current": -2481, + "cycle": 56, + "bq_ntc": [30, 29], + "mcu_ntc": [33, 32], + }, + "foot_force": [97, 84, 81, 81], + "temperature_ntc1": 48, + "power_v": 28.331045, + }, +} + + +class MotorState(TypedDict): + q: float + temperature: int + lost: int + reserve: list[int] + + +class ImuState(TypedDict): + rpy: list[float] + + +class BmsState(TypedDict): + version_high: int + version_low: int + soc: int + current: int + cycle: int + bq_ntc: list[int] + mcu_ntc: list[int] + + +class LowStateData(TypedDict): + imu_state: ImuState + motor_state: list[MotorState] + bms_state: BmsState + foot_force: list[int] + temperature_ntc1: int + power_v: float + + +class LowStateMsg(TypedDict): + type: Literal["msg"] + topic: str + data: LowStateData diff --git a/dimos/robot/unitree/type/map.py b/dimos/robot/unitree/type/map.py new file mode 100644 index 0000000000..195e4d66a3 --- /dev/null +++ b/dimos/robot/unitree/type/map.py @@ -0,0 +1,128 @@ +# 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. + +from pathlib import Path +import time +from typing import Any + +import open3d as o3d # type: ignore[import-untyped] +from reactivex import interval +from reactivex.disposable import Disposable + +from dimos.core import DimosCluster, In, LCMTransport, Module, Out, rpc +from dimos.core.global_config import GlobalConfig +from dimos.mapping.pointclouds.accumulators.general import GeneralPointCloudAccumulator +from dimos.mapping.pointclouds.accumulators.protocol import PointCloudAccumulator +from dimos.mapping.pointclouds.occupancy import general_occupancy +from dimos.msgs.nav_msgs import OccupancyGrid +from dimos.msgs.sensor_msgs import PointCloud2 +from dimos.robot.unitree.go2.connection import Go2ConnectionProtocol + + +class Map(Module): + lidar: In[PointCloud2] + global_map: Out[PointCloud2] + global_costmap: Out[OccupancyGrid] + + _point_cloud_accumulator: PointCloudAccumulator + _global_config: GlobalConfig + _preloaded_occupancy: OccupancyGrid | None = None + + def __init__( # type: ignore[no-untyped-def] + self, + voxel_size: float = 0.05, + cost_resolution: float = 0.05, + global_publish_interval: float | None = None, + min_height: float = 0.10, + max_height: float = 0.5, + global_config: GlobalConfig | None = None, + **kwargs, + ) -> None: + self.voxel_size = voxel_size + self.cost_resolution = cost_resolution + self.global_publish_interval = global_publish_interval + self.min_height = min_height + self.max_height = max_height + self._global_config = global_config or GlobalConfig() + self._point_cloud_accumulator = GeneralPointCloudAccumulator( + self.voxel_size, self._global_config + ) + + if self._global_config.simulation: + self.min_height = 0.3 + + super().__init__(**kwargs) + + @rpc + def start(self) -> None: + super().start() + + self._disposables.add(Disposable(self.lidar.subscribe(self.add_frame))) + + if self.global_publish_interval is not None: + unsub = interval(self.global_publish_interval).subscribe(self._publish) + self._disposables.add(unsub) + + @rpc + def stop(self) -> None: + super().stop() + + def to_PointCloud2(self) -> PointCloud2: + return PointCloud2( + pointcloud=self._point_cloud_accumulator.get_point_cloud(), + ts=time.time(), + ) + + # TODO: Why is this RPC? + @rpc + def add_frame(self, frame: PointCloud2) -> None: + self._point_cloud_accumulator.add(frame.pointcloud) + + @property + def o3d_geometry(self) -> o3d.geometry.PointCloud: + return self._point_cloud_accumulator.get_point_cloud() + + def _publish(self, _: Any) -> None: + self.global_map.publish(self.to_PointCloud2()) + + occupancygrid = general_occupancy( + self.to_PointCloud2(), + resolution=self.cost_resolution, + min_height=self.min_height, + max_height=self.max_height, + ) + + # When debugging occupancy navigation, load a predefined occupancy grid. + if self._global_config.mujoco_global_costmap_from_occupancy: + if self._preloaded_occupancy is None: + path = Path(self._global_config.mujoco_global_costmap_from_occupancy) + self._preloaded_occupancy = OccupancyGrid.from_path(path) + occupancygrid = self._preloaded_occupancy + + self.global_costmap.publish(occupancygrid) + + +mapper = Map.blueprint + + +def deploy(dimos: DimosCluster, connection: Go2ConnectionProtocol): # type: ignore[no-untyped-def] + mapper = dimos.deploy(Map, global_publish_interval=1.0) # type: ignore[attr-defined] + mapper.global_map.transport = LCMTransport("/global_map", PointCloud2) + mapper.global_costmap.transport = LCMTransport("/global_costmap", OccupancyGrid) + mapper.lidar.connect(connection.pointcloud) # type: ignore[attr-defined] + mapper.start() + return mapper + + +__all__ = ["Map", "mapper"] diff --git a/dimos/robot/unitree/type/odometry.py b/dimos/robot/unitree/type/odometry.py new file mode 100644 index 0000000000..aa664b32ef --- /dev/null +++ b/dimos/robot/unitree/type/odometry.py @@ -0,0 +1,102 @@ +# 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. +from typing import Literal, TypedDict + +from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Vector3 +from dimos.robot.unitree.type.timeseries import ( + Timestamped, +) +from dimos.types.timestamped import to_timestamp + +raw_odometry_msg_sample = { + "type": "msg", + "topic": "rt/utlidar/robot_pose", + "data": { + "header": {"stamp": {"sec": 1746565669, "nanosec": 448350564}, "frame_id": "odom"}, + "pose": { + "position": {"x": 5.961965, "y": -2.916958, "z": 0.319509}, + "orientation": {"x": 0.002787, "y": -0.000902, "z": -0.970244, "w": -0.242112}, + }, + }, +} + + +class TimeStamp(TypedDict): + sec: int + nanosec: int + + +class Header(TypedDict): + stamp: TimeStamp + frame_id: str + + +class RawPosition(TypedDict): + x: float + y: float + z: float + + +class Orientation(TypedDict): + x: float + y: float + z: float + w: float + + +class PoseData(TypedDict): + position: RawPosition + orientation: Orientation + + +class OdometryData(TypedDict): + header: Header + pose: PoseData + + +class RawOdometryMessage(TypedDict): + type: Literal["msg"] + topic: str + data: OdometryData + + +class Odometry(PoseStamped, Timestamped): # type: ignore[misc] + name = "geometry_msgs.PoseStamped" + + def __init__(self, frame_id: str = "base_link", *args, **kwargs) -> None: # type: ignore[no-untyped-def] + super().__init__(frame_id=frame_id, *args, **kwargs) # type: ignore[misc] + + @classmethod + def from_msg(cls, msg: RawOdometryMessage) -> "Odometry": + pose = msg["data"]["pose"] + + # Extract position + pos = Vector3( + pose["position"].get("x"), + pose["position"].get("y"), + pose["position"].get("z"), + ) + + rot = Quaternion( + pose["orientation"].get("x"), + pose["orientation"].get("y"), + pose["orientation"].get("z"), + pose["orientation"].get("w"), + ) + + ts = to_timestamp(msg["data"]["header"]["stamp"]) + return Odometry(position=pos, orientation=rot, ts=ts, frame_id="world") + + def __repr__(self) -> str: + return f"Odom pos({self.position}), rot({self.orientation})" diff --git a/dimos/robot/unitree_webrtc/type/test_lidar.py b/dimos/robot/unitree/type/test_lidar.py similarity index 92% rename from dimos/robot/unitree_webrtc/type/test_lidar.py rename to dimos/robot/unitree/type/test_lidar.py index 7543fe63a7..a58cc5821c 100644 --- a/dimos/robot/unitree_webrtc/type/test_lidar.py +++ b/dimos/robot/unitree/type/test_lidar.py @@ -16,7 +16,7 @@ import itertools from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar from dimos.utils.testing import SensorReplay diff --git a/dimos/robot/unitree_webrtc/type/test_odometry.py b/dimos/robot/unitree/type/test_odometry.py similarity index 97% rename from dimos/robot/unitree_webrtc/type/test_odometry.py rename to dimos/robot/unitree/type/test_odometry.py index e277455cdd..0fdd5f5ad9 100644 --- a/dimos/robot/unitree_webrtc/type/test_odometry.py +++ b/dimos/robot/unitree/type/test_odometry.py @@ -19,7 +19,7 @@ import pytest import reactivex.operators as ops -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.testing import SensorReplay _EXPECTED_TOTAL_RAD = -4.05212 diff --git a/dimos/robot/unitree_webrtc/type/test_timeseries.py b/dimos/robot/unitree/type/test_timeseries.py similarity index 95% rename from dimos/robot/unitree_webrtc/type/test_timeseries.py rename to dimos/robot/unitree/type/test_timeseries.py index 2c7606d9f2..5164d91a94 100644 --- a/dimos/robot/unitree_webrtc/type/test_timeseries.py +++ b/dimos/robot/unitree/type/test_timeseries.py @@ -14,7 +14,7 @@ from datetime import datetime, timedelta -from dimos.robot.unitree_webrtc.type.timeseries import TEvent, TList +from dimos.robot.unitree.type.timeseries import TEvent, TList fixed_date = datetime(2025, 5, 13, 15, 2, 5).astimezone() start_event = TEvent(fixed_date, 1) diff --git a/dimos/robot/unitree/type/timeseries.py b/dimos/robot/unitree/type/timeseries.py new file mode 100644 index 0000000000..b75a41b932 --- /dev/null +++ b/dimos/robot/unitree/type/timeseries.py @@ -0,0 +1,149 @@ +# 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. + +from __future__ import annotations + +from abc import ABC, abstractmethod +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, Union + +if TYPE_CHECKING: + from collections.abc import Iterable + +PAYLOAD = TypeVar("PAYLOAD") + + +class RosStamp(TypedDict): + sec: int + nanosec: int + + +EpochLike = Union[int, float, datetime, RosStamp] + + +def from_ros_stamp(stamp: dict[str, int], tz: timezone | None = None) -> datetime: + """Convert ROS-style timestamp {'sec': int, 'nanosec': int} to datetime.""" + return datetime.fromtimestamp(stamp["sec"] + stamp["nanosec"] / 1e9, tz=tz) + + +def to_human_readable(ts: EpochLike) -> str: + dt = to_datetime(ts) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + +def to_datetime(ts: EpochLike, tz: timezone | None = None) -> datetime: + if isinstance(ts, datetime): + # if ts.tzinfo is None: + # ts = ts.astimezone(tz) + return ts + if isinstance(ts, int | float): + return datetime.fromtimestamp(ts, tz=tz) + if isinstance(ts, dict) and "sec" in ts and "nanosec" in ts: + return datetime.fromtimestamp(ts["sec"] + ts["nanosec"] / 1e9, tz=tz) + raise TypeError("unsupported timestamp type") + + +class Timestamped(ABC): + """Abstract class for an event with a timestamp.""" + + ts: datetime + + def __init__(self, ts: EpochLike) -> None: + self.ts = to_datetime(ts) + + +class TEvent(Timestamped, Generic[PAYLOAD]): + """Concrete class for an event with a timestamp and data.""" + + def __init__(self, timestamp: EpochLike, data: PAYLOAD) -> None: + super().__init__(timestamp) + self.data = data + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TEvent): + return NotImplemented + return self.ts == other.ts and self.data == other.data + + def __repr__(self) -> str: + return f"TEvent(ts={self.ts}, data={self.data})" + + +EVENT = TypeVar("EVENT", bound=Timestamped) # any object that is a subclass of Timestamped + + +class Timeseries(ABC, Generic[EVENT]): + """Abstract class for an iterable of events with timestamps.""" + + @abstractmethod + def __iter__(self) -> Iterable[EVENT]: ... + + @property + def start_time(self) -> datetime: + """Return the timestamp of the earliest event, assuming the data is sorted.""" + return next(iter(self)).ts # type: ignore[call-overload, no-any-return, type-var] + + @property + def end_time(self) -> datetime: + """Return the timestamp of the latest event, assuming the data is sorted.""" + return next(reversed(list(self))).ts # type: ignore[call-overload, no-any-return] + + @property + def frequency(self) -> float: + """Calculate the frequency of events in Hz.""" + return len(list(self)) / (self.duration().total_seconds() or 1) # type: ignore[call-overload] + + def time_range(self) -> tuple[datetime, datetime]: + """Return (earliest_ts, latest_ts). Empty input ⇒ ValueError.""" + return self.start_time, self.end_time + + def duration(self) -> timedelta: + """Total time spanned by the iterable (Δ = last - first).""" + return self.end_time - self.start_time + + def closest_to(self, timestamp: EpochLike) -> EVENT: + """Return the event closest to the given timestamp. Assumes timeseries is sorted.""" + print("closest to", timestamp) + target = to_datetime(timestamp) + print("converted to", target) + target_ts = target.timestamp() + + closest = None + min_dist = float("inf") + + for event in self: # type: ignore[attr-defined] + dist = abs(event.ts - target_ts) + if dist > min_dist: + break + + min_dist = dist + closest = event + + print(f"closest: {closest}") + return closest # type: ignore[return-value] + + def __repr__(self) -> str: + """Return a string representation of the Timeseries.""" + return f"Timeseries(date={self.start_time.strftime('%Y-%m-%d')}, start={self.start_time.strftime('%H:%M:%S')}, end={self.end_time.strftime('%H:%M:%S')}, duration={self.duration()}, events={len(list(self))}, freq={self.frequency:.2f}Hz)" # type: ignore[call-overload] + + def __str__(self) -> str: + """Return a string representation of the Timeseries.""" + return self.__repr__() + + +class TList(list[EVENT], Timeseries[EVENT]): + """A test class that inherits from both list and Timeseries.""" + + def __repr__(self) -> str: + """Return a string representation of the TList using Timeseries repr method.""" + return Timeseries.__repr__(self) diff --git a/dimos/robot/unitree/type/vector.py b/dimos/robot/unitree/type/vector.py new file mode 100644 index 0000000000..58438c0a98 --- /dev/null +++ b/dimos/robot/unitree/type/vector.py @@ -0,0 +1,442 @@ +# 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. + +import builtins +from collections.abc import Iterable +from typing import ( + Any, + Protocol, + TypeVar, + Union, + runtime_checkable, +) + +import numpy as np +from numpy.typing import NDArray + +T = TypeVar("T", bound="Vector") + + +class Vector: + """A wrapper around numpy arrays for vector operations with intuitive syntax.""" + + def __init__(self, *args: Any) -> None: + """Initialize a vector from components or another iterable. + + Examples: + Vector(1, 2) # 2D vector + Vector(1, 2, 3) # 3D vector + Vector([1, 2, 3]) # From list + Vector(np.array([1, 2, 3])) # From numpy array + """ + if len(args) == 1 and hasattr(args[0], "__iter__"): + self._data = np.array(args[0], dtype=float) + elif len(args) == 1: + self._data = np.array([args[0].x, args[0].y, args[0].z], dtype=float) + + else: + self._data = np.array(args, dtype=float) + + @property + def yaw(self) -> float: + return self.x + + @property + def tuple(self) -> tuple[float, ...]: + """Tuple representation of the vector.""" + return tuple(self._data) + + @property + def x(self) -> float: + """X component of the vector.""" + return self._data[0] if len(self._data) > 0 else 0.0 + + @property + def y(self) -> float: + """Y component of the vector.""" + return self._data[1] if len(self._data) > 1 else 0.0 + + @property + def z(self) -> float: + """Z component of the vector.""" + return self._data[2] if len(self._data) > 2 else 0.0 + + @property + def dim(self) -> int: + """Dimensionality of the vector.""" + return len(self._data) + + @property + def data(self) -> NDArray[np.float64]: + """Get the underlying numpy array.""" + return self._data + + def __len__(self) -> int: + return len(self._data) + + def __getitem__(self, idx: int) -> float: + return float(self._data[idx]) + + def __iter__(self) -> Iterable[float]: + return iter(self._data) # type: ignore[no-any-return] + + def __repr__(self) -> str: + components = ",".join(f"{x:.6g}" for x in self._data) + return f"({components})" + + def __str__(self) -> str: + if self.dim < 2: + return self.__repr__() + + def getArrow() -> str: + repr = ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"] + + if self.y == 0 and self.x == 0: + return "·" + + # Calculate angle in radians and convert to directional index + angle = np.arctan2(self.y, self.x) + # Map angle to 0-7 index (8 directions) with proper orientation + dir_index = int(((angle + np.pi) * 4 / np.pi) % 8) + # Get directional arrow symbol + return repr[dir_index] + + return f"{getArrow()} Vector {self.__repr__()}" + + def serialize(self) -> dict: # type: ignore[type-arg] + """Serialize the vector to a dictionary.""" + return {"type": "vector", "c": self._data.tolist()} + + def __eq__(self, other: Any) -> bool: + if isinstance(other, Vector): + return np.array_equal(self._data, other._data) + return np.array_equal(self._data, np.array(other, dtype=float)) + + def __add__(self: T, other: Union["Vector", Iterable[float]]) -> T: + if isinstance(other, Vector): + return self.__class__(self._data + other._data) + return self.__class__(self._data + np.array(other, dtype=float)) + + def __sub__(self: T, other: Union["Vector", Iterable[float]]) -> T: + if isinstance(other, Vector): + return self.__class__(self._data - other._data) + return self.__class__(self._data - np.array(other, dtype=float)) + + def __mul__(self: T, scalar: float) -> T: + return self.__class__(self._data * scalar) + + def __rmul__(self: T, scalar: float) -> T: + return self.__mul__(scalar) + + def __truediv__(self: T, scalar: float) -> T: + return self.__class__(self._data / scalar) + + def __neg__(self: T) -> T: + return self.__class__(-self._data) + + def dot(self, other: Union["Vector", Iterable[float]]) -> float: + """Compute dot product.""" + if isinstance(other, Vector): + return float(np.dot(self._data, other._data)) + return float(np.dot(self._data, np.array(other, dtype=float))) + + def cross(self: T, other: Union["Vector", Iterable[float]]) -> T: + """Compute cross product (3D vectors only).""" + if self.dim != 3: + raise ValueError("Cross product is only defined for 3D vectors") + + if isinstance(other, Vector): + other_data = other._data + else: + other_data = np.array(other, dtype=float) + + if len(other_data) != 3: + raise ValueError("Cross product requires two 3D vectors") + + return self.__class__(np.cross(self._data, other_data)) + + def length(self) -> float: + """Compute the Euclidean length (magnitude) of the vector.""" + return float(np.linalg.norm(self._data)) + + def length_squared(self) -> float: + """Compute the squared length of the vector (faster than length()).""" + return float(np.sum(self._data * self._data)) + + def normalize(self: T) -> T: + """Return a normalized unit vector in the same direction.""" + length = self.length() + if length < 1e-10: # Avoid division by near-zero + return self.__class__(np.zeros_like(self._data)) + return self.__class__(self._data / length) + + def to_2d(self: T) -> T: + """Convert a vector to a 2D vector by taking only the x and y components.""" + return self.__class__(self._data[:2]) + + def distance(self, other: Union["Vector", Iterable[float]]) -> float: + """Compute Euclidean distance to another vector.""" + if isinstance(other, Vector): + return float(np.linalg.norm(self._data - other._data)) + return float(np.linalg.norm(self._data - np.array(other, dtype=float))) + + def distance_squared(self, other: Union["Vector", Iterable[float]]) -> float: + """Compute squared Euclidean distance to another vector (faster than distance()).""" + if isinstance(other, Vector): + diff = self._data - other._data + else: + diff = self._data - np.array(other, dtype=float) + return float(np.sum(diff * diff)) + + def angle(self, other: Union["Vector", Iterable[float]]) -> float: + """Compute the angle (in radians) between this vector and another.""" + if self.length() < 1e-10 or (isinstance(other, Vector) and other.length() < 1e-10): + return 0.0 + + if isinstance(other, Vector): + other_data = other._data + else: + other_data = np.array(other, dtype=float) + + cos_angle = np.clip( + np.dot(self._data, other_data) + / (np.linalg.norm(self._data) * np.linalg.norm(other_data)), + -1.0, + 1.0, + ) + return float(np.arccos(cos_angle)) + + def project(self: T, onto: Union["Vector", Iterable[float]]) -> T: + """Project this vector onto another vector.""" + if isinstance(onto, Vector): + onto_data = onto._data + else: + onto_data = np.array(onto, dtype=float) + + onto_length_sq = np.sum(onto_data * onto_data) + if onto_length_sq < 1e-10: + return self.__class__(np.zeros_like(self._data)) + + scalar_projection = np.dot(self._data, onto_data) / onto_length_sq + return self.__class__(scalar_projection * onto_data) + + @classmethod + def zeros(cls: type[T], dim: int) -> T: + """Create a zero vector of given dimension.""" + return cls(np.zeros(dim)) + + @classmethod + def ones(cls: type[T], dim: int) -> T: + """Create a vector of ones with given dimension.""" + return cls(np.ones(dim)) + + @classmethod + def unit_x(cls: type[T], dim: int = 3) -> T: + """Create a unit vector in the x direction.""" + v = np.zeros(dim) + v[0] = 1.0 + return cls(v) + + @classmethod + def unit_y(cls: type[T], dim: int = 3) -> T: + """Create a unit vector in the y direction.""" + v = np.zeros(dim) + v[1] = 1.0 + return cls(v) + + @classmethod + def unit_z(cls: type[T], dim: int = 3) -> T: + """Create a unit vector in the z direction.""" + v = np.zeros(dim) + if dim > 2: + v[2] = 1.0 + return cls(v) + + def to_list(self) -> list[float]: + """Convert the vector to a list.""" + return [float(x) for x in self._data] + + def to_tuple(self) -> builtins.tuple[float, ...]: + """Convert the vector to a tuple.""" + return tuple(self._data) + + def to_numpy(self) -> NDArray[np.float64]: + """Convert the vector to a numpy array.""" + return self._data + + +# Protocol approach for static type checking +@runtime_checkable +class VectorLike(Protocol): + """Protocol for types that can be treated as vectors.""" + + def __getitem__(self, key: int) -> float: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterable[float]: ... + + +def to_numpy(value: VectorLike) -> NDArray[np.float64]: + """Convert a vector-compatible value to a numpy array. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + Numpy array representation + """ + if isinstance(value, Vector): + return value.data + elif isinstance(value, np.ndarray): + return value + else: + return np.array(value, dtype=float) + + +def to_vector(value: VectorLike) -> Vector: + """Convert a vector-compatible value to a Vector object. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + Vector object + """ + if isinstance(value, Vector): + return value + else: + return Vector(value) + + +def to_tuple(value: VectorLike) -> tuple[float, ...]: + """Convert a vector-compatible value to a tuple. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + Tuple of floats + """ + if isinstance(value, Vector): + return tuple(float(x) for x in value.data) + elif isinstance(value, np.ndarray): + return tuple(float(x) for x in value) + elif isinstance(value, tuple): + return tuple(float(x) for x in value) + else: + # Convert to list first to ensure we have an indexable sequence + data = [value[i] for i in range(len(value))] + return tuple(float(x) for x in data) + + +def to_list(value: VectorLike) -> list[float]: + """Convert a vector-compatible value to a list. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + List of floats + """ + if isinstance(value, Vector): + return [float(x) for x in value.data] + elif isinstance(value, np.ndarray): + return [float(x) for x in value] + elif isinstance(value, list): + return [float(x) for x in value] + else: + # Convert to list using indexing + return [float(value[i]) for i in range(len(value))] + + +# Helper functions to check dimensionality +def is_2d(value: VectorLike) -> bool: + """Check if a vector-compatible value is 2D. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + True if the value is 2D + """ + if isinstance(value, Vector): + return len(value) == 2 + elif isinstance(value, np.ndarray): + return value.shape[-1] == 2 or value.size == 2 + else: + return len(value) == 2 + + +def is_3d(value: VectorLike) -> bool: + """Check if a vector-compatible value is 3D. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + True if the value is 3D + """ + if isinstance(value, Vector): + return len(value) == 3 + elif isinstance(value, np.ndarray): + return value.shape[-1] == 3 or value.size == 3 + else: + return len(value) == 3 + + +# Extraction functions for XYZ components +def x(value: VectorLike) -> float: + """Get the X component of a vector-compatible value. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + X component as a float + """ + if isinstance(value, Vector): + return value.x + else: + return float(to_numpy(value)[0]) + + +def y(value: VectorLike) -> float: + """Get the Y component of a vector-compatible value. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + Y component as a float + """ + if isinstance(value, Vector): + return value.y + else: + arr = to_numpy(value) + return float(arr[1]) if len(arr) > 1 else 0.0 + + +def z(value: VectorLike) -> float: + """Get the Z component of a vector-compatible value. + + Args: + value: Any vector-like object (Vector, numpy array, tuple, list) + + Returns: + Z component as a float + """ + if isinstance(value, Vector): + return value.z + else: + arr = to_numpy(value) + return float(arr[2]) if len(arr) > 2 else 0.0 diff --git a/dimos/robot/unitree_webrtc/unitree_skill_container.py b/dimos/robot/unitree/unitree_skill_container.py similarity index 98% rename from dimos/robot/unitree_webrtc/unitree_skill_container.py rename to dimos/robot/unitree/unitree_skill_container.py index c3dea43424..5cab15c369 100644 --- a/dimos/robot/unitree_webrtc/unitree_skill_container.py +++ b/dimos/robot/unitree/unitree_skill_container.py @@ -27,7 +27,7 @@ from dimos.navigation.base import NavigationState from dimos.protocol.skill.skill import skill from dimos.protocol.skill.type import Reducer, Stream -from dimos.robot.unitree_webrtc.unitree_skills import UNITREE_WEBRTC_CONTROLS +from dimos.robot.unitree.unitree_skills import UNITREE_WEBRTC_CONTROLS from dimos.utils.logging_config import setup_logger logger = setup_logger() diff --git a/dimos/robot/unitree_webrtc/unitree_skills.py b/dimos/robot/unitree/unitree_skills.py similarity index 100% rename from dimos/robot/unitree_webrtc/unitree_skills.py rename to dimos/robot/unitree/unitree_skills.py diff --git a/dimos/robot/unitree_webrtc/__init__.py b/dimos/robot/unitree_webrtc/__init__.py index e69de29bb2..b8660ff6fd 100644 --- a/dimos/robot/unitree_webrtc/__init__.py +++ b/dimos/robot/unitree_webrtc/__init__.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# 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. + +"""Compatibility package for legacy dimos.robot.unitree_webrtc imports.""" + +from importlib import import_module +import sys + +_ALIAS_MODULES = { + "demo_error_on_name_conflicts": "dimos.robot.unitree.demo_error_on_name_conflicts", + "depth_module": "dimos.robot.unitree.depth_module", + "keyboard_teleop": "dimos.robot.unitree.keyboard_teleop", + "mujoco_connection": "dimos.robot.unitree.mujoco_connection", + "type": "dimos.robot.unitree.type", + "unitree_g1_skill_container": "dimos.robot.unitree.g1.skill_container", + "unitree_skill_container": "dimos.robot.unitree.unitree_skill_container", + "unitree_skills": "dimos.robot.unitree.unitree_skills", +} + +for alias, target in _ALIAS_MODULES.items(): + sys.modules[f"{__name__}.{alias}"] = import_module(target) diff --git a/dimos/robot/unitree_webrtc/type/__init__.py b/dimos/robot/unitree_webrtc/type/__init__.py index e69de29bb2..31c8412f88 100644 --- a/dimos/robot/unitree_webrtc/type/__init__.py +++ b/dimos/robot/unitree_webrtc/type/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# 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. + +"""Compatibility re-exports for legacy dimos.robot.unitree_webrtc.type.* imports.""" + +from dimos.robot.unitree.type import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/lidar.py b/dimos/robot/unitree_webrtc/type/lidar.py index df2909dc38..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/lidar.py +++ b/dimos/robot/unitree_webrtc/type/lidar.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,63 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Unitree WebRTC lidar message parsing utilities.""" +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -import time -from typing import TypedDict - -import numpy as np -import open3d as o3d # type: ignore[import-untyped] - -from dimos.msgs.sensor_msgs import PointCloud2 - -# Backwards compatibility alias for pickled data -LidarMessage = PointCloud2 - - -class RawLidarPoints(TypedDict): - points: np.ndarray # type: ignore[type-arg] # Shape (N, 3) array of 3D points [x, y, z] - - -class RawLidarData(TypedDict): - """Data portion of the LIDAR message""" - - frame_id: str - origin: list[float] - resolution: float - src_size: int - stamp: float - width: list[int] - data: RawLidarPoints - - -class RawLidarMsg(TypedDict): - """Static type definition for raw LIDAR message from Unitree WebRTC.""" - - type: str - topic: str - data: RawLidarData - - -def pointcloud2_from_webrtc_lidar(raw_message: RawLidarMsg, ts: float | None = None) -> PointCloud2: - """Convert a raw Unitree WebRTC lidar message to PointCloud2. - - Args: - raw_message: Raw lidar message from Unitree WebRTC API - ts: Optional timestamp override. If None, uses current time. - - Returns: - PointCloud2 message with the lidar points - """ - data = raw_message["data"] - points = data["data"]["points"] - - pointcloud = o3d.geometry.PointCloud() - pointcloud.points = o3d.utility.Vector3dVector(points) - - return PointCloud2( - pointcloud=pointcloud, - # webrtc stamp is broken (e.g., "stamp": 1.758148e+09), use current time - ts=ts if ts is not None else time.time(), - frame_id="world", - ) +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/type/lowstate.py b/dimos/robot/unitree_webrtc/type/lowstate.py index 3e7926424a..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/lowstate.py +++ b/dimos/robot/unitree_webrtc/type/lowstate.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,82 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Literal, TypedDict +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -raw_odom_msg_sample = { - "type": "msg", - "topic": "rt/lf/lowstate", - "data": { - "imu_state": {"rpy": [0.008086, -0.007515, 2.981771]}, - "motor_state": [ - {"q": 0.098092, "temperature": 40, "lost": 0, "reserve": [0, 674]}, - {"q": 0.757921, "temperature": 32, "lost": 0, "reserve": [0, 674]}, - {"q": -1.490911, "temperature": 38, "lost": 6, "reserve": [0, 674]}, - {"q": -0.072477, "temperature": 42, "lost": 0, "reserve": [0, 674]}, - {"q": 1.020276, "temperature": 32, "lost": 5, "reserve": [0, 674]}, - {"q": -2.007172, "temperature": 38, "lost": 5, "reserve": [0, 674]}, - {"q": 0.071382, "temperature": 50, "lost": 5, "reserve": [0, 674]}, - {"q": 0.963379, "temperature": 36, "lost": 6, "reserve": [0, 674]}, - {"q": -1.978311, "temperature": 40, "lost": 5, "reserve": [0, 674]}, - {"q": -0.051066, "temperature": 48, "lost": 0, "reserve": [0, 674]}, - {"q": 0.73103, "temperature": 34, "lost": 10, "reserve": [0, 674]}, - {"q": -1.466473, "temperature": 38, "lost": 6, "reserve": [0, 674]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - {"q": 0, "temperature": 0, "lost": 0, "reserve": [0, 0]}, - ], - "bms_state": { - "version_high": 1, - "version_low": 18, - "soc": 55, - "current": -2481, - "cycle": 56, - "bq_ntc": [30, 29], - "mcu_ntc": [33, 32], - }, - "foot_force": [97, 84, 81, 81], - "temperature_ntc1": 48, - "power_v": 28.331045, - }, -} - - -class MotorState(TypedDict): - q: float - temperature: int - lost: int - reserve: list[int] - - -class ImuState(TypedDict): - rpy: list[float] - - -class BmsState(TypedDict): - version_high: int - version_low: int - soc: int - current: int - cycle: int - bq_ntc: list[int] - mcu_ntc: list[int] - - -class LowStateData(TypedDict): - imu_state: ImuState - motor_state: list[MotorState] - bms_state: BmsState - foot_force: list[int] - temperature_ntc1: int - power_v: float - - -class LowStateMsg(TypedDict): - type: Literal["msg"] - topic: str - data: LowStateData +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/type/map.py b/dimos/robot/unitree_webrtc/type/map.py index f9abd96b88..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/map.py +++ b/dimos/robot/unitree_webrtc/type/map.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,117 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pathlib import Path -import time -from typing import Any +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -import open3d as o3d # type: ignore[import-untyped] -from reactivex import interval -from reactivex.disposable import Disposable - -from dimos.core import DimosCluster, In, LCMTransport, Module, Out, rpc -from dimos.core.global_config import GlobalConfig -from dimos.mapping.pointclouds.accumulators.general import GeneralPointCloudAccumulator -from dimos.mapping.pointclouds.accumulators.protocol import PointCloudAccumulator -from dimos.mapping.pointclouds.occupancy import general_occupancy -from dimos.msgs.nav_msgs import OccupancyGrid -from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree.connection.go2 import Go2ConnectionProtocol - - -class Map(Module): - lidar: In[PointCloud2] - global_map: Out[PointCloud2] - global_costmap: Out[OccupancyGrid] - - _point_cloud_accumulator: PointCloudAccumulator - _global_config: GlobalConfig - _preloaded_occupancy: OccupancyGrid | None = None - - def __init__( # type: ignore[no-untyped-def] - self, - voxel_size: float = 0.05, - cost_resolution: float = 0.05, - global_publish_interval: float | None = None, - min_height: float = 0.10, - max_height: float = 0.5, - global_config: GlobalConfig | None = None, - **kwargs, - ) -> None: - self.voxel_size = voxel_size - self.cost_resolution = cost_resolution - self.global_publish_interval = global_publish_interval - self.min_height = min_height - self.max_height = max_height - self._global_config = global_config or GlobalConfig() - self._point_cloud_accumulator = GeneralPointCloudAccumulator( - self.voxel_size, self._global_config - ) - - if self._global_config.simulation: - self.min_height = 0.3 - - super().__init__(**kwargs) - - @rpc - def start(self) -> None: - super().start() - - self._disposables.add(Disposable(self.lidar.subscribe(self.add_frame))) - - if self.global_publish_interval is not None: - unsub = interval(self.global_publish_interval).subscribe(self._publish) - self._disposables.add(unsub) - - @rpc - def stop(self) -> None: - super().stop() - - def to_PointCloud2(self) -> PointCloud2: - return PointCloud2( - pointcloud=self._point_cloud_accumulator.get_point_cloud(), - ts=time.time(), - ) - - # TODO: Why is this RPC? - @rpc - def add_frame(self, frame: PointCloud2) -> None: - self._point_cloud_accumulator.add(frame.pointcloud) - - @property - def o3d_geometry(self) -> o3d.geometry.PointCloud: - return self._point_cloud_accumulator.get_point_cloud() - - def _publish(self, _: Any) -> None: - self.global_map.publish(self.to_PointCloud2()) - - occupancygrid = general_occupancy( - self.to_PointCloud2(), - resolution=self.cost_resolution, - min_height=self.min_height, - max_height=self.max_height, - ) - - # When debugging occupancy navigation, load a predefined occupancy grid. - if self._global_config.mujoco_global_costmap_from_occupancy: - if self._preloaded_occupancy is None: - path = Path(self._global_config.mujoco_global_costmap_from_occupancy) - self._preloaded_occupancy = OccupancyGrid.from_path(path) - occupancygrid = self._preloaded_occupancy - - self.global_costmap.publish(occupancygrid) - - -mapper = Map.blueprint - - -def deploy(dimos: DimosCluster, connection: Go2ConnectionProtocol): # type: ignore[no-untyped-def] - mapper = dimos.deploy(Map, global_publish_interval=1.0) # type: ignore[attr-defined] - mapper.global_map.transport = LCMTransport("/global_map", PointCloud2) - mapper.global_costmap.transport = LCMTransport("/global_costmap", OccupancyGrid) - mapper.lidar.connect(connection.pointcloud) # type: ignore[attr-defined] - mapper.start() - return mapper - - -__all__ = ["Map", "mapper"] +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/type/odometry.py b/dimos/robot/unitree_webrtc/type/odometry.py index 9f0b400691..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/odometry.py +++ b/dimos/robot/unitree_webrtc/type/odometry.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,92 +12,7 @@ # 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. -from typing import Literal, TypedDict -from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Vector3 -from dimos.robot.unitree_webrtc.type.timeseries import ( - Timestamped, -) -from dimos.types.timestamped import to_timestamp +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -raw_odometry_msg_sample = { - "type": "msg", - "topic": "rt/utlidar/robot_pose", - "data": { - "header": {"stamp": {"sec": 1746565669, "nanosec": 448350564}, "frame_id": "odom"}, - "pose": { - "position": {"x": 5.961965, "y": -2.916958, "z": 0.319509}, - "orientation": {"x": 0.002787, "y": -0.000902, "z": -0.970244, "w": -0.242112}, - }, - }, -} - - -class TimeStamp(TypedDict): - sec: int - nanosec: int - - -class Header(TypedDict): - stamp: TimeStamp - frame_id: str - - -class RawPosition(TypedDict): - x: float - y: float - z: float - - -class Orientation(TypedDict): - x: float - y: float - z: float - w: float - - -class PoseData(TypedDict): - position: RawPosition - orientation: Orientation - - -class OdometryData(TypedDict): - header: Header - pose: PoseData - - -class RawOdometryMessage(TypedDict): - type: Literal["msg"] - topic: str - data: OdometryData - - -class Odometry(PoseStamped, Timestamped): # type: ignore[misc] - name = "geometry_msgs.PoseStamped" - - def __init__(self, frame_id: str = "base_link", *args, **kwargs) -> None: # type: ignore[no-untyped-def] - super().__init__(frame_id=frame_id, *args, **kwargs) # type: ignore[misc] - - @classmethod - def from_msg(cls, msg: RawOdometryMessage) -> "Odometry": - pose = msg["data"]["pose"] - - # Extract position - pos = Vector3( - pose["position"].get("x"), - pose["position"].get("y"), - pose["position"].get("z"), - ) - - rot = Quaternion( - pose["orientation"].get("x"), - pose["orientation"].get("y"), - pose["orientation"].get("z"), - pose["orientation"].get("w"), - ) - - ts = to_timestamp(msg["data"]["header"]["stamp"]) - return Odometry(position=pos, orientation=rot, ts=ts, frame_id="world") - - def __repr__(self) -> str: - return f"Odom pos({self.position}), rot({self.orientation})" +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/type/timeseries.py b/dimos/robot/unitree_webrtc/type/timeseries.py index b75a41b932..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/timeseries.py +++ b/dimos/robot/unitree_webrtc/type/timeseries.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,138 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import annotations +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from abc import ABC, abstractmethod -from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, Union - -if TYPE_CHECKING: - from collections.abc import Iterable - -PAYLOAD = TypeVar("PAYLOAD") - - -class RosStamp(TypedDict): - sec: int - nanosec: int - - -EpochLike = Union[int, float, datetime, RosStamp] - - -def from_ros_stamp(stamp: dict[str, int], tz: timezone | None = None) -> datetime: - """Convert ROS-style timestamp {'sec': int, 'nanosec': int} to datetime.""" - return datetime.fromtimestamp(stamp["sec"] + stamp["nanosec"] / 1e9, tz=tz) - - -def to_human_readable(ts: EpochLike) -> str: - dt = to_datetime(ts) - return dt.strftime("%Y-%m-%d %H:%M:%S") - - -def to_datetime(ts: EpochLike, tz: timezone | None = None) -> datetime: - if isinstance(ts, datetime): - # if ts.tzinfo is None: - # ts = ts.astimezone(tz) - return ts - if isinstance(ts, int | float): - return datetime.fromtimestamp(ts, tz=tz) - if isinstance(ts, dict) and "sec" in ts and "nanosec" in ts: - return datetime.fromtimestamp(ts["sec"] + ts["nanosec"] / 1e9, tz=tz) - raise TypeError("unsupported timestamp type") - - -class Timestamped(ABC): - """Abstract class for an event with a timestamp.""" - - ts: datetime - - def __init__(self, ts: EpochLike) -> None: - self.ts = to_datetime(ts) - - -class TEvent(Timestamped, Generic[PAYLOAD]): - """Concrete class for an event with a timestamp and data.""" - - def __init__(self, timestamp: EpochLike, data: PAYLOAD) -> None: - super().__init__(timestamp) - self.data = data - - def __eq__(self, other: object) -> bool: - if not isinstance(other, TEvent): - return NotImplemented - return self.ts == other.ts and self.data == other.data - - def __repr__(self) -> str: - return f"TEvent(ts={self.ts}, data={self.data})" - - -EVENT = TypeVar("EVENT", bound=Timestamped) # any object that is a subclass of Timestamped - - -class Timeseries(ABC, Generic[EVENT]): - """Abstract class for an iterable of events with timestamps.""" - - @abstractmethod - def __iter__(self) -> Iterable[EVENT]: ... - - @property - def start_time(self) -> datetime: - """Return the timestamp of the earliest event, assuming the data is sorted.""" - return next(iter(self)).ts # type: ignore[call-overload, no-any-return, type-var] - - @property - def end_time(self) -> datetime: - """Return the timestamp of the latest event, assuming the data is sorted.""" - return next(reversed(list(self))).ts # type: ignore[call-overload, no-any-return] - - @property - def frequency(self) -> float: - """Calculate the frequency of events in Hz.""" - return len(list(self)) / (self.duration().total_seconds() or 1) # type: ignore[call-overload] - - def time_range(self) -> tuple[datetime, datetime]: - """Return (earliest_ts, latest_ts). Empty input ⇒ ValueError.""" - return self.start_time, self.end_time - - def duration(self) -> timedelta: - """Total time spanned by the iterable (Δ = last - first).""" - return self.end_time - self.start_time - - def closest_to(self, timestamp: EpochLike) -> EVENT: - """Return the event closest to the given timestamp. Assumes timeseries is sorted.""" - print("closest to", timestamp) - target = to_datetime(timestamp) - print("converted to", target) - target_ts = target.timestamp() - - closest = None - min_dist = float("inf") - - for event in self: # type: ignore[attr-defined] - dist = abs(event.ts - target_ts) - if dist > min_dist: - break - - min_dist = dist - closest = event - - print(f"closest: {closest}") - return closest # type: ignore[return-value] - - def __repr__(self) -> str: - """Return a string representation of the Timeseries.""" - return f"Timeseries(date={self.start_time.strftime('%Y-%m-%d')}, start={self.start_time.strftime('%H:%M:%S')}, end={self.end_time.strftime('%H:%M:%S')}, duration={self.duration()}, events={len(list(self))}, freq={self.frequency:.2f}Hz)" # type: ignore[call-overload] - - def __str__(self) -> str: - """Return a string representation of the Timeseries.""" - return self.__repr__() - - -class TList(list[EVENT], Timeseries[EVENT]): - """A test class that inherits from both list and Timeseries.""" - - def __repr__(self) -> str: - """Return a string representation of the TList using Timeseries repr method.""" - return Timeseries.__repr__(self) +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/type/vector.py b/dimos/robot/unitree_webrtc/type/vector.py index 58438c0a98..437e194277 100644 --- a/dimos/robot/unitree_webrtc/type/vector.py +++ b/dimos/robot/unitree_webrtc/type/vector.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2025-2026 Dimensional Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,431 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import builtins -from collections.abc import Iterable -from typing import ( - Any, - Protocol, - TypeVar, - Union, - runtime_checkable, -) +"""Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -import numpy as np -from numpy.typing import NDArray - -T = TypeVar("T", bound="Vector") - - -class Vector: - """A wrapper around numpy arrays for vector operations with intuitive syntax.""" - - def __init__(self, *args: Any) -> None: - """Initialize a vector from components or another iterable. - - Examples: - Vector(1, 2) # 2D vector - Vector(1, 2, 3) # 3D vector - Vector([1, 2, 3]) # From list - Vector(np.array([1, 2, 3])) # From numpy array - """ - if len(args) == 1 and hasattr(args[0], "__iter__"): - self._data = np.array(args[0], dtype=float) - elif len(args) == 1: - self._data = np.array([args[0].x, args[0].y, args[0].z], dtype=float) - - else: - self._data = np.array(args, dtype=float) - - @property - def yaw(self) -> float: - return self.x - - @property - def tuple(self) -> tuple[float, ...]: - """Tuple representation of the vector.""" - return tuple(self._data) - - @property - def x(self) -> float: - """X component of the vector.""" - return self._data[0] if len(self._data) > 0 else 0.0 - - @property - def y(self) -> float: - """Y component of the vector.""" - return self._data[1] if len(self._data) > 1 else 0.0 - - @property - def z(self) -> float: - """Z component of the vector.""" - return self._data[2] if len(self._data) > 2 else 0.0 - - @property - def dim(self) -> int: - """Dimensionality of the vector.""" - return len(self._data) - - @property - def data(self) -> NDArray[np.float64]: - """Get the underlying numpy array.""" - return self._data - - def __len__(self) -> int: - return len(self._data) - - def __getitem__(self, idx: int) -> float: - return float(self._data[idx]) - - def __iter__(self) -> Iterable[float]: - return iter(self._data) # type: ignore[no-any-return] - - def __repr__(self) -> str: - components = ",".join(f"{x:.6g}" for x in self._data) - return f"({components})" - - def __str__(self) -> str: - if self.dim < 2: - return self.__repr__() - - def getArrow() -> str: - repr = ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"] - - if self.y == 0 and self.x == 0: - return "·" - - # Calculate angle in radians and convert to directional index - angle = np.arctan2(self.y, self.x) - # Map angle to 0-7 index (8 directions) with proper orientation - dir_index = int(((angle + np.pi) * 4 / np.pi) % 8) - # Get directional arrow symbol - return repr[dir_index] - - return f"{getArrow()} Vector {self.__repr__()}" - - def serialize(self) -> dict: # type: ignore[type-arg] - """Serialize the vector to a dictionary.""" - return {"type": "vector", "c": self._data.tolist()} - - def __eq__(self, other: Any) -> bool: - if isinstance(other, Vector): - return np.array_equal(self._data, other._data) - return np.array_equal(self._data, np.array(other, dtype=float)) - - def __add__(self: T, other: Union["Vector", Iterable[float]]) -> T: - if isinstance(other, Vector): - return self.__class__(self._data + other._data) - return self.__class__(self._data + np.array(other, dtype=float)) - - def __sub__(self: T, other: Union["Vector", Iterable[float]]) -> T: - if isinstance(other, Vector): - return self.__class__(self._data - other._data) - return self.__class__(self._data - np.array(other, dtype=float)) - - def __mul__(self: T, scalar: float) -> T: - return self.__class__(self._data * scalar) - - def __rmul__(self: T, scalar: float) -> T: - return self.__mul__(scalar) - - def __truediv__(self: T, scalar: float) -> T: - return self.__class__(self._data / scalar) - - def __neg__(self: T) -> T: - return self.__class__(-self._data) - - def dot(self, other: Union["Vector", Iterable[float]]) -> float: - """Compute dot product.""" - if isinstance(other, Vector): - return float(np.dot(self._data, other._data)) - return float(np.dot(self._data, np.array(other, dtype=float))) - - def cross(self: T, other: Union["Vector", Iterable[float]]) -> T: - """Compute cross product (3D vectors only).""" - if self.dim != 3: - raise ValueError("Cross product is only defined for 3D vectors") - - if isinstance(other, Vector): - other_data = other._data - else: - other_data = np.array(other, dtype=float) - - if len(other_data) != 3: - raise ValueError("Cross product requires two 3D vectors") - - return self.__class__(np.cross(self._data, other_data)) - - def length(self) -> float: - """Compute the Euclidean length (magnitude) of the vector.""" - return float(np.linalg.norm(self._data)) - - def length_squared(self) -> float: - """Compute the squared length of the vector (faster than length()).""" - return float(np.sum(self._data * self._data)) - - def normalize(self: T) -> T: - """Return a normalized unit vector in the same direction.""" - length = self.length() - if length < 1e-10: # Avoid division by near-zero - return self.__class__(np.zeros_like(self._data)) - return self.__class__(self._data / length) - - def to_2d(self: T) -> T: - """Convert a vector to a 2D vector by taking only the x and y components.""" - return self.__class__(self._data[:2]) - - def distance(self, other: Union["Vector", Iterable[float]]) -> float: - """Compute Euclidean distance to another vector.""" - if isinstance(other, Vector): - return float(np.linalg.norm(self._data - other._data)) - return float(np.linalg.norm(self._data - np.array(other, dtype=float))) - - def distance_squared(self, other: Union["Vector", Iterable[float]]) -> float: - """Compute squared Euclidean distance to another vector (faster than distance()).""" - if isinstance(other, Vector): - diff = self._data - other._data - else: - diff = self._data - np.array(other, dtype=float) - return float(np.sum(diff * diff)) - - def angle(self, other: Union["Vector", Iterable[float]]) -> float: - """Compute the angle (in radians) between this vector and another.""" - if self.length() < 1e-10 or (isinstance(other, Vector) and other.length() < 1e-10): - return 0.0 - - if isinstance(other, Vector): - other_data = other._data - else: - other_data = np.array(other, dtype=float) - - cos_angle = np.clip( - np.dot(self._data, other_data) - / (np.linalg.norm(self._data) * np.linalg.norm(other_data)), - -1.0, - 1.0, - ) - return float(np.arccos(cos_angle)) - - def project(self: T, onto: Union["Vector", Iterable[float]]) -> T: - """Project this vector onto another vector.""" - if isinstance(onto, Vector): - onto_data = onto._data - else: - onto_data = np.array(onto, dtype=float) - - onto_length_sq = np.sum(onto_data * onto_data) - if onto_length_sq < 1e-10: - return self.__class__(np.zeros_like(self._data)) - - scalar_projection = np.dot(self._data, onto_data) / onto_length_sq - return self.__class__(scalar_projection * onto_data) - - @classmethod - def zeros(cls: type[T], dim: int) -> T: - """Create a zero vector of given dimension.""" - return cls(np.zeros(dim)) - - @classmethod - def ones(cls: type[T], dim: int) -> T: - """Create a vector of ones with given dimension.""" - return cls(np.ones(dim)) - - @classmethod - def unit_x(cls: type[T], dim: int = 3) -> T: - """Create a unit vector in the x direction.""" - v = np.zeros(dim) - v[0] = 1.0 - return cls(v) - - @classmethod - def unit_y(cls: type[T], dim: int = 3) -> T: - """Create a unit vector in the y direction.""" - v = np.zeros(dim) - v[1] = 1.0 - return cls(v) - - @classmethod - def unit_z(cls: type[T], dim: int = 3) -> T: - """Create a unit vector in the z direction.""" - v = np.zeros(dim) - if dim > 2: - v[2] = 1.0 - return cls(v) - - def to_list(self) -> list[float]: - """Convert the vector to a list.""" - return [float(x) for x in self._data] - - def to_tuple(self) -> builtins.tuple[float, ...]: - """Convert the vector to a tuple.""" - return tuple(self._data) - - def to_numpy(self) -> NDArray[np.float64]: - """Convert the vector to a numpy array.""" - return self._data - - -# Protocol approach for static type checking -@runtime_checkable -class VectorLike(Protocol): - """Protocol for types that can be treated as vectors.""" - - def __getitem__(self, key: int) -> float: ... - def __len__(self) -> int: ... - def __iter__(self) -> Iterable[float]: ... - - -def to_numpy(value: VectorLike) -> NDArray[np.float64]: - """Convert a vector-compatible value to a numpy array. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - Numpy array representation - """ - if isinstance(value, Vector): - return value.data - elif isinstance(value, np.ndarray): - return value - else: - return np.array(value, dtype=float) - - -def to_vector(value: VectorLike) -> Vector: - """Convert a vector-compatible value to a Vector object. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - Vector object - """ - if isinstance(value, Vector): - return value - else: - return Vector(value) - - -def to_tuple(value: VectorLike) -> tuple[float, ...]: - """Convert a vector-compatible value to a tuple. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - Tuple of floats - """ - if isinstance(value, Vector): - return tuple(float(x) for x in value.data) - elif isinstance(value, np.ndarray): - return tuple(float(x) for x in value) - elif isinstance(value, tuple): - return tuple(float(x) for x in value) - else: - # Convert to list first to ensure we have an indexable sequence - data = [value[i] for i in range(len(value))] - return tuple(float(x) for x in data) - - -def to_list(value: VectorLike) -> list[float]: - """Convert a vector-compatible value to a list. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - List of floats - """ - if isinstance(value, Vector): - return [float(x) for x in value.data] - elif isinstance(value, np.ndarray): - return [float(x) for x in value] - elif isinstance(value, list): - return [float(x) for x in value] - else: - # Convert to list using indexing - return [float(value[i]) for i in range(len(value))] - - -# Helper functions to check dimensionality -def is_2d(value: VectorLike) -> bool: - """Check if a vector-compatible value is 2D. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - True if the value is 2D - """ - if isinstance(value, Vector): - return len(value) == 2 - elif isinstance(value, np.ndarray): - return value.shape[-1] == 2 or value.size == 2 - else: - return len(value) == 2 - - -def is_3d(value: VectorLike) -> bool: - """Check if a vector-compatible value is 3D. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - True if the value is 3D - """ - if isinstance(value, Vector): - return len(value) == 3 - elif isinstance(value, np.ndarray): - return value.shape[-1] == 3 or value.size == 3 - else: - return len(value) == 3 - - -# Extraction functions for XYZ components -def x(value: VectorLike) -> float: - """Get the X component of a vector-compatible value. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - X component as a float - """ - if isinstance(value, Vector): - return value.x - else: - return float(to_numpy(value)[0]) - - -def y(value: VectorLike) -> float: - """Get the Y component of a vector-compatible value. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - Y component as a float - """ - if isinstance(value, Vector): - return value.y - else: - arr = to_numpy(value) - return float(arr[1]) if len(arr) > 1 else 0.0 - - -def z(value: VectorLike) -> float: - """Get the Z component of a vector-compatible value. - - Args: - value: Any vector-like object (Vector, numpy array, tuple, list) - - Returns: - Z component as a float - """ - if isinstance(value, Vector): - return value.z - else: - arr = to_numpy(value) - return float(arr[2]) if len(arr) > 2 else 0.0 +from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 diff --git a/dimos/robot/unitree_webrtc/unitree_g1_blueprints.py b/dimos/robot/unitree_webrtc/unitree_g1_blueprints.py deleted file mode 100644 index 0dd003a30e..0000000000 --- a/dimos/robot/unitree_webrtc/unitree_g1_blueprints.py +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env python3 -# 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. - -"""Blueprint configurations for Unitree G1 humanoid robot. - -This module provides pre-configured blueprints for various G1 robot setups, -from basic teleoperation to full autonomous agent configurations. -""" - -from dimos_lcm.foxglove_msgs import SceneUpdate -from dimos_lcm.foxglove_msgs.ImageAnnotations import ( - ImageAnnotations, -) -from dimos_lcm.sensor_msgs import CameraInfo - -from dimos.agents.agent import llm_agent -from dimos.agents.cli.human import human_input -from dimos.agents.skills.navigation import navigation_skill -from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE -from dimos.core.blueprints import autoconnect -from dimos.core.transport import LCMTransport, pSHMTransport -from dimos.dashboard.tf_rerun_module import tf_rerun -from dimos.hardware.sensors.camera import zed -from dimos.hardware.sensors.camera.module import camera_module # type: ignore[attr-defined] -from dimos.hardware.sensors.camera.webcam import Webcam -from dimos.mapping.costmapper import cost_mapper -from dimos.mapping.voxels import voxel_mapper -from dimos.msgs.geometry_msgs import ( - PoseStamped, - Quaternion, - Transform, - Twist, - Vector3, -) -from dimos.msgs.nav_msgs import Odometry, Path -from dimos.msgs.sensor_msgs import Image, PointCloud2 -from dimos.msgs.std_msgs import Bool -from dimos.msgs.vision_msgs import Detection2DArray -from dimos.navigation.frontier_exploration import wavefront_frontier_explorer -from dimos.navigation.replanning_a_star.module import replanning_a_star_planner -from dimos.navigation.rosnav import ros_nav -from dimos.perception.detection.detectors.person.yolo import YoloPersonDetector -from dimos.perception.detection.module3D import Detection3DModule, detection3d_module -from dimos.perception.detection.moduleDB import ObjectDBModule, detection_db_module -from dimos.perception.detection.person_tracker import PersonTracker, person_tracker_module -from dimos.perception.object_tracker import object_tracking -from dimos.perception.spatial_perception import spatial_memory -from dimos.robot.foxglove_bridge import foxglove_bridge -from dimos.robot.unitree.connection.g1 import g1_connection -from dimos.robot.unitree.connection.g1sim import g1_sim_connection -from dimos.robot.unitree_webrtc.keyboard_teleop import keyboard_teleop -from dimos.robot.unitree_webrtc.unitree_g1_skill_container import g1_skills -from dimos.utils.monitoring import utilization -from dimos.web.websocket_vis.websocket_vis_module import websocket_vis - -_basic_no_nav = ( - autoconnect( - camera_module( - transform=Transform( - translation=Vector3(0.05, 0.0, 0.6), # height of camera on G1 robot - rotation=Quaternion.from_euler(Vector3(0.0, 0.2, 0.0)), - frame_id="sensor", - child_frame_id="camera_link", - ), - hardware=lambda: Webcam( - camera_index=0, - fps=15, - stereo_slice="left", - camera_info=zed.CameraInfo.SingleWebcam, - ), - ), - voxel_mapper(voxel_size=0.1), - cost_mapper(), - wavefront_frontier_explorer(), - # Visualization - websocket_vis(), - foxglove_bridge(), - tf_rerun( - robot_frame="base_link", - cameras=[ - ("world/robot/camera", "camera_optical", zed.CameraInfo.SingleWebcam), - ], - ), - ) - .global_config(n_dask_workers=4, robot_model="unitree_g1") - .transports( - { - # G1 uses Twist for movement commands - ("cmd_vel", Twist): LCMTransport("/cmd_vel", Twist), - # State estimation from ROS - ("state_estimation", Odometry): LCMTransport("/state_estimation", Odometry), - # Odometry output from ROSNavigationModule - ("odom", PoseStamped): LCMTransport("/odom", PoseStamped), - # Navigation module topics from nav_bot - ("goal_req", PoseStamped): LCMTransport("/goal_req", PoseStamped), - ("goal_active", PoseStamped): LCMTransport("/goal_active", PoseStamped), - ("path_active", Path): LCMTransport("/path_active", Path), - ("pointcloud", PointCloud2): LCMTransport("/lidar", PointCloud2), - ("global_pointcloud", PointCloud2): LCMTransport("/map", PointCloud2), - # Original navigation topics for backwards compatibility - ("goal_pose", PoseStamped): LCMTransport("/goal_pose", PoseStamped), - ("goal_reached", Bool): LCMTransport("/goal_reached", Bool), - ("cancel_goal", Bool): LCMTransport("/cancel_goal", Bool), - # Camera topics (if camera module is added) - ("color_image", Image): LCMTransport("/g1/color_image", Image), - ("camera_info", CameraInfo): LCMTransport("/g1/camera_info", CameraInfo), - } - ) -) - -unitree_g1_basic = autoconnect( - _basic_no_nav, - g1_connection(), - ros_nav(), -) - -unitree_g1_basic_sim = autoconnect( - _basic_no_nav, - g1_sim_connection(), - replanning_a_star_planner(), -) - -_perception_and_memory = autoconnect( - spatial_memory(), - object_tracking(frame_id="camera_link"), - utilization(), -) - -unitree_g1 = autoconnect( - unitree_g1_basic, - _perception_and_memory, -).global_config(n_dask_workers=8) - -unitree_g1_sim = autoconnect( - unitree_g1_basic_sim, - _perception_and_memory, -).global_config(n_dask_workers=8) - -# Optimized configuration using shared memory for images -unitree_g1_shm = autoconnect( - unitree_g1.transports( - { - ("color_image", Image): pSHMTransport( - "/g1/color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE - ), - } - ), - foxglove_bridge( - shm_channels=[ - "/g1/color_image#sensor_msgs.Image", - ] - ), -) - -_agentic_skills = autoconnect( - llm_agent(), - human_input(), - navigation_skill(), - g1_skills(), -) - -# Full agentic configuration with LLM and skills -unitree_g1_agentic = autoconnect( - unitree_g1, - _agentic_skills, -) - -unitree_g1_agentic_sim = autoconnect( - unitree_g1_sim, - _agentic_skills, -) - -# Configuration with joystick control for teleoperation -unitree_g1_joystick = autoconnect( - unitree_g1_basic, - keyboard_teleop(), # Pygame-based joystick control -) - -# Detection configuration with person tracking and 3D detection -unitree_g1_detection = ( - autoconnect( - unitree_g1_basic, - # Person detection modules with YOLO - detection3d_module( - camera_info=zed.CameraInfo.SingleWebcam, - detector=YoloPersonDetector, - ), - detection_db_module( - camera_info=zed.CameraInfo.SingleWebcam, - filter=lambda det: det.class_id == 0, # Filter for person class only - ), - person_tracker_module( - cameraInfo=zed.CameraInfo.SingleWebcam, - ), - ) - .global_config(n_dask_workers=8) - .remappings( - [ - # Connect detection modules to camera and lidar - (Detection3DModule, "image", "color_image"), - (Detection3DModule, "pointcloud", "pointcloud"), - (ObjectDBModule, "image", "color_image"), - (ObjectDBModule, "pointcloud", "pointcloud"), - (PersonTracker, "image", "color_image"), - (PersonTracker, "detections", "detections_2d"), - ] - ) - .transports( - { - # Detection 3D module outputs - ("detections", Detection3DModule): LCMTransport( - "/detector3d/detections", Detection2DArray - ), - ("annotations", Detection3DModule): LCMTransport( - "/detector3d/annotations", ImageAnnotations - ), - ("scene_update", Detection3DModule): LCMTransport( - "/detector3d/scene_update", SceneUpdate - ), - ("detected_pointcloud_0", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/0", PointCloud2 - ), - ("detected_pointcloud_1", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/1", PointCloud2 - ), - ("detected_pointcloud_2", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/2", PointCloud2 - ), - ("detected_image_0", Detection3DModule): LCMTransport("/detector3d/image/0", Image), - ("detected_image_1", Detection3DModule): LCMTransport("/detector3d/image/1", Image), - ("detected_image_2", Detection3DModule): LCMTransport("/detector3d/image/2", Image), - # Detection DB module outputs - ("detections", ObjectDBModule): LCMTransport( - "/detectorDB/detections", Detection2DArray - ), - ("annotations", ObjectDBModule): LCMTransport( - "/detectorDB/annotations", ImageAnnotations - ), - ("scene_update", ObjectDBModule): LCMTransport("/detectorDB/scene_update", SceneUpdate), - ("detected_pointcloud_0", ObjectDBModule): LCMTransport( - "/detectorDB/pointcloud/0", PointCloud2 - ), - ("detected_pointcloud_1", ObjectDBModule): LCMTransport( - "/detectorDB/pointcloud/1", PointCloud2 - ), - ("detected_pointcloud_2", ObjectDBModule): LCMTransport( - "/detectorDB/pointcloud/2", PointCloud2 - ), - ("detected_image_0", ObjectDBModule): LCMTransport("/detectorDB/image/0", Image), - ("detected_image_1", ObjectDBModule): LCMTransport("/detectorDB/image/1", Image), - ("detected_image_2", ObjectDBModule): LCMTransport("/detectorDB/image/2", Image), - # Person tracker outputs - ("target", PersonTracker): LCMTransport("/person_tracker/target", PoseStamped), - } - ) -) - -# Full featured configuration with everything -unitree_g1_full = autoconnect( - unitree_g1_shm, - _agentic_skills, - keyboard_teleop(), -) diff --git a/dimos/utils/testing/test_moment.py b/dimos/utils/testing/test_moment.py index 92b71e59ac..6764610d0e 100644 --- a/dimos/utils/testing/test_moment.py +++ b/dimos/utils/testing/test_moment.py @@ -17,7 +17,7 @@ from dimos.msgs.geometry_msgs import PoseStamped, Transform from dimos.msgs.sensor_msgs import CameraInfo, Image, PointCloud2 from dimos.protocol.tf import TF -from dimos.robot.unitree.connection import go2 +from dimos.robot.unitree.go2 import connection from dimos.utils.data import get_data from dimos.utils.testing.moment import Moment, SensorMoment @@ -43,14 +43,14 @@ def transforms(self) -> list[Transform]: # back and forth through time and foxglove doesn't get confused odom = self.odom.value odom.ts = time.time() - return go2.GO2Connection._odom_to_tf(odom) + return connection.GO2Connection._odom_to_tf(odom) def publish(self) -> None: t = TF() t.publish(*self.transforms) t.stop() - camera_info = go2._camera_info_static() + camera_info = connection._camera_info_static() camera_info.ts = time.time() camera_info_transport: LCMTransport[CameraInfo] = LCMTransport("/camera_info", CameraInfo) camera_info_transport.publish(camera_info) diff --git a/dimos/utils/testing/test_replay.py b/dimos/utils/testing/test_replay.py index 640fe92979..6b16525148 100644 --- a/dimos/utils/testing/test_replay.py +++ b/dimos/utils/testing/test_replay.py @@ -17,8 +17,8 @@ from reactivex import operators as ops from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree_webrtc.type.lidar import pointcloud2_from_webrtc_lidar -from dimos.robot.unitree_webrtc.type.odometry import Odometry +from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.odometry import Odometry from dimos.utils.data import get_data from dimos.utils.testing import replay From d1e5de40b8ef308ccdaf00355392a0318b182876 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 17:55:39 -0800 Subject: [PATCH 03/45] fixup imports for pickle, and docs --- dimos/robot/unitree_webrtc/README.md | 1 + dimos/robot/unitree_webrtc/type/lidar.py | 2 +- dimos/robot/unitree_webrtc/type/lowstate.py | 2 +- dimos/robot/unitree_webrtc/type/map.py | 2 +- dimos/robot/unitree_webrtc/type/odometry.py | 2 +- dimos/robot/unitree_webrtc/type/timeseries.py | 2 +- dimos/robot/unitree_webrtc/type/vector.py | 2 +- docs/api/sensor_streams/storage_replay.md | 2 +- docs/api/visualization.md | 6 +++--- docs/concepts/transports.md | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 dimos/robot/unitree_webrtc/README.md diff --git a/dimos/robot/unitree_webrtc/README.md b/dimos/robot/unitree_webrtc/README.md new file mode 100644 index 0000000000..ce39201c8b --- /dev/null +++ b/dimos/robot/unitree_webrtc/README.md @@ -0,0 +1 @@ +This directory only exists because some of the --replay tests depend on its existence (python pickle uses module names/paths so we would need to redo the pickle files). diff --git a/dimos/robot/unitree_webrtc/type/lidar.py b/dimos/robot/unitree_webrtc/type/lidar.py index 437e194277..d8dbe98fd2 100644 --- a/dimos/robot/unitree_webrtc/type/lidar.py +++ b/dimos/robot/unitree_webrtc/type/lidar.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.lidar import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/lowstate.py b/dimos/robot/unitree_webrtc/type/lowstate.py index 437e194277..d92ee4d5b1 100644 --- a/dimos/robot/unitree_webrtc/type/lowstate.py +++ b/dimos/robot/unitree_webrtc/type/lowstate.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.lowstate import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/map.py b/dimos/robot/unitree_webrtc/type/map.py index 437e194277..69bbb409c7 100644 --- a/dimos/robot/unitree_webrtc/type/map.py +++ b/dimos/robot/unitree_webrtc/type/map.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.map import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/odometry.py b/dimos/robot/unitree_webrtc/type/odometry.py index 437e194277..111ba0b945 100644 --- a/dimos/robot/unitree_webrtc/type/odometry.py +++ b/dimos/robot/unitree_webrtc/type/odometry.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.odometry import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/timeseries.py b/dimos/robot/unitree_webrtc/type/timeseries.py index 437e194277..34f9587ade 100644 --- a/dimos/robot/unitree_webrtc/type/timeseries.py +++ b/dimos/robot/unitree_webrtc/type/timeseries.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.timeseries import * # noqa: F403 diff --git a/dimos/robot/unitree_webrtc/type/vector.py b/dimos/robot/unitree_webrtc/type/vector.py index 437e194277..20d07c76e8 100644 --- a/dimos/robot/unitree_webrtc/type/vector.py +++ b/dimos/robot/unitree_webrtc/type/vector.py @@ -15,4 +15,4 @@ """Compatibility re-export for dimos.robot.unitree_webrtc.type.${name}.""" -from dimos.robot.unitree.type.${name} import * # noqa: F401,F403 +from dimos.robot.unitree.type.vector import * # noqa: F403 diff --git a/docs/api/sensor_streams/storage_replay.md b/docs/api/sensor_streams/storage_replay.md index 1a31591736..c5cbe306a8 100644 --- a/docs/api/sensor_streams/storage_replay.md +++ b/docs/api/sensor_streams/storage_replay.md @@ -159,7 +159,7 @@ replay.stream( ## Usage: Stub Connections for Testing -A common pattern is creating replay-based connection stubs for testing without hardware. From [`robot/unitree/connection/go2.py`](/dimos/robot/unitree/connection/go2.py#L83): +A common pattern is creating replay-based connection stubs for testing without hardware. From [`robot/unitree/go2/connection.py`](/dimos/robot/unitree/go2/connection.py#L83): This is a bit primitive. We'd like to write a higher-order API for recording full module I/O for any module, but this is a work in progress at the moment. diff --git a/docs/api/visualization.md b/docs/api/visualization.md index 2c222c4f78..3cd556401d 100644 --- a/docs/api/visualization.md +++ b/docs/api/visualization.md @@ -71,7 +71,7 @@ This happens on lower-end hardware (NUC, older laptops) with large maps. ### Increase Voxel Size -Edit [`dimos/robot/unitree_webrtc/unitree_go2_blueprints.py`](/dimos/robot/unitree_webrtc/unitree_go2_blueprints.py) line 82: +Edit [`dimos/robot/unitree/go2/all_blueprints.py`](/dimos/robot/unitree/go2/all_blueprints.py) line 82: ```python # Before (high detail, slower on large maps) @@ -98,7 +98,7 @@ Rerun on `dev` is **module-driven**: modules decide what to log, and `Blueprint. - **Worker processes must connect before logging** - If a module is going to call `rr.log(...)`, it should call `connect_rerun(global_config=...)` first (see examples in: - - [`dimos/robot/unitree/connection/go2.py`](/dimos/robot/unitree/connection/go2.py) + - [`dimos/robot/unitree/go2/connection.py`](/dimos/robot/unitree/go2/connection.py) - [`dimos/mapping/costmapper.py`](/dimos/mapping/costmapper.py) - [`dimos/mapping/voxels.py`](/dimos/mapping/voxels.py) - [`dimos/navigation/replanning_a_star/module.py`](/dimos/navigation/replanning_a_star/module.py) @@ -235,7 +235,7 @@ This appendix is an **inventory of every current Rerun touchpoint** in the repos ### Robot/device visualization (GO2) - **GO2 connection: sensor data logging + TF publishing** - - **File**: [`dimos/robot/unitree/connection/go2.py`](/dimos/robot/unitree/connection/go2.py) + - **File**: [`dimos/robot/unitree/go2/connection.py`](/dimos/robot/unitree/go2/connection.py) - **What**: - Connects to Rerun via `connect_rerun()` (only to log sensor data). - Publishes TF transforms via `self.tf.publish(...)` (base_link, camera_link, camera_optical). diff --git a/docs/concepts/transports.md b/docs/concepts/transports.md index faac9a2ec5..9adc0f21df 100644 --- a/docs/concepts/transports.md +++ b/docs/concepts/transports.md @@ -81,7 +81,7 @@ We’ll go through these layers top-down. See [Blueprints](blueprints.md) for the blueprint API. -From [`unitree_go2_blueprints.py`](/dimos/robot/unitree_webrtc/unitree_go2_blueprints.py). +From [`unitree/go2/all_blueprints.py`](/dimos/robot/unitree/go2/all_blueprints.py). Example: rebind a few streams from the default `LCMTransport` to `ROSTransport` (defined at [`transport.py`](/dimos/core/transport.py#L226)) so you can visualize in **rviz2**. From 9618a01d5565ba66b57eb8d561b2bf37ba0a9fed Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 18:42:10 -0800 Subject: [PATCH 04/45] break up unitree go2 blueprints --- dimos/protocol/mcp/test_mcp_module.py | 2 +- dimos/robot/all_blueprints.py | 22 +- dimos/robot/unitree/go2/all_blueprints.py | 234 ------------------ .../robot/unitree/go2/blueprints/__init__.py | 49 ++++ .../go2/blueprints/agentic/_common_agentic.py | 34 +++ .../blueprints/agentic/unitree_go2_agentic.py | 28 +++ .../unitree_go2_agentic_huggingface.py | 32 +++ .../agentic/unitree_go2_agentic_mcp.py | 26 ++ .../agentic/unitree_go2_agentic_ollama.py | 35 +++ .../agentic/unitree_go2_temporal_memory.py | 26 ++ .../go2/blueprints/basic/unitree_go2_basic.py | 67 +++++ .../go2/blueprints/smart/_with_jpeg.py | 42 ++++ .../go2/blueprints/smart/unitree_go2.py | 32 +++ .../blueprints/smart/unitree_go2_detection.py | 70 ++++++ .../go2/blueprints/smart/unitree_go2_ros.py | 31 +++ .../blueprints/smart/unitree_go2_spatial.py | 28 +++ .../smart/unitree_go2_vlm_stream_test.py | 28 +++ docs/api/visualization.md | 2 +- docs/concepts/transports.md | 2 +- 19 files changed, 542 insertions(+), 248 deletions(-) delete mode 100644 dimos/robot/unitree/go2/all_blueprints.py create mode 100644 dimos/robot/unitree/go2/blueprints/__init__.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/_common_agentic.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py create mode 100644 dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py diff --git a/dimos/protocol/mcp/test_mcp_module.py b/dimos/protocol/mcp/test_mcp_module.py index 8ae587221e..74e3fe3aa2 100644 --- a/dimos/protocol/mcp/test_mcp_module.py +++ b/dimos/protocol/mcp/test_mcp_module.py @@ -30,7 +30,7 @@ def test_unitree_blueprint_has_mcp() -> None: - contents = Path("dimos/robot/unitree/go2/all_blueprints.py").read_text() + contents = Path("dimos/robot/unitree/go2/blueprints/__init__.py").read_text() assert "agentic_mcp" in contents assert "MCPModule.blueprint()" in contents diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index d5c60afda0..34861fd688 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -48,17 +48,17 @@ "unitree-g1-joystick": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick:unitree_g1_joystick", "unitree-g1-shm": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm:unitree_g1_shm", "unitree-g1-sim": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim:unitree_g1_sim", - "unitree-go2": "dimos.robot.unitree.go2.all_blueprints:unitree_go2", - "unitree-go2-agentic": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic", - "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_huggingface", - "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_mcp", - "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_agentic_ollama", - "unitree-go2-basic": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_basic", - "unitree-go2-detection": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_detection", - "unitree-go2-ros": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_ros", - "unitree-go2-spatial": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_spatial", - "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_temporal_memory", - "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.all_blueprints:unitree_go2_vlm_stream_test", + "unitree-go2": "dimos.robot.unitree.go2.blueprints:unitree_go2", + "unitree-go2-agentic": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic", + "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_huggingface", + "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_mcp", + "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_ollama", + "unitree-go2-basic": "dimos.robot.unitree.go2.blueprints:unitree_go2_basic", + "unitree-go2-detection": "dimos.robot.unitree.go2.blueprints:unitree_go2_detection", + "unitree-go2-ros": "dimos.robot.unitree.go2.blueprints:unitree_go2_ros", + "unitree-go2-spatial": "dimos.robot.unitree.go2.blueprints:unitree_go2_spatial", + "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.blueprints:unitree_go2_temporal_memory", + "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.blueprints:unitree_go2_vlm_stream_test", "xarm6-planner-only": "dimos.manipulation.manipulation_blueprints:xarm6_planner_only", "xarm7-planner-coordinator": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator", } diff --git a/dimos/robot/unitree/go2/all_blueprints.py b/dimos/robot/unitree/go2/all_blueprints.py deleted file mode 100644 index ecb72cdac2..0000000000 --- a/dimos/robot/unitree/go2/all_blueprints.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 - -# 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. - -from pathlib import Path -import platform - -from dimos_lcm.foxglove_msgs.ImageAnnotations import ( - ImageAnnotations, # type: ignore[import-untyped] -) -from dimos_lcm.foxglove_msgs.SceneUpdate import SceneUpdate # type: ignore[import-untyped] - -from dimos.agents.agent import llm_agent -from dimos.agents.cli.human import human_input -from dimos.agents.cli.web import web_input -from dimos.agents.ollama_agent import ollama_installed -from dimos.agents.skills.navigation import navigation_skill -from dimos.agents.skills.person_follow import person_follow_skill -from dimos.agents.skills.speak_skill import speak_skill -from dimos.agents.spec import Provider -from dimos.agents.vlm_agent import vlm_agent -from dimos.agents.vlm_stream_tester import vlm_stream_tester -from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE -from dimos.core.blueprints import autoconnect -from dimos.core.transport import ( - JpegLcmTransport, - JpegShmTransport, - LCMTransport, - ROSTransport, - pSHMTransport, -) -from dimos.dashboard.tf_rerun_module import tf_rerun -from dimos.mapping.costmapper import cost_mapper -from dimos.mapping.voxels import voxel_mapper -from dimos.msgs.geometry_msgs import PoseStamped -from dimos.msgs.sensor_msgs import Image, PointCloud2 -from dimos.msgs.vision_msgs import Detection2DArray -from dimos.navigation.frontier_exploration import ( - wavefront_frontier_explorer, -) -from dimos.navigation.replanning_a_star.module import ( - replanning_a_star_planner, -) -from dimos.perception.detection.module3D import Detection3DModule, detection3d_module -from dimos.perception.experimental.temporal_memory import temporal_memory -from dimos.perception.spatial_perception import spatial_memory -from dimos.protocol.mcp.mcp import MCPModule -from dimos.robot.foxglove_bridge import foxglove_bridge -import dimos.robot.unitree.go2.connection as _go2_mod -from dimos.robot.unitree.go2.connection import GO2Connection, go2_connection -from dimos.robot.unitree.unitree_skill_container import unitree_skills -from dimos.utils.monitoring import utilization -from dimos.web.websocket_vis.websocket_vis_module import websocket_vis - -_GO2_URDF = Path(_go2_mod.__file__).parent.parent / "go2" / "go2.urdf" - -# Mac has some issue with high bandwidth UDP -# -# so we use pSHMTransport for color_image -# (Could we adress this on the system config layer? Is this fixable on mac?) -_mac = autoconnect( - foxglove_bridge( - shm_channels=[ - "/color_image#sensor_msgs.Image", - ] - ), -).transports( - { - ("color_image", Image): pSHMTransport( - "color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE - ), - } -) - - -_linux = autoconnect(foxglove_bridge()) - -unitree_go2_basic = autoconnect( - go2_connection(), - _linux if platform.system() == "Linux" else _mac, - websocket_vis(), - tf_rerun( - urdf_path=str(_GO2_URDF), - cameras=[ - ("world/robot/camera", "camera_optical", GO2Connection.camera_info_static), - ], - ), -).global_config(n_dask_workers=4, robot_model="unitree_go2") - -unitree_go2 = autoconnect( - unitree_go2_basic, - voxel_mapper(voxel_size=0.1), - cost_mapper(), - replanning_a_star_planner(), - wavefront_frontier_explorer(), -).global_config(n_dask_workers=6, robot_model="unitree_go2") - -unitree_go2_ros = unitree_go2.transports( - { - ("lidar", PointCloud2): ROSTransport("lidar", PointCloud2), - ("global_map", PointCloud2): ROSTransport("global_map", PointCloud2), - ("odom", PoseStamped): ROSTransport("odom", PoseStamped), - ("color_image", Image): ROSTransport("color_image", Image), - } -) - -unitree_go2_detection = ( - autoconnect( - unitree_go2, - detection3d_module( - camera_info=GO2Connection.camera_info_static, - ), - ) - .remappings( - [ - (Detection3DModule, "pointcloud", "global_map"), - ] - ) - .transports( - { - # Detection 3D module outputs - ("detections", Detection3DModule): LCMTransport( - "/detector3d/detections", Detection2DArray - ), - ("annotations", Detection3DModule): LCMTransport( - "/detector3d/annotations", ImageAnnotations - ), - ("scene_update", Detection3DModule): LCMTransport( - "/detector3d/scene_update", SceneUpdate - ), - ("detected_pointcloud_0", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/0", PointCloud2 - ), - ("detected_pointcloud_1", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/1", PointCloud2 - ), - ("detected_pointcloud_2", Detection3DModule): LCMTransport( - "/detector3d/pointcloud/2", PointCloud2 - ), - ("detected_image_0", Detection3DModule): LCMTransport("/detector3d/image/0", Image), - ("detected_image_1", Detection3DModule): LCMTransport("/detector3d/image/1", Image), - ("detected_image_2", Detection3DModule): LCMTransport("/detector3d/image/2", Image), - } - ) -) - - -unitree_go2_spatial = autoconnect( - unitree_go2, - spatial_memory(), - utilization(), -).global_config(n_dask_workers=8) - -_with_jpeglcm = unitree_go2.transports( - { - ("color_image", Image): JpegLcmTransport("/color_image", Image), - } -) - -_with_jpegshm = autoconnect( - unitree_go2.transports( - { - ("color_image", Image): JpegShmTransport("/color_image", quality=75), - } - ), - foxglove_bridge( - jpeg_shm_channels=[ - "/color_image#sensor_msgs.Image", - ] - ), -) - -_common_agentic = autoconnect( - human_input(), - navigation_skill(), - person_follow_skill(camera_info=GO2Connection.camera_info_static), - unitree_skills(), - web_input(), - speak_skill(), -) - -unitree_go2_agentic = autoconnect( - unitree_go2_spatial, - llm_agent(), - _common_agentic, -) - -unitree_go2_agentic_mcp = autoconnect( - unitree_go2_agentic, - MCPModule.blueprint(), -) - -unitree_go2_agentic_ollama = autoconnect( - unitree_go2_spatial, - llm_agent( - model="qwen3:8b", - provider=Provider.OLLAMA, # type: ignore[attr-defined] - ), - _common_agentic, -).requirements( - ollama_installed, -) - -unitree_go2_agentic_huggingface = autoconnect( - unitree_go2_spatial, - llm_agent( - model="Qwen/Qwen2.5-1.5B-Instruct", - provider=Provider.HUGGINGFACE, # type: ignore[attr-defined] - ), - _common_agentic, -) - -unitree_go2_vlm_stream_test = autoconnect( - unitree_go2_basic, - vlm_agent(), - vlm_stream_tester(), -) - -unitree_go2_temporal_memory = autoconnect( - unitree_go2_agentic, - temporal_memory(), -) diff --git a/dimos/robot/unitree/go2/blueprints/__init__.py b/dimos/robot/unitree/go2/blueprints/__init__.py new file mode 100644 index 0000000000..409d54ae30 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/__init__.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# 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. + +"""Cascaded GO2 blueprints split into focused modules.""" + +from .agentic._common_agentic import _common_agentic +from .agentic.unitree_go2_agentic import unitree_go2_agentic +from .agentic.unitree_go2_agentic_huggingface import unitree_go2_agentic_huggingface +from .agentic.unitree_go2_agentic_mcp import unitree_go2_agentic_mcp +from .agentic.unitree_go2_agentic_ollama import unitree_go2_agentic_ollama +from .agentic.unitree_go2_temporal_memory import unitree_go2_temporal_memory +from .basic.unitree_go2_basic import _linux, _mac, unitree_go2_basic +from .smart._with_jpeg import _with_jpeglcm, _with_jpegshm +from .smart.unitree_go2 import unitree_go2 +from .smart.unitree_go2_detection import unitree_go2_detection +from .smart.unitree_go2_ros import unitree_go2_ros +from .smart.unitree_go2_spatial import unitree_go2_spatial +from .smart.unitree_go2_vlm_stream_test import unitree_go2_vlm_stream_test + +__all__ = [ + "_common_agentic", + "_linux", + "_mac", + "_with_jpeglcm", + "_with_jpegshm", + "unitree_go2", + "unitree_go2_agentic", + "unitree_go2_agentic_huggingface", + "unitree_go2_agentic_mcp", + "unitree_go2_agentic_ollama", + "unitree_go2_basic", + "unitree_go2_detection", + "unitree_go2_ros", + "unitree_go2_spatial", + "unitree_go2_temporal_memory", + "unitree_go2_vlm_stream_test", +] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/_common_agentic.py b/dimos/robot/unitree/go2/blueprints/agentic/_common_agentic.py new file mode 100644 index 0000000000..cf9a0ae086 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/_common_agentic.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.agents.cli.human import human_input +from dimos.agents.cli.web import web_input +from dimos.agents.skills.navigation import navigation_skill +from dimos.agents.skills.person_follow import person_follow_skill +from dimos.agents.skills.speak_skill import speak_skill +from dimos.core.blueprints import autoconnect +from dimos.robot.unitree.go2.connection import GO2Connection +from dimos.robot.unitree.unitree_skill_container import unitree_skills + +_common_agentic = autoconnect( + human_input(), + navigation_skill(), + person_follow_skill(camera_info=GO2Connection.camera_info_static), + unitree_skills(), + web_input(), + speak_skill(), +) + +__all__ = ["_common_agentic"] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py new file mode 100644 index 0000000000..69b950cb55 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.agents.agent import llm_agent +from dimos.core.blueprints import autoconnect + +from ..smart.unitree_go2_spatial import unitree_go2_spatial +from ._common_agentic import _common_agentic + +unitree_go2_agentic = autoconnect( + unitree_go2_spatial, + llm_agent(), + _common_agentic, +) + +__all__ = ["unitree_go2_agentic"] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py new file mode 100644 index 0000000000..4a1df44bac --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.agents.agent import llm_agent +from dimos.agents.spec import Provider +from dimos.core.blueprints import autoconnect + +from ..smart.unitree_go2_spatial import unitree_go2_spatial +from ._common_agentic import _common_agentic + +unitree_go2_agentic_huggingface = autoconnect( + unitree_go2_spatial, + llm_agent( + model="Qwen/Qwen2.5-1.5B-Instruct", + provider=Provider.HUGGINGFACE, # type: ignore[attr-defined] + ), + _common_agentic, +) + +__all__ = ["unitree_go2_agentic_huggingface"] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py new file mode 100644 index 0000000000..0dd645872b --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.blueprints import autoconnect +from dimos.protocol.mcp.mcp import MCPModule + +from .unitree_go2_agentic import unitree_go2_agentic + +unitree_go2_agentic_mcp = autoconnect( + unitree_go2_agentic, + MCPModule.blueprint(), +) + +__all__ = ["unitree_go2_agentic_mcp"] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py new file mode 100644 index 0000000000..04ee2999f0 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.agents.agent import llm_agent +from dimos.agents.ollama_agent import ollama_installed +from dimos.agents.spec import Provider +from dimos.core.blueprints import autoconnect + +from ..smart.unitree_go2_spatial import unitree_go2_spatial +from ._common_agentic import _common_agentic + +unitree_go2_agentic_ollama = autoconnect( + unitree_go2_spatial, + llm_agent( + model="qwen3:8b", + provider=Provider.OLLAMA, # type: ignore[attr-defined] + ), + _common_agentic, +).requirements( + ollama_installed, +) + +__all__ = ["unitree_go2_agentic_ollama"] diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py new file mode 100644 index 0000000000..0ad706d6d7 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.blueprints import autoconnect +from dimos.perception.experimental.temporal_memory import temporal_memory + +from .unitree_go2_agentic import unitree_go2_agentic + +unitree_go2_temporal_memory = autoconnect( + unitree_go2_agentic, + temporal_memory(), +) + +__all__ = ["unitree_go2_temporal_memory"] diff --git a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py new file mode 100644 index 0000000000..4c315f27a3 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +# 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. + +from pathlib import Path +import platform + +from dimos.constants import DEFAULT_CAPACITY_COLOR_IMAGE +from dimos.core.blueprints import autoconnect +from dimos.core.transport import pSHMTransport +from dimos.dashboard.tf_rerun_module import tf_rerun +from dimos.msgs.sensor_msgs import Image +from dimos.robot.foxglove_bridge import foxglove_bridge +from dimos.robot.unitree.go2.connection import GO2Connection, go2_connection +from dimos.web.websocket_vis.websocket_vis_module import websocket_vis + +_GO2_URDF = Path(__file__).parent.parent / "go2.urdf" + +# Mac has some issue with high bandwidth UDP +# +# so we use pSHMTransport for color_image +# (Could we address this on the system config layer? Is this fixable on mac?) +_mac = autoconnect( + foxglove_bridge( + shm_channels=[ + "/color_image#sensor_msgs.Image", + ] + ), +).transports( + { + ("color_image", Image): pSHMTransport( + "color_image", default_capacity=DEFAULT_CAPACITY_COLOR_IMAGE + ), + } +) + +_linux = autoconnect(foxglove_bridge()) + +unitree_go2_basic = autoconnect( + go2_connection(), + _linux if platform.system() == "Linux" else _mac, + websocket_vis(), + tf_rerun( + urdf_path=str(_GO2_URDF), + cameras=[ + ("world/robot/camera", "camera_optical", GO2Connection.camera_info_static), + ], + ), +).global_config(n_dask_workers=4, robot_model="unitree_go2") + +__all__ = [ + "_linux", + "_mac", + "unitree_go2_basic", +] diff --git a/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py b/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py new file mode 100644 index 0000000000..e63e0b6306 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.blueprints import autoconnect +from dimos.core.transport import JpegLcmTransport, JpegShmTransport +from dimos.msgs.sensor_msgs import Image +from dimos.robot.foxglove_bridge import foxglove_bridge + +from .unitree_go2 import unitree_go2 + +_with_jpeglcm = unitree_go2.transports( + { + ("color_image", Image): JpegLcmTransport("/color_image", Image), + } +) + +_with_jpegshm = autoconnect( + unitree_go2.transports( + { + ("color_image", Image): JpegShmTransport("/color_image", quality=75), + } + ), + foxglove_bridge( + jpeg_shm_channels=[ + "/color_image#sensor_msgs.Image", + ] + ), +) + +__all__ = ["_with_jpeglcm", "_with_jpegshm"] diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py new file mode 100644 index 0000000000..60d691a5a4 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.blueprints import autoconnect +from dimos.mapping.costmapper import cost_mapper +from dimos.mapping.voxels import voxel_mapper +from dimos.navigation.frontier_exploration import wavefront_frontier_explorer +from dimos.navigation.replanning_a_star.module import replanning_a_star_planner + +from ..basic.unitree_go2_basic import unitree_go2_basic + +unitree_go2 = autoconnect( + unitree_go2_basic, + voxel_mapper(voxel_size=0.1), + cost_mapper(), + replanning_a_star_planner(), + wavefront_frontier_explorer(), +).global_config(n_dask_workers=6, robot_model="unitree_go2") + +__all__ = ["unitree_go2"] diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py new file mode 100644 index 0000000000..dbbd18695b --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# 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. + +from dimos_lcm.foxglove_msgs.ImageAnnotations import ( + ImageAnnotations, # type: ignore[import-untyped] +) +from dimos_lcm.foxglove_msgs.SceneUpdate import SceneUpdate # type: ignore[import-untyped] + +from dimos.core.blueprints import autoconnect +from dimos.core.transport import LCMTransport +from dimos.msgs.sensor_msgs import Image, PointCloud2 +from dimos.msgs.vision_msgs import Detection2DArray +from dimos.perception.detection.module3D import Detection3DModule, detection3d_module +from dimos.robot.unitree.go2.connection import GO2Connection + +from .unitree_go2 import unitree_go2 + +unitree_go2_detection = ( + autoconnect( + unitree_go2, + detection3d_module( + camera_info=GO2Connection.camera_info_static, + ), + ) + .remappings( + [ + (Detection3DModule, "pointcloud", "global_map"), + ] + ) + .transports( + { + # Detection 3D module outputs + ("detections", Detection3DModule): LCMTransport( + "/detector3d/detections", Detection2DArray + ), + ("annotations", Detection3DModule): LCMTransport( + "/detector3d/annotations", ImageAnnotations + ), + ("scene_update", Detection3DModule): LCMTransport( + "/detector3d/scene_update", SceneUpdate + ), + ("detected_pointcloud_0", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/0", PointCloud2 + ), + ("detected_pointcloud_1", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/1", PointCloud2 + ), + ("detected_pointcloud_2", Detection3DModule): LCMTransport( + "/detector3d/pointcloud/2", PointCloud2 + ), + ("detected_image_0", Detection3DModule): LCMTransport("/detector3d/image/0", Image), + ("detected_image_1", Detection3DModule): LCMTransport("/detector3d/image/1", Image), + ("detected_image_2", Detection3DModule): LCMTransport("/detector3d/image/2", Image), + } + ) +) + +__all__ = ["unitree_go2_detection"] diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py new file mode 100644 index 0000000000..fbb9e7b966 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.transport import ROSTransport +from dimos.msgs.geometry_msgs import PoseStamped +from dimos.msgs.sensor_msgs import Image, PointCloud2 + +from .unitree_go2 import unitree_go2 + +unitree_go2_ros = unitree_go2.transports( + { + ("lidar", PointCloud2): ROSTransport("lidar", PointCloud2), + ("global_map", PointCloud2): ROSTransport("global_map", PointCloud2), + ("odom", PoseStamped): ROSTransport("odom", PoseStamped), + ("color_image", Image): ROSTransport("color_image", Image), + } +) + +__all__ = ["unitree_go2_ros"] diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py new file mode 100644 index 0000000000..1eee3cf227 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.core.blueprints import autoconnect +from dimos.perception.spatial_perception import spatial_memory +from dimos.utils.monitoring import utilization + +from .unitree_go2 import unitree_go2 + +unitree_go2_spatial = autoconnect( + unitree_go2, + spatial_memory(), + utilization(), +).global_config(n_dask_workers=8) + +__all__ = ["unitree_go2_spatial"] diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py new file mode 100644 index 0000000000..f7619fb374 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# 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. + +from dimos.agents.vlm_agent import vlm_agent +from dimos.agents.vlm_stream_tester import vlm_stream_tester +from dimos.core.blueprints import autoconnect + +from ..basic.unitree_go2_basic import unitree_go2_basic + +unitree_go2_vlm_stream_test = autoconnect( + unitree_go2_basic, + vlm_agent(), + vlm_stream_tester(), +) + +__all__ = ["unitree_go2_vlm_stream_test"] diff --git a/docs/api/visualization.md b/docs/api/visualization.md index 3cd556401d..451ceb7667 100644 --- a/docs/api/visualization.md +++ b/docs/api/visualization.md @@ -71,7 +71,7 @@ This happens on lower-end hardware (NUC, older laptops) with large maps. ### Increase Voxel Size -Edit [`dimos/robot/unitree/go2/all_blueprints.py`](/dimos/robot/unitree/go2/all_blueprints.py) line 82: +Edit [`dimos/robot/unitree/go2/blueprints/__init__.py`](/dimos/robot/unitree/go2/blueprints/__init__.py) line 82: ```python # Before (high detail, slower on large maps) diff --git a/docs/concepts/transports.md b/docs/concepts/transports.md index 9adc0f21df..e4b62b01ce 100644 --- a/docs/concepts/transports.md +++ b/docs/concepts/transports.md @@ -81,7 +81,7 @@ We’ll go through these layers top-down. See [Blueprints](blueprints.md) for the blueprint API. -From [`unitree/go2/all_blueprints.py`](/dimos/robot/unitree/go2/all_blueprints.py). +From [`unitree/go2/blueprints/__init__.py`](/dimos/robot/unitree/go2/blueprints/__init__.py). Example: rebind a few streams from the default `LCMTransport` to `ROSTransport` (defined at [`transport.py`](/dimos/core/transport.py#L226)) so you can visualize in **rviz2**. From 2129157eeb9ebe083c9737b70cad51c010b16862 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 22:51:02 -0800 Subject: [PATCH 05/45] fix and switch to absolute pathed imports --- dimos/robot/unitree/g1/blueprints/__init__.py | 27 +++++++------ .../blueprints/agentic/unitree_g1_agentic.py | 4 +- .../agentic/unitree_g1_agentic_sim.py | 4 +- .../g1/blueprints/agentic/unitree_g1_full.py | 4 +- .../g1/blueprints/basic/unitree_g1_basic.py | 5 ++- .../blueprints/basic/unitree_g1_basic_sim.py | 3 +- .../blueprints/basic/unitree_g1_joystick.py | 3 +- .../g1/blueprints/perceptive/unitree_g1.py | 7 ++-- .../perceptive/unitree_g1_detection.py | 3 +- .../blueprints/perceptive/unitree_g1_shm.py | 3 +- .../blueprints/perceptive/unitree_g1_sim.py | 7 ++-- .../robot/unitree/go2/blueprints/__init__.py | 40 +++++++++++++------ .../blueprints/agentic/unitree_go2_agentic.py | 5 +-- .../unitree_go2_agentic_huggingface.py | 5 +-- .../agentic/unitree_go2_agentic_mcp.py | 3 +- .../agentic/unitree_go2_agentic_ollama.py | 5 +-- .../agentic/unitree_go2_temporal_memory.py | 3 +- .../go2/blueprints/smart/_with_jpeg.py | 3 +- .../go2/blueprints/smart/unitree_go2.py | 3 +- .../blueprints/smart/unitree_go2_detection.py | 3 +- .../go2/blueprints/smart/unitree_go2_ros.py | 3 +- .../blueprints/smart/unitree_go2_spatial.py | 3 +- .../smart/unitree_go2_vlm_stream_test.py | 3 +- 23 files changed, 76 insertions(+), 73 deletions(-) diff --git a/dimos/robot/unitree/g1/blueprints/__init__.py b/dimos/robot/unitree/g1/blueprints/__init__.py index 08efb8c82b..4b5fad400a 100644 --- a/dimos/robot/unitree/g1/blueprints/__init__.py +++ b/dimos/robot/unitree/g1/blueprints/__init__.py @@ -15,22 +15,23 @@ """Cascaded G1 blueprints split into focused modules.""" -from .agentic._agentic_skills import _agentic_skills -from .agentic.unitree_g1_agentic import unitree_g1_agentic -from .agentic.unitree_g1_agentic_sim import unitree_g1_agentic_sim -from .agentic.unitree_g1_full import unitree_g1_full -from .basic.unitree_g1_basic import unitree_g1_basic -from .basic.unitree_g1_basic_sim import unitree_g1_basic_sim -from .basic.unitree_g1_joystick import unitree_g1_joystick -from .primitive.uintree_g1_primitive_no_nav import ( +from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills +from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic import unitree_g1_agentic +from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim import unitree_g1_agentic_sim +from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_full import unitree_g1_full +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim import unitree_g1_basic_sim +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick import unitree_g1_joystick +from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( uintree_g1_basic_no_nav, uintree_g1_basic_no_nav as basic_no_nav, ) -from .smart._perception_and_memory import _perception_and_memory -from .smart.unitree_g1 import unitree_g1 -from .smart.unitree_g1_detection import unitree_g1_detection -from .smart.unitree_g1_shm import unitree_g1_shm -from .smart.unitree_g1_sim import unitree_g1_sim + +from .perceptive._perception_and_memory import _perception_and_memory +from .perceptive.unitree_g1 import unitree_g1 +from .perceptive.unitree_g1_detection import unitree_g1_detection +from .perceptive.unitree_g1_shm import unitree_g1_shm +from .perceptive.unitree_g1_sim import unitree_g1_sim __all__ = [ "_agentic_skills", diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py index 9f4167e326..061152bbbd 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py @@ -16,9 +16,9 @@ """Full G1 stack with agentic skills.""" from dimos.core.blueprints import autoconnect +from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills -from ..smart.unitree_g1 import unitree_g1 -from ._agentic_skills import _agentic_skills +from ..perceptive.unitree_g1 import unitree_g1 unitree_g1_agentic = autoconnect( unitree_g1, diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py index 4cf27eae89..6a532e1a69 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py @@ -16,9 +16,9 @@ """Agentic G1 sim stack.""" from dimos.core.blueprints import autoconnect +from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills -from ..smart.unitree_g1_sim import unitree_g1_sim -from ._agentic_skills import _agentic_skills +from ..perceptive.unitree_g1_sim import unitree_g1_sim unitree_g1_agentic_sim = autoconnect( unitree_g1_sim, diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py index 951a44c813..cd51df2546 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py @@ -16,10 +16,10 @@ """Full featured G1 stack with agentic skills and teleop.""" from dimos.core.blueprints import autoconnect +from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills from dimos.robot.unitree.keyboard_teleop import keyboard_teleop -from ..smart.unitree_g1_shm import unitree_g1_shm -from ._agentic_skills import _agentic_skills +from ..perceptive.unitree_g1_shm import unitree_g1_shm unitree_g1_full = autoconnect( unitree_g1_shm, diff --git a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py index 5310918a25..f6d61ce429 100644 --- a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py @@ -17,10 +17,11 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.rosnav import ros_nav +from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( + uintree_g1_basic_no_nav, +) from dimos.robot.unitree.g1.connection import g1_connection -from ..primitive.uintree_g1_primitive_no_nav import uintree_g1_basic_no_nav - unitree_g1_basic = autoconnect( uintree_g1_basic_no_nav, g1_connection(), diff --git a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py index c740ea7fe6..75af592703 100644 --- a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py @@ -17,10 +17,9 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.replanning_a_star.module import replanning_a_star_planner +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic from dimos.robot.unitree.g1.sim import g1_sim_connection -from .unitree_g1_basic import unitree_g1_basic - unitree_g1_basic_sim = autoconnect( unitree_g1_basic.uintree_g1_basic_no_nav, g1_sim_connection(), diff --git a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py index fab068fa7e..0242556189 100644 --- a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_joystick.py @@ -16,10 +16,9 @@ """G1 stack with keyboard teleop.""" from dimos.core.blueprints import autoconnect +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic from dimos.robot.unitree.keyboard_teleop import keyboard_teleop -from .unitree_g1_basic import unitree_g1_basic - unitree_g1_joystick = autoconnect( unitree_g1_basic, keyboard_teleop(), # Pygame-based joystick control diff --git a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py index f185eac164..483928ec54 100644 --- a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1.py @@ -16,9 +16,10 @@ """G1 stack with perception and memory.""" from dimos.core.blueprints import autoconnect - -from ..basic.unitree_g1_basic import unitree_g1_basic -from ._perception_and_memory import _perception_and_memory +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic +from dimos.robot.unitree.g1.blueprints.perceptive._perception_and_memory import ( + _perception_and_memory, +) unitree_g1 = autoconnect( unitree_g1_basic, diff --git a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py index cb2119cbf8..6e2da40a2c 100644 --- a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_detection.py @@ -28,8 +28,7 @@ from dimos.perception.detection.module3D import Detection3DModule, detection3d_module from dimos.perception.detection.moduleDB import ObjectDBModule, detection_db_module from dimos.perception.detection.person_tracker import PersonTracker, person_tracker_module - -from ..basic.unitree_g1_basic import unitree_g1_basic +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic unitree_g1_detection = ( autoconnect( diff --git a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py index 668c6eaf49..5d2eefad94 100644 --- a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_shm.py @@ -20,8 +20,7 @@ from dimos.core.transport import pSHMTransport from dimos.msgs.sensor_msgs import Image from dimos.robot.foxglove_bridge import foxglove_bridge - -from .unitree_g1 import unitree_g1 +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1 import unitree_g1 unitree_g1_shm = autoconnect( unitree_g1.transports( diff --git a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py index 713dc35ce5..059102c7a5 100644 --- a/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py +++ b/dimos/robot/unitree/g1/blueprints/perceptive/unitree_g1_sim.py @@ -16,9 +16,10 @@ """G1 sim stack with perception and memory.""" from dimos.core.blueprints import autoconnect - -from ..basic.unitree_g1_basic_sim import unitree_g1_basic_sim -from ._perception_and_memory import _perception_and_memory +from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim import unitree_g1_basic_sim +from dimos.robot.unitree.g1.blueprints.perceptive._perception_and_memory import ( + _perception_and_memory, +) unitree_g1_sim = autoconnect( unitree_g1_basic_sim, diff --git a/dimos/robot/unitree/go2/blueprints/__init__.py b/dimos/robot/unitree/go2/blueprints/__init__.py index 409d54ae30..b461591028 100644 --- a/dimos/robot/unitree/go2/blueprints/__init__.py +++ b/dimos/robot/unitree/go2/blueprints/__init__.py @@ -15,19 +15,33 @@ """Cascaded GO2 blueprints split into focused modules.""" -from .agentic._common_agentic import _common_agentic -from .agentic.unitree_go2_agentic import unitree_go2_agentic -from .agentic.unitree_go2_agentic_huggingface import unitree_go2_agentic_huggingface -from .agentic.unitree_go2_agentic_mcp import unitree_go2_agentic_mcp -from .agentic.unitree_go2_agentic_ollama import unitree_go2_agentic_ollama -from .agentic.unitree_go2_temporal_memory import unitree_go2_temporal_memory -from .basic.unitree_go2_basic import _linux, _mac, unitree_go2_basic -from .smart._with_jpeg import _with_jpeglcm, _with_jpegshm -from .smart.unitree_go2 import unitree_go2 -from .smart.unitree_go2_detection import unitree_go2_detection -from .smart.unitree_go2_ros import unitree_go2_ros -from .smart.unitree_go2_spatial import unitree_go2_spatial -from .smart.unitree_go2_vlm_stream_test import unitree_go2_vlm_stream_test +from dimos.robot.unitree.go2.blueprints.agentic._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic import unitree_go2_agentic +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_huggingface import ( + unitree_go2_agentic_huggingface, +) +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_mcp import ( + unitree_go2_agentic_mcp, +) +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_ollama import ( + unitree_go2_agentic_ollama, +) +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_temporal_memory import ( + unitree_go2_temporal_memory, +) +from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import ( + _linux, + _mac, + unitree_go2_basic, +) +from dimos.robot.unitree.go2.blueprints.smart._with_jpeg import _with_jpeglcm, _with_jpegshm +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_detection import unitree_go2_detection +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_ros import unitree_go2_ros +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial import unitree_go2_spatial +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_vlm_stream_test import ( + unitree_go2_vlm_stream_test, +) __all__ = [ "_common_agentic", diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py index 69b950cb55..0db6d16980 100644 --- a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic.py @@ -15,9 +15,8 @@ from dimos.agents.agent import llm_agent from dimos.core.blueprints import autoconnect - -from ..smart.unitree_go2_spatial import unitree_go2_spatial -from ._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.agentic._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial import unitree_go2_spatial unitree_go2_agentic = autoconnect( unitree_go2_spatial, diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py index 4a1df44bac..90198c2493 100644 --- a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_huggingface.py @@ -16,9 +16,8 @@ from dimos.agents.agent import llm_agent from dimos.agents.spec import Provider from dimos.core.blueprints import autoconnect - -from ..smart.unitree_go2_spatial import unitree_go2_spatial -from ._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.agentic._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial import unitree_go2_spatial unitree_go2_agentic_huggingface = autoconnect( unitree_go2_spatial, diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py index 0dd645872b..bbc3e4c216 100644 --- a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py @@ -15,8 +15,7 @@ from dimos.core.blueprints import autoconnect from dimos.protocol.mcp.mcp import MCPModule - -from .unitree_go2_agentic import unitree_go2_agentic +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic import unitree_go2_agentic unitree_go2_agentic_mcp = autoconnect( unitree_go2_agentic, diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py index 04ee2999f0..529f50f4ae 100644 --- a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_ollama.py @@ -17,9 +17,8 @@ from dimos.agents.ollama_agent import ollama_installed from dimos.agents.spec import Provider from dimos.core.blueprints import autoconnect - -from ..smart.unitree_go2_spatial import unitree_go2_spatial -from ._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.agentic._common_agentic import _common_agentic +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial import unitree_go2_spatial unitree_go2_agentic_ollama = autoconnect( unitree_go2_spatial, diff --git a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py index 0ad706d6d7..017ccaba2b 100644 --- a/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py +++ b/dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_temporal_memory.py @@ -15,8 +15,7 @@ from dimos.core.blueprints import autoconnect from dimos.perception.experimental.temporal_memory import temporal_memory - -from .unitree_go2_agentic import unitree_go2_agentic +from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic import unitree_go2_agentic unitree_go2_temporal_memory = autoconnect( unitree_go2_agentic, diff --git a/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py b/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py index e63e0b6306..f2aad357f1 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py +++ b/dimos/robot/unitree/go2/blueprints/smart/_with_jpeg.py @@ -17,8 +17,7 @@ from dimos.core.transport import JpegLcmTransport, JpegShmTransport from dimos.msgs.sensor_msgs import Image from dimos.robot.foxglove_bridge import foxglove_bridge - -from .unitree_go2 import unitree_go2 +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 _with_jpeglcm = unitree_go2.transports( { diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py index 60d691a5a4..5d096444d5 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2.py @@ -18,8 +18,7 @@ from dimos.mapping.voxels import voxel_mapper from dimos.navigation.frontier_exploration import wavefront_frontier_explorer from dimos.navigation.replanning_a_star.module import replanning_a_star_planner - -from ..basic.unitree_go2_basic import unitree_go2_basic +from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import unitree_go2_basic unitree_go2 = autoconnect( unitree_go2_basic, diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py index dbbd18695b..f2edf2cb3b 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_detection.py @@ -23,10 +23,9 @@ from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.module3D import Detection3DModule, detection3d_module +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 from dimos.robot.unitree.go2.connection import GO2Connection -from .unitree_go2 import unitree_go2 - unitree_go2_detection = ( autoconnect( unitree_go2, diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py index fbb9e7b966..a335b1e9af 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_ros.py @@ -16,8 +16,7 @@ from dimos.core.transport import ROSTransport from dimos.msgs.geometry_msgs import PoseStamped from dimos.msgs.sensor_msgs import Image, PointCloud2 - -from .unitree_go2 import unitree_go2 +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 unitree_go2_ros = unitree_go2.transports( { diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py index 1eee3cf227..e2695f9bfb 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_spatial.py @@ -15,10 +15,9 @@ from dimos.core.blueprints import autoconnect from dimos.perception.spatial_perception import spatial_memory +from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 from dimos.utils.monitoring import utilization -from .unitree_go2 import unitree_go2 - unitree_go2_spatial = autoconnect( unitree_go2, spatial_memory(), diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py index f7619fb374..194d3973c6 100644 --- a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_vlm_stream_test.py @@ -16,8 +16,7 @@ from dimos.agents.vlm_agent import vlm_agent from dimos.agents.vlm_stream_tester import vlm_stream_tester from dimos.core.blueprints import autoconnect - -from ..basic.unitree_go2_basic import unitree_go2_basic +from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import unitree_go2_basic unitree_go2_vlm_stream_test = autoconnect( unitree_go2_basic, From 8136806113a599836e51a384d1ae70316178bb8a Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 22:51:15 -0800 Subject: [PATCH 06/45] - --- dimos/robot/unitree/g1/blueprints/__init__.py | 13 +++++++------ .../g1/blueprints/agentic/unitree_g1_agentic.py | 3 +-- .../g1/blueprints/agentic/unitree_g1_agentic_sim.py | 3 +-- .../g1/blueprints/agentic/unitree_g1_full.py | 3 +-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dimos/robot/unitree/g1/blueprints/__init__.py b/dimos/robot/unitree/g1/blueprints/__init__.py index 4b5fad400a..26a043563c 100644 --- a/dimos/robot/unitree/g1/blueprints/__init__.py +++ b/dimos/robot/unitree/g1/blueprints/__init__.py @@ -22,17 +22,18 @@ from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim import unitree_g1_basic_sim from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick import unitree_g1_joystick +from dimos.robot.unitree.g1.blueprints.perceptive._perception_and_memory import ( + _perception_and_memory, +) +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1 import unitree_g1 +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_detection import unitree_g1_detection +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm import unitree_g1_shm +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim import unitree_g1_sim from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( uintree_g1_basic_no_nav, uintree_g1_basic_no_nav as basic_no_nav, ) -from .perceptive._perception_and_memory import _perception_and_memory -from .perceptive.unitree_g1 import unitree_g1 -from .perceptive.unitree_g1_detection import unitree_g1_detection -from .perceptive.unitree_g1_shm import unitree_g1_shm -from .perceptive.unitree_g1_sim import unitree_g1_sim - __all__ = [ "_agentic_skills", "_perception_and_memory", diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py index 061152bbbd..a90c2bfe2c 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic.py @@ -17,8 +17,7 @@ from dimos.core.blueprints import autoconnect from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills - -from ..perceptive.unitree_g1 import unitree_g1 +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1 import unitree_g1 unitree_g1_agentic = autoconnect( unitree_g1, diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py index 6a532e1a69..b7371b96b5 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_agentic_sim.py @@ -17,8 +17,7 @@ from dimos.core.blueprints import autoconnect from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills - -from ..perceptive.unitree_g1_sim import unitree_g1_sim +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim import unitree_g1_sim unitree_g1_agentic_sim = autoconnect( unitree_g1_sim, diff --git a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py index cd51df2546..7f826f2eec 100644 --- a/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py +++ b/dimos/robot/unitree/g1/blueprints/agentic/unitree_g1_full.py @@ -17,10 +17,9 @@ from dimos.core.blueprints import autoconnect from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills +from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm import unitree_g1_shm from dimos.robot.unitree.keyboard_teleop import keyboard_teleop -from ..perceptive.unitree_g1_shm import unitree_g1_shm - unitree_g1_full = autoconnect( unitree_g1_shm, _agentic_skills, From 05287b2f44b94a846b00e3f930f3b85481200e4b Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 23:28:16 -0800 Subject: [PATCH 07/45] revert --- dimos/control/tasks/__init__.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/dimos/control/tasks/__init__.py b/dimos/control/tasks/__init__.py index c4b386e151..bbb77dc129 100644 --- a/dimos/control/tasks/__init__.py +++ b/dimos/control/tasks/__init__.py @@ -14,16 +14,10 @@ """Task implementations for the ControlCoordinator.""" -_HAS_PINOCCHIO = True -try: - from dimos.control.tasks.cartesian_ik_task import ( - CartesianIKTask, - CartesianIKTaskConfig, - ) -except ModuleNotFoundError as exc: - if exc.name != "pinocchio": - raise - _HAS_PINOCCHIO = False +from dimos.control.tasks.cartesian_ik_task import ( + CartesianIKTask, + CartesianIKTaskConfig, +) from dimos.control.tasks.servo_task import ( JointServoTask, JointServoTaskConfig, @@ -38,6 +32,8 @@ ) __all__ = [ + "CartesianIKTask", + "CartesianIKTaskConfig", "JointServoTask", "JointServoTaskConfig", "JointTrajectoryTask", @@ -45,11 +41,3 @@ "JointVelocityTask", "JointVelocityTaskConfig", ] - -if _HAS_PINOCCHIO: - __all__.extend( - [ - "CartesianIKTask", - "CartesianIKTaskConfig", - ] - ) From dbfb6d64a8fa9d24f834e6d9477fbb8983965f3f Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Fri, 6 Feb 2026 23:33:28 -0800 Subject: [PATCH 08/45] minor test fix --- dimos/protocol/mcp/test_mcp_module.py | 2 +- dimos/robot/all_blueprints.py | 22 +++++++++---------- .../unitree/g1/blueprints/agentic/__init__.py | 16 ++++++++++++++ .../unitree/g1/blueprints/basic/__init__.py | 16 ++++++++++++++ .../g1/blueprints/perceptive/__init__.py | 16 ++++++++++++++ .../g1/blueprints/primitive/__init__.py | 16 ++++++++++++++ .../go2/blueprints/agentic/__init__.py | 16 ++++++++++++++ .../unitree/go2/blueprints/basic/__init__.py | 16 ++++++++++++++ .../unitree/go2/blueprints/smart/__init__.py | 16 ++++++++++++++ 9 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 dimos/robot/unitree/g1/blueprints/agentic/__init__.py create mode 100644 dimos/robot/unitree/g1/blueprints/basic/__init__.py create mode 100644 dimos/robot/unitree/g1/blueprints/perceptive/__init__.py create mode 100644 dimos/robot/unitree/g1/blueprints/primitive/__init__.py create mode 100644 dimos/robot/unitree/go2/blueprints/agentic/__init__.py create mode 100644 dimos/robot/unitree/go2/blueprints/basic/__init__.py create mode 100644 dimos/robot/unitree/go2/blueprints/smart/__init__.py diff --git a/dimos/protocol/mcp/test_mcp_module.py b/dimos/protocol/mcp/test_mcp_module.py index 74e3fe3aa2..cd1cecb026 100644 --- a/dimos/protocol/mcp/test_mcp_module.py +++ b/dimos/protocol/mcp/test_mcp_module.py @@ -30,7 +30,7 @@ def test_unitree_blueprint_has_mcp() -> None: - contents = Path("dimos/robot/unitree/go2/blueprints/__init__.py").read_text() + contents = Path("dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py").read_text() assert "agentic_mcp" in contents assert "MCPModule.blueprint()" in contents diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index 34861fd688..057cd77645 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -48,17 +48,17 @@ "unitree-g1-joystick": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick:unitree_g1_joystick", "unitree-g1-shm": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm:unitree_g1_shm", "unitree-g1-sim": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim:unitree_g1_sim", - "unitree-go2": "dimos.robot.unitree.go2.blueprints:unitree_go2", - "unitree-go2-agentic": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic", - "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_huggingface", - "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_mcp", - "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.blueprints:unitree_go2_agentic_ollama", - "unitree-go2-basic": "dimos.robot.unitree.go2.blueprints:unitree_go2_basic", - "unitree-go2-detection": "dimos.robot.unitree.go2.blueprints:unitree_go2_detection", - "unitree-go2-ros": "dimos.robot.unitree.go2.blueprints:unitree_go2_ros", - "unitree-go2-spatial": "dimos.robot.unitree.go2.blueprints:unitree_go2_spatial", - "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.blueprints:unitree_go2_temporal_memory", - "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.blueprints:unitree_go2_vlm_stream_test", + "unitree-go2": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2:unitree_go2", + "unitree-go2-agentic": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic:unitree_go2_agentic", + "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_huggingface:unitree_go2_agentic_huggingface", + "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_mcp:unitree_go2_agentic_mcp", + "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_ollama:unitree_go2_agentic_ollama", + "unitree-go2-basic": "dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic:unitree_go2_basic", + "unitree-go2-detection": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_detection:unitree_go2_detection", + "unitree-go2-ros": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_ros:unitree_go2_ros", + "unitree-go2-spatial": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial:unitree_go2_spatial", + "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_temporal_memory:unitree_go2_temporal_memory", + "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_vlm_stream_test:unitree_go2_vlm_stream_test", "xarm6-planner-only": "dimos.manipulation.manipulation_blueprints:xarm6_planner_only", "xarm7-planner-coordinator": "dimos.manipulation.manipulation_blueprints:xarm7_planner_coordinator", } diff --git a/dimos/robot/unitree/g1/blueprints/agentic/__init__.py b/dimos/robot/unitree/g1/blueprints/agentic/__init__.py new file mode 100644 index 0000000000..5e6db90d91 --- /dev/null +++ b/dimos/robot/unitree/g1/blueprints/agentic/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Agentic blueprints for Unitree G1.""" diff --git a/dimos/robot/unitree/g1/blueprints/basic/__init__.py b/dimos/robot/unitree/g1/blueprints/basic/__init__.py new file mode 100644 index 0000000000..87e6586f56 --- /dev/null +++ b/dimos/robot/unitree/g1/blueprints/basic/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Basic blueprints for Unitree G1.""" diff --git a/dimos/robot/unitree/g1/blueprints/perceptive/__init__.py b/dimos/robot/unitree/g1/blueprints/perceptive/__init__.py new file mode 100644 index 0000000000..9bd838e8b8 --- /dev/null +++ b/dimos/robot/unitree/g1/blueprints/perceptive/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Perceptive blueprints for Unitree G1.""" diff --git a/dimos/robot/unitree/g1/blueprints/primitive/__init__.py b/dimos/robot/unitree/g1/blueprints/primitive/__init__.py new file mode 100644 index 0000000000..833f767728 --- /dev/null +++ b/dimos/robot/unitree/g1/blueprints/primitive/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Primitive blueprints for Unitree G1.""" diff --git a/dimos/robot/unitree/go2/blueprints/agentic/__init__.py b/dimos/robot/unitree/go2/blueprints/agentic/__init__.py new file mode 100644 index 0000000000..84d1b41b23 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/agentic/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Agentic blueprints for Unitree GO2.""" diff --git a/dimos/robot/unitree/go2/blueprints/basic/__init__.py b/dimos/robot/unitree/go2/blueprints/basic/__init__.py new file mode 100644 index 0000000000..79964b0297 --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/basic/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Basic blueprints for Unitree GO2.""" diff --git a/dimos/robot/unitree/go2/blueprints/smart/__init__.py b/dimos/robot/unitree/go2/blueprints/smart/__init__.py new file mode 100644 index 0000000000..7d5bdbc3ab --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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. + +"""Smart blueprints for Unitree GO2.""" From 829412b95e0781769284ec3b36607921c6e23307 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 14:27:40 -0800 Subject: [PATCH 09/45] make blueprint imports lazy --- dimos/robot/unitree/g1/blueprints/__init__.py | 53 ++++++--------- .../robot/unitree/go2/blueprints/__init__.py | 64 ++++++------------- pyproject.toml | 1 + uv.lock | 14 ++++ 4 files changed, 53 insertions(+), 79 deletions(-) diff --git a/dimos/robot/unitree/g1/blueprints/__init__.py b/dimos/robot/unitree/g1/blueprints/__init__.py index 26a043563c..634b7a1948 100644 --- a/dimos/robot/unitree/g1/blueprints/__init__.py +++ b/dimos/robot/unitree/g1/blueprints/__init__.py @@ -15,38 +15,23 @@ """Cascaded G1 blueprints split into focused modules.""" -from dimos.robot.unitree.g1.blueprints.agentic._agentic_skills import _agentic_skills -from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic import unitree_g1_agentic -from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim import unitree_g1_agentic_sim -from dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_full import unitree_g1_full -from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic -from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim import unitree_g1_basic_sim -from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick import unitree_g1_joystick -from dimos.robot.unitree.g1.blueprints.perceptive._perception_and_memory import ( - _perception_and_memory, -) -from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1 import unitree_g1 -from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_detection import unitree_g1_detection -from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm import unitree_g1_shm -from dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim import unitree_g1_sim -from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( - uintree_g1_basic_no_nav, - uintree_g1_basic_no_nav as basic_no_nav, -) +import lazy_loader as lazy -__all__ = [ - "_agentic_skills", - "_perception_and_memory", - "basic_no_nav", - "uintree_g1_basic_no_nav", - "unitree_g1", - "unitree_g1_agentic", - "unitree_g1_agentic_sim", - "unitree_g1_basic", - "unitree_g1_basic_sim", - "unitree_g1_detection", - "unitree_g1_full", - "unitree_g1_joystick", - "unitree_g1_shm", - "unitree_g1_sim", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "agentic._agentic_skills": ["_agentic_skills"], + "agentic.unitree_g1_agentic": ["unitree_g1_agentic"], + "agentic.unitree_g1_agentic_sim": ["unitree_g1_agentic_sim"], + "agentic.unitree_g1_full": ["unitree_g1_full"], + "basic.unitree_g1_basic": ["unitree_g1_basic"], + "basic.unitree_g1_basic_sim": ["unitree_g1_basic_sim"], + "basic.unitree_g1_joystick": ["unitree_g1_joystick"], + "perceptive._perception_and_memory": ["_perception_and_memory"], + "perceptive.unitree_g1": ["unitree_g1"], + "perceptive.unitree_g1_detection": ["unitree_g1_detection"], + "perceptive.unitree_g1_shm": ["unitree_g1_shm"], + "perceptive.unitree_g1_sim": ["unitree_g1_sim"], + "primitive.uintree_g1_primitive_no_nav": ["uintree_g1_basic_no_nav", "basic_no_nav"], + }, +) diff --git a/dimos/robot/unitree/go2/blueprints/__init__.py b/dimos/robot/unitree/go2/blueprints/__init__.py index b461591028..f8539fcab3 100644 --- a/dimos/robot/unitree/go2/blueprints/__init__.py +++ b/dimos/robot/unitree/go2/blueprints/__init__.py @@ -15,49 +15,23 @@ """Cascaded GO2 blueprints split into focused modules.""" -from dimos.robot.unitree.go2.blueprints.agentic._common_agentic import _common_agentic -from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic import unitree_go2_agentic -from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_huggingface import ( - unitree_go2_agentic_huggingface, -) -from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_mcp import ( - unitree_go2_agentic_mcp, -) -from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_ollama import ( - unitree_go2_agentic_ollama, -) -from dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_temporal_memory import ( - unitree_go2_temporal_memory, -) -from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import ( - _linux, - _mac, - unitree_go2_basic, -) -from dimos.robot.unitree.go2.blueprints.smart._with_jpeg import _with_jpeglcm, _with_jpegshm -from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2 -from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_detection import unitree_go2_detection -from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_ros import unitree_go2_ros -from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial import unitree_go2_spatial -from dimos.robot.unitree.go2.blueprints.smart.unitree_go2_vlm_stream_test import ( - unitree_go2_vlm_stream_test, -) +import lazy_loader as lazy -__all__ = [ - "_common_agentic", - "_linux", - "_mac", - "_with_jpeglcm", - "_with_jpegshm", - "unitree_go2", - "unitree_go2_agentic", - "unitree_go2_agentic_huggingface", - "unitree_go2_agentic_mcp", - "unitree_go2_agentic_ollama", - "unitree_go2_basic", - "unitree_go2_detection", - "unitree_go2_ros", - "unitree_go2_spatial", - "unitree_go2_temporal_memory", - "unitree_go2_vlm_stream_test", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "agentic._common_agentic": ["_common_agentic"], + "agentic.unitree_go2_agentic": ["unitree_go2_agentic"], + "agentic.unitree_go2_agentic_huggingface": ["unitree_go2_agentic_huggingface"], + "agentic.unitree_go2_agentic_mcp": ["unitree_go2_agentic_mcp"], + "agentic.unitree_go2_agentic_ollama": ["unitree_go2_agentic_ollama"], + "agentic.unitree_go2_temporal_memory": ["unitree_go2_temporal_memory"], + "basic.unitree_go2_basic": ["_linux", "_mac", "unitree_go2_basic"], + "smart._with_jpeg": ["_with_jpeglcm", "_with_jpegshm"], + "smart.unitree_go2": ["unitree_go2"], + "smart.unitree_go2_detection": ["unitree_go2_detection"], + "smart.unitree_go2_ros": ["unitree_go2_ros"], + "smart.unitree_go2_spatial": ["unitree_go2_spatial"], + "smart.unitree_go2_vlm_stream_test": ["unitree_go2_vlm_stream_test"], + }, +) diff --git a/pyproject.toml b/pyproject.toml index 6a7553cffd..3f1c523e03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dependencies = [ "pydantic", "python-dotenv", "annotation-protocol>=1.4.0", + "lazy_loader", # Multiprocess "dask[complete]==2025.5.1", diff --git a/uv.lock b/uv.lock index e89766e853..4cd476aa57 100644 --- a/uv.lock +++ b/uv.lock @@ -1732,6 +1732,7 @@ dependencies = [ { name = "colorlog" }, { name = "dask", extra = ["complete"] }, { name = "dimos-lcm" }, + { name = "lazy-loader" }, { name = "llvmlite" }, { name = "numba" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -2002,6 +2003,7 @@ requires-dist = [ { name = "langchain-text-splitters", marker = "extra == 'agents'", specifier = ">=1,<2" }, { name = "lap", marker = "extra == 'perception'", specifier = ">=0.5.12" }, { name = "lark", marker = "extra == 'misc'" }, + { name = "lazy-loader" }, { name = "llvmlite", specifier = ">=0.42.0" }, { name = "lxml-stubs", marker = "extra == 'dev'", specifier = ">=0.5.1,<1" }, { name = "matplotlib", marker = "extra == 'manipulation'", specifier = ">=3.7.1" }, @@ -4137,6 +4139,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + [[package]] name = "lcm" version = "1.5.2" From e99248260f29ece7904e188c14f907d872fa3676 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 15:01:03 -0800 Subject: [PATCH 10/45] make all __init__ imports lazy --- dimos/agents/__init__.py | 28 +++----- dimos/agents/_skill_exports.py | 18 +++++ dimos/control/__init__.py | 78 ++++++++------------ dimos/core/__init__.py | 80 ++++++++------------- dimos/core/_dask_exports.py | 17 +++++ dimos/core/_protocol_exports.py | 19 +++++ dimos/dashboard/__init__.py | 9 ++- dimos/manipulation/planning/__init__.py | 75 +++++++------------ dimos/models/vl/__init__.py | 27 ++++--- dimos/perception/detection/__init__.py | 18 ++--- dimos/perception/detection/type/__init__.py | 71 +++++++----------- dimos/robot/drone/__init__.py | 15 ++-- dimos/robot/unitree_webrtc/type/__init__.py | 17 ++++- dimos/utils/testing/__init__.py | 18 +++-- dimos/web/dimos_interface/__init__.py | 9 ++- 15 files changed, 246 insertions(+), 253 deletions(-) create mode 100644 dimos/agents/_skill_exports.py create mode 100644 dimos/core/_dask_exports.py create mode 100644 dimos/core/_protocol_exports.py diff --git a/dimos/agents/__init__.py b/dimos/agents/__init__.py index f058f24d2f..ab3c67cfa5 100644 --- a/dimos/agents/__init__.py +++ b/dimos/agents/__init__.py @@ -1,18 +1,12 @@ -from dimos.agents.agent import Agent, deploy -from dimos.agents.spec import AgentSpec -from dimos.agents.vlm_agent import VLMAgent -from dimos.agents.vlm_stream_tester import VlmStreamTester -from dimos.protocol.skill.skill import skill -from dimos.protocol.skill.type import Output, Reducer, Stream +import lazy_loader as lazy -__all__ = [ - "Agent", - "AgentSpec", - "Output", - "Reducer", - "Stream", - "VLMAgent", - "VlmStreamTester", - "deploy", - "skill", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "agent": ["Agent", "deploy"], + "spec": ["AgentSpec"], + "vlm_agent": ["VLMAgent"], + "vlm_stream_tester": ["VlmStreamTester"], + "_skill_exports": ["skill", "Output", "Reducer", "Stream"], + }, +) diff --git a/dimos/agents/_skill_exports.py b/dimos/agents/_skill_exports.py new file mode 100644 index 0000000000..a04093b20b --- /dev/null +++ b/dimos/agents/_skill_exports.py @@ -0,0 +1,18 @@ +# Copyright 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. + +from dimos.protocol.skill.skill import skill +from dimos.protocol.skill.type import Output, Reducer, Stream + +__all__ = ["Output", "Reducer", "Stream", "skill"] diff --git a/dimos/control/__init__.py b/dimos/control/__init__.py index 50d0330107..23ac02836b 100644 --- a/dimos/control/__init__.py +++ b/dimos/control/__init__.py @@ -49,52 +49,34 @@ >>> coord.start() """ -from dimos.control.components import ( - HardwareComponent, - HardwareId, - HardwareType, - JointName, - JointState, - make_joints, -) -from dimos.control.coordinator import ( - ControlCoordinator, - ControlCoordinatorConfig, - TaskConfig, - control_coordinator, -) -from dimos.control.hardware_interface import ConnectedHardware -from dimos.control.task import ( - ControlMode, - ControlTask, - CoordinatorState, - JointCommandOutput, - JointStateSnapshot, - ResourceClaim, -) -from dimos.control.tick_loop import TickLoop +import lazy_loader as lazy -__all__ = [ - # Connected hardware - "ConnectedHardware", - # Coordinator - "ControlCoordinator", - "ControlCoordinatorConfig", - "ControlMode", - # Task protocol and types - "ControlTask", - "CoordinatorState", - "HardwareComponent", - "HardwareId", - "HardwareType", - "JointCommandOutput", - "JointName", - "JointState", - "JointStateSnapshot", - "ResourceClaim", - "TaskConfig", - # Tick loop - "TickLoop", - "control_coordinator", - "make_joints", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "components": [ + "HardwareComponent", + "HardwareId", + "HardwareType", + "JointName", + "JointState", + "make_joints", + ], + "coordinator": [ + "ControlCoordinator", + "ControlCoordinatorConfig", + "TaskConfig", + "control_coordinator", + ], + "hardware_interface": ["ConnectedHardware"], + "task": [ + "ControlMode", + "ControlTask", + "CoordinatorState", + "JointCommandOutput", + "JointStateSnapshot", + "ResourceClaim", + ], + "tick_loop": ["TickLoop"], + }, +) diff --git a/dimos/core/__init__.py b/dimos/core/__init__.py index 5a4acc1762..53e8531792 100644 --- a/dimos/core/__init__.py +++ b/dimos/core/__init__.py @@ -5,60 +5,37 @@ import time from typing import TYPE_CHECKING, cast -from dask.distributed import Client, LocalCluster -from rich.console import Console - -import dimos.core.colors as colors -from dimos.core.core import rpc -from dimos.core.module import Module, ModuleBase, ModuleConfig, ModuleConfigT -from dimos.core.rpc_client import RPCClient -from dimos.core.stream import In, Out, RemoteIn, RemoteOut, Transport -from dimos.core.transport import ( - LCMTransport, - SHMTransport, - ZenohTransport, - pLCMTransport, - pSHMTransport, -) -from dimos.protocol.rpc import LCMRPC -from dimos.protocol.rpc.spec import RPCSpec -from dimos.protocol.tf import LCMTF, TF, PubSubTF, TFConfig, TFSpec -from dimos.utils.actor_registry import ActorRegistry +import lazy_loader as lazy + from dimos.utils.logging_config import setup_logger if TYPE_CHECKING: # Avoid runtime import to prevent circular import; ruff's TC001 would otherwise move it. + from dask.distributed import Client as DimosCluster + from dimos.core.rpc_client import ModuleProxy logger = setup_logger() -__all__ = [ - "LCMRPC", - "LCMTF", - "TF", - "DimosCluster", - "In", - "LCMTransport", - "Module", - "ModuleBase", - "ModuleConfig", - "ModuleConfigT", - "Out", - "PubSubTF", - "RPCSpec", - "RemoteIn", - "RemoteOut", - "SHMTransport", - "TFConfig", - "TFSpec", - "Transport", - "ZenohTransport", - "colors", - "pLCMTransport", - "pSHMTransport", - "rpc", - "start", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["colors"], + submod_attrs={ + "_dask_exports": ["DimosCluster"], + "_protocol_exports": ["LCMRPC", "RPCSpec", "LCMTF", "TF", "PubSubTF", "TFConfig", "TFSpec"], + "core": ["rpc"], + "module": ["Module", "ModuleBase", "ModuleConfig", "ModuleConfigT"], + "stream": ["In", "Out", "RemoteIn", "RemoteOut", "Transport"], + "transport": [ + "LCMTransport", + "SHMTransport", + "ZenohTransport", + "pLCMTransport", + "pSHMTransport", + ], + }, +) +__all__ += ["start", "wait_exit"] class CudaCleanupPlugin: @@ -91,10 +68,10 @@ def teardown(self, worker) -> None: # type: ignore[no-untyped-def] def patch_actor(actor, cls) -> None: ... # type: ignore[no-untyped-def] -DimosCluster = Client +def patchdask(dask_client: DimosCluster, local_cluster: LocalCluster) -> DimosCluster: + from dimos.core.rpc_client import RPCClient + from dimos.utils.actor_registry import ActorRegistry - -def patchdask(dask_client: Client, local_cluster: LocalCluster) -> DimosCluster: def deploy( # type: ignore[no-untyped-def] actor_class: type[Module], *args, @@ -129,6 +106,8 @@ def deploy( # type: ignore[no-untyped-def] def check_worker_memory() -> None: """Check memory usage of all workers.""" info = dask_client.scheduler_info() + from rich.console import Console + console = Console() total_workers = len(info.get("workers", {})) total_memory_used = 0 @@ -263,6 +242,9 @@ def start(n: int | None = None, memory_limit: str = "auto") -> DimosCluster: DimosCluster: A patched Dask client with deploy(), check_worker_memory(), stop(), and close_all() methods """ + from dask.distributed import Client, LocalCluster + from rich.console import Console + console = Console() if not n: n = mp.cpu_count() diff --git a/dimos/core/_dask_exports.py b/dimos/core/_dask_exports.py new file mode 100644 index 0000000000..cb257e7804 --- /dev/null +++ b/dimos/core/_dask_exports.py @@ -0,0 +1,17 @@ +# Copyright 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. + +from dask.distributed import Client as DimosCluster + +__all__ = ["DimosCluster"] diff --git a/dimos/core/_protocol_exports.py b/dimos/core/_protocol_exports.py new file mode 100644 index 0000000000..be77fd8323 --- /dev/null +++ b/dimos/core/_protocol_exports.py @@ -0,0 +1,19 @@ +# Copyright 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. + +from dimos.protocol.rpc import LCMRPC +from dimos.protocol.rpc.spec import RPCSpec +from dimos.protocol.tf import LCMTF, TF, PubSubTF, TFConfig, TFSpec + +__all__ = ["LCMRPC", "LCMTF", "TF", "PubSubTF", "RPCSpec", "TFConfig", "TFSpec"] diff --git a/dimos/dashboard/__init__.py b/dimos/dashboard/__init__.py index fc97805936..7c6f6d9ceb 100644 --- a/dimos/dashboard/__init__.py +++ b/dimos/dashboard/__init__.py @@ -29,6 +29,11 @@ def start(self): rr.log("my/entity", my_data.to_rerun()) """ -from dimos.dashboard.rerun_init import connect_rerun, init_rerun_server, shutdown_rerun +import lazy_loader as lazy -__all__ = ["connect_rerun", "init_rerun_server", "shutdown_rerun"] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "rerun_init": ["connect_rerun", "init_rerun_server", "shutdown_rerun"], + }, +) diff --git a/dimos/manipulation/planning/__init__.py b/dimos/manipulation/planning/__init__.py index a9a89a96cd..8aaf0caa25 100644 --- a/dimos/manipulation/planning/__init__.py +++ b/dimos/manipulation/planning/__init__.py @@ -57,55 +57,28 @@ ``` """ -# Factory functions -from dimos.manipulation.planning.factory import ( - create_kinematics, - create_planner, - create_planning_stack, - create_world, +import lazy_loader as lazy + +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "factory": ["create_kinematics", "create_planner", "create_planning_stack", "create_world"], + "spec": [ + "CollisionObjectMessage", + "IKResult", + "IKStatus", + "JointPath", + "KinematicsSpec", + "Obstacle", + "ObstacleType", + "PlannerSpec", + "PlanningResult", + "PlanningStatus", + "RobotModelConfig", + "RobotName", + "WorldRobotID", + "WorldSpec", + ], + "trajectory_generator.joint_trajectory_generator": ["JointTrajectoryGenerator"], + }, ) - -# Data classes and Protocols -from dimos.manipulation.planning.spec import ( - CollisionObjectMessage, - IKResult, - IKStatus, - JointPath, - KinematicsSpec, - Obstacle, - ObstacleType, - PlannerSpec, - PlanningResult, - PlanningStatus, - RobotModelConfig, - RobotName, - WorldRobotID, - WorldSpec, -) - -# Trajectory generation -from dimos.manipulation.planning.trajectory_generator.joint_trajectory_generator import ( - JointTrajectoryGenerator, -) - -__all__ = [ - "CollisionObjectMessage", - "IKResult", - "IKStatus", - "JointPath", - "JointTrajectoryGenerator", - "KinematicsSpec", - "Obstacle", - "ObstacleType", - "PlannerSpec", - "PlanningResult", - "PlanningStatus", - "RobotModelConfig", - "RobotName", - "WorldRobotID", - "WorldSpec", - "create_kinematics", - "create_planner", - "create_planning_stack", - "create_world", -] diff --git a/dimos/models/vl/__init__.py b/dimos/models/vl/__init__.py index e4bb68e03c..482a907cbd 100644 --- a/dimos/models/vl/__init__.py +++ b/dimos/models/vl/__init__.py @@ -1,16 +1,13 @@ -from dimos.models.vl.base import Captioner, VlModel -from dimos.models.vl.florence import Florence2Model -from dimos.models.vl.moondream import MoondreamVlModel -from dimos.models.vl.moondream_hosted import MoondreamHostedVlModel -from dimos.models.vl.openai import OpenAIVlModel -from dimos.models.vl.qwen import QwenVlModel +import lazy_loader as lazy -__all__ = [ - "Captioner", - "Florence2Model", - "MoondreamHostedVlModel", - "MoondreamVlModel", - "OpenAIVlModel", - "QwenVlModel", - "VlModel", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "base": ["Captioner", "VlModel"], + "florence": ["Florence2Model"], + "moondream": ["MoondreamVlModel"], + "moondream_hosted": ["MoondreamHostedVlModel"], + "openai": ["OpenAIVlModel"], + "qwen": ["QwenVlModel"], + }, +) diff --git a/dimos/perception/detection/__init__.py b/dimos/perception/detection/__init__.py index b76f497425..ae9f8cb14d 100644 --- a/dimos/perception/detection/__init__.py +++ b/dimos/perception/detection/__init__.py @@ -1,10 +1,10 @@ -from dimos.perception.detection.detectors import Detector, Yolo2DDetector -from dimos.perception.detection.module2D import Detection2DModule -from dimos.perception.detection.module3D import Detection3DModule +import lazy_loader as lazy -__all__ = [ - "Detection2DModule", - "Detection3DModule", - "Detector", - "Yolo2DDetector", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "detectors": ["Detector", "Yolo2DDetector"], + "module2D": ["Detection2DModule"], + "module3D": ["Detection3DModule"], + }, +) diff --git a/dimos/perception/detection/type/__init__.py b/dimos/perception/detection/type/__init__.py index d69d00ba97..00cf943db3 100644 --- a/dimos/perception/detection/type/__init__.py +++ b/dimos/perception/detection/type/__init__.py @@ -1,45 +1,28 @@ -from dimos.perception.detection.type.detection2d import ( # type: ignore[attr-defined] - Detection2D, - Detection2DBBox, - Detection2DPerson, - Detection2DPoint, - Filter2D, - ImageDetections2D, -) -from dimos.perception.detection.type.detection3d import ( - Detection3D, - Detection3DBBox, - Detection3DPC, - ImageDetections3DPC, - PointCloudFilter, - height_filter, - radius_outlier, - raycast, - statistical, -) -from dimos.perception.detection.type.imageDetections import ImageDetections -from dimos.perception.detection.type.utils import TableStr +import lazy_loader as lazy -__all__ = [ - # 2D Detection types - "Detection2D", - "Detection2DBBox", - "Detection2DPerson", - "Detection2DPoint", - # 3D Detection types - "Detection3D", - "Detection3DBBox", - "Detection3DPC", - "Filter2D", - # Base types - "ImageDetections", - "ImageDetections2D", - "ImageDetections3DPC", - # Point cloud filters - "PointCloudFilter", - "TableStr", - "height_filter", - "radius_outlier", - "raycast", - "statistical", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "detection2d": [ + "Detection2D", + "Detection2DBBox", + "Detection2DPerson", + "Detection2DPoint", + "Filter2D", + "ImageDetections2D", + ], + "detection3d": [ + "Detection3D", + "Detection3DBBox", + "Detection3DPC", + "ImageDetections3DPC", + "PointCloudFilter", + "height_filter", + "radius_outlier", + "raycast", + "statistical", + ], + "imageDetections": ["ImageDetections"], + "utils": ["TableStr"], + }, +) diff --git a/dimos/robot/drone/__init__.py b/dimos/robot/drone/__init__.py index 5d4eed4dae..1ed8521b8b 100644 --- a/dimos/robot/drone/__init__.py +++ b/dimos/robot/drone/__init__.py @@ -14,9 +14,14 @@ """Generic drone module for MAVLink-based drones.""" -from .camera_module import DroneCameraModule -from .connection_module import DroneConnectionModule -from .drone import Drone -from .mavlink_connection import MavlinkConnection +import lazy_loader as lazy -__all__ = ["Drone", "DroneCameraModule", "DroneConnectionModule", "MavlinkConnection"] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "camera_module": ["DroneCameraModule"], + "connection_module": ["DroneConnectionModule"], + "drone": ["Drone"], + "mavlink_connection": ["MavlinkConnection"], + }, +) diff --git a/dimos/robot/unitree_webrtc/type/__init__.py b/dimos/robot/unitree_webrtc/type/__init__.py index 31c8412f88..03ff4f4563 100644 --- a/dimos/robot/unitree_webrtc/type/__init__.py +++ b/dimos/robot/unitree_webrtc/type/__init__.py @@ -15,4 +15,19 @@ """Compatibility re-exports for legacy dimos.robot.unitree_webrtc.type.* imports.""" -from dimos.robot.unitree.type import * # noqa: F403 +import importlib + +__all__ = [] + + +def __getattr__(name: str): # type: ignore[no-untyped-def] + module = importlib.import_module("dimos.robot.unitree.type") + try: + return getattr(module, name) + except AttributeError as exc: + raise AttributeError(f"No {__name__} attribute {name}") from exc + + +def __dir__() -> list[str]: + module = importlib.import_module("dimos.robot.unitree.type") + return [name for name in dir(module) if not name.startswith("_")] diff --git a/dimos/utils/testing/__init__.py b/dimos/utils/testing/__init__.py index ffb640de39..568cd3604f 100644 --- a/dimos/utils/testing/__init__.py +++ b/dimos/utils/testing/__init__.py @@ -1,11 +1,9 @@ -from dimos.utils.testing.moment import Moment, OutputMoment, SensorMoment -from dimos.utils.testing.replay import SensorReplay, TimedSensorReplay, TimedSensorStorage +import lazy_loader as lazy -__all__ = [ - "Moment", - "OutputMoment", - "SensorMoment", - "SensorReplay", - "TimedSensorReplay", - "TimedSensorStorage", -] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "moment": ["Moment", "OutputMoment", "SensorMoment"], + "replay": ["SensorReplay", "TimedSensorReplay", "TimedSensorStorage"], + }, +) diff --git a/dimos/web/dimos_interface/__init__.py b/dimos/web/dimos_interface/__init__.py index 5ca28b30e5..3bdc622cee 100644 --- a/dimos/web/dimos_interface/__init__.py +++ b/dimos/web/dimos_interface/__init__.py @@ -2,6 +2,11 @@ Dimensional Interface package """ -from .api.server import FastAPIServer +import lazy_loader as lazy -__all__ = ["FastAPIServer"] +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submod_attrs={ + "api.server": ["FastAPIServer"], + }, +) From 0d8dd3f053517d6528c55046084aec32f9a091c8 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 17:21:41 -0800 Subject: [PATCH 11/45] mark cuda required tests --- dimos/models/depth/test_metric3d.py | 5 +++-- .../contact_graspnet_pytorch/test_contact_graspnet.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dimos/models/depth/test_metric3d.py b/dimos/models/depth/test_metric3d.py index 050100047b..835f09adcc 100644 --- a/dimos/models/depth/test_metric3d.py +++ b/dimos/models/depth/test_metric3d.py @@ -11,7 +11,7 @@ def sample_intrinsics() -> list[float]: """Sample camera intrinsics [fx, fy, cx, cy].""" return [500.0, 500.0, 320.0, 240.0] - +@pytest.mark.cuda @pytest.mark.gpu def test_metric3d_init(sample_intrinsics: list[float]) -> None: """Test Metric3D initialization.""" @@ -30,7 +30,6 @@ def test_metric3d_update_intrinsic(sample_intrinsics: list[float]) -> None: model.update_intrinsic(new_intrinsics) assert model.intrinsic == new_intrinsics - @pytest.mark.gpu def test_metric3d_update_intrinsic_invalid(sample_intrinsics: list[float]) -> None: """Test that invalid intrinsics raise an error.""" @@ -40,6 +39,7 @@ def test_metric3d_update_intrinsic_invalid(sample_intrinsics: list[float]) -> No model.update_intrinsic([1.0, 2.0]) # Only 2 values +@pytest.mark.cuda @pytest.mark.gpu def test_metric3d_infer_depth(sample_intrinsics: list[float]) -> None: """Test depth inference on a sample image.""" @@ -65,6 +65,7 @@ def test_metric3d_infer_depth(sample_intrinsics: list[float]) -> None: model.stop() +@pytest.mark.cuda @pytest.mark.gpu def test_metric3d_multiple_inferences(sample_intrinsics: list[float]) -> None: """Test multiple depth inferences.""" diff --git a/dimos/models/manipulation/contact_graspnet_pytorch/test_contact_graspnet.py b/dimos/models/manipulation/contact_graspnet_pytorch/test_contact_graspnet.py index 8ae990db92..5c36a9070c 100644 --- a/dimos/models/manipulation/contact_graspnet_pytorch/test_contact_graspnet.py +++ b/dimos/models/manipulation/contact_graspnet_pytorch/test_contact_graspnet.py @@ -14,6 +14,7 @@ def is_manipulation_installed() -> bool: return False @pytest.mark.integration +@pytest.mark.cuda @pytest.mark.skipif(not is_manipulation_installed(), reason="This test requires 'pip install .[manipulation]' to be run") def test_contact_graspnet_inference() -> None: From 9f76c6cbafdae0437eec9c8a2c4b051b6969d47d Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 17:31:00 -0800 Subject: [PATCH 12/45] add hook filter for cuda --- dimos/conftest.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dimos/conftest.py b/dimos/conftest.py index e0544bea1c..f0535a8048 100644 --- a/dimos/conftest.py +++ b/dimos/conftest.py @@ -18,6 +18,29 @@ import pytest +def _has_cuda(): + try: + import torch + except Exception: + return False + + try: + return bool(torch.cuda.is_available()) + except Exception: + return False + + +@pytest.hookimpl() +def pytest_collection_modifyitems(_config, items): + if not _has_cuda(): + skip_marker = pytest.mark.skip( + reason="CUDA is not available (torch.cuda.is_available() returned False)" + ) + for item in items: + if item.get_closest_marker("cuda"): + item.add_marker(skip_marker) + + @pytest.fixture def event_loop(): loop = asyncio.new_event_loop() From 1a530a15209066127b48f7bdec07c816cd92cf55 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 17:34:11 -0800 Subject: [PATCH 13/45] add to toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d88e9ff0bf..c456201aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -389,6 +389,7 @@ markers = [ "lcm: tests that run actual LCM bus (can't execute in CI)", "module: tests that need to run directly as modules", "gpu: tests that require GPU", + "cuda: tests which require CUDA (specifically CUDA not just GPU acceleration)", "tofix: temporarily disabled test", "e2e: end to end tests", "integration: slower integration tests", From a1f5352c5d4f8fbfbdff58f6c210af35503dab90 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 18:00:08 -0800 Subject: [PATCH 14/45] fix var name --- dimos/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/conftest.py b/dimos/conftest.py index f0535a8048..2eb273ab3c 100644 --- a/dimos/conftest.py +++ b/dimos/conftest.py @@ -31,7 +31,7 @@ def _has_cuda(): @pytest.hookimpl() -def pytest_collection_modifyitems(_config, items): +def pytest_collection_modifyitems(config, items): if not _has_cuda(): skip_marker = pytest.mark.skip( reason="CUDA is not available (torch.cuda.is_available() returned False)" From e0aba66556b1f6179afcee7dbcecf0792d72ec86 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 21:00:01 -0800 Subject: [PATCH 15/45] make dimos --help fast --- dimos/robot/cli/dimos.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/dimos/robot/cli/dimos.py b/dimos/robot/cli/dimos.py index 5dad9d10ad..614049850d 100644 --- a/dimos/robot/cli/dimos.py +++ b/dimos/robot/cli/dimos.py @@ -19,13 +19,8 @@ import typer -from dimos.core.blueprints import autoconnect from dimos.core.global_config import GlobalConfig -from dimos.protocol import pubsub from dimos.robot.all_blueprints import all_blueprints -from dimos.robot.cli.topic import topic_echo, topic_send -from dimos.robot.get_all_blueprints import get_blueprint_by_name, get_module_by_name -from dimos.utils.logging_config import setup_exception_handler RobotType = Enum("RobotType", {key.replace("-", "_").upper(): key for key in all_blueprints.keys()}) # type: ignore[misc] @@ -110,6 +105,11 @@ def run( ), ) -> None: """Start a robot blueprint""" + from dimos.core.blueprints import autoconnect + from dimos.protocol import pubsub + from dimos.robot.get_all_blueprints import get_blueprint_by_name, get_module_by_name + from dimos.utils.logging_config import setup_exception_handler + setup_exception_handler() cli_config_overrides: dict[str, Any] = ctx.obj @@ -127,6 +127,8 @@ def run( @main.command() def show_config(ctx: typer.Context) -> None: """Show current config settings and their values.""" + from dimos.core.global_config import GlobalConfig + cli_config_overrides: dict[str, Any] = ctx.obj config = GlobalConfig().model_copy(update=cli_config_overrides) @@ -137,6 +139,8 @@ def show_config(ctx: typer.Context) -> None: @main.command() def list() -> None: """List all available blueprints.""" + from dimos.robot.all_blueprints import all_blueprints + blueprints = [name for name in all_blueprints.keys() if not name.startswith("demo-")] for blueprint_name in sorted(blueprints): typer.echo(blueprint_name) @@ -190,6 +194,8 @@ def echo( help="Optional message type (e.g., PoseStamped). If omitted, infer from '/topic#pkg.Msg'.", ), ) -> None: + from dimos.robot.cli.topic import topic_echo + topic_echo(topic, type_name) @@ -198,6 +204,8 @@ def send( topic: str = typer.Argument(..., help="Topic name to send to (e.g., /goal_request)"), message_expr: str = typer.Argument(..., help="Python expression for the message"), ) -> None: + from dimos.robot.cli.topic import topic_send + topic_send(topic, message_expr) From 290253a7ed32a7f69cb71ebd0e51cefeeb52cbf0 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 21:16:17 -0800 Subject: [PATCH 16/45] name issue --- dimos/perception/detection/test_moduleDB.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dimos/perception/detection/test_moduleDB.py b/dimos/perception/detection/test_moduleDB.py index 8445372bf6..f60ed98c8e 100644 --- a/dimos/perception/detection/test_moduleDB.py +++ b/dimos/perception/detection/test_moduleDB.py @@ -22,12 +22,12 @@ from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.moduleDB import ObjectDBModule -from dimos.robot.unitree.go2 import connection +from dimos.robot.unitree.go2 import connection as go2_connection @pytest.mark.module def test_moduleDB(dimos_cluster) -> None: - connection = connection.deploy(dimos_cluster, "fake") + connection = go2_connection.deploy(dimos_cluster, "fake") moduleDB = dimos_cluster.deploy( ObjectDBModule, From ba6e29aa93221d4cba7c28ec6207ba29110ea64d Mon Sep 17 00:00:00 2001 From: jeff-hykin <17692058+jeff-hykin@users.noreply.github.com> Date: Sun, 8 Feb 2026 05:18:52 +0000 Subject: [PATCH 17/45] CI code cleanup --- dimos/protocol/mcp/test_mcp_module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dimos/protocol/mcp/test_mcp_module.py b/dimos/protocol/mcp/test_mcp_module.py index cd1cecb026..15ed512845 100644 --- a/dimos/protocol/mcp/test_mcp_module.py +++ b/dimos/protocol/mcp/test_mcp_module.py @@ -30,7 +30,9 @@ def test_unitree_blueprint_has_mcp() -> None: - contents = Path("dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py").read_text() + contents = Path( + "dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_agentic_mcp.py" + ).read_text() assert "agentic_mcp" in contents assert "MCPModule.blueprint()" in contents From 36c69bb98f68c1c63f2cab8413a5fc22a9bed6aa Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:00:03 -0800 Subject: [PATCH 18/45] mypy stuff --- dimos/core/__init__.py | 6 +++--- dimos/msgs/sensor_msgs/test_PointCloud2.py | 2 +- dimos/robot/unitree/type/test_lidar.py | 5 +++-- dimos/utils/testing/replay.py | 16 ++++++++-------- lazy_loader/__init__.pyi | 11 +++++++++++ 5 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 lazy_loader/__init__.pyi diff --git a/dimos/core/__init__.py b/dimos/core/__init__.py index 53e8531792..970c828867 100644 --- a/dimos/core/__init__.py +++ b/dimos/core/__init__.py @@ -5,14 +5,14 @@ import time from typing import TYPE_CHECKING, cast -import lazy_loader as lazy - from dimos.utils.logging_config import setup_logger +import lazy_loader as lazy if TYPE_CHECKING: # Avoid runtime import to prevent circular import; ruff's TC001 would otherwise move it. - from dask.distributed import Client as DimosCluster + from dask.distributed import Client as DimosCluster, LocalCluster + from dimos.core.module import Module from dimos.core.rpc_client import ModuleProxy logger = setup_logger() diff --git a/dimos/msgs/sensor_msgs/test_PointCloud2.py b/dimos/msgs/sensor_msgs/test_PointCloud2.py index 437ac65e4d..64f003e7a7 100644 --- a/dimos/msgs/sensor_msgs/test_PointCloud2.py +++ b/dimos/msgs/sensor_msgs/test_PointCloud2.py @@ -107,7 +107,7 @@ def test_ros_conversion() -> None: print("\nTesting ROS PointCloud2 conversion...") # Create a simple test point cloud - import open3d as o3d + import open3d as o3d # type: ignore[import-untyped] points = np.array( [ diff --git a/dimos/robot/unitree/type/test_lidar.py b/dimos/robot/unitree/type/test_lidar.py index a58cc5821c..719088d77a 100644 --- a/dimos/robot/unitree/type/test_lidar.py +++ b/dimos/robot/unitree/type/test_lidar.py @@ -14,9 +14,10 @@ # limitations under the License. import itertools +from typing import cast from dimos.msgs.sensor_msgs import PointCloud2 -from dimos.robot.unitree.type.lidar import pointcloud2_from_webrtc_lidar +from dimos.robot.unitree.type.lidar import RawLidarMsg, pointcloud2_from_webrtc_lidar from dimos.utils.testing import SensorReplay @@ -25,5 +26,5 @@ def test_init() -> None: for raw_frame in itertools.islice(lidar.iterate(), 5): assert isinstance(raw_frame, dict) - frame = pointcloud2_from_webrtc_lidar(raw_frame) + frame = pointcloud2_from_webrtc_lidar(cast("RawLidarMsg", raw_frame)) assert isinstance(frame, PointCloud2) diff --git a/dimos/utils/testing/replay.py b/dimos/utils/testing/replay.py index 89225c322e..d98ffd81c8 100644 --- a/dimos/utils/testing/replay.py +++ b/dimos/utils/testing/replay.py @@ -47,12 +47,12 @@ def __init__(self, name: str, autocast: Callable[[Any], T] | None = None) -> Non self.root_dir = get_data(name) self.autocast = autocast - def load(self, *names: int | str) -> T | Any | list[T] | list[Any]: + def load(self, *names: int | str) -> T | list[T]: if len(names) == 1: return self.load_one(names[0]) return list(map(lambda name: self.load_one(name), names)) - def load_one(self, name: int | str | Path) -> T | Any: + def load_one(self, name: int | str | Path) -> T: if isinstance(name, int): full_path = self.root_dir / f"/{name:03d}.pickle" elif isinstance(name, Path): @@ -66,7 +66,7 @@ def load_one(self, name: int | str | Path) -> T | Any: return self.autocast(data) return data - def first(self) -> T | Any | None: + def first(self) -> T | None: try: return next(self.iterate()) except StopIteration: @@ -85,14 +85,14 @@ def extract_number(filepath): # type: ignore[no-untyped-def] key=extract_number, ) - def iterate(self, loop: bool = False) -> Iterator[T | Any]: + def iterate(self, loop: bool = False) -> Iterator[T]: while True: for file_path in self.files: yield self.load_one(Path(file_path)) if not loop: break - def stream(self, rate_hz: float | None = None, loop: bool = False) -> Observable[T | Any]: + def stream(self, rate_hz: float | None = None, loop: bool = False) -> Observable[T]: if rate_hz is None: return from_iterable(self.iterate(loop=loop)) @@ -177,7 +177,7 @@ def save_one(self, frame: T) -> int: class TimedSensorReplay(SensorReplay[T]): - def load_one(self, name: int | str | Path) -> T | Any: + def load_one(self, name: int | str | Path) -> T: if isinstance(name, int): full_path = self.root_dir / f"/{name:03d}.pickle" elif isinstance(name, Path): @@ -191,7 +191,7 @@ def load_one(self, name: int | str | Path) -> T | Any: return (data[0], self.autocast(data[1])) return data - def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | Any | None: + def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | None: """Find the frame closest to the given timestamp. Args: @@ -222,7 +222,7 @@ def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | def find_closest_seek( self, relative_seconds: float, tolerance: float | None = None - ) -> T | Any | None: + ) -> T | None: """Find the frame closest to a time relative to the start. Args: diff --git a/lazy_loader/__init__.pyi b/lazy_loader/__init__.pyi new file mode 100644 index 0000000000..b3c4be8608 --- /dev/null +++ b/lazy_loader/__init__.pyi @@ -0,0 +1,11 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from typing import Any + +def attach( + package_name: str, + *, + submodules: Sequence[str] | None = None, + submod_attrs: Mapping[str, Sequence[str]] | None = None, +) -> tuple[Callable[[str], Any], Callable[[], list[str]], list[str]]: ... From 28271c17ae226a5af152a565bd50356d783188ae Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:00:35 -0800 Subject: [PATCH 19/45] remove dead/broken code (revealed by mypy) --- dimos/core/test_core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dimos/core/test_core.py b/dimos/core/test_core.py index a322f063e1..ddacb9dbf8 100644 --- a/dimos/core/test_core.py +++ b/dimos/core/test_core.py @@ -24,7 +24,6 @@ Out, pLCMTransport, rpc, - start, ) from dimos.core.testing import MockRobotClient, dimos from dimos.msgs.geometry_msgs import Vector3 @@ -138,8 +137,3 @@ def test_basic_deployment(dimos) -> None: assert nav.lidar_msg_count >= 8 dimos.shutdown() - - -if __name__ == "__main__": - client = start(1) # single process for CI memory - test_deployment(client) From fe5b6a1a2b979551a534be182e143e47a240b372 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:03:04 -0800 Subject: [PATCH 20/45] fix name (mypy revealed error) --- dimos/perception/detection/test_moduleDB.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/perception/detection/test_moduleDB.py b/dimos/perception/detection/test_moduleDB.py index f60ed98c8e..8af56652c3 100644 --- a/dimos/perception/detection/test_moduleDB.py +++ b/dimos/perception/detection/test_moduleDB.py @@ -31,7 +31,7 @@ def test_moduleDB(dimos_cluster) -> None: moduleDB = dimos_cluster.deploy( ObjectDBModule, - camera_info=connection._camera_info_static(), + camera_info=connection.camera_info_static(), goto=lambda obj_id: print(f"Going to {obj_id}"), ) moduleDB.image.connect(connection.video) From 3614dca16ea2a96d410e94c703a8a687e23559f6 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:04:31 -0800 Subject: [PATCH 21/45] another name fix --- dimos/perception/detection/test_moduleDB.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/perception/detection/test_moduleDB.py b/dimos/perception/detection/test_moduleDB.py index 8af56652c3..1f28bb874e 100644 --- a/dimos/perception/detection/test_moduleDB.py +++ b/dimos/perception/detection/test_moduleDB.py @@ -34,7 +34,7 @@ def test_moduleDB(dimos_cluster) -> None: camera_info=connection.camera_info_static(), goto=lambda obj_id: print(f"Going to {obj_id}"), ) - moduleDB.image.connect(connection.video) + moduleDB.image.connect(connection.color_image) moduleDB.pointcloud.connect(connection.lidar) moduleDB.annotations.transport = LCMTransport("/annotations", ImageAnnotations) From 72a9542bb2c0a29000d42646c24858f8643d02b6 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:09:19 -0800 Subject: [PATCH 22/45] mypy fixes --- dimos/agents/test_mock_agent.py | 10 +- dimos/conftest.py | 2 +- .../perception/test_spatial_memory_module.py | 6 +- dimos/robot/cli/dimos.py | 8 +- dimos/robot/unitree/b1/connection.py | 22 ++-- dimos/robot/unitree/b1/joystick_module.py | 6 +- dimos/robot/unitree/b1/test_connection.py | 112 +++++++++--------- dimos/utils/testing/replay.py | 47 +++++--- 8 files changed, 113 insertions(+), 100 deletions(-) diff --git a/dimos/agents/test_mock_agent.py b/dimos/agents/test_mock_agent.py index 37eac2bf31..e16224461d 100644 --- a/dimos/agents/test_mock_agent.py +++ b/dimos/agents/test_mock_agent.py @@ -101,7 +101,7 @@ def test_image_tool_call() -> None: system_prompt="You are a helpful robot assistant with camera capabilities.", ) - test_skill_module = dimos.deploy(SkillContainerTest) + test_skill_module = dimos.deploy(SkillContainerTest) # type: ignore[attr-defined] agent.register_skills(test_skill_module) agent.start() @@ -128,7 +128,7 @@ def test_image_tool_call() -> None: assert len(human_messages_with_images) >= 0 # May have image messages agent.stop() test_skill_module.stop() - dimos.close_all() + dimos.close_all() # type: ignore[attr-defined] @pytest.mark.tool @@ -158,7 +158,7 @@ def test_tool_call_implicit_detections() -> None: system_prompt="You are a helpful robot assistant with camera capabilities.", ) - robot_connection = dimos.deploy(GO2Connection, connection_type="fake") + robot_connection = dimos.deploy(GO2Connection, connection_type="fake") # type: ignore[attr-defined] robot_connection.lidar.transport = LCMTransport("/lidar", PointCloud2) robot_connection.odom.transport = LCMTransport("/odom", PoseStamped) robot_connection.video.transport = LCMTransport("/image", Image) @@ -166,7 +166,7 @@ def test_tool_call_implicit_detections() -> None: robot_connection.camera_info.transport = LCMTransport("/camera_info", CameraInfo) robot_connection.start() - test_skill_module = dimos.deploy(SkillContainerTest) + test_skill_module = dimos.deploy(SkillContainerTest) # type: ignore[attr-defined] agent.register_skills(test_skill_module) agent.start() @@ -200,4 +200,4 @@ def test_tool_call_implicit_detections() -> None: agent.stop() test_skill_module.stop() robot_connection.stop() - dimos.stop() + dimos.stop() # type: ignore[attr-defined] diff --git a/dimos/conftest.py b/dimos/conftest.py index 2eb273ab3c..d902fbcb3a 100644 --- a/dimos/conftest.py +++ b/dimos/conftest.py @@ -64,7 +64,7 @@ def dimos_cluster(): try: yield dimos finally: - dimos.stop() + dimos.stop() # type: ignore[attr-defined] @pytest.hookimpl() diff --git a/dimos/perception/test_spatial_memory_module.py b/dimos/perception/test_spatial_memory_module.py index 11b8eec562..1b8eacebe1 100644 --- a/dimos/perception/test_spatial_memory_module.py +++ b/dimos/perception/test_spatial_memory_module.py @@ -135,15 +135,15 @@ async def test_spatial_memory_module_with_replay(self, temp_dir): # Deploy modules # Video replay module - video_module = dimos.deploy(VideoReplayModule, video_path) + video_module = dimos.deploy(VideoReplayModule, video_path) # type: ignore[attr-defined] video_module.video_out.transport = core.LCMTransport("/test_video", Image) # Odometry replay module - odom_module = dimos.deploy(OdometryReplayModule, odom_path) + odom_module = dimos.deploy(OdometryReplayModule, odom_path) # type: ignore[attr-defined] odom_module.odom_out.transport = core.LCMTransport("/test_odom", Odometry) # Spatial memory module - spatial_memory = dimos.deploy( + spatial_memory = dimos.deploy( # type: ignore[attr-defined] SpatialMemory, collection_name="test_spatial_memory", embedding_model="clip", diff --git a/dimos/robot/cli/dimos.py b/dimos/robot/cli/dimos.py index 614049850d..c8452cadf1 100644 --- a/dimos/robot/cli/dimos.py +++ b/dimos/robot/cli/dimos.py @@ -15,7 +15,7 @@ from enum import Enum import inspect import sys -from typing import Any, Optional, get_args, get_origin +from typing import Any, get_args, get_origin import typer @@ -44,7 +44,7 @@ def create_dynamic_callback(): # type: ignore[no-untyped-def] # Handle Optional types # Check for Optional/Union with None - if get_origin(field_type) is type(Optional[str]): # noqa: UP045 + if get_origin(field_type) is type(str | None): inner_types = get_args(field_type) if len(inner_types) == 2 and type(None) in inner_types: # It's Optional[T], get the actual type T @@ -68,7 +68,7 @@ def create_dynamic_callback(): # type: ignore[no-untyped-def] f"--{cli_option_name}/--no-{cli_option_name}", help=f"Override {field_name} in GlobalConfig", ), - annotation=Optional[bool], # noqa: UP045 + annotation=bool | None, ) else: # For non-boolean fields, use regular option @@ -80,7 +80,7 @@ def create_dynamic_callback(): # type: ignore[no-untyped-def] f"--{cli_option_name}", help=f"Override {field_name} in GlobalConfig", ), - annotation=Optional[actual_type], # noqa: UP045 + annotation=actual_type | None, ) params.append(param) diff --git a/dimos/robot/unitree/b1/connection.py b/dimos/robot/unitree/b1/connection.py index f0cb5317e6..6964b1f4ea 100644 --- a/dimos/robot/unitree/b1/connection.py +++ b/dimos/robot/unitree/b1/connection.py @@ -24,7 +24,7 @@ from reactivex.disposable import Disposable -from dimos.core import In, Module, Out, rpc +from dimos.core import In, Module, Out, rpc # type: ignore[attr-defined] from dimos.msgs.geometry_msgs import PoseStamped, Twist, TwistStamped from dimos.msgs.nav_msgs.Odometry import Odometry from dimos.msgs.std_msgs import Int32 @@ -87,7 +87,7 @@ def __init__( # type: ignore[no-untyped-def] self.watchdog_running = False self.timeout_active = False - @rpc + @rpc # type: ignore[untyped-decorator] def start(self) -> None: """Start the connection and subscribe to command streams.""" @@ -123,7 +123,7 @@ def start(self) -> None: self.watchdog_thread = threading.Thread(target=self._watchdog_loop, daemon=True) # type: ignore[assignment] self.watchdog_thread.start() # type: ignore[attr-defined] - @rpc + @rpc # type: ignore[untyped-decorator] def stop(self) -> None: """Stop the connection and send stop commands.""" @@ -208,7 +208,7 @@ def handle_mode(self, mode_msg: Int32) -> None: logger.info(f"[TEST] Received mode change: {mode_msg.data}") self.set_mode(mode_msg.data) - @rpc + @rpc # type: ignore[untyped-decorator] def set_mode(self, mode: int) -> bool: """Set robot mode (0=idle, 1=stand, 2=walk, 6=recovery).""" self.current_mode = mode @@ -320,31 +320,31 @@ def _watchdog_loop(self) -> None: if self.watchdog_running: logger.error(f"Watchdog error: {e}") - @rpc + @rpc # type: ignore[untyped-decorator] def idle(self) -> bool: """Set robot to idle mode.""" self.set_mode(RobotMode.IDLE) return True - @rpc + @rpc # type: ignore[untyped-decorator] def pose(self) -> bool: """Set robot to stand/pose mode for reaching ground objects with manipulator.""" self.set_mode(RobotMode.STAND) return True - @rpc + @rpc # type: ignore[untyped-decorator] def walk(self) -> bool: """Set robot to walk mode.""" self.set_mode(RobotMode.WALK) return True - @rpc + @rpc # type: ignore[untyped-decorator] def recovery(self) -> bool: """Set robot to recovery mode.""" self.set_mode(RobotMode.RECOVERY) return True - @rpc + @rpc # type: ignore[untyped-decorator] def move(self, twist_stamped: TwistStamped, duration: float = 0.0) -> bool: """Direct RPC method for sending TwistStamped commands. @@ -391,10 +391,10 @@ def _send_loop(self) -> None: self.packet_count += 1 time.sleep(0.020) - @rpc + @rpc # type: ignore[untyped-decorator] def start(self) -> None: super().start() - @rpc + @rpc # type: ignore[untyped-decorator] def stop(self) -> None: super().stop() diff --git a/dimos/robot/unitree/b1/joystick_module.py b/dimos/robot/unitree/b1/joystick_module.py index 9aa02af058..655bfb43e6 100644 --- a/dimos/robot/unitree/b1/joystick_module.py +++ b/dimos/robot/unitree/b1/joystick_module.py @@ -25,7 +25,7 @@ import time -from dimos.core import Module, Out, rpc +from dimos.core import Module, Out, rpc # type: ignore[attr-defined] from dimos.msgs.geometry_msgs import Twist, TwistStamped, Vector3 from dimos.msgs.std_msgs import Int32 @@ -46,7 +46,7 @@ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.running = False self.current_mode = 0 # Start in IDLE mode for safety - @rpc + @rpc # type: ignore[untyped-decorator] def start(self) -> bool: """Initialize pygame and start control loop.""" @@ -68,7 +68,7 @@ def start(self) -> bool: return True - @rpc + @rpc # type: ignore[untyped-decorator] def stop(self) -> None: """Stop the joystick module.""" diff --git a/dimos/robot/unitree/b1/test_connection.py b/dimos/robot/unitree/b1/test_connection.py index e43a3124dc..89c8726b24 100644 --- a/dimos/robot/unitree/b1/test_connection.py +++ b/dimos/robot/unitree/b1/test_connection.py @@ -39,10 +39,10 @@ def test_watchdog_actually_zeros_commands(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Send a forward command twist_stamped = TwistStamped( @@ -71,8 +71,8 @@ def test_watchdog_actually_zeros_commands(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_watchdog_resets_on_new_command(self) -> None: @@ -80,10 +80,10 @@ def test_watchdog_resets_on_new_command(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Send first command twist1 = TwistStamped( @@ -119,8 +119,8 @@ def test_watchdog_resets_on_new_command(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_watchdog_thread_efficiency(self) -> None: @@ -128,10 +128,10 @@ def test_watchdog_thread_efficiency(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Count threads before sending commands initial_thread_count = threading.active_count() @@ -153,8 +153,8 @@ def test_watchdog_thread_efficiency(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_watchdog_with_send_loop_blocking(self) -> None: @@ -171,13 +171,13 @@ def blocking_send_loop() -> None: # Then run normally original_send_loop() - conn._send_loop = blocking_send_loop + conn._send_loop = blocking_send_loop # type: ignore[method-assign] conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Send command twist = TwistStamped( @@ -200,8 +200,8 @@ def blocking_send_loop() -> None: block_event.set() conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_continuous_commands_prevent_timeout(self) -> None: @@ -209,10 +209,10 @@ def test_continuous_commands_prevent_timeout(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Send commands continuously for 500ms (should prevent timeout) start = time.time() @@ -235,8 +235,8 @@ def test_continuous_commands_prevent_timeout(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_watchdog_timing_accuracy(self) -> None: @@ -244,10 +244,10 @@ def test_watchdog_timing_accuracy(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Send command and record time start_time = time.time() @@ -276,8 +276,8 @@ def test_watchdog_timing_accuracy(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_mode_changes_with_watchdog(self) -> None: @@ -285,10 +285,10 @@ def test_mode_changes_with_watchdog(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Give threads time to initialize time.sleep(0.05) @@ -319,8 +319,8 @@ def test_mode_changes_with_watchdog(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_watchdog_stops_movement_when_commands_stop(self) -> None: @@ -328,10 +328,10 @@ def test_watchdog_stops_movement_when_commands_stop(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Simulate sending movement commands for a while for _i in range(5): @@ -377,8 +377,8 @@ def test_watchdog_stops_movement_when_commands_stop(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() def test_rapid_command_thread_safety(self) -> None: @@ -386,16 +386,16 @@ def test_rapid_command_thread_safety(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) - conn.send_thread.start() - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) - conn.watchdog_thread.start() + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] + conn.send_thread.start() # type: ignore[attr-defined] + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] + conn.watchdog_thread.start() # type: ignore[attr-defined] # Count initial threads initial_threads = threading.active_count() # Send commands from multiple threads rapidly - def send_commands(thread_id) -> None: + def send_commands(thread_id) -> None: # type: ignore[no-untyped-def] for _i in range(10): twist = TwistStamped( ts=time.time(), @@ -426,6 +426,6 @@ def send_commands(thread_id) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) - conn.watchdog_thread.join(timeout=0.5) + conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] conn._close_module() diff --git a/dimos/utils/testing/replay.py b/dimos/utils/testing/replay.py index d98ffd81c8..1913c7eb36 100644 --- a/dimos/utils/testing/replay.py +++ b/dimos/utils/testing/replay.py @@ -19,7 +19,7 @@ import pickle import re import time -from typing import Any, Generic, TypeVar +from typing import Any, Generic, TypeVar, cast from reactivex import ( from_iterable, @@ -64,7 +64,7 @@ def load_one(self, name: int | str | Path) -> T: data = pickle.load(f) if self.autocast: return self.autocast(data) - return data + return cast("T", data) def first(self) -> T | None: try: @@ -136,7 +136,7 @@ def __init__(self, name: str, autocast: Callable[[T], Any] | None = None) -> Non def consume_stream(self, observable: Observable[T | Any]) -> None: """Consume an observable stream of sensor data without saving.""" - return observable.subscribe(self.save_one) # type: ignore[arg-type, return-value] + observable.subscribe(self.save_one) # type: ignore[arg-type] def save_stream(self, observable: Observable[T | Any]) -> Observable[int]: """Save an observable stream of sensor data to pickle files.""" @@ -176,8 +176,15 @@ def save_one(self, frame: T) -> int: return super().save_one((time.time(), frame)) -class TimedSensorReplay(SensorReplay[T]): - def load_one(self, name: int | str | Path) -> T: +U = TypeVar("U") + + +class TimedSensorReplay(SensorReplay[tuple[float, U]]): + def __init__(self, name: str, autocast: Callable[[Any], U] | None = None) -> None: + super().__init__(name, autocast=None) + self._timed_autocast = autocast + + def load_one(self, name: int | str | Path) -> tuple[float, U]: if isinstance(name, int): full_path = self.root_dir / f"/{name:03d}.pickle" elif isinstance(name, Path): @@ -187,11 +194,11 @@ def load_one(self, name: int | str | Path) -> T: with open(full_path, "rb") as f: data = pickle.load(f) - if self.autocast: - return (data[0], self.autocast(data[1])) - return data + if self._timed_autocast: + return (data[0], self._timed_autocast(data[1])) + return cast("tuple[float, U]", data) - def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | None: + def find_closest(self, timestamp: float, tolerance: float | None = None) -> U | None: """Find the frame closest to the given timestamp. Args: @@ -222,7 +229,7 @@ def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | def find_closest_seek( self, relative_seconds: float, tolerance: float | None = None - ) -> T | None: + ) -> U | None: """Find the frame closest to a time relative to the start. Args: @@ -295,23 +302,28 @@ def iterate_ts( duration: float | None = None, from_timestamp: float | None = None, loop: bool = False, - ) -> Iterator[tuple[float, T] | Any]: + ) -> Iterator[tuple[float, U]]: """Iterate with absolute timestamps, with optional seek and duration.""" - first_ts = None + first_ts: float | None = None if (seek is not None) or (duration is not None): first_ts = self.first_timestamp() if first_ts is None: return if seek is not None: - from_timestamp = first_ts + seek # type: ignore[operator] + if first_ts is None: + return + from_timestamp = first_ts + seek - end_timestamp = None + end_timestamp: float | None = None if duration is not None: - end_timestamp = (from_timestamp if from_timestamp else first_ts) + duration # type: ignore[operator] + base = from_timestamp if from_timestamp is not None else first_ts + if base is None: + return + end_timestamp = base + duration while True: - for ts, data in super().iterate(): # type: ignore[misc] + for ts, data in super().iterate(): if from_timestamp is None or ts >= from_timestamp: if end_timestamp is not None and ts >= end_timestamp: break @@ -326,7 +338,7 @@ def stream( # type: ignore[override] duration: float | None = None, from_timestamp: float | None = None, loop: bool = False, - ) -> Observable[T | Any]: + ) -> Observable[U]: def _subscribe(observer, scheduler=None): # type: ignore[no-untyped-def] from reactivex.disposable import CompositeDisposable, Disposable @@ -353,6 +365,7 @@ def _subscribe(observer, scheduler=None): # type: ignore[no-untyped-def] observer.on_next(first_data) # Pre-load next message + next_message: tuple[float, U] | None try: next_message = next(iterator) except StopIteration: From 40f03ba6e827f7a8d99026adf52642d1f2168db6 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:24:35 -0800 Subject: [PATCH 23/45] correct g1 blueprint name --- dimos/robot/all_blueprints.py | 2 +- dimos/robot/unitree/g1/blueprints/__init__.py | 2 +- dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py | 4 ++-- .../unitree/g1/blueprints/basic/unitree_g1_basic_sim.py | 6 ++++-- .../g1/blueprints/primitive/uintree_g1_primitive_no_nav.py | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index a705dfba2d..398e70c3fb 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -38,7 +38,7 @@ "demo-osm": "dimos.mapping.osm.demo_osm:demo_osm", "demo-skill": "dimos.agents.skills.demo_skill:demo_skill", "dual-xarm6-planner": "dimos.manipulation.manipulation_blueprints:dual_xarm6_planner", - "uintree-g1-basic-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_basic_no_nav", + "uintree-g1-basic-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_primitive_no_nav", "unitree-g1": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1:unitree_g1", "unitree-g1-agentic": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic:unitree_g1_agentic", "unitree-g1-agentic-sim": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim:unitree_g1_agentic_sim", diff --git a/dimos/robot/unitree/g1/blueprints/__init__.py b/dimos/robot/unitree/g1/blueprints/__init__.py index 634b7a1948..ebc18da8d3 100644 --- a/dimos/robot/unitree/g1/blueprints/__init__.py +++ b/dimos/robot/unitree/g1/blueprints/__init__.py @@ -32,6 +32,6 @@ "perceptive.unitree_g1_detection": ["unitree_g1_detection"], "perceptive.unitree_g1_shm": ["unitree_g1_shm"], "perceptive.unitree_g1_sim": ["unitree_g1_sim"], - "primitive.uintree_g1_primitive_no_nav": ["uintree_g1_basic_no_nav", "basic_no_nav"], + "primitive.uintree_g1_primitive_no_nav": ["uintree_g1_primitive_no_nav", "basic_no_nav"], }, ) diff --git a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py index f6d61ce429..1fb591e895 100644 --- a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic.py @@ -18,12 +18,12 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.rosnav import ros_nav from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( - uintree_g1_basic_no_nav, + uintree_g1_primitive_no_nav, ) from dimos.robot.unitree.g1.connection import g1_connection unitree_g1_basic = autoconnect( - uintree_g1_basic_no_nav, + uintree_g1_primitive_no_nav, g1_connection(), ros_nav(), ) diff --git a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py index 75af592703..603a9535ee 100644 --- a/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py +++ b/dimos/robot/unitree/g1/blueprints/basic/unitree_g1_basic_sim.py @@ -17,11 +17,13 @@ from dimos.core.blueprints import autoconnect from dimos.navigation.replanning_a_star.module import replanning_a_star_planner -from dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic import unitree_g1_basic +from dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav import ( + uintree_g1_primitive_no_nav, +) from dimos.robot.unitree.g1.sim import g1_sim_connection unitree_g1_basic_sim = autoconnect( - unitree_g1_basic.uintree_g1_basic_no_nav, + uintree_g1_primitive_no_nav, g1_sim_connection(), replanning_a_star_planner(), ) diff --git a/dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py b/dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py index fe90e0ca84..34fb914d74 100644 --- a/dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py +++ b/dimos/robot/unitree/g1/blueprints/primitive/uintree_g1_primitive_no_nav.py @@ -33,7 +33,7 @@ from dimos.robot.foxglove_bridge import foxglove_bridge from dimos.web.websocket_vis.websocket_vis_module import websocket_vis -uintree_g1_basic_no_nav = ( +uintree_g1_primitive_no_nav = ( autoconnect( camera_module( transform=Transform( @@ -88,4 +88,4 @@ ) ) -__all__ = ["uintree_g1_basic_no_nav"] +__all__ = ["uintree_g1_primitive_no_nav"] From 364ad8ff9b3b532ee85244076c3832bc9911b5dd Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:49:28 -0800 Subject: [PATCH 24/45] fix rpc typing --- dimos/core/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dimos/core/__init__.py b/dimos/core/__init__.py index 970c828867..118d600960 100644 --- a/dimos/core/__init__.py +++ b/dimos/core/__init__.py @@ -5,6 +5,7 @@ import time from typing import TYPE_CHECKING, cast +from dimos.core.core import rpc from dimos.utils.logging_config import setup_logger import lazy_loader as lazy @@ -23,7 +24,6 @@ submod_attrs={ "_dask_exports": ["DimosCluster"], "_protocol_exports": ["LCMRPC", "RPCSpec", "LCMTF", "TF", "PubSubTF", "TFConfig", "TFSpec"], - "core": ["rpc"], "module": ["Module", "ModuleBase", "ModuleConfig", "ModuleConfigT"], "stream": ["In", "Out", "RemoteIn", "RemoteOut", "Transport"], "transport": [ @@ -35,7 +35,7 @@ ], }, ) -__all__ += ["start", "wait_exit"] +__all__ += ["rpc", "start", "wait_exit"] class CudaCleanupPlugin: From e9891f8f29f275a14f9d61d497a49eb43cc7bc0c Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 22:51:26 -0800 Subject: [PATCH 25/45] undo the ignores for rpc calls --- dimos/core/__init__.py | 2 +- dimos/robot/unitree/b1/connection.py | 20 ++++++++++---------- dimos/robot/unitree/b1/joystick_module.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dimos/core/__init__.py b/dimos/core/__init__.py index 118d600960..cc8097dee8 100644 --- a/dimos/core/__init__.py +++ b/dimos/core/__init__.py @@ -35,7 +35,7 @@ ], }, ) -__all__ += ["rpc", "start", "wait_exit"] +__all__ += ["DimosCluster", "Module", "rpc", "start", "wait_exit"] class CudaCleanupPlugin: diff --git a/dimos/robot/unitree/b1/connection.py b/dimos/robot/unitree/b1/connection.py index 6964b1f4ea..83d56f9e11 100644 --- a/dimos/robot/unitree/b1/connection.py +++ b/dimos/robot/unitree/b1/connection.py @@ -87,7 +87,7 @@ def __init__( # type: ignore[no-untyped-def] self.watchdog_running = False self.timeout_active = False - @rpc # type: ignore[untyped-decorator] + @rpc def start(self) -> None: """Start the connection and subscribe to command streams.""" @@ -123,7 +123,7 @@ def start(self) -> None: self.watchdog_thread = threading.Thread(target=self._watchdog_loop, daemon=True) # type: ignore[assignment] self.watchdog_thread.start() # type: ignore[attr-defined] - @rpc # type: ignore[untyped-decorator] + @rpc def stop(self) -> None: """Stop the connection and send stop commands.""" @@ -208,7 +208,7 @@ def handle_mode(self, mode_msg: Int32) -> None: logger.info(f"[TEST] Received mode change: {mode_msg.data}") self.set_mode(mode_msg.data) - @rpc # type: ignore[untyped-decorator] + @rpc def set_mode(self, mode: int) -> bool: """Set robot mode (0=idle, 1=stand, 2=walk, 6=recovery).""" self.current_mode = mode @@ -320,31 +320,31 @@ def _watchdog_loop(self) -> None: if self.watchdog_running: logger.error(f"Watchdog error: {e}") - @rpc # type: ignore[untyped-decorator] + @rpc def idle(self) -> bool: """Set robot to idle mode.""" self.set_mode(RobotMode.IDLE) return True - @rpc # type: ignore[untyped-decorator] + @rpc def pose(self) -> bool: """Set robot to stand/pose mode for reaching ground objects with manipulator.""" self.set_mode(RobotMode.STAND) return True - @rpc # type: ignore[untyped-decorator] + @rpc def walk(self) -> bool: """Set robot to walk mode.""" self.set_mode(RobotMode.WALK) return True - @rpc # type: ignore[untyped-decorator] + @rpc def recovery(self) -> bool: """Set robot to recovery mode.""" self.set_mode(RobotMode.RECOVERY) return True - @rpc # type: ignore[untyped-decorator] + @rpc def move(self, twist_stamped: TwistStamped, duration: float = 0.0) -> bool: """Direct RPC method for sending TwistStamped commands. @@ -391,10 +391,10 @@ def _send_loop(self) -> None: self.packet_count += 1 time.sleep(0.020) - @rpc # type: ignore[untyped-decorator] + @rpc def start(self) -> None: super().start() - @rpc # type: ignore[untyped-decorator] + @rpc def stop(self) -> None: super().stop() diff --git a/dimos/robot/unitree/b1/joystick_module.py b/dimos/robot/unitree/b1/joystick_module.py index 655bfb43e6..ecdf534484 100644 --- a/dimos/robot/unitree/b1/joystick_module.py +++ b/dimos/robot/unitree/b1/joystick_module.py @@ -46,7 +46,7 @@ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.running = False self.current_mode = 0 # Start in IDLE mode for safety - @rpc # type: ignore[untyped-decorator] + @rpc def start(self) -> bool: """Initialize pygame and start control loop.""" @@ -68,7 +68,7 @@ def start(self) -> bool: return True - @rpc # type: ignore[untyped-decorator] + @rpc def stop(self) -> None: """Stop the joystick module.""" From 49e174b65f0d821b256f415ea04ac75a986be429 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sat, 7 Feb 2026 23:23:39 -0800 Subject: [PATCH 26/45] - --- dimos/robot/all_blueprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index 398e70c3fb..796067f454 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -38,7 +38,7 @@ "demo-osm": "dimos.mapping.osm.demo_osm:demo_osm", "demo-skill": "dimos.agents.skills.demo_skill:demo_skill", "dual-xarm6-planner": "dimos.manipulation.manipulation_blueprints:dual_xarm6_planner", - "uintree-g1-basic-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_primitive_no_nav", + "uintree-g1-primitive-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_primitive_no_nav", "unitree-g1": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1:unitree_g1", "unitree-g1-agentic": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic:unitree_g1_agentic", "unitree-g1-agentic-sim": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim:unitree_g1_agentic_sim", From 52da9c7d256e91d22d8bffccd258886c46b147fd Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 00:22:26 -0800 Subject: [PATCH 27/45] mark test_tool_call_implicit_detections as tofix because it currently never ends --- dimos/agents/test_mock_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dimos/agents/test_mock_agent.py b/dimos/agents/test_mock_agent.py index e16224461d..2f047605ba 100644 --- a/dimos/agents/test_mock_agent.py +++ b/dimos/agents/test_mock_agent.py @@ -131,6 +131,7 @@ def test_image_tool_call() -> None: dimos.close_all() # type: ignore[attr-defined] +@pytest.mark.tofix # runs forever but shouldnt @pytest.mark.tool def test_tool_call_implicit_detections() -> None: """Test agent with image tool call execution.""" From 6d43c420612475f637d7ca6ea48723a24b058e12 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 00:35:24 -0800 Subject: [PATCH 28/45] undo unnecessary mypy changes --- dimos/agents/test_mock_agent.py | 11 +- dimos/conftest.py | 2 +- .../perception/test_spatial_memory_module.py | 6 +- dimos/robot/unitree/b1/connection.py | 2 +- dimos/robot/unitree/b1/joystick_module.py | 2 +- dimos/robot/unitree/b1/test_connection.py | 112 +++++++++--------- 6 files changed, 67 insertions(+), 68 deletions(-) diff --git a/dimos/agents/test_mock_agent.py b/dimos/agents/test_mock_agent.py index 2f047605ba..37eac2bf31 100644 --- a/dimos/agents/test_mock_agent.py +++ b/dimos/agents/test_mock_agent.py @@ -101,7 +101,7 @@ def test_image_tool_call() -> None: system_prompt="You are a helpful robot assistant with camera capabilities.", ) - test_skill_module = dimos.deploy(SkillContainerTest) # type: ignore[attr-defined] + test_skill_module = dimos.deploy(SkillContainerTest) agent.register_skills(test_skill_module) agent.start() @@ -128,10 +128,9 @@ def test_image_tool_call() -> None: assert len(human_messages_with_images) >= 0 # May have image messages agent.stop() test_skill_module.stop() - dimos.close_all() # type: ignore[attr-defined] + dimos.close_all() -@pytest.mark.tofix # runs forever but shouldnt @pytest.mark.tool def test_tool_call_implicit_detections() -> None: """Test agent with image tool call execution.""" @@ -159,7 +158,7 @@ def test_tool_call_implicit_detections() -> None: system_prompt="You are a helpful robot assistant with camera capabilities.", ) - robot_connection = dimos.deploy(GO2Connection, connection_type="fake") # type: ignore[attr-defined] + robot_connection = dimos.deploy(GO2Connection, connection_type="fake") robot_connection.lidar.transport = LCMTransport("/lidar", PointCloud2) robot_connection.odom.transport = LCMTransport("/odom", PoseStamped) robot_connection.video.transport = LCMTransport("/image", Image) @@ -167,7 +166,7 @@ def test_tool_call_implicit_detections() -> None: robot_connection.camera_info.transport = LCMTransport("/camera_info", CameraInfo) robot_connection.start() - test_skill_module = dimos.deploy(SkillContainerTest) # type: ignore[attr-defined] + test_skill_module = dimos.deploy(SkillContainerTest) agent.register_skills(test_skill_module) agent.start() @@ -201,4 +200,4 @@ def test_tool_call_implicit_detections() -> None: agent.stop() test_skill_module.stop() robot_connection.stop() - dimos.stop() # type: ignore[attr-defined] + dimos.stop() diff --git a/dimos/conftest.py b/dimos/conftest.py index d902fbcb3a..2eb273ab3c 100644 --- a/dimos/conftest.py +++ b/dimos/conftest.py @@ -64,7 +64,7 @@ def dimos_cluster(): try: yield dimos finally: - dimos.stop() # type: ignore[attr-defined] + dimos.stop() @pytest.hookimpl() diff --git a/dimos/perception/test_spatial_memory_module.py b/dimos/perception/test_spatial_memory_module.py index 1b8eacebe1..11b8eec562 100644 --- a/dimos/perception/test_spatial_memory_module.py +++ b/dimos/perception/test_spatial_memory_module.py @@ -135,15 +135,15 @@ async def test_spatial_memory_module_with_replay(self, temp_dir): # Deploy modules # Video replay module - video_module = dimos.deploy(VideoReplayModule, video_path) # type: ignore[attr-defined] + video_module = dimos.deploy(VideoReplayModule, video_path) video_module.video_out.transport = core.LCMTransport("/test_video", Image) # Odometry replay module - odom_module = dimos.deploy(OdometryReplayModule, odom_path) # type: ignore[attr-defined] + odom_module = dimos.deploy(OdometryReplayModule, odom_path) odom_module.odom_out.transport = core.LCMTransport("/test_odom", Odometry) # Spatial memory module - spatial_memory = dimos.deploy( # type: ignore[attr-defined] + spatial_memory = dimos.deploy( SpatialMemory, collection_name="test_spatial_memory", embedding_model="clip", diff --git a/dimos/robot/unitree/b1/connection.py b/dimos/robot/unitree/b1/connection.py index 83d56f9e11..f0cb5317e6 100644 --- a/dimos/robot/unitree/b1/connection.py +++ b/dimos/robot/unitree/b1/connection.py @@ -24,7 +24,7 @@ from reactivex.disposable import Disposable -from dimos.core import In, Module, Out, rpc # type: ignore[attr-defined] +from dimos.core import In, Module, Out, rpc from dimos.msgs.geometry_msgs import PoseStamped, Twist, TwistStamped from dimos.msgs.nav_msgs.Odometry import Odometry from dimos.msgs.std_msgs import Int32 diff --git a/dimos/robot/unitree/b1/joystick_module.py b/dimos/robot/unitree/b1/joystick_module.py index ecdf534484..9aa02af058 100644 --- a/dimos/robot/unitree/b1/joystick_module.py +++ b/dimos/robot/unitree/b1/joystick_module.py @@ -25,7 +25,7 @@ import time -from dimos.core import Module, Out, rpc # type: ignore[attr-defined] +from dimos.core import Module, Out, rpc from dimos.msgs.geometry_msgs import Twist, TwistStamped, Vector3 from dimos.msgs.std_msgs import Int32 diff --git a/dimos/robot/unitree/b1/test_connection.py b/dimos/robot/unitree/b1/test_connection.py index 89c8726b24..e43a3124dc 100644 --- a/dimos/robot/unitree/b1/test_connection.py +++ b/dimos/robot/unitree/b1/test_connection.py @@ -39,10 +39,10 @@ def test_watchdog_actually_zeros_commands(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Send a forward command twist_stamped = TwistStamped( @@ -71,8 +71,8 @@ def test_watchdog_actually_zeros_commands(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_watchdog_resets_on_new_command(self) -> None: @@ -80,10 +80,10 @@ def test_watchdog_resets_on_new_command(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Send first command twist1 = TwistStamped( @@ -119,8 +119,8 @@ def test_watchdog_resets_on_new_command(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_watchdog_thread_efficiency(self) -> None: @@ -128,10 +128,10 @@ def test_watchdog_thread_efficiency(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Count threads before sending commands initial_thread_count = threading.active_count() @@ -153,8 +153,8 @@ def test_watchdog_thread_efficiency(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_watchdog_with_send_loop_blocking(self) -> None: @@ -171,13 +171,13 @@ def blocking_send_loop() -> None: # Then run normally original_send_loop() - conn._send_loop = blocking_send_loop # type: ignore[method-assign] + conn._send_loop = blocking_send_loop conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Send command twist = TwistStamped( @@ -200,8 +200,8 @@ def blocking_send_loop() -> None: block_event.set() conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_continuous_commands_prevent_timeout(self) -> None: @@ -209,10 +209,10 @@ def test_continuous_commands_prevent_timeout(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Send commands continuously for 500ms (should prevent timeout) start = time.time() @@ -235,8 +235,8 @@ def test_continuous_commands_prevent_timeout(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_watchdog_timing_accuracy(self) -> None: @@ -244,10 +244,10 @@ def test_watchdog_timing_accuracy(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Send command and record time start_time = time.time() @@ -276,8 +276,8 @@ def test_watchdog_timing_accuracy(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_mode_changes_with_watchdog(self) -> None: @@ -285,10 +285,10 @@ def test_mode_changes_with_watchdog(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Give threads time to initialize time.sleep(0.05) @@ -319,8 +319,8 @@ def test_mode_changes_with_watchdog(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_watchdog_stops_movement_when_commands_stop(self) -> None: @@ -328,10 +328,10 @@ def test_watchdog_stops_movement_when_commands_stop(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Simulate sending movement commands for a while for _i in range(5): @@ -377,8 +377,8 @@ def test_watchdog_stops_movement_when_commands_stop(self) -> None: conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() def test_rapid_command_thread_safety(self) -> None: @@ -386,16 +386,16 @@ def test_rapid_command_thread_safety(self) -> None: conn = MockB1ConnectionModule(ip="127.0.0.1", port=9090) conn.running = True conn.watchdog_running = True - conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) # type: ignore[assignment] - conn.send_thread.start() # type: ignore[attr-defined] - conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) # type: ignore[assignment] - conn.watchdog_thread.start() # type: ignore[attr-defined] + conn.send_thread = threading.Thread(target=conn._send_loop, daemon=True) + conn.send_thread.start() + conn.watchdog_thread = threading.Thread(target=conn._watchdog_loop, daemon=True) + conn.watchdog_thread.start() # Count initial threads initial_threads = threading.active_count() # Send commands from multiple threads rapidly - def send_commands(thread_id) -> None: # type: ignore[no-untyped-def] + def send_commands(thread_id) -> None: for _i in range(10): twist = TwistStamped( ts=time.time(), @@ -426,6 +426,6 @@ def send_commands(thread_id) -> None: # type: ignore[no-untyped-def] conn.running = False conn.watchdog_running = False - conn.send_thread.join(timeout=0.5) # type: ignore[attr-defined] - conn.watchdog_thread.join(timeout=0.5) # type: ignore[attr-defined] + conn.send_thread.join(timeout=0.5) + conn.watchdog_thread.join(timeout=0.5) conn._close_module() From 1e8930e34bd7ab098391722ebd45d4e51f100663 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 01:30:18 -0800 Subject: [PATCH 29/45] fixup readme with naming changes --- README.md | 14 ++++++-------- dimos/core/__init__.py | 8 +++++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ae91ecfbaf..572bf73605 100644 --- a/README.md +++ b/README.md @@ -148,11 +148,9 @@ Modules are subsystems on a robot that operate autonomously and communicate with ```py import threading, time, numpy as np -from dimos.core import In, Module, Out, rpc -from dimos.core.blueprints import autoconnect +from dimos.core import In, Module, Out, rpc, autoconnect from dimos.msgs.geometry_msgs import Twist -from dimos.msgs.sensor_msgs import Image -from dimos.msgs.sensor_msgs.Image import ImageFormat +from dimos.msgs.sensor_msgs import Image, ImageFormat class RobotConnection(Module): cmd_vel: In[Twist] @@ -195,10 +193,9 @@ Blueprints can be composed, remapped, and have transports overridden if `autocon A blueprint example that connects the image stream from a robot to an LLM Agent for reasoning and action execution. ```py -from dimos.core.blueprints import autoconnect -from dimos.core.transport import LCMTransport +from dimos.core import autoconnect, LCMTransport from dimos.msgs.sensor_msgs import Image -from dimos.robot.unitree.connection.go2 import go2_connection +from dimos.robot.unitree.go2.connection import go2_connection from dimos.agents.agent import llm_agent blueprint = autoconnect( @@ -207,7 +204,8 @@ blueprint = autoconnect( ).transports({("color_image", Image): LCMTransport("/color_image", Image)}) # Run the blueprint -blueprint.build().loop() +if __name__ == "__main__": + blueprint.build().loop() ``` # Development diff --git a/dimos/core/__init__.py b/dimos/core/__init__.py index cc8097dee8..55bd566657 100644 --- a/dimos/core/__init__.py +++ b/dimos/core/__init__.py @@ -5,14 +5,17 @@ import time from typing import TYPE_CHECKING, cast +from rich.console import Console + from dimos.core.core import rpc from dimos.utils.logging_config import setup_logger import lazy_loader as lazy if TYPE_CHECKING: # Avoid runtime import to prevent circular import; ruff's TC001 would otherwise move it. - from dask.distributed import Client as DimosCluster, LocalCluster + from dask.distributed import LocalCluster + from dimos.core._dask_exports import DimosCluster from dimos.core.module import Module from dimos.core.rpc_client import ModuleProxy @@ -22,6 +25,7 @@ __name__, submodules=["colors"], submod_attrs={ + "blueprints": ["autoconnect", "Blueprint"], "_dask_exports": ["DimosCluster"], "_protocol_exports": ["LCMRPC", "RPCSpec", "LCMTF", "TF", "PubSubTF", "TFConfig", "TFSpec"], "module": ["Module", "ModuleBase", "ModuleConfig", "ModuleConfigT"], @@ -106,7 +110,6 @@ def deploy( # type: ignore[no-untyped-def] def check_worker_memory() -> None: """Check memory usage of all workers.""" info = dask_client.scheduler_info() - from rich.console import Console console = Console() total_workers = len(info.get("workers", {})) @@ -243,7 +246,6 @@ def start(n: int | None = None, memory_limit: str = "auto") -> DimosCluster: """ from dask.distributed import Client, LocalCluster - from rich.console import Console console = Console() if not n: From c7335876a27a8e6878f312b1367fcb005af088db Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 01:30:27 -0800 Subject: [PATCH 30/45] fix --- dimos/perception/detection/test_moduleDB.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/perception/detection/test_moduleDB.py b/dimos/perception/detection/test_moduleDB.py index 1f28bb874e..23885a1c60 100644 --- a/dimos/perception/detection/test_moduleDB.py +++ b/dimos/perception/detection/test_moduleDB.py @@ -31,7 +31,7 @@ def test_moduleDB(dimos_cluster) -> None: moduleDB = dimos_cluster.deploy( ObjectDBModule, - camera_info=connection.camera_info_static(), + camera_info=go2_connection._camera_info_static(), goto=lambda obj_id: print(f"Going to {obj_id}"), ) moduleDB.image.connect(connection.color_image) From 5e653a8e6e2406e893c7918d339ebdee7507571c Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 01:30:36 -0800 Subject: [PATCH 31/45] revert --- dimos/utils/testing/replay.py | 57 ++++++++++++++--------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/dimos/utils/testing/replay.py b/dimos/utils/testing/replay.py index 1913c7eb36..89225c322e 100644 --- a/dimos/utils/testing/replay.py +++ b/dimos/utils/testing/replay.py @@ -19,7 +19,7 @@ import pickle import re import time -from typing import Any, Generic, TypeVar, cast +from typing import Any, Generic, TypeVar from reactivex import ( from_iterable, @@ -47,12 +47,12 @@ def __init__(self, name: str, autocast: Callable[[Any], T] | None = None) -> Non self.root_dir = get_data(name) self.autocast = autocast - def load(self, *names: int | str) -> T | list[T]: + def load(self, *names: int | str) -> T | Any | list[T] | list[Any]: if len(names) == 1: return self.load_one(names[0]) return list(map(lambda name: self.load_one(name), names)) - def load_one(self, name: int | str | Path) -> T: + def load_one(self, name: int | str | Path) -> T | Any: if isinstance(name, int): full_path = self.root_dir / f"/{name:03d}.pickle" elif isinstance(name, Path): @@ -64,9 +64,9 @@ def load_one(self, name: int | str | Path) -> T: data = pickle.load(f) if self.autocast: return self.autocast(data) - return cast("T", data) + return data - def first(self) -> T | None: + def first(self) -> T | Any | None: try: return next(self.iterate()) except StopIteration: @@ -85,14 +85,14 @@ def extract_number(filepath): # type: ignore[no-untyped-def] key=extract_number, ) - def iterate(self, loop: bool = False) -> Iterator[T]: + def iterate(self, loop: bool = False) -> Iterator[T | Any]: while True: for file_path in self.files: yield self.load_one(Path(file_path)) if not loop: break - def stream(self, rate_hz: float | None = None, loop: bool = False) -> Observable[T]: + def stream(self, rate_hz: float | None = None, loop: bool = False) -> Observable[T | Any]: if rate_hz is None: return from_iterable(self.iterate(loop=loop)) @@ -136,7 +136,7 @@ def __init__(self, name: str, autocast: Callable[[T], Any] | None = None) -> Non def consume_stream(self, observable: Observable[T | Any]) -> None: """Consume an observable stream of sensor data without saving.""" - observable.subscribe(self.save_one) # type: ignore[arg-type] + return observable.subscribe(self.save_one) # type: ignore[arg-type, return-value] def save_stream(self, observable: Observable[T | Any]) -> Observable[int]: """Save an observable stream of sensor data to pickle files.""" @@ -176,15 +176,8 @@ def save_one(self, frame: T) -> int: return super().save_one((time.time(), frame)) -U = TypeVar("U") - - -class TimedSensorReplay(SensorReplay[tuple[float, U]]): - def __init__(self, name: str, autocast: Callable[[Any], U] | None = None) -> None: - super().__init__(name, autocast=None) - self._timed_autocast = autocast - - def load_one(self, name: int | str | Path) -> tuple[float, U]: +class TimedSensorReplay(SensorReplay[T]): + def load_one(self, name: int | str | Path) -> T | Any: if isinstance(name, int): full_path = self.root_dir / f"/{name:03d}.pickle" elif isinstance(name, Path): @@ -194,11 +187,11 @@ def load_one(self, name: int | str | Path) -> tuple[float, U]: with open(full_path, "rb") as f: data = pickle.load(f) - if self._timed_autocast: - return (data[0], self._timed_autocast(data[1])) - return cast("tuple[float, U]", data) + if self.autocast: + return (data[0], self.autocast(data[1])) + return data - def find_closest(self, timestamp: float, tolerance: float | None = None) -> U | None: + def find_closest(self, timestamp: float, tolerance: float | None = None) -> T | Any | None: """Find the frame closest to the given timestamp. Args: @@ -229,7 +222,7 @@ def find_closest(self, timestamp: float, tolerance: float | None = None) -> U | def find_closest_seek( self, relative_seconds: float, tolerance: float | None = None - ) -> U | None: + ) -> T | Any | None: """Find the frame closest to a time relative to the start. Args: @@ -302,28 +295,23 @@ def iterate_ts( duration: float | None = None, from_timestamp: float | None = None, loop: bool = False, - ) -> Iterator[tuple[float, U]]: + ) -> Iterator[tuple[float, T] | Any]: """Iterate with absolute timestamps, with optional seek and duration.""" - first_ts: float | None = None + first_ts = None if (seek is not None) or (duration is not None): first_ts = self.first_timestamp() if first_ts is None: return if seek is not None: - if first_ts is None: - return - from_timestamp = first_ts + seek + from_timestamp = first_ts + seek # type: ignore[operator] - end_timestamp: float | None = None + end_timestamp = None if duration is not None: - base = from_timestamp if from_timestamp is not None else first_ts - if base is None: - return - end_timestamp = base + duration + end_timestamp = (from_timestamp if from_timestamp else first_ts) + duration # type: ignore[operator] while True: - for ts, data in super().iterate(): + for ts, data in super().iterate(): # type: ignore[misc] if from_timestamp is None or ts >= from_timestamp: if end_timestamp is not None and ts >= end_timestamp: break @@ -338,7 +326,7 @@ def stream( # type: ignore[override] duration: float | None = None, from_timestamp: float | None = None, loop: bool = False, - ) -> Observable[U]: + ) -> Observable[T | Any]: def _subscribe(observer, scheduler=None): # type: ignore[no-untyped-def] from reactivex.disposable import CompositeDisposable, Disposable @@ -365,7 +353,6 @@ def _subscribe(observer, scheduler=None): # type: ignore[no-untyped-def] observer.on_next(first_data) # Pre-load next message - next_message: tuple[float, U] | None try: next_message = next(iterator) except StopIteration: From 9bf224d3604fbdb6fb36a99eb37f65ec1d5cefa7 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 01:51:29 -0800 Subject: [PATCH 32/45] use direct imports (for both performance and type hinting) --- dimos/core/core.py | 8 ++++++-- .../camera/gstreamer/gstreamer_camera.py | 4 +++- dimos/hardware/sensors/camera/module.py | 7 +++++-- .../sensors/camera/realsense/camera.py | 4 +++- dimos/hardware/sensors/camera/zed/camera.py | 7 ++++++- dimos/hardware/sensors/fake_zed_module.py | 4 +++- .../manipulation/control/coordinator_client.py | 4 +++- .../control/dual_trajectory_setter.py | 4 +++- .../manipulation/control/trajectory_setter.py | 4 +++- dimos/navigation/rosnav.py | 10 +++++++--- dimos/perception/detection/module3D.py | 18 ++++++++++++------ dimos/perception/detection/moduleDB.py | 6 ++++-- dimos/perception/detection/reid/module.py | 6 ++++-- dimos/perception/detection/reid/type.py | 6 +++++- .../temporal_memory/temporal_memory.py | 5 +++-- .../temporal_memory/temporal_memory_deploy.py | 12 ++++++++---- dimos/perception/object_tracker.py | 4 +++- dimos/perception/object_tracker_2d.py | 4 +++- dimos/perception/spatial_perception.py | 2 +- dimos/protocol/skill/skill.py | 18 +++++++++++------- dimos/robot/drone/connection_module.py | 4 +++- 21 files changed, 99 insertions(+), 42 deletions(-) diff --git a/dimos/core/core.py b/dimos/core/core.py index e7a7d09f58..6c95700926 100644 --- a/dimos/core/core.py +++ b/dimos/core/core.py @@ -17,7 +17,6 @@ from typing import ( TYPE_CHECKING, - Any, TypeVar, ) @@ -30,7 +29,12 @@ register_picklers() T = TypeVar("T") +from typing import ParamSpec, TypeVar -def rpc(fn: Callable[..., Any]) -> Callable[..., Any]: +P = ParamSpec("P") +R = TypeVar("R") + + +def rpc(fn: Callable[P, R]) -> Callable[P, R]: fn.__rpc__ = True # type: ignore[attr-defined] return fn diff --git a/dimos/hardware/sensors/camera/gstreamer/gstreamer_camera.py b/dimos/hardware/sensors/camera/gstreamer/gstreamer_camera.py index 949330881a..9161185d50 100644 --- a/dimos/hardware/sensors/camera/gstreamer/gstreamer_camera.py +++ b/dimos/hardware/sensors/camera/gstreamer/gstreamer_camera.py @@ -22,7 +22,9 @@ import numpy as np -from dimos.core import Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import Out from dimos.msgs.sensor_msgs import Image, ImageFormat from dimos.utils.logging_config import setup_logger diff --git a/dimos/hardware/sensors/camera/module.py b/dimos/hardware/sensors/camera/module.py index 54c4b338f5..cc3a9f140d 100644 --- a/dimos/hardware/sensors/camera/module.py +++ b/dimos/hardware/sensors/camera/module.py @@ -21,16 +21,19 @@ from reactivex import operators as ops import rerun as rr -from dimos.agents import Output, Reducer, Stream, skill -from dimos.core import Module, ModuleConfig, Out, rpc from dimos.core.blueprints import autoconnect +from dimos.core.core import rpc from dimos.core.global_config import GlobalConfig +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import Out from dimos.dashboard.rerun_init import connect_rerun from dimos.hardware.sensors.camera.spec import CameraHardware from dimos.hardware.sensors.camera.webcam import Webcam from dimos.msgs.geometry_msgs import Quaternion, Transform, Vector3 from dimos.msgs.sensor_msgs.CameraInfo import CameraInfo from dimos.msgs.sensor_msgs.Image import Image, sharpness_barrier +from dimos.protocol.skill.skill import skill +from dimos.protocol.skill.type import Output, Reducer, Stream from dimos.spec import perception from dimos.utils.reactive import iter_observable diff --git a/dimos/hardware/sensors/camera/realsense/camera.py b/dimos/hardware/sensors/camera/realsense/camera.py index 3613dbf0a2..8f31441d20 100644 --- a/dimos/hardware/sensors/camera/realsense/camera.py +++ b/dimos/hardware/sensors/camera/realsense/camera.py @@ -23,8 +23,10 @@ import reactivex as rx from scipy.spatial.transform import Rotation # type: ignore[import-untyped] -from dimos.core import Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig from dimos.core.module_coordinator import ModuleCoordinator +from dimos.core.stream import Out from dimos.core.transport import LCMTransport from dimos.hardware.sensors.camera.spec import ( OPTICAL_ROTATION, diff --git a/dimos/hardware/sensors/camera/zed/camera.py b/dimos/hardware/sensors/camera/zed/camera.py index 67d80af4b0..171b706ff9 100644 --- a/dimos/hardware/sensors/camera/zed/camera.py +++ b/dimos/hardware/sensors/camera/zed/camera.py @@ -18,12 +18,14 @@ from dataclasses import dataclass, field import threading import time +from typing import TYPE_CHECKING import cv2 import pyzed.sl as sl import reactivex as rx -from dimos.core import Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig from dimos.core.module_coordinator import ModuleCoordinator from dimos.core.transport import LCMTransport from dimos.hardware.sensors.camera.spec import ( @@ -39,6 +41,9 @@ from dimos.spec import perception from dimos.utils.reactive import backpressure +if TYPE_CHECKING: + from dimos.core.stream import Out + def default_base_transform() -> Transform: """Default identity transform for camera mounting.""" diff --git a/dimos/hardware/sensors/fake_zed_module.py b/dimos/hardware/sensors/fake_zed_module.py index e8fc51bf31..ec5613077d 100644 --- a/dimos/hardware/sensors/fake_zed_module.py +++ b/dimos/hardware/sensors/fake_zed_module.py @@ -24,7 +24,9 @@ from dimos_lcm.sensor_msgs import CameraInfo import numpy as np -from dimos.core import Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import Out from dimos.msgs.geometry_msgs import PoseStamped from dimos.msgs.sensor_msgs import Image, ImageFormat from dimos.msgs.std_msgs import Header diff --git a/dimos/manipulation/control/coordinator_client.py b/dimos/manipulation/control/coordinator_client.py index b30c1f46f7..5bc3513ae6 100644 --- a/dimos/manipulation/control/coordinator_client.py +++ b/dimos/manipulation/control/coordinator_client.py @@ -49,7 +49,9 @@ from dimos.control.coordinator import ControlCoordinator from dimos.core.rpc_client import RPCClient -from dimos.manipulation.planning import JointTrajectoryGenerator +from dimos.manipulation.planning.trajectory_generator.joint_trajectory_generator import ( + JointTrajectoryGenerator, +) if TYPE_CHECKING: from dimos.msgs.trajectory_msgs import JointTrajectory diff --git a/dimos/manipulation/control/dual_trajectory_setter.py b/dimos/manipulation/control/dual_trajectory_setter.py index 4b54f0e3e5..4f8a8802e1 100644 --- a/dimos/manipulation/control/dual_trajectory_setter.py +++ b/dimos/manipulation/control/dual_trajectory_setter.py @@ -34,7 +34,9 @@ import time from dimos import core -from dimos.manipulation.planning import JointTrajectoryGenerator +from dimos.manipulation.planning.trajectory_generator.joint_trajectory_generator import ( + JointTrajectoryGenerator, +) from dimos.msgs.sensor_msgs import JointState from dimos.msgs.trajectory_msgs import JointTrajectory diff --git a/dimos/manipulation/control/trajectory_setter.py b/dimos/manipulation/control/trajectory_setter.py index 5b8b2ff234..bad3854521 100644 --- a/dimos/manipulation/control/trajectory_setter.py +++ b/dimos/manipulation/control/trajectory_setter.py @@ -33,7 +33,9 @@ import time from dimos import core -from dimos.manipulation.planning import JointTrajectoryGenerator +from dimos.manipulation.planning.trajectory_generator.joint_trajectory_generator import ( + JointTrajectoryGenerator, +) from dimos.msgs.sensor_msgs import JointState from dimos.msgs.trajectory_msgs import JointTrajectory diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index 88fa7985eb..514aba704a 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -45,9 +45,11 @@ from tf2_msgs.msg import TFMessage as ROSTFMessage # type: ignore[attr-defined] from dimos import spec -from dimos.agents import Reducer, Stream, skill # type: ignore[attr-defined] -from dimos.core import DimosCluster, In, LCMTransport, Module, Out, pSHMTransport, rpc -from dimos.core.module import ModuleConfig +from dimos.core._dask_exports import DimosCluster +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import In, Out +from dimos.core.transport import LCMTransport, pSHMTransport from dimos.msgs.geometry_msgs import ( PoseStamped, Quaternion, @@ -60,6 +62,8 @@ from dimos.msgs.std_msgs import Bool from dimos.msgs.tf2_msgs.TFMessage import TFMessage from dimos.navigation.base import NavigationInterface, NavigationState +from dimos.protocol.skill.skill import skill +from dimos.protocol.skill.type import Reducer, Stream from dimos.utils.logging_config import setup_logger from dimos.utils.transform_utils import euler_to_quaternion diff --git a/dimos/perception/detection/module3D.py b/dimos/perception/detection/module3D.py index 037376f995..9fe5bd9c6c 100644 --- a/dimos/perception/detection/module3D.py +++ b/dimos/perception/detection/module3D.py @@ -13,6 +13,8 @@ # limitations under the License. +from typing import TYPE_CHECKING, Any + from dimos_lcm.foxglove_msgs.ImageAnnotations import ( ImageAnnotations, ) @@ -21,20 +23,24 @@ from reactivex.observable import Observable from dimos import spec -from dimos.agents import skill # type: ignore[attr-defined] -from dimos.core import DimosCluster, In, Out, rpc +from dimos.core.core import rpc +from dimos.core.stream import In, Out from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Transform, Vector3 from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.module2D import Detection2DModule -from dimos.perception.detection.type import ( - ImageDetections2D, - ImageDetections3DPC, -) +from dimos.perception.detection.type.detection2d.imageDetections2D import ImageDetections2D from dimos.perception.detection.type.detection3d import Detection3DPC +from dimos.perception.detection.type.detection3d.imageDetections3DPC import ImageDetections3DPC +from dimos.protocol.skill.skill import skill from dimos.types.timestamped import align_timestamped from dimos.utils.reactive import backpressure +if TYPE_CHECKING: + from dask.distributed import Client as DimosCluster +else: + DimosCluster = Any + class Detection3DModule(Detection2DModule): color_image: In[Image] diff --git a/dimos/perception/detection/moduleDB.py b/dimos/perception/detection/moduleDB.py index bbfd45143a..bc0a346a59 100644 --- a/dimos/perception/detection/moduleDB.py +++ b/dimos/perception/detection/moduleDB.py @@ -23,13 +23,15 @@ from lcm_msgs.foxglove_msgs import SceneUpdate # type: ignore[import-not-found] from reactivex.observable import Observable -from dimos.core import In, Out, rpc +from dimos.core.core import rpc +from dimos.core.stream import In, Out from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Transform, Vector3 from dimos.msgs.sensor_msgs import Image, PointCloud2 from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.module3D import Detection3DModule -from dimos.perception.detection.type import ImageDetections3DPC, TableStr from dimos.perception.detection.type.detection3d import Detection3DPC +from dimos.perception.detection.type.detection3d.imageDetections3DPC import ImageDetections3DPC +from dimos.perception.detection.type.utils import TableStr # Represents an object in space, as collection of 3d detections over time diff --git a/dimos/perception/detection/reid/module.py b/dimos/perception/detection/reid/module.py index f3f2a5a126..0a359746d3 100644 --- a/dimos/perception/detection/reid/module.py +++ b/dimos/perception/detection/reid/module.py @@ -20,13 +20,15 @@ from reactivex import operators as ops from reactivex.observable import Observable -from dimos.core import In, Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import In, Out from dimos.msgs.foxglove_msgs.Color import Color from dimos.msgs.sensor_msgs import Image from dimos.msgs.vision_msgs import Detection2DArray from dimos.perception.detection.reid.embedding_id_system import EmbeddingIDSystem from dimos.perception.detection.reid.type import IDSystem -from dimos.perception.detection.type import ImageDetections2D +from dimos.perception.detection.type.detection2d.imageDetections2D import ImageDetections2D from dimos.types.timestamped import align_timestamped, to_ros_stamp from dimos.utils.reactive import backpressure diff --git a/dimos/perception/detection/reid/type.py b/dimos/perception/detection/reid/type.py index 28ea719f81..61571e418f 100644 --- a/dimos/perception/detection/reid/type.py +++ b/dimos/perception/detection/reid/type.py @@ -15,8 +15,12 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import TYPE_CHECKING -from dimos.perception.detection.type import Detection2DBBox, ImageDetections2D +from dimos.perception.detection.type.detection2d.bbox import Detection2DBBox + +if TYPE_CHECKING: + from dimos.perception.detection.type.detection2d.imageDetections2D import ImageDetections2D class IDSystem(ABC): diff --git a/dimos/perception/experimental/temporal_memory/temporal_memory.py b/dimos/perception/experimental/temporal_memory/temporal_memory.py index 29d4ecf3d9..b884b0886f 100644 --- a/dimos/perception/experimental/temporal_memory/temporal_memory.py +++ b/dimos/perception/experimental/temporal_memory/temporal_memory.py @@ -32,13 +32,14 @@ from reactivex import Subject, interval from reactivex.disposable import Disposable -from dimos.agents import skill -from dimos.core import In, rpc +from dimos.core.core import rpc from dimos.core.module import ModuleConfig from dimos.core.skill_module import SkillModule +from dimos.core.stream import In from dimos.models.vl.base import VlModel from dimos.msgs.sensor_msgs import Image from dimos.msgs.sensor_msgs.Image import sharpness_barrier +from dimos.protocol.skill.skill import skill from . import temporal_utils as tu from .clip_filter import ( diff --git a/dimos/perception/experimental/temporal_memory/temporal_memory_deploy.py b/dimos/perception/experimental/temporal_memory/temporal_memory_deploy.py index 611385630e..ab3cc7a0f5 100644 --- a/dimos/perception/experimental/temporal_memory/temporal_memory_deploy.py +++ b/dimos/perception/experimental/temporal_memory/temporal_memory_deploy.py @@ -17,17 +17,21 @@ """ import os +from typing import TYPE_CHECKING -from dimos import spec -from dimos.core import DimosCluster +from dimos.core._dask_exports import DimosCluster from dimos.models.vl.base import VlModel +from dimos.spec import Camera as CameraSpec from .temporal_memory import TemporalMemory, TemporalMemoryConfig +if TYPE_CHECKING: + from dimos.msgs.sensor_msgs import Image + def deploy( dimos: DimosCluster, - camera: spec.Camera, + camera: CameraSpec, vlm: VlModel | None = None, config: TemporalMemoryConfig | None = None, ) -> TemporalMemory: @@ -52,7 +56,7 @@ def deploy( if camera.color_image.transport is None: from dimos.core.transport import JpegShmTransport - transport = JpegShmTransport("/temporal_memory/color_image") + transport: JpegShmTransport[Image] = JpegShmTransport("/temporal_memory/color_image") camera.color_image.transport = transport temporal_memory.color_image.connect(camera.color_image) diff --git a/dimos/perception/object_tracker.py b/dimos/perception/object_tracker.py index 54a5873435..3180645a98 100644 --- a/dimos/perception/object_tracker.py +++ b/dimos/perception/object_tracker.py @@ -27,7 +27,9 @@ import numpy as np from reactivex.disposable import Disposable -from dimos.core import In, Module, ModuleConfig, Out, rpc +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 import Pose, Quaternion, Transform, Vector3 from dimos.msgs.sensor_msgs import ( CameraInfo, diff --git a/dimos/perception/object_tracker_2d.py b/dimos/perception/object_tracker_2d.py index f5d39745c3..bb43198053 100644 --- a/dimos/perception/object_tracker_2d.py +++ b/dimos/perception/object_tracker_2d.py @@ -31,7 +31,9 @@ import numpy as np from reactivex.disposable import Disposable -from dimos.core import In, Module, ModuleConfig, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.core.stream import In, Out from dimos.msgs.sensor_msgs import Image, ImageFormat from dimos.msgs.std_msgs import Header from dimos.msgs.vision_msgs import Detection2DArray diff --git a/dimos/perception/spatial_perception.py b/dimos/perception/spatial_perception.py index e33820f22c..7bb83b67cd 100644 --- a/dimos/perception/spatial_perception.py +++ b/dimos/perception/spatial_perception.py @@ -203,7 +203,7 @@ def set_video(image_msg: Image) -> None: # Start periodic processing using interval unsub = interval(self._process_interval).subscribe(lambda _: self._process_frame()) # type: ignore[assignment] - self._disposables.add(Disposable(unsub)) + self._disposables.add(unsub) @rpc def stop(self) -> None: diff --git a/dimos/protocol/skill/skill.py b/dimos/protocol/skill/skill.py index 373bb463a7..33e71a590b 100644 --- a/dimos/protocol/skill/skill.py +++ b/dimos/protocol/skill/skill.py @@ -16,7 +16,7 @@ from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass -from typing import Any +from typing import Any, ParamSpec, TypeVar, cast # from dimos.core.core import rpc from dimos.protocol.skill.comms import LCMSkillComms, SkillCommsSpec @@ -57,7 +57,11 @@ # the average of all values is returned to the agent -def rpc(fn: Callable[..., Any]) -> Callable[..., Any]: +P = ParamSpec("P") +R = TypeVar("R") + + +def rpc(fn: Callable[P, R]) -> Callable[P, R]: fn.__rpc__ = True # type: ignore[attr-defined] return fn @@ -68,16 +72,16 @@ def skill( ret: Return = Return.call_agent, output: Output = Output.standard, hide_skill: bool = False, -) -> Callable: # type: ignore[type-arg] - def decorator(f: Callable[..., Any]) -> Any: - def wrapper(self, *args, **kwargs): # type: ignore[no-untyped-def] +) -> Callable[[Callable[P, R]], Callable[P, R]]: + def decorator(f: Callable[P, R]) -> Callable[P, R]: + def wrapper(self: Any, *args: P.args, **kwargs: P.kwargs) -> R: skill = f"{f.__name__}" call_id = kwargs.get("call_id", None) if call_id: del kwargs["call_id"] - return self.call_skill(call_id, skill, args, kwargs) + return cast("R", self.call_skill(call_id, skill, args, kwargs)) # type: ignore[attr-defined] # def run_function(): # return self.call_skill(call_id, skill, args, kwargs) # @@ -108,7 +112,7 @@ def wrapper(self, *args, **kwargs): # type: ignore[no-untyped-def] wrapper._skill_config = skill_config # type: ignore[attr-defined] wrapper.__name__ = f.__name__ # Preserve original function name wrapper.__doc__ = f.__doc__ # Preserve original docstring - return wrapper + return cast("Callable[P, R]", wrapper) return decorator diff --git a/dimos/robot/drone/connection_module.py b/dimos/robot/drone/connection_module.py index 865d98c3d3..ac3dc33591 100644 --- a/dimos/robot/drone/connection_module.py +++ b/dimos/robot/drone/connection_module.py @@ -24,7 +24,9 @@ from dimos_lcm.std_msgs import String from reactivex.disposable import CompositeDisposable, Disposable -from dimos.core import In, Module, Out, rpc +from dimos.core.core import rpc +from dimos.core.module import Module +from dimos.core.stream import In, Out from dimos.mapping.types import LatLon from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Transform, Twist, Vector3 from dimos.msgs.sensor_msgs import Image From d5575c0612323b9fb21288fa4ab55c2fb4611248 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 01:52:32 -0800 Subject: [PATCH 33/45] fixup typing --- dimos/robot/drone/connection_module.py | 6 +++--- dimos/robot/unitree/b1/joystick_module.py | 6 +++--- dimos/robot/unitree/keyboard_teleop.py | 4 ++-- dimos/robot/unitree/rosnav.py | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dimos/robot/drone/connection_module.py b/dimos/robot/drone/connection_module.py index ac3dc33591..92dfb1db38 100644 --- a/dimos/robot/drone/connection_module.py +++ b/dimos/robot/drone/connection_module.py @@ -103,7 +103,7 @@ def __init__( Module.__init__(self, *args, **kwargs) @rpc - def start(self) -> bool: + def start(self) -> None: """Start the connection and subscribe to sensor streams.""" # Check for replay mode if self.connection_string == "replay": @@ -120,7 +120,7 @@ def start(self) -> bool: if not self.connection.connected: logger.error("Failed to connect to drone") - return False + return # Start video stream (already created above) if self.video_stream.start(): @@ -172,7 +172,7 @@ def start(self) -> bool: self._telemetry_thread.start() logger.info("Drone connection module started") - return True + return def _store_and_publish_frame(self, frame: Image) -> None: """Store the latest video frame and publish it.""" diff --git a/dimos/robot/unitree/b1/joystick_module.py b/dimos/robot/unitree/b1/joystick_module.py index 9aa02af058..bb07094973 100644 --- a/dimos/robot/unitree/b1/joystick_module.py +++ b/dimos/robot/unitree/b1/joystick_module.py @@ -47,7 +47,7 @@ def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.current_mode = 0 # Start in IDLE mode for safety @rpc - def start(self) -> bool: + def start(self) -> None: """Initialize pygame and start control loop.""" super().start() @@ -56,7 +56,7 @@ def start(self) -> bool: import pygame # noqa: F401 except ImportError: print("ERROR: pygame not installed. Install with: pip install pygame") - return False + return self.keys_held = set() # type: ignore[var-annotated] self.pygame_ready = True @@ -66,7 +66,7 @@ def start(self) -> bool: self._thread = threading.Thread(target=self._pygame_loop, daemon=True) self._thread.start() - return True + return @rpc def stop(self) -> None: diff --git a/dimos/robot/unitree/keyboard_teleop.py b/dimos/robot/unitree/keyboard_teleop.py index 8e0d987127..3d7d4c263e 100644 --- a/dimos/robot/unitree/keyboard_teleop.py +++ b/dimos/robot/unitree/keyboard_teleop.py @@ -45,7 +45,7 @@ def __init__(self) -> None: self._stop_event = threading.Event() @rpc - def start(self) -> bool: + def start(self) -> None: super().start() self._keys_held = set() @@ -54,7 +54,7 @@ def start(self) -> bool: self._thread = threading.Thread(target=self._pygame_loop, daemon=True) self._thread.start() - return True + return @rpc def stop(self) -> None: diff --git a/dimos/robot/unitree/rosnav.py b/dimos/robot/unitree/rosnav.py index 3244ecfd05..7a9b98b678 100644 --- a/dimos/robot/unitree/rosnav.py +++ b/dimos/robot/unitree/rosnav.py @@ -119,7 +119,7 @@ def go_to(self, pose: PoseStamped, timeout: float = 60.0) -> bool: return False @rpc - def stop(self) -> bool: + def stop(self) -> None: """ Cancel current navigation by publishing to cancel_goal. @@ -131,6 +131,6 @@ def stop(self) -> bool: if self.cancel_goal: cancel_msg = Bool(data=True) self.cancel_goal.publish(cancel_msg) - return True + return - return False + return From 44ba48b0f0df45a3da7727a5b1b90436120e1f6d Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 02:13:06 -0800 Subject: [PATCH 34/45] fix test --- dimos/robot/drone/test_drone.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dimos/robot/drone/test_drone.py b/dimos/robot/drone/test_drone.py index bfbaa9ed54..199c6aa236 100644 --- a/dimos/robot/drone/test_drone.py +++ b/dimos/robot/drone/test_drone.py @@ -264,9 +264,8 @@ def test_connection_module_replay_mode(self) -> None: try: # Start should use Fake classes - result = module.start() + module.start() - self.assertTrue(result) mock_fake_conn.assert_called_once_with("replay") mock_fake_video.assert_called_once() finally: @@ -380,20 +379,19 @@ def replay_side_effect(store_name: str): try: print("\n[TEST] Starting connection module in replay mode...") - result = module.start() + module.start() # Give time for messages to process import time time.sleep(0.1) - print(f"\n[TEST] Module started: {result}") + print("\n[TEST] Module started") print(f"[TEST] Total odom messages published: {len(published_odom)}") print(f"[TEST] Total video frames published: {len(published_video)}") print(f"[TEST] Total status messages published: {len(published_status)}") # Verify module started and is processing messages - self.assertTrue(result) self.assertIsNotNone(module.connection) self.assertIsNotNone(module.video_stream) From dbe419d918e80d0c0b814a861fcb37bf782cb4f3 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Sun, 8 Feb 2026 02:19:06 -0800 Subject: [PATCH 35/45] fix test --- dimos/robot/drone/test_drone.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dimos/robot/drone/test_drone.py b/dimos/robot/drone/test_drone.py index 199c6aa236..d9075beae3 100644 --- a/dimos/robot/drone/test_drone.py +++ b/dimos/robot/drone/test_drone.py @@ -875,8 +875,7 @@ def replay_stream_subscribe(callback) -> None: module.movecmd = MagicMock() # Start module - result = module.start() - self.assertTrue(result) + module.start() # Give time for processing time.sleep(0.2) From 565b8a76c78a8cafd36f495d63e716cf6938807f Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Mon, 9 Feb 2026 11:19:24 -0800 Subject: [PATCH 36/45] mypy --- dimos/robot/drone/camera_module.py | 6 +++--- dimos/robot/drone/drone_tracking_module.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dimos/robot/drone/camera_module.py b/dimos/robot/drone/camera_module.py index 7806c3eab8..8ba88fd028 100644 --- a/dimos/robot/drone/camera_module.py +++ b/dimos/robot/drone/camera_module.py @@ -98,11 +98,11 @@ def __init__( logger.info(f"DroneCameraModule initialized with intrinsics: {camera_intrinsics}") @rpc - def start(self) -> bool: + def start(self) -> None: """Start the camera module.""" if self._running: logger.warning("Camera module already running") - return True + return # Start processing thread for depth (which will init Metric3D and handle video) self._running = True @@ -111,7 +111,7 @@ def start(self) -> bool: self._processing_thread.start() logger.info("Camera module started") - return True + return def _on_video_frame(self, frame: Image) -> None: """Handle incoming video frame.""" diff --git a/dimos/robot/drone/drone_tracking_module.py b/dimos/robot/drone/drone_tracking_module.py index e6560142d1..6c752a42e5 100644 --- a/dimos/robot/drone/drone_tracking_module.py +++ b/dimos/robot/drone/drone_tracking_module.py @@ -113,7 +113,7 @@ def _get_latest_frame(self) -> np.ndarray[Any, np.dtype[Any]] | None: return data @rpc - def start(self) -> bool: + def start(self) -> None: """Start the tracking module and subscribe to video input.""" if self.video_input.transport: self.video_input.subscribe(self._on_new_frame) @@ -124,7 +124,7 @@ def start(self) -> bool: if self.follow_object_cmd.transport: self.follow_object_cmd.subscribe(self._on_follow_object_cmd) - return True + return @rpc def stop(self) -> None: From 1f685dc36cbb116f9e8d400ca0378545ce3c4632 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Mon, 9 Feb 2026 12:07:41 -0800 Subject: [PATCH 37/45] more mypy --- dimos/perception/object_tracker.py | 6 ++++-- dimos/perception/object_tracker_2d.py | 6 ++++-- dimos/robot/drone/drone_tracking_module.py | 9 +++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/dimos/perception/object_tracker.py b/dimos/perception/object_tracker.py index 3180645a98..eca2552373 100644 --- a/dimos/perception/object_tracker.py +++ b/dimos/perception/object_tracker.py @@ -15,6 +15,7 @@ from dataclasses import dataclass import threading import time +from typing import cast import cv2 @@ -25,6 +26,7 @@ ObjectHypothesisWithPose, ) import numpy as np +from numpy.typing import NDArray from reactivex.disposable import Disposable from dimos.core.core import rpc @@ -557,7 +559,7 @@ def _process_tracking(self) -> None: viz_msg = Image.from_numpy(viz_image) self.tracked_overlay.publish(viz_msg) - def _draw_reid_matches(self, image: np.ndarray) -> np.ndarray: # type: ignore[type-arg] + def _draw_reid_matches(self, image: NDArray[np.uint8]) -> NDArray[np.uint8]: """Draw REID feature matches on the image.""" viz_image = image.copy() @@ -599,7 +601,7 @@ def _draw_reid_matches(self, image: np.ndarray) -> np.ndarray: # type: ignore[t viz_image, status_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, status_color, 2 ) - return viz_image + return cast("NDArray[np.uint8]", viz_image) def _get_depth_from_bbox(self, bbox: list[int], depth_frame: np.ndarray) -> float | None: # type: ignore[type-arg] """Calculate depth from bbox using the 25th percentile of closest points. diff --git a/dimos/perception/object_tracker_2d.py b/dimos/perception/object_tracker_2d.py index bb43198053..d05e2b06c5 100644 --- a/dimos/perception/object_tracker_2d.py +++ b/dimos/perception/object_tracker_2d.py @@ -16,6 +16,7 @@ import logging import threading import time +from typing import cast import cv2 @@ -29,6 +30,7 @@ Pose2D, ) import numpy as np +from numpy.typing import NDArray from reactivex.disposable import Disposable from dimos.core.core import rpc @@ -291,10 +293,10 @@ def _process_tracking(self) -> None: viz_msg = Image.from_numpy(viz_copy, format=ImageFormat.RGB) self.tracked_overlay.publish(viz_msg) - def _draw_visualization(self, image: np.ndarray, bbox: list[int]) -> np.ndarray: # type: ignore[type-arg] + def _draw_visualization(self, image: NDArray[np.uint8], bbox: list[int]) -> NDArray[np.uint8]: """Draw tracking visualization.""" viz_image = image.copy() x1, y1, x2, y2 = bbox cv2.rectangle(viz_image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(viz_image, "TRACKING", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) - return viz_image + return cast("NDArray[np.uint8]", viz_image) diff --git a/dimos/robot/drone/drone_tracking_module.py b/dimos/robot/drone/drone_tracking_module.py index 6c752a42e5..702ea44f95 100644 --- a/dimos/robot/drone/drone_tracking_module.py +++ b/dimos/robot/drone/drone_tracking_module.py @@ -18,11 +18,12 @@ import json import threading import time -from typing import Any +from typing import Any, cast import cv2 from dimos_lcm.std_msgs import String import numpy as np +from numpy.typing import NDArray from dimos.core import In, Module, Out, rpc from dimos.models.qwen.video_query import get_bbox_from_qwen_frame @@ -308,10 +309,10 @@ def _visual_servoing_loop(self, tracker: Any, duration: float) -> None: def _draw_tracking_overlay( self, - frame: np.ndarray[Any, np.dtype[Any]], + frame: NDArray[np.uint8], bbox: tuple[int, int, int, int], center: tuple[int, int], - ) -> np.ndarray[Any, np.dtype[Any]]: + ) -> NDArray[np.uint8]: """Draw tracking visualization overlay. Args: @@ -351,7 +352,7 @@ def _draw_tracking_overlay( overlay, error_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1 ) - return overlay + return cast("NDArray[np.uint8]", overlay) def _publish_status(self, status: dict[str, Any]) -> None: """Publish tracking status as JSON. From beb93f1c59071eb2140c1801766b14ad6287d0c2 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Tue, 10 Feb 2026 13:59:15 -0800 Subject: [PATCH 38/45] - --- dimos/robot/all_blueprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index 73527a42da..1bab90f9f3 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -106,8 +106,8 @@ "spatial_memory": "dimos.perception.spatial_perception", "speak_skill": "dimos.agents.skills.speak_skill", "temporal_memory": "dimos.perception.experimental.temporal_memory.temporal_memory", - "unitree_skills": "dimos.robot.unitree.unitree_skill_container", "twist_teleop_module": "dimos.teleop.quest.quest_extensions", + "unitree_skills": "dimos.robot.unitree.unitree_skill_container", "utilization": "dimos.utils.monitoring", "visualizing_teleop_module": "dimos.teleop.quest.quest_extensions", "vlm_agent": "dimos.agents.vlm_agent", From dd552d00c04908ae436dfac286a2c34abf20237c Mon Sep 17 00:00:00 2001 From: Paul Nechifor Date: Wed, 11 Feb 2026 07:13:04 +0200 Subject: [PATCH 39/45] pin langchain --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 64e18fdb5d..3f5ce9bbd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ visualization = [ ] agents = [ - "langchain>=1,<2", + "langchain>=1,<1.2.4", "langchain-chroma>=1,<2", "langchain-core>=1,<2", "langchain-openai>=1,<2", diff --git a/uv.lock b/uv.lock index a4c6ec4e5b..0e98f5dcc9 100644 --- a/uv.lock +++ b/uv.lock @@ -2021,7 +2021,7 @@ requires-dist = [ { name = "hydra-core", marker = "extra == 'perception'", specifier = ">=1.3.0" }, { name = "ipykernel", marker = "extra == 'misc'" }, { name = "kaleido", marker = "extra == 'manipulation'", specifier = ">=0.2.1" }, - { name = "langchain", marker = "extra == 'agents'", specifier = ">=1,<2" }, + { name = "langchain", marker = "extra == 'agents'", specifier = ">=1,<1.2.4" }, { name = "langchain-chroma", marker = "extra == 'agents'", specifier = ">=1,<2" }, { name = "langchain-core", marker = "extra == 'agents'", specifier = ">=1,<2" }, { name = "langchain-huggingface", marker = "extra == 'agents'", specifier = ">=1,<2" }, From 302877cb10e169b7294337e851a3dbdc4a83a9d1 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 13:46:10 -0800 Subject: [PATCH 40/45] fix for CI --- dimos/agents/spec.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/dimos/agents/spec.py b/dimos/agents/spec.py index b0a0324e89..7e86bb4b2e 100644 --- a/dimos/agents/spec.py +++ b/dimos/agents/spec.py @@ -22,7 +22,6 @@ if TYPE_CHECKING: from dimos.protocol.skill.skill import SkillContainer -from langchain.chat_models.base import _SUPPORTED_PROVIDERS from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import ( AIMessage, @@ -45,7 +44,31 @@ logger = setup_logger() -# Dynamically create ModelProvider enum from LangChain's supported providers +# FIXME: I dont see a stable (and dynamic) way to get these, this is only for type-hints and Paul's PR should replace this in a couple of days (this is a stop-gap change to get CI passing) +_SUPPORTED_PROVIDERS = [ + "ANTHROPIC", + "AZURE_AI", + "AZURE_OPENAI", + "BEDROCK", + "BEDROCK_CONVERSE", + "COHERE", + "DEEPSEEK", + "FIREWORKS", + "GOOGLE_ANTHROPIC_VERTEX", + "GOOGLE_GENAI", + "GOOGLE_VERTEXAI", + "GROQ", + "HUGGINGFACE", + "IBM", + "MISTRALAI", + "NVIDIA", + "OLLAMA", + "OPENAI", + "PERPLEXITY", + "TOGETHER", + "UPSTAGE", + "XAI", +] _providers = {provider.upper(): provider for provider in _SUPPORTED_PROVIDERS} Provider = Enum("Provider", _providers, type=str) # type: ignore[misc] From 7488427d689f2579beb8086688c34181eb1aea4d Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 13:49:00 -0800 Subject: [PATCH 41/45] - --- dimos/agents/spec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dimos/agents/spec.py b/dimos/agents/spec.py index 7e86bb4b2e..02eef359de 100644 --- a/dimos/agents/spec.py +++ b/dimos/agents/spec.py @@ -45,7 +45,7 @@ # FIXME: I dont see a stable (and dynamic) way to get these, this is only for type-hints and Paul's PR should replace this in a couple of days (this is a stop-gap change to get CI passing) -_SUPPORTED_PROVIDERS = [ +_providers = [ "ANTHROPIC", "AZURE_AI", "AZURE_OPENAI", @@ -69,7 +69,6 @@ "UPSTAGE", "XAI", ] -_providers = {provider.upper(): provider for provider in _SUPPORTED_PROVIDERS} Provider = Enum("Provider", _providers, type=str) # type: ignore[misc] From 406bf99bbeadbd945e39fc1ebd4a5a54f2b17daa Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 22:59:32 -0800 Subject: [PATCH 42/45] fix mypy --- dimos/manipulation/manipulation_module.py | 2 +- dimos/navigation/rosnav.py | 4 ++-- dimos/perception/object_tracker.py | 3 +-- dimos/perception/object_tracker_2d.py | 3 +-- dimos/robot/drone/drone_tracking_module.py | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dimos/manipulation/manipulation_module.py b/dimos/manipulation/manipulation_module.py index 33dea33697..cc0689f660 100644 --- a/dimos/manipulation/manipulation_module.py +++ b/dimos/manipulation/manipulation_module.py @@ -784,7 +784,7 @@ def _get_gripper_hardware_id(self, robot_name: RobotName | None = None) -> str | if not config.gripper_hardware_id: logger.warning(f"No gripper_hardware_id configured for '{config.name}'") return None - return config.gripper_hardware_id + return str(config.gripper_hardware_id) @rpc def set_gripper(self, position: float, robot_name: RobotName | None = None) -> bool: diff --git a/dimos/navigation/rosnav.py b/dimos/navigation/rosnav.py index edc50f4b42..2aa492a509 100644 --- a/dimos/navigation/rosnav.py +++ b/dimos/navigation/rosnav.py @@ -224,7 +224,7 @@ def current_position(self): # type: ignore[no-untyped-def] continue yield f"current position {tf.translation.x}, {tf.translation.y}" - @skill(stream=Stream.call_agent, reducer=Reducer.string) # type: ignore[arg-type] + @skill(stream=Stream.call_agent, reducer=Reducer.string) # type: ignore[untyped-decorator] def goto(self, x: float, y: float): # type: ignore[no-untyped-def] """ move the robot in relative coordinates @@ -243,7 +243,7 @@ def goto(self, x: float, y: float): # type: ignore[no-untyped-def] self.navigate_to(pose_to) yield "arrived" - @skill(stream=Stream.call_agent, reducer=Reducer.string) # type: ignore[arg-type] + @skill(stream=Stream.call_agent, reducer=Reducer.string) # type: ignore[untyped-decorator] def goto_global(self, x: float, y: float) -> Generator[str, None, None]: """ go to coordinates x,y in the map frame diff --git a/dimos/perception/object_tracker.py b/dimos/perception/object_tracker.py index eca2552373..37e05db86b 100644 --- a/dimos/perception/object_tracker.py +++ b/dimos/perception/object_tracker.py @@ -15,7 +15,6 @@ from dataclasses import dataclass import threading import time -from typing import cast import cv2 @@ -601,7 +600,7 @@ def _draw_reid_matches(self, image: NDArray[np.uint8]) -> NDArray[np.uint8]: viz_image, status_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, status_color, 2 ) - return cast("NDArray[np.uint8]", viz_image) + return viz_image def _get_depth_from_bbox(self, bbox: list[int], depth_frame: np.ndarray) -> float | None: # type: ignore[type-arg] """Calculate depth from bbox using the 25th percentile of closest points. diff --git a/dimos/perception/object_tracker_2d.py b/dimos/perception/object_tracker_2d.py index d05e2b06c5..2616e66933 100644 --- a/dimos/perception/object_tracker_2d.py +++ b/dimos/perception/object_tracker_2d.py @@ -16,7 +16,6 @@ import logging import threading import time -from typing import cast import cv2 @@ -299,4 +298,4 @@ def _draw_visualization(self, image: NDArray[np.uint8], bbox: list[int]) -> NDAr x1, y1, x2, y2 = bbox cv2.rectangle(viz_image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(viz_image, "TRACKING", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) - return cast("NDArray[np.uint8]", viz_image) + return viz_image diff --git a/dimos/robot/drone/drone_tracking_module.py b/dimos/robot/drone/drone_tracking_module.py index 702ea44f95..c21cee9759 100644 --- a/dimos/robot/drone/drone_tracking_module.py +++ b/dimos/robot/drone/drone_tracking_module.py @@ -18,7 +18,7 @@ import json import threading import time -from typing import Any, cast +from typing import Any import cv2 from dimos_lcm.std_msgs import String @@ -352,7 +352,7 @@ def _draw_tracking_overlay( overlay, error_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1 ) - return cast("NDArray[np.uint8]", overlay) + return overlay def _publish_status(self, status: dict[str, Any]) -> None: """Publish tracking status as JSON. From 183302624597e719a9f7f121c66013100db63106 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 23:13:18 -0800 Subject: [PATCH 43/45] all_blueprints update --- dimos/robot/all_blueprints.py | 43 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index e822d9fa99..11c520466a 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -40,27 +40,28 @@ "demo-osm": "dimos.mapping.osm.demo_osm:demo_osm", "demo-skill": "dimos.agents.skills.demo_skill:demo_skill", "dual-xarm6-planner": "dimos.manipulation.manipulation_blueprints:dual_xarm6_planner", - "unitree-g1": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1", - "unitree-g1-agentic": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_agentic", - "unitree-g1-agentic-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_agentic_sim", - "unitree-g1-basic": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_basic", - "unitree-g1-basic-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_basic_sim", - "unitree-g1-detection": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_detection", - "unitree-g1-full": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_full", - "unitree-g1-joystick": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_joystick", - "unitree-g1-shm": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_shm", - "unitree-g1-sim": "dimos.robot.unitree_webrtc.unitree_g1_blueprints:unitree_g1_sim", - "unitree-go2": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2", - "unitree-go2-agentic": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic", - "unitree-go2-agentic-huggingface": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_huggingface", - "unitree-go2-agentic-mcp": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_mcp", - "unitree-go2-agentic-ollama": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_agentic_ollama", - "unitree-go2-basic": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_basic", - "unitree-go2-detection": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_detection", - "unitree-go2-ros": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_ros", - "unitree-go2-spatial": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_spatial", - "unitree-go2-temporal-memory": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_temporal_memory", - "unitree-go2-vlm-stream-test": "dimos.robot.unitree_webrtc.unitree_go2_blueprints:unitree_go2_vlm_stream_test", + "uintree-g1-primitive-no-nav": "dimos.robot.unitree.g1.blueprints.primitive.uintree_g1_primitive_no_nav:uintree_g1_primitive_no_nav", + "unitree-g1": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1:unitree_g1", + "unitree-g1-agentic": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic:unitree_g1_agentic", + "unitree-g1-agentic-sim": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_agentic_sim:unitree_g1_agentic_sim", + "unitree-g1-basic": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic:unitree_g1_basic", + "unitree-g1-basic-sim": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_basic_sim:unitree_g1_basic_sim", + "unitree-g1-detection": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_detection:unitree_g1_detection", + "unitree-g1-full": "dimos.robot.unitree.g1.blueprints.agentic.unitree_g1_full:unitree_g1_full", + "unitree-g1-joystick": "dimos.robot.unitree.g1.blueprints.basic.unitree_g1_joystick:unitree_g1_joystick", + "unitree-g1-shm": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_shm:unitree_g1_shm", + "unitree-g1-sim": "dimos.robot.unitree.g1.blueprints.perceptive.unitree_g1_sim:unitree_g1_sim", + "unitree-go2": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2:unitree_go2", + "unitree-go2-agentic": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic:unitree_go2_agentic", + "unitree-go2-agentic-huggingface": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_huggingface:unitree_go2_agentic_huggingface", + "unitree-go2-agentic-mcp": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_mcp:unitree_go2_agentic_mcp", + "unitree-go2-agentic-ollama": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_agentic_ollama:unitree_go2_agentic_ollama", + "unitree-go2-basic": "dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic:unitree_go2_basic", + "unitree-go2-detection": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_detection:unitree_go2_detection", + "unitree-go2-ros": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_ros:unitree_go2_ros", + "unitree-go2-spatial": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_spatial:unitree_go2_spatial", + "unitree-go2-temporal-memory": "dimos.robot.unitree.go2.blueprints.agentic.unitree_go2_temporal_memory:unitree_go2_temporal_memory", + "unitree-go2-vlm-stream-test": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_vlm_stream_test:unitree_go2_vlm_stream_test", "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", From bf1e3cba6ccd129b3a078ad2f42f927e12daa3d4 Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 23:42:54 -0800 Subject: [PATCH 44/45] fix mypy --- dimos/control/tasks/cartesian_ik_task.py | 5 +++-- dimos/manipulation/planning/utils/kinematics_utils.py | 3 ++- dimos/manipulation/planning/world/drake_world.py | 6 ++++-- dimos/msgs/sensor_msgs/Image.py | 2 +- dimos/perception/object_tracker.py | 4 ++-- dimos/perception/object_tracker_2d.py | 4 ++-- dimos/perception/object_tracker_3d.py | 2 +- dimos/protocol/rpc/pubsubrpc.py | 2 +- dimos/robot/drone/drone_tracking_module.py | 4 ++-- dimos/simulation/mujoco/mujoco_process.py | 3 ++- dimos/stream/video_operators.py | 2 +- 11 files changed, 21 insertions(+), 16 deletions(-) diff --git a/dimos/control/tasks/cartesian_ik_task.py b/dimos/control/tasks/cartesian_ik_task.py index 3f0ad70915..7ff5b21e52 100644 --- a/dimos/control/tasks/cartesian_ik_task.py +++ b/dimos/control/tasks/cartesian_ik_task.py @@ -248,10 +248,11 @@ def _get_current_joints(self, state: CoordinatorState) -> NDArray[np.floating[An if pos is None: # Fallback to last solution if self._last_q_solution is not None: - return self._last_q_solution.copy() + result: NDArray[np.floating[Any]] = self._last_q_solution.copy() + return result return None positions.append(pos) - return np.array(positions) + return np.array(positions, dtype=np.float64) def _solve_ik( self, diff --git a/dimos/manipulation/planning/utils/kinematics_utils.py b/dimos/manipulation/planning/utils/kinematics_utils.py index 80536304a0..c9f3f95a3d 100644 --- a/dimos/manipulation/planning/utils/kinematics_utils.py +++ b/dimos/manipulation/planning/utils/kinematics_utils.py @@ -66,7 +66,8 @@ def damped_pseudoinverse( """ JJT = J @ J.T I = np.eye(JJT.shape[0]) - return J.T @ np.linalg.inv(JJT + damping**2 * I) + result: NDArray[np.float64] = J.T @ np.linalg.inv(JJT + damping**2 * I) + return result def check_singularity( diff --git a/dimos/manipulation/planning/world/drake_world.py b/dimos/manipulation/planning/world/drake_world.py index 532ca6d548..151e21709d 100644 --- a/dimos/manipulation/planning/world/drake_world.py +++ b/dimos/manipulation/planning/world/drake_world.py @@ -480,7 +480,7 @@ def _create_shape(self, obstacle: Obstacle) -> Any: elif obstacle.obstacle_type == ObstacleType.MESH: if not obstacle.mesh_path: raise ValueError("MESH obstacle requires mesh_path") - return Convex(obstacle.mesh_path) + return Convex(Path(obstacle.mesh_path)) else: raise ValueError(f"Unsupported obstacle type: {obstacle.obstacle_type}") @@ -540,6 +540,8 @@ def clear_obstacles(self) -> None: def _set_preview_colors(self) -> None: """Set all preview robot visual geometries to yellow/semi-transparent.""" source_id = self._plant.get_source_id() + if source_id is None: + return preview_color = Rgba(1.0, 0.8, 0.0, 0.4) for robot_data in self._robots.values(): @@ -550,7 +552,7 @@ def _set_preview_colors(self) -> None: for geom_id in self._plant.GetVisualGeometriesForBody(body): props = IllustrationProperties() props.AddProperty("phong", "diffuse", preview_color) - self._scene_graph.AssignRole(source_id, geom_id, props, RoleAssign.kReplace) + self._scene_graph.AssignRole(source_id, geom_id, props, RoleAssign.kReplace) # type: ignore[call-overload] def _remove_preview_collision_roles(self) -> None: """Remove proximity (collision) role from all preview robot geometries.""" diff --git a/dimos/msgs/sensor_msgs/Image.py b/dimos/msgs/sensor_msgs/Image.py index 2a8aa2c017..eb12b64c0a 100644 --- a/dimos/msgs/sensor_msgs/Image.py +++ b/dimos/msgs/sensor_msgs/Image.py @@ -465,7 +465,7 @@ def lcm_encode(self, frame_id: str | None = None) -> bytes: channels = 1 if self.data.ndim == 2 else self.data.shape[2] msg.step = self.width * self.dtype.itemsize * channels - view = memoryview(np.ascontiguousarray(self.data)).cast("B") + view = memoryview(np.ascontiguousarray(self.data)).cast("B") # type: ignore[arg-type] msg.data_length = len(view) msg.data = view diff --git a/dimos/perception/object_tracker.py b/dimos/perception/object_tracker.py index 37e05db86b..da415ac32a 100644 --- a/dimos/perception/object_tracker.py +++ b/dimos/perception/object_tracker.py @@ -558,9 +558,9 @@ def _process_tracking(self) -> None: viz_msg = Image.from_numpy(viz_image) self.tracked_overlay.publish(viz_msg) - def _draw_reid_matches(self, image: NDArray[np.uint8]) -> NDArray[np.uint8]: + def _draw_reid_matches(self, image: NDArray[np.uint8]) -> NDArray[np.uint8]: # type: ignore[type-arg] """Draw REID feature matches on the image.""" - viz_image = image.copy() + viz_image: NDArray[np.uint8] = image.copy() # type: ignore[type-arg] x1, y1, _x2, _y2 = self.last_roi_bbox # type: ignore[misc] diff --git a/dimos/perception/object_tracker_2d.py b/dimos/perception/object_tracker_2d.py index 2616e66933..1264b0e92b 100644 --- a/dimos/perception/object_tracker_2d.py +++ b/dimos/perception/object_tracker_2d.py @@ -292,9 +292,9 @@ def _process_tracking(self) -> None: viz_msg = Image.from_numpy(viz_copy, format=ImageFormat.RGB) self.tracked_overlay.publish(viz_msg) - def _draw_visualization(self, image: NDArray[np.uint8], bbox: list[int]) -> NDArray[np.uint8]: + def _draw_visualization(self, image: NDArray[np.uint8], bbox: list[int]) -> NDArray[np.uint8]: # type: ignore[type-arg] """Draw tracking visualization.""" - viz_image = image.copy() + viz_image: NDArray[np.uint8] = image.copy() # type: ignore[type-arg] x1, y1, x2, y2 = bbox cv2.rectangle(viz_image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(viz_image, "TRACKING", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) diff --git a/dimos/perception/object_tracker_3d.py b/dimos/perception/object_tracker_3d.py index fa6361ac65..f8143dc861 100644 --- a/dimos/perception/object_tracker_3d.py +++ b/dimos/perception/object_tracker_3d.py @@ -283,7 +283,7 @@ def _draw_reid_overlay(self, image: np.ndarray) -> np.ndarray: # type: ignore[t """Draw Re-ID feature matches on visualization.""" import cv2 - viz_image = image.copy() + viz_image: np.ndarray = image.copy() # type: ignore[type-arg] x1, y1, _x2, _y2 = self.last_roi_bbox # type: ignore[attr-defined] # Draw keypoints diff --git a/dimos/protocol/rpc/pubsubrpc.py b/dimos/protocol/rpc/pubsubrpc.py index 394f1afc15..3b77227218 100644 --- a/dimos/protocol/rpc/pubsubrpc.py +++ b/dimos/protocol/rpc/pubsubrpc.py @@ -81,7 +81,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def __getstate__(self) -> dict[str, Any]: state: dict[str, Any] if hasattr(super(), "__getstate__"): - state = super().__getstate__() # type: ignore[assignment] + state = super().__getstate__() # type: ignore[assignment, misc] else: state = self.__dict__.copy() diff --git a/dimos/robot/drone/drone_tracking_module.py b/dimos/robot/drone/drone_tracking_module.py index c21cee9759..e1b633a05b 100644 --- a/dimos/robot/drone/drone_tracking_module.py +++ b/dimos/robot/drone/drone_tracking_module.py @@ -312,7 +312,7 @@ def _draw_tracking_overlay( frame: NDArray[np.uint8], bbox: tuple[int, int, int, int], center: tuple[int, int], - ) -> NDArray[np.uint8]: + ) -> NDArray[np.uint8]: # type: ignore[type-arg] """Draw tracking visualization overlay. Args: @@ -323,7 +323,7 @@ def _draw_tracking_overlay( Returns: Frame with overlay drawn """ - overlay = frame.copy() + overlay: NDArray[np.uint8] = frame.copy() # type: ignore[type-arg] x, y, w, h = bbox # Draw tracking box (green) diff --git a/dimos/simulation/mujoco/mujoco_process.py b/dimos/simulation/mujoco/mujoco_process.py index f3e6eba279..8529de976b 100755 --- a/dimos/simulation/mujoco/mujoco_process.py +++ b/dimos/simulation/mujoco/mujoco_process.py @@ -63,7 +63,8 @@ def get_command(self) -> NDArray[Any]: self._command[0] = linear[0] # forward/backward self._command[1] = linear[1] # left/right self._command[2] = angular[2] # rotation - return self._command.copy() + result: NDArray[Any] = self._command.copy() + return result def stop(self) -> None: """Stop method to satisfy InputController protocol.""" diff --git a/dimos/stream/video_operators.py b/dimos/stream/video_operators.py index 548bba7598..a94b6fa3a1 100644 --- a/dimos/stream/video_operators.py +++ b/dimos/stream/video_operators.py @@ -231,7 +231,7 @@ def _encode_image(image: np.ndarray) -> tuple[str, tuple[int, int]]: # type: ig _, buffer = cv2.imencode(".jpg", image) if buffer is None: raise ValueError("Failed to encode image") - base64_image = base64.b64encode(buffer).decode("utf-8") + base64_image = base64.b64encode(buffer.tobytes()).decode("utf-8") return base64_image, (width, height) except Exception as e: raise e From 694d1b3e57ca4f958bd7376ac9d46ad3b7661bfb Mon Sep 17 00:00:00 2001 From: Jeff Hykin Date: Wed, 11 Feb 2026 23:51:55 -0800 Subject: [PATCH 45/45] - --- dimos/manipulation/planning/world/drake_world.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dimos/manipulation/planning/world/drake_world.py b/dimos/manipulation/planning/world/drake_world.py index 151e21709d..b8c5197d94 100644 --- a/dimos/manipulation/planning/world/drake_world.py +++ b/dimos/manipulation/planning/world/drake_world.py @@ -539,9 +539,7 @@ def clear_obstacles(self) -> None: def _set_preview_colors(self) -> None: """Set all preview robot visual geometries to yellow/semi-transparent.""" - source_id = self._plant.get_source_id() - if source_id is None: - return + source_id: Any = self._plant.get_source_id() preview_color = Rgba(1.0, 0.8, 0.0, 0.4) for robot_data in self._robots.values(): @@ -556,7 +554,7 @@ def _set_preview_colors(self) -> None: def _remove_preview_collision_roles(self) -> None: """Remove proximity (collision) role from all preview robot geometries.""" - source_id = self._plant.get_source_id() + source_id: Any = self._plant.get_source_id() # SourceId for robot_data in self._robots.values(): if robot_data.preview_model_instance is None: