Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b4fce2b
Transform buffer
aclauer Jun 30, 2026
6a5564e
Use base to sensor transform
aclauer Jul 2, 2026
1adf95b
Show robot and sensor axis
aclauer Jul 2, 2026
7ca5c9b
Merge remote-tracking branch 'origin/main' into andrew/feat/rust-tran…
aclauer Jul 2, 2026
65b14d0
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 3, 2026
2d956a9
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 7, 2026
ee6187a
Plan from below base link and visual changes
aclauer Jul 8, 2026
ae3aa87
Save in path
aclauer Jul 8, 2026
c4fe131
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 8, 2026
1568816
Remove body
aclauer Jul 8, 2026
ace0c02
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 8, 2026
f42fa10
Refactor tolerance
aclauer Jul 9, 2026
bba4186
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 9, 2026
92d0fc8
Rust tf example
aclauer Jul 9, 2026
67f9662
Clean up
aclauer Jul 9, 2026
13ab180
Merge branch 'main' into andrew/feat/rust-transform-get
jeff-hykin Jul 13, 2026
03965d1
BFS fix
aclauer Jul 14, 2026
c4ce81c
Remove unused code
aclauer Jul 14, 2026
5a4dd3e
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 14, 2026
1e37298
Merge remote-tracking branch 'origin/main' into andrew/feat/rust-tran…
leshy Jul 23, 2026
b3b5438
Tf publish
aclauer Jul 23, 2026
ca14135
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 23, 2026
0de58f4
Switch to vecdeque
aclauer Jul 23, 2026
274b405
Remove odom body frame and use tf
aclauer Jul 23, 2026
f0bca99
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 23, 2026
865bd90
Cleaning
aclauer Jul 23, 2026
2fe01d0
Merge branch 'andrew/feat/rust-transform-get' of github.com:dimension…
aclauer Jul 23, 2026
3c58e40
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 23, 2026
c5c4f75
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 24, 2026
49d69cd
Fix tf
aclauer Jul 24, 2026
9c6acec
Merge branch 'andrew/feat/rust-transform-get' of github.com:dimension…
aclauer Jul 24, 2026
6f31de4
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 30, 2026
a69ad91
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 30, 2026
ca0d73b
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Jul 31, 2026
aae072f
IO ports
aclauer Jul 31, 2026
15e5256
Rewrite the semantics a bit
aclauer Jul 31, 2026
09f998d
Explicitly subscribe to tf io
aclauer Jul 31, 2026
7d1f11c
Enforce one to one topic naming
aclauer Jul 31, 2026
5ed0648
Bound look up time by buffer size
aclauer Jul 31, 2026
8947d70
Support within, at, tolerance
aclauer Jul 31, 2026
120ff33
Warn on unresolved transforms
aclauer Jul 31, 2026
ae98f4b
Clean up
aclauer Jul 31, 2026
72b8ebc
Toggle base link from go2
aclauer Jul 31, 2026
a35ae8e
Remove bad test
aclauer Jul 31, 2026
7303c34
Merge branch 'main' into andrew/feat/rust-transform-get
aclauer Aug 1, 2026
7a84cf6
Merge branch 'main' into andrew/feat/rust-transform-get
leshy Aug 3, 2026
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
26 changes: 15 additions & 11 deletions dimos/core/native_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""NativeModule: blueprint-integrated wrapper for native (C/C++) executables.
"""NativeModule: blueprint-integrated wrapper for native executables.

A NativeModule is a thin Python Module subclass that declares In/Out ports
A NativeModule is a thin Python Module subclass that declares In/Out/IO ports
for blueprint wiring but delegates all real work to a managed subprocess.
The native process receives its LCM topic names via CLI args and does
pub/sub directly on the LCM multicast bus.
The native process receives its topic names via CLI args, or as a JSON line on
stdin when ``stdin_config`` is set, and does pub/sub on them directly.

Example usage::

Expand Down Expand Up @@ -178,15 +178,19 @@ class NativeModule(Module):
"""
Module that wraps a native executable as a managed subprocess.

Subclass this, declare In/Out ports, and annotate ``config`` with a
Subclass this, declare In/Out/IO ports, and annotate ``config`` with a
:class:`NativeModuleConfig` subclass pointing at the executable.

On ``start()``, the binary is launched with CLI args::

<executable> --<port_name> <lcm_topic_string> ... <extra_args>
<executable> --<port_name> <topic> ... --<config_field> <value> ... <extra_args>

The native process should parse these args and pub/sub on the given
LCM topics directly. On ``stop()``, the process receives SIGTERM.
Each topic is the wire channel for that port on the transport named by the
``DIMOS_TRANSPORT`` env var. With ``stdin_config``, those same topics plus
the config and any publisher QoS also arrive as one JSON line on stdin.

The native process should parse whichever it uses and pub/sub on the given
topics directly. On ``stop()``, the process receives SIGTERM.
"""

config: NativeModuleConfig
Expand Down Expand Up @@ -471,7 +475,7 @@ def _maybe_build(self) -> None:

def _collect_topics(self) -> dict[str, str]:
topics: dict[str, str] = {}
for name in list(self.inputs) + list(self.outputs):
for name in list(self.inputs) + list(self.outputs) + list(self.ios):
stream = getattr(self, name, None)
if stream is None:
continue
Expand All @@ -484,9 +488,9 @@ def _collect_topics(self) -> dict[str, str]:
return topics

def _collect_output_qos(self) -> dict[str, dict[str, str]]:
"""Publisher QoS per output channel, keyed by channel."""
"""Publisher QoS per published channel, keyed by channel."""
qos_map: dict[str, dict[str, str]] = {}
for name in self.outputs:
for name in list(self.outputs) + list(self.ios):
stream = getattr(self, name, None)
if stream is None:
continue
Expand Down
61 changes: 59 additions & 2 deletions dimos/core/test_native_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
The echo script writes received CLI args to a temp file for assertions.
"""

import contextlib
from io import BytesIO
import json
from pathlib import Path
Expand All @@ -33,11 +34,13 @@
from dimos.core.core import rpc
from dimos.core.module import Module
from dimos.core.native_module import LogFormat, NativeModule, NativeModuleConfig
from dimos.core.stream import In, Out
from dimos.core.transport import LCMTransport
from dimos.core.stream import IO, In, Out
from dimos.core.transport import LCMTransport, ZenohTransport
from dimos.msgs.geometry_msgs.Twist import Twist
from dimos.msgs.sensor_msgs.Imu import Imu
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
from dimos.msgs.tf2_msgs.TFMessage import TFMessage
from dimos.protocol.pubsub.impl.zenohpubsub import QOS_NEVER_DROP, Topic as ZenohTopic

_ECHO = str(Path(__file__).parent / "demos" / "native_echo.py")

Expand Down Expand Up @@ -87,6 +90,12 @@ class StubNativeModule(NativeModule):
cmd_vel: In[Twist]


class StubIoModule(NativeModule):
config: StubNativeConfig
cmd_vel: In[Twist]
tf: IO[TFMessage]


class StubConsumer(Module):
pointcloud: In[PointCloud2]
imu: In[Imu]
Expand Down Expand Up @@ -160,6 +169,54 @@ def test_manual(dimos_cluster: ModuleCoordinator, args_file: str) -> None:
}


def test_io_port_topic_reaches_the_native_process() -> None:
"""An IO port is both a subscriber and a publisher, so it needs its topic."""
module = StubIoModule(executable=_ECHO)
transports = [LCMTransport("/cmd_vel", Twist), LCMTransport("/tf", TFMessage)]
try:
module.set_transport("cmd_vel", transports[0])
module.set_transport("tf", transports[1])

assert module._collect_topics() == {
"cmd_vel": "/cmd_vel#geometry_msgs.Twist",
"tf": "/tf#tf2_msgs.TFMessage",
}
finally:
module.stop()
for transport in transports:
with contextlib.suppress(Exception):
transport.stop()


def test_tf_topic_comes_from_the_declared_port_only() -> None:
"""No tf port declared means no tf topic, rather than a silently injected one."""
module = StubNativeModule(executable=_ECHO)
transport = LCMTransport("/cmd_vel", Twist)
try:
module.set_transport("cmd_vel", transport)

assert module._collect_topics() == {"cmd_vel": "/cmd_vel#geometry_msgs.Twist"}
finally:
module.stop()
with contextlib.suppress(Exception):
transport.stop()


def test_io_port_publisher_qos_reaches_the_native_process() -> None:
module = StubIoModule(executable=_ECHO)
transport = ZenohTransport(ZenohTopic("/tf", TFMessage, qos=QOS_NEVER_DROP))
try:
module.set_transport("tf", transport)

assert module._collect_output_qos() == {
transport.channel: {"reliability": "reliable", "congestion_control": "block"},
}
finally:
module.stop()
with contextlib.suppress(Exception):
transport.stop()


def test_autoconnect(args_file: str) -> None:
"""autoconnect passes correct topic args to the native subprocess."""
blueprint = autoconnect(
Expand Down
147 changes: 147 additions & 0 deletions dimos/hardware/sensors/lidar/virtual_mid360/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading