Repository navigation
Replies: 8 comments 10 replies
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
GoalEvery robot is a connection module in its own process. The control coordinator talks to all of them with one message type, decides which controller drives which part of which robot, and owns no hardware. flowchart LR
subgraph CC["ControlCoordinator"]
T["tasks: trajectory, velocity, teleop, policies"] --> A["arbitration: one owner per resource"]
end
IN["nav, planners"] <-- "Twist, JointState (standard)" --> CC
TN["teleop"] -- "Twist, JointState (standard)" --> CC
M["manipulation, viz, recorders"] <-- "joint_state · JointState (standard)" --> CC
A -- "control_command · ControlValues" --> X["XArmConnectionModule"]
A -- "control_command" --> G["G1ConnectionModule"]
X -- "control_state · ControlValues" --> CC
G -- "control_state" --> CC
Standard messages stay on the software side. Only the link between the coordinator and the hardware uses 1. The wire:
|
| Field | Meaning |
|---|---|
source |
the robot's name; the first segment of all its keys |
resources |
its parts: name, kind (joint, base, sensor), what each reports, what each accepts, units |
limits |
per command key; a command past a limit is refused |
state_rate_hz |
how often it reports |
deadman_timeout_s |
how long without a command before it halts itself |
- Sensors in a description are readings the robot reports in its own control packet, alongside the joints( example G1's IMU in LowState)
- Presets build a description in one call:
manipulator_description,pd_joint_description,twist_base_description. Given the robot'sRobotModel, the arm and body presets read joint names, and limits from it. `ControlDescription.
3. A standard way to define a robot ConnectionModule
One small base class. A driver is one file:
class XArmConnectionModule(ConnectionModule):
def describe(self): # from self.config, no hardware
return manipulator_description("arm", JOINTS, model=self.config.model)
def connect(self): ... # open the SDK
def read_state(self) -> dict: ... # called at the state rate
def write(self, values): ... # validated values; switch SDK mode here if needed
def halt(self): ... # stop moving; idempotentThe base class does the rest:
- On start:
describe()→validate_description(fails loudly) →connect()→ publish state at the state rate. - On each command frame: keep this module's keys, run
validate_command), thenwrite(). Limits are checked at the connection, so every path to hardware gets the same checks, whoever sent the command. - Deadman: once commands have started, if none arrives within
deadman_timeout_s,halt(). write()andhalt()never run at the same time. Ifwrite()raises:halt(), and the error shows instatus().- RPCs:
describe_control()(the description, from config) andstatus(). - It works alone: no coordinator is needed to start, publish state, or test.
4. How does Control Coordinator know how to control a Robot
-
Finding connections. The coordinator implements
on_system_modules(modules), the hookMcpServeruses to find skills. The coordinator asks every module that has adescribe_controlRPC for its description. -
Tick loop : read state → compute tasks → arbitrate per resource → write. The write goes to
control_command, as a complete frame per connection. -
Standard messages on the software side: the coordinator keeps its
Twist/JointStateinputs and publishesjoint_state. Tasks get joints and sensor readings in one per-tick snapshot (JointStateSnapshot,SensorStateSnapshot). -
Estop : stop publishing and call every connection's
halt(). If the coordinator itself dies, every connection's deadman halts it.
5. "Why we removed standardized abstraction, and we are rebuilding it internally only in this module?"
Standard messages describe what kind of command something is (a velocity, a joint target). They don't say which motor it is for or who controls that motor right now, and arbitration needs both.
With a typed port per command type on each robot:
- One part, two kinds of command: a planner sends an arm positions while teleop sends it velocities. That's two ports into the same joints, and something has to know that to pick a winner.
- Moves that must happen together: an R1 Pro's base, torso and arms in one step. Separate topics arrive separately, and stopping one doesn't stop the others.
- Accidental second senders: autoconnect wires every
cmd_vel: Out[Twist]to a robot'scmd_vel: In[Twist]without complaint, bypassing arbitration. - Safety checks repeated per message type in every driver.
ros2_control made the same split: hardware exposes named values (joint1/position), a manager decides who owns them, and standard messages go into controllers.
For full context check this comment:
#3390 (comment)
can you give examples of how this message looks like? let's say controlling twist of go2 or robot arm poses plz no AI generated text for clarity for readers |
main issue, custom message, rewriting all driversI believe you are leaking an abstraction that belongs internally into a controlcoordinator, requiring a rewrite of all drivers, and causing incompatibility with any other system, to add a custom message that adds no new information, and removes individual topics, types etc that other systems understand. you are ignoring topics and frames, using a single one in dimos, then re-implementing topics and frames in your new custom message I would say you can do all above that you'd like but keep those messages internal to controlcoordinator and translate into standards externally your answers/questionsfrom discord:
in a rare case something needs a custom control message, we can add it, but in most cases these are standard messages. custom messages don't justify removing all standard messages - but adding new custom messages All your examples above carry the same (or less!) information then standard messages so I don't see why you are inventing a new type?
Yes but that's exactly why controlcoordinator exists? I send twist to controlcoordinator, and it controls driver modules with whatever language they understand, those SDKs and drivers don't have to understand global complex motion or msg type conversions
Idea is not to do this at all. you keep your abstractions internal to controlcoordinator, and drivers do driver things. for example go2 understands twist, arm understands jointstates, there is never need internally for go2 driver to understand global coordination happening, the same way that go2 twist doesn't understand navigation or trajectory control but is able to navigate.
they do describe motor/entity by frame_id or topic (you actually invented new topic/frame abstraction in your message examples, just use topics or frame ids?)
yeah, controlcoordinator, above whole section is supposed to be answering my question on "why new topics" but is justifying existance of controlcoordinator, why? we agree there should be a controlcoordinator.
but you have external modules, so your topics are still separate, this doesn't help the problem but imo also it's not a problem, our pubsub is low latency, your proposal uses separate topics, my proposal uses separate topics, and that's ok
depends on the safety layer and driver responsibilities, go2 twist should not do colision checks for example, that's a higher order operation, but it can make sure to stop if twist input stops, which is a low level driver responsibility and will stay there, low level driver should never blindly respect twist sent 5 minutes ago, even if controlcoordinator crashed. so there are different layers of safety on different layers of abstractions general on topics/frames etcit's possible that something is lacking and inconvinient here in terms of existing topics frames etc (we'll nail it down now) but then we should fix topics, frames etc instead of reimplement |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
General Direction
Instead of owning the hardware connection through adapter, align with the connection module design, which provides a single connection for a dedicated hardware, and control coordinator uses a unified high-performance transport to send high-frequency control command (like shm).
Pain Point/Wishlist
robot_idconcept from higher level (manipulation module)this makes manipulation module semantic much cleaner.
jointsemantic to more genericcommand_interfaceThis is useful consider that you have a robot arm/simulation which only provides high-level interface for control (for example eef-pose). I think we already 'hacked' the interface for go2 control.
global_config. the current control coordinator owning hardware design is hacking the dimos core concept and makes config passing hard. Once the refactor is done we should be able to pass in hardware config through module cli args easily.All reactions