diff --git a/dimos/core/global_config.py b/dimos/core/global_config.py index 3660a957dc..15c37186ac 100644 --- a/dimos/core/global_config.py +++ b/dimos/core/global_config.py @@ -19,7 +19,7 @@ from dimos.mapping.occupancy.path_map import NavigationStrategy -ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "foxglove", "none"] +ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "rerun-connect", "foxglove", "none"] def _get_all_numbers(s: str) -> list[float]: diff --git a/dimos/navigation/replanning_a_star/module.py b/dimos/navigation/replanning_a_star/module.py index 60dfad73ae..4dad9a2843 100644 --- a/dimos/navigation/replanning_a_star/module.py +++ b/dimos/navigation/replanning_a_star/module.py @@ -21,7 +21,7 @@ from dimos.core.global_config import GlobalConfig, global_config from dimos.core.module import Module from dimos.core.stream import In, Out -from dimos.msgs.geometry_msgs import PoseStamped, Twist +from dimos.msgs.geometry_msgs import PointStamped, PoseStamped, Twist from dimos.msgs.nav_msgs import OccupancyGrid, Path from dimos.navigation.base import NavigationInterface, NavigationState from dimos.navigation.replanning_a_star.global_planner import GlobalPlanner @@ -31,6 +31,7 @@ class ReplanningAStarPlanner(Module, NavigationInterface): odom: In[PoseStamped] # TODO: Use TF. global_costmap: In[OccupancyGrid] goal_request: In[PoseStamped] + clicked_point: In[PointStamped] target: In[PoseStamped] goal_reached: Out[Bool] @@ -60,6 +61,14 @@ def start(self) -> None: ) self._disposables.add(Disposable(self.target.subscribe(self._planner.handle_goal_request))) + self._disposables.add( + Disposable( + self.clicked_point.subscribe( + lambda pt: self._planner.handle_goal_request(pt.to_pose_stamped()) + ) + ) + ) + self._disposables.add(self._planner.path.subscribe(self.path.publish)) self._disposables.add(self._planner.cmd_vel.subscribe(self.cmd_vel.publish)) diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index 6026572388..2bf585273b 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -76,6 +76,7 @@ "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-click-nav": "dimos.robot.unitree.go2.blueprints.smart.unitree_go2_click_nav:unitree_go2_click_nav", "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", diff --git a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py index 2537e86632..92e48fbb95 100644 --- a/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py +++ b/dimos/robot/unitree/go2/blueprints/basic/unitree_go2_basic.py @@ -105,6 +105,12 @@ def _static_base_link(rr: Any) -> list[Any]: from dimos.visualization.rerun.bridge import rerun_bridge with_vis = autoconnect(_transports_base, rerun_bridge(**rerun_config)) + case "rerun-connect": + from dimos.visualization.rerun.bridge import rerun_bridge + + with_vis = autoconnect( + _transports_base, rerun_bridge(viewer_mode="connect", **rerun_config) + ) case "rerun-web": from dimos.visualization.rerun.bridge import rerun_bridge diff --git a/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_click_nav.py b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_click_nav.py new file mode 100644 index 0000000000..fb083a289c --- /dev/null +++ b/dimos/robot/unitree/go2/blueprints/smart/unitree_go2_click_nav.py @@ -0,0 +1,39 @@ +#!/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 LCMTransport +from dimos.mapping.costmapper import cost_mapper +from dimos.mapping.voxels import voxel_mapper +from dimos.msgs.geometry_msgs import PointStamped +from dimos.navigation.replanning_a_star.module import replanning_a_star_planner +from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import unitree_go2_basic + +unitree_go2_click_nav = ( + autoconnect( + unitree_go2_basic, + voxel_mapper(voxel_size=0.1), + cost_mapper(), + replanning_a_star_planner(), + ) + .transports( + { + ("clicked_point", PointStamped): LCMTransport("/clicked_point", PointStamped), + } + ) + .global_config(n_workers=6, robot_model="unitree_go2") +) + +__all__ = ["unitree_go2_click_nav"] diff --git a/dimos/visualization/rerun/bridge.py b/dimos/visualization/rerun/bridge.py index af91f1b8b8..47bce27dcf 100644 --- a/dimos/visualization/rerun/bridge.py +++ b/dimos/visualization/rerun/bridge.py @@ -123,7 +123,7 @@ class RerunConvertible(Protocol): def to_rerun(self) -> RerunData: ... -ViewerMode = Literal["native", "web", "none"] +ViewerMode = Literal["native", "web", "connect", "none"] def _default_blueprint() -> Blueprint: @@ -158,6 +158,7 @@ class Config(ModuleConfig): entity_prefix: str = "world" topic_to_entity: Callable[[Any], str] | None = None viewer_mode: ViewerMode = "native" + connect_url: str = "rerun+http://127.0.0.1:9877/proxy" memory_limit: str = "25%" # Blueprint factory: callable(rrb) -> Blueprint for viewer layout configuration @@ -265,6 +266,8 @@ def start(self) -> None: elif self.config.viewer_mode == "web": server_uri = rr.serve_grpc() rr.serve_web_viewer(connect_to=server_uri, open_browser=False) + elif self.config.viewer_mode == "connect": + rr.connect_grpc(self.config.connect_url) # "none" - just init, no viewer (connect externally) if self.config.blueprint: