Skip to main content
Prerequisite for this is to understand dimos Modules and Blueprints. Native modules let you wrap any executable as a first-class DimOS module, given it speaks LCM. Python will handle blueprint wiring, lifecycle, and logging. Native binary handles the actual computation, publishing and subscribing directly on LCM. Python module never touches the pubsub data. It just passes configuration and LCM topic to use via CLI args to your executable. On how to speak LCM with the rest of dimos, you can read our LCM intro

Defining a native module

Python side native module is just a definition of a config dataclass and module class specifying pubsub I/O. Both the config dataclass and pubsub topics get converted to CLI args passed down to your executable once the module is started.
no-result session=nativemodule
That’s it. MyLidar is a full DimOS module. You can use it with autoconnect, blueprints, transport overrides, and specs. Once this module is started, your ./build/my_lidar will get called with specific CLI args.

How it works

When start() is called, NativeModule:
  1. Builds the executable if it doesn’t exist and build_command is set.
  2. Collects topics from blueprint-assigned transports on each declared port.
  3. Builds the command line: <executable> --<port> <topic> ... --<config_field> <value> ...
  4. Launches the subprocess with Popen, piping stdout/stderr.
  5. Starts a watchdog thread that calls stop() if the process crashes.
For the example above, the launched command would look like:
skip
ansi=false session=nativemodule skip
Topic strings use the format /<name>#<msg_type>, which is the LCM channel name that Python LCMTransport subscribers use. The native binary publishes on these exact channels. When stop() is called, the process receives SIGTERM. If it doesn’t exit within shutdown_timeout seconds (default 10), it gets SIGKILL.

Config

NativeModuleConfig extends ModuleConfig with subprocess fields:

Auto CLI arg generation

Any field you add to your config subclass automatically becomes a --name value CLI arg. Fields from NativeModuleConfig itself (like executable, extra_args, cwd) are not passed — they’re for Python-side orchestration only.
skip
  • None values are skipped.
  • Booleans are lowercased (true/false).
  • Lists are comma-joined.

Excluding fields

If a config field shouldn’t be a CLI arg, add it to cli_exclude:
skip

Using with blueprints

Native modules work with autoconnect exactly like Python modules:
skip
autoconnect matches ports by (name, type), assigns LCM topics, and passes them to the native binary as CLI args. You can override transports as usual:
skip

Logging

NativeModule pipes subprocess stdout and stderr through structlog:
  • stdout is logged at info level.
  • stderr is logged at warning level.

JSON log format

If your native binary outputs structured JSON lines, set log_format=LogFormat.JSON:
skip
The module will parse each line as JSON and feed the key-value pairs into structlog. The event key becomes the log message:
Malformed lines fall back to plain text logging.

Writing the C++ side

A header-only helper is provided at dimos/hardware/sensors/lidar/common/dimos_native_module.hpp:
The helper provides: It also includes make_header() and time_from_seconds() for building ROS-compatible stamped messages.

Examples

For language interop examples (subscribing to DimOS topics from C++, TypeScript, Lua), see /examples/language-interop/.

Livox Mid-360 Module

The Livox Mid-360 LiDAR driver is a complete example at dimos/hardware/sensors/lidar/livox/module.py:
skip
Usage:
skip

Auto Building

If build_command is set in the module config, and the executable doesn’t exist when start() is called, NativeModule runs the build command automatically. Build output is piped through structlog (stdout at info, stderr at warning).
skip
cwd is used for both the build command and the runtime subprocess. Relative paths are resolved against the directory of the Python file that defines the module If the executable already exists, the build step is skipped entirely.

Faster builds via the Cachix substituter

CI pre-builds the cmu_nav native modules and pushes the Nix store paths to the dimensionalos Cachix cache. Opt in locally to skip cold compiles when the cache has them: