Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 127 additions & 1 deletion docs/usage/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ print(CameraModule.io())
└┬─────────────┘
├─ color_image: Image
├─ camera_info: CameraInfo
├─ tf: TFMessage
├─ RPC build() -> None
├─ RPC get_skills() -> list
├─ RPC peek_stream(stream_name: str, timeout: float) -> Any
├─ RPC set_module_ref(name: str, module_ref: RPCClient) -> None
├─ RPC set_transport(stream_name: str, transport: Transport) -> bool
├─ RPC start() -> None
Expand Down Expand Up @@ -139,7 +141,7 @@ from dimos.hardware.sensors.camera.module import CameraModule
camera = CameraModule()
detector = Detection2DModule()

detector.image.connect(camera.color_image)
detector.color_image.connect(camera.color_image)

camera.start()
detector.start()
Expand Down Expand Up @@ -472,3 +474,127 @@ to_svg(agentic, "assets/go2_agentic.svg")
![output](assets/go2_agentic.svg)

To see more information on how to use Blueprints, see [Blueprints](/docs/usage/blueprints.md).

## Low level manual plumbing

Most of the time this is a bad idea, but you can peak behind blueprints, autoconnect etc, and deal with pubsub directly at lower and lower layers of abstraction

### Connecting via transports

`.connect` wires two modules living in the same process. Alternatively, you can assign a **transport** a typed pub/sub channel such as LCM or Zenoh. The modules never hold a reference to each other, so they can run as separate scripts.

`camera_script.py`:

```python skip
import time
from dimos.core.transport import LCMTransport
from dimos.hardware.sensors.camera.module import CameraModule
from dimos.msgs.sensor_msgs.Image import Image

camera = CameraModule()
camera.color_image.transport = LCMTransport("/camera/rgb", Image)

camera.start()
time.sleep(10)
camera.stop()
```

`detector_script.py`:

```python skip
import time
from dimos.core.transport import LCMTransport
from dimos.msgs.sensor_msgs.Image import Image
from dimos.perception.detection.module2D import Detection2DModule

detector = Detection2DModule()
detector.color_image.transport = LCMTransport("/camera/rgb", Image)

detector.start()
detector.detections.subscribe(print)
time.sleep(10)
detector.stop()
```

Run each in its own terminal and detections start printing as soon as both are up. The channel name and message type must match on both sides `LCMTransport("/camera/rgb", Image)` maps to the typed channel `/camera/rgb#sensor_msgs.Image`, which you can watch with `dimos spy` or `dimos topic echo /camera/rgb`.

Available transports live in `dimos.core.transport` (`LCMTransport`, `ZenohTransport`, `SHMTransport`, ...); see [Transports](/docs/usage/transports/index.md) for choosing between them.

### Raw transports (no modules)

A transport works on its own — init one and send/receive from a plain script, no module or stream declarations needed:

```python skip
from dimos.core.transport import LCMTransport
from dimos.msgs.std_msgs.String import String

chat = LCMTransport("/chat", String)

unsubscribe = chat.subscribe(print) # receive
chat.publish(String(data="hello")) # send

msg = chat.get_next() # or block for the next message
chat.stop()
```

Any other script (or module stream) on the same channel sees the traffic. This is handy for quick probes and debug scripts. For a robot system, prefer modules so the streams show up in blueprints and introspection.

### Dynamic streams

Our system is very flexible, but this is dangerous as you lose essentially all support above pubsub. Autoconnect, blueprints, configuration support (dimos --transport=...) etc

Streams don't have to be class annotations, a module can grow inputs and outputs at runtime, and they can even be attached externally.
Construct the stream and assign it as an attribute.

module has `inputs`/`outputs`/`io()` attributes and is a ble to discover streams attached.

```python ansi=false
from dimos.core.module import Module
from dimos.core.stream import In, Out
from dimos.core.transport import LCMTransport
from dimos.msgs.std_msgs.String import String

class Dyn(Module):
def start(self):
# module can add a random input
self.echo = In(String, "echo", m)
print("Externally attached output:", self.words)
Comment on lines +557 to +561

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Dynamic example bypasses module startup

Readers copying this pattern into a module with main() or handle_<input> methods will override ModuleBase.start() without invoking it, so the framework never initializes the main lifecycle or binds input handlers and incoming messages remain unhandled.


m = Dyn()

m.words = Out(String, "words", m)
m.start()


m.words.transport = LCMTransport("/words", String)
m.echo.transport = LCMTransport("/words", String)


print("\nInputs:")
print(m.inputs)
print(m.inputs['echo'])
print("\nOutputs:")
print(m.outputs)
print(m.outputs['words'])

print("\Send/Receive Test:")

# we can subscribe to topics from anywhere also
m.echo.subscribe(print)
m.words.publish(String(data="loopback over LCM"))

```

```results
Externally attached output: Out words[String] @ Dyn

Inputs:
{'echo': <dimos.core.stream.In object at 0x7f03b4bb32c0>}
In echo[String] @ Dyn via LCMTransport(/words#std_msgs.String)

Outputs:
{'words': <dimos.core.stream.Out object at 0x7f03b4b7f860>}
Out words[String] @ Dyn via LCMTransport(/words#std_msgs.String)
\Send/Receive Test:
<dimos.msgs.std_msgs.String.String object at 0x7f03eb2d10d0>
```
Loading