Feature: Control Coordinator support for mobile base#1277
Conversation
Greptile SummaryExtends the
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant KB as KeyboardTeleop
participant LCM as LCM /cmd_vel
participant CC as ControlCoordinator
participant VJ as Virtual Joints
participant TL as TickLoop
participant CTB as ConnectedTwistBase
participant Adapter as TwistBaseAdapter
KB->>LCM: Twist(linear, angular)
LCM->>CC: twist_command subscription
CC->>CC: _on_twist_command()
Note over CC: Map Twist fields to virtual joints<br/>base_vx ← linear.x<br/>base_vy ← linear.y<br/>base_wz ← angular.z
CC->>CC: _on_joint_command(JointState)
CC->>VJ: Route to velocity task
loop Every tick (100Hz)
TL->>CTB: read_state()
CTB->>Adapter: read_velocities() + read_odometry()
Adapter-->>CTB: velocities, odometry
CTB-->>TL: {joint: JointState}
TL->>TL: Arbitrate (per-joint, priority)
TL->>CTB: write_command(velocities, mode)
CTB->>Adapter: write_velocities([vx, vy, wz])
end
Last reviewed commit: d62d44e |
dimos/control/coordinator.py
Outdated
| component: HardwareComponent, | ||
| ) -> bool: | ||
| """Register a hardware adapter with the coordinator.""" | ||
| from dimos.hardware.drive_trains.spec import TwistBaseAdapter as TwistBaseAdapterProto |
There was a problem hiding this comment.
Can't this be at the top?
| self._current_mode: ControlMode | None = None | ||
|
|
||
| @property | ||
| def adapter(self) -> TwistBaseAdapter: # type: ignore[override] |
There was a problem hiding this comment.
Please fix type: ignore
There was a problem hiding this comment.
There's still an ignore here.
| def connect(self) -> bool: | ||
| """Connect to FlowBase controller via Portal RPC.""" | ||
| try: | ||
| import portal # type: ignore[import-not-found] |
There was a problem hiding this comment.
What is this? It's not in pyproject.toml.
There was a problem hiding this comment.
It's the RPC protocol that the flowbase hardware uses. I need to send messages over it to be able to communicate with the robot.
This is locally installed on the hardware so probable why I never needed to add it to the toml.
These sort of random libraries and packages will be specific to the hardware we work with, Does it make sense still add them to the toml? The risk being the toml keeps growing as we keep adding more hardware and their dependency. Is there another option?
@paul-nechifor
There was a problem hiding this comment.
Sorry, I get so much GitHub spam that I regularly miss updates from GitHub.
I think you should add it to a group in Oh, it is in pyproject.toml otherwise people won't know what portal is and how to install it.pyproject.toml now. 😅
beea8ec to
8495219
Compare
| if hw is None: | ||
| logger.warning(f"Hardware '{hardware_id}' not found for gripper command") | ||
| return False | ||
| if isinstance(hw, ConnectedTwistBase): |
There was a problem hiding this comment.
ideally want to automatically check things like this with protocols. So as we scale up to integrate with many robots that don't have an end effector we can do checks like this more systematically
There was a problem hiding this comment.
Agreed.
The plan is to categories all possible hardware in 4-5 different buckets, for example Whole Body Controllers, Twist Base, Manipulators, End effectors etc.
A more sophisticated system will be introduced to perform these checks as you suggested.
Creating a issue ticket to track this.
| if hw.component.hardware_type != HardwareType.BASE: | ||
| continue | ||
| for joint_name in hw.joint_names: | ||
| # Extract suffix (e.g., "base_vx" → "vx") |
There was a problem hiding this comment.
Youre assuming here that joint names MUST have vy and vy and vz etc. Do you know this for sure?
There was a problem hiding this comment.
The ControlCoordinator is hardware-agnostic, it only operates on named joints, whether the hardware is a 6-DOF arm, 7-DOF arm, or 29-DOF humanoid.
Mobile bases are fundamentally different: they don't accept joint state commands, they accept Twist (cmd_vel). To keep the coordinator architecture uniform, we model a mobile base as a virtual 3-DOF robot with abstract joints suffixed _vx, _vy, _wz. The coordinator ticks these joints like any other, and ConnectedTwistBase unpacks them back into a Twist before dispatching to the hardware.
Any robot accepting Twist for e.g FlowBase, quadrupeds, the G1 works as long as the joints follow this convention.
There was a problem hiding this comment.
This makes sense just a bit hacky. Would propose a quick fix in future PR but low priority since we only have one type of base integrated.
|
Weird module behavior: python -m dimos.control.examples.twist_base_keyboard_teleop
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
◟ Initializing dimos local cluster with 2 workers/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
◜ Initializing dimos local cluster with 2 workerspygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
Hello from the pygame community. https://www.pygame.org/contribute.html
Initialized dimos local cluster with 2 workers, memory limit: auto
2026-02-21T10:47:28.433204Z [info ] Deploying module. [dimos/core/__init__.py] module=ControlCoordinator
2026-02-21T10:47:28.455967Z [info ] ControlCoordinator initialized at 100.0Hz [dimos/control/coordinator.py]
2026-02-21T10:47:28.460761Z [info ] Deployed module. [dimos/core/__init__.py] module=ControlCoordinator worker_id=1
2026-02-21T10:47:28.480172Z [info ] Transport [dimos/core/blueprints.py] module=ControlCoordinator name=joint_state original_name=joint_state topic=/coordinator/joint_state#sensor_msgs.JointState transport=LCMTransport type=dimos.msgs.sensor_msgs.JointState.JointState
2026-02-21T10:47:28.480737Z [info ] Transport [dimos/core/blueprints.py] module=ControlCoordinator name=joint_command original_name=joint_command topic=/joint_command#sensor_msgs.JointState transport=LCMTransport type=dimos.msgs.sensor_msgs.JointState.JointState
2026-02-21T10:47:28.481238Z [info ] Transport [dimos/core/blueprints.py] module=ControlCoordinator name=cartesian_command original_name=cartesian_command topic=/cartesian_command#geometry_msgs.PoseStamped transport=LCMTransport type=dimos.msgs.geometry_msgs.PoseStamped.PoseStamped
2026-02-21T10:47:28.481740Z [info ] Transport [dimos/core/blueprints.py] module=ControlCoordinator name=twist_command original_name=twist_command topic=/cmd_vel#geometry_msgs.Twist transport=LCMTransport type=dimos.msgs.geometry_msgs.Twist.Twist
2026-02-21T10:47:28.482181Z [info ] Transport [dimos/core/blueprints.py] module=ControlCoordinator name=buttons original_name=buttons topic=/buttons#std_msgs.UInt32 transport=LCMTransport type=dimos.teleop.quest.quest_types.Buttons
2026-02-21T10:47:28.485935Z [info ] Added hardware base with joints: ['base_vx', 'base_vy', 'base_wz'] [dimos/control/coordinator.py]
2026-02-21T10:47:30.429536Z [info ] JointVelocityTask vel_base initialized for joints: ['base_vx', 'base_vy', 'base_wz'] [dimos/control/tasks/velocity_task.py]
2026-02-21T10:47:30.429852Z [info ] Added task vel_base [dimos/control/coordinator.py]
2026-02-21T10:47:30.430934Z [info ] TickLoop started at 100.0Hz [dimos/control/tick_loop.py]
2026-02-21T10:47:30.448155Z [info ] Subscribed to joint_command for streaming tasks [dimos/control/coordinator.py]
2026-02-21T10:47:30.462401Z [info ] Subscribed to twist_command for twist base control [dimos/control/coordinator.py]
2026-02-21T10:47:30.462722Z [info ] ControlCoordinator started at 100.0Hz [dimos/control/coordinator.py]
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/distributed/node.py
:187: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 43257 instead
warnings.warn(
◟ Initializing dimos local cluster with 2 workers/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
◜ Initializing dimos local cluster with 2 workerspygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
Initialized dimos local cluster with 2 workers, memory limit: auto
2026-02-21T10:47:33.676646Z [info ] Deploying module. [dimos/core/__init__.py] module=KeyboardTeleop
2026-02-21T10:47:33.708969Z [info ] Deployed module. [dimos/core/__init__.py] module=KeyboardTeleop worker_id=1
2026-02-21T10:47:33.724523Z [info ] Transport [dimos/core/blueprints.py] module=KeyboardTeleop name=cmd_vel original_name=cmd_vel topic=/cmd_vel#geometry_msgs.Twist transport=LCMTransport type=dimos.msgs.geometry_msgs.Twist.Twist
Starting mock twist base coordinator + keyboard teleop...
Coordinator tick loop: 100Hz
Keyboard teleop: 50Hz on /cmd_vel
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/distributed/node.py
:187: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 46219 instead
warnings.warn(
◝ Initializing dimos local cluster with 2 workers/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
Initialized dimos local cluster with 2 workers, memory limit: auto
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/distributed/node.py
:187: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 43401 instead
warnings.warn(
◟ Initializing dimos local cluster with 2 workers/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
/home/stash/dimensional/dimos/.venv/lib/python3.12/site-packages/pygame/pkgdata.py:25: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import resource_stream, resource_exists
pygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
◜ Initializing dimos local cluster with 2 workerspygame 2.6.1 (SDL 2.28.4, Python 3.12.12)
Hello from the pygame community. https://www.pygame.org/contribute.html
Initialized dimos local cluster with 2 workers, memory limit: auto |
|
And then more problematic one of your 'how to test' commands doesn't run. echo cmd vel doesnt exist (dimos) stash@daneelsbrain:~/dimensional/dimos$ python -m dimos.control.examples.echo_cmd_vel
/home/stash/dimensional/dimos/.venv/bin/python: No module named dimos.control.examples.echo_cmd_vel
(dimos) stash@daneelsbrain:~/dimensional/dimos$ |
8495219 to
ddf29ef
Compare
|
|
||
| Usage: | ||
| python -m dimos.control.examples.echo_cmd_vel | ||
| python -m dimos.control.examples.echo_cmd_vel --topic /my_cmd_vel |
There was a problem hiding this comment.
We already have this:
uv run dimos topic echo /cmd_velThere was a problem hiding this comment.
Understood. Will deprecate in next pass.
There was a problem hiding this comment.
Let me update the PR description and deprecate file now.
ddf29ef to
148f9fb
Compare
| if self._joint_command_unsub: | ||
| self._joint_command_unsub() | ||
| self._joint_command_unsub = None | ||
| if self._cartesian_command_unsub: | ||
| self._cartesian_command_unsub() | ||
| self._cartesian_command_unsub = None | ||
| if self._twist_command_unsub: | ||
| self._twist_command_unsub() | ||
| self._twist_command_unsub = None | ||
| if self._buttons_unsub: | ||
| self._buttons_unsub() | ||
| self._buttons_unsub = None |
There was a problem hiding this comment.
Rule of three: if you have three duplicate things, it's best to generalize. You can add:
self._disposables = CompositeDisposable()
in __init__ and do self._disposables.dispose() here.
| - ConnectedHardware: Wraps ManipulatorAdapter for joint-controlled arms | ||
| - ConnectedTwistBase: Wraps TwistBaseAdapter for velocity-commanded platforms |
There was a problem hiding this comment.
Could this information be specified in the actual names? I.e. should they be renamed to JointControlledHardware and VelocityControlledHardware?
There was a problem hiding this comment.
Good point.
I will need to make a few more of these interfaces. I am waiting to re-categorize based on that.
But your suggestion might be the most ideal scenario.
Problem
The ControlCoordinator only supports joint-level control (manipulator arms). Mobile bases, quadrupeds, and drones take Twist (velocity) commands, so there's no way to control them through the coordinator — blocking mobile manipulation.
Solution
Virtual joints map velocity DOFs into the coordinator's existing joint-centric model (
base_vx,base_vy,base_wz) A newtwist_command: In[Twist]port converts Twist → virtual joint velocities, feeding the existing routing pipeline.New
TwistBaseAdapterprotocol (10 methods, SI units) provides a lightweight hardware abstraction.ConnectedTwistBaseinheritsConnectedHardwareto keep the tick loop uniform. IncludesMockTwistBaseAdapterfor testing andFlowBaseAdapterfor real holonomic base hardware via Portal RPC.Breaking Changes
None
How to Test
python -m dimos.control.examples.twist_base_keyboard_teleop/cmd_velin another terminal:uv run dimos topic echo /cmd_velcloses DIM-547
closes DIM-546