Skip to content

Feature: nativemodule livox and fastlio2#1235

Merged
paul-nechifor merged 52 commits intodevfrom
feat/livox
Feb 19, 2026
Merged

Feature: nativemodule livox and fastlio2#1235
paul-nechifor merged 52 commits intodevfrom
feat/livox

Conversation

@leshy
Copy link
Contributor

@leshy leshy commented Feb 11, 2026

NativeModule arch, Livox Mid-360 LiDAR + FAST-LIO2 Integration, no ros

I have to add screenshots look how cool this sensor is

2026-02-13_19-52 2026-02-13_22-06

How do I review?

docs are at https://github.com/dimensionalOS/dimos/blob/3142b5bceb232222982d39d7960319f3d5ec3cf6/docs/usage/native_modules.md

I think NativeModule stuff is important, maybe the way I handle builds via nix, livox/fastlio is just a dumb glue that seems fine in terms of config and computational efficiency so wouldn't bother too much

C++ voxel mapper is just a hashmap. shouldn't look at it, this is vibed and will go away once I write voxel deletion

Summary

NativeModule framework, enables integration third party executables into the dimos module/blueprint system

Native C++ Dimos Module for the Livox mid360
Native C++ Dimos Module for FAST-LIO2

NativeModule

(dimos/core/native_module.py)

should this be named externalmodule or something like that?

Hosts third party process, passes topics to use via CLI to the binary.
Passes also config settings as CLI args

Assumes binary speaks LCM, in the future we can pass also the protocol.
Doesn't support RPC

C++ binaries participate in blueprints like any Python module

Livox Mid-360 driver

dimos/hardware/sensors/lidar/livox/

  • Direct Livox SDK2 C API integration (no ROS dependency)
  • C++ binary publishes PointCloud2 and IMU at configurable rates over LCM
  • Python wrapper: Mid360Module with network config (host/lidar IPs, ports)
    pointcloud: Out[PointCloud2]
    imu: Out[Imu]
mid360 = autoconnect(
    Mid360Module.blueprint(),
    rerun_bridge(),
).global_config(n_dask_workers=2, robot_model="mid360")

Testing

cd dimos/hardware/sensors/lidar/livox/cpp
nix build .#mid360_native
dimos run mid360

for native build check cpp/readme.md

FAST-LIO2

(dimos/hardware/sensors/lidar/fastlio2/)

Fastlio binary has livox SDK baked in, talks to mid360 directly, LCM topics lose full spectrum of mid360 data which fastlio depends on.

raw mid360 data is so raw that I assume we'll always use it through fastlio. (for example lidar hits need compensation for rotation state of the scanner and motion of the lidar in space)

    lidar: Out[PointCloud2]
    odometry: Out[Odometry]
    global_map: Out[PointCloud2]
mid360_fastlio = autoconnect(
    FastLio2.blueprint()
    rerun_bridge(),
)
cd dimos/hardware/sensors/lidar/fastlio2/cpp
nix build .#fastlio2_native

# raw fastlio
dimos run mid360-fastlio

# original mapper
dimos run mid360-fastlio-voxels

# cpp mapper
dimos run mid360-fastlio-voxels-native

for native build check cpp/readme.md

Voxel mapper tests

fastlio has three blueprint configs:
basic ODOM + Pointcloud, ODOM + GlobalMap, ODOM + CPPGlobalMap,

CPP voxel mapper uses PCL library https://pointclouds.org/
and is 2x more efficient then already pretty efficient py/cuda mapper

no voxel deleteons yet

needs a few days of experimenting

Nix build infrastructure

  • Hermetic builds for both mid360_native and fastlio2_native via Nix flakes
  • Livox SDK2 compiled from source, FAST-LIO fetched from GitHub

Blueprints

  • mid360 — raw LiDAR
  • mid360-fastlio — SLAM with 0.15m voxel filtering
  • mid360-fastlio-voxels — SLAM + downstream VoxelGridMapper
  • mid360-fastlio-voxels-native — SLAM + C++ global voxel map at 3Hz

…notations`

The future annotations import made Out[PointCloud2] and Out[Imu] lazy
strings, preventing the Module metaclass from creating real port
descriptors. Move type imports out of TYPE_CHECKING block so ports
are properly resolved at class definition time.
Livox-specific module (LivoxLidarModule, LivoxLidarModuleConfig) now
lives in dimos/hardware/sensors/lidar/livox/module.py alongside the
driver and SDK code. Generic LidarModule stays in the parent.
@greptile-apps
Copy link

greptile-apps bot commented Feb 11, 2026

Greptile Overview

Greptile Summary

This PR introduces a comprehensive NativeModule framework that enables C++ executables to participate in the dimos blueprint system, plus native drivers for Livox Mid-360 LiDAR and FAST-LIO2 SLAM.

Key Changes:

  • NativeModule Framework (dimos/core/native_module.py): Subprocess wrapper with robust lifecycle management, automatic CLI arg generation from config dataclasses, and proper signal handling (SIGTERM → SIGKILL escalation)
  • Livox Mid-360 Driver: Direct SDK2 integration publishing PointCloud2 and IMU over LCM at configurable rates
  • FAST-LIO2 Integration: Custom non-ROS rewrite with point cloud filtering pipeline (voxel downsampling, statistical outlier removal) and optional global voxel map with raycasting-based free space clearing
  • In-Memory SDK Config: Elegant memfd_create solution eliminates temp file management issues
  • Hermetic Builds: Nix flakes provide reproducible builds for both binaries

Architecture:

  • Python modules declare In/Out ports and pass LCM topics as CLI args to C++ binaries
  • C++ binaries use lightweight header-only dimos_native_module.hpp helper for arg parsing
  • FAST-LIO2 talks directly to Livox SDK for raw hardware data, bypassing standard LCM topics (preserves timing-critical scanner rotation compensation)

Test Coverage:

  • Comprehensive NativeModule tests covering subprocess lifecycle, watchdog behavior, and blueprint integration
  • Spec compliance tests for port declarations

Confidence Score: 5/5

  • This PR is safe to merge - well-architected framework with comprehensive testing and thoughtful implementation details
  • The code demonstrates high quality across the board: robust subprocess management with proper signal handling and timeout escalation, thread-safe data structures with mutex protection, clean separation of concerns between Python and C++, comprehensive test coverage for the core framework, elegant solutions like memfd_create for config management, and proper resource cleanup. The only minor issue is uncaught exceptions in string-to-numeric conversions which has a low impact since inputs come from trusted Python config.
  • dimos/hardware/sensors/lidar/livox/cpp/dimos_native_module.hpp and dimos/hardware/sensors/lidar/fastlio2/cpp/dimos_native_module.hpp - consider adding exception handling for stof/stoi

Important Files Changed

Filename Overview
dimos/core/native_module.py Core NativeModule framework with subprocess management, signal handling, and robust lifecycle management
dimos/hardware/sensors/lidar/livox/cpp/main.cpp Livox SDK2 integration with proper signal handling, mutex-protected frame accumulation, and clean LCM publishing
dimos/hardware/sensors/lidar/livox/cpp/dimos_native_module.hpp CLI arg parser helper - potential exception issues with stof/stoi on invalid input
dimos/hardware/sensors/lidar/common/livox_sdk_config.hpp Elegant memfd_create solution for in-memory SDK config, replacing temp file approach
dimos/hardware/sensors/lidar/fastlio2/cpp/main.cpp FAST-LIO2 integration with Livox SDK, point cloud filtering, and optional voxel map publishing
dimos/hardware/sensors/lidar/fastlio2/cpp/voxel_map.hpp Efficient voxel map with hash-based storage, dynamic FOV discovery, and 3D DDA raycasting for free space clearing

Sequence Diagram

sequenceDiagram
    participant Python as Python NativeModule
    participant Subprocess as C++ Binary
    participant LivoxSDK as Livox SDK2
    participant FASTLIO as FAST-LIO2
    participant LCM as LCM Bus

    Python->>Python: start() - collect port topics
    Python->>Subprocess: spawn with --port topic args
    Subprocess->>Subprocess: parse CLI args (dimos_native_module.hpp)
    Subprocess->>LivoxSDK: init SDK with memfd config
    Subprocess->>LCM: initialize publisher

    alt Mid360 Driver
        LivoxSDK->>Subprocess: on_point_cloud(raw packets)
        Subprocess->>Subprocess: accumulate points in buffer
        loop Every 1/frequency seconds
            Subprocess->>LCM: publish PointCloud2
        end
        LivoxSDK->>Subprocess: on_imu_data(IMU packets)
        Subprocess->>LCM: publish Imu (immediately)
    else FAST-LIO2
        LivoxSDK->>Subprocess: on_point_cloud(raw packets)
        Subprocess->>Subprocess: accumulate CustomMsg points
        LivoxSDK->>Subprocess: on_imu_data(IMU)
        Subprocess->>FASTLIO: feed_imu(imu_msg)
        loop Main processing loop
            Subprocess->>FASTLIO: feed_lidar(accumulated points)
            FASTLIO->>FASTLIO: EKF-LOAM SLAM
            FASTLIO->>Subprocess: return registered cloud + odometry
            Subprocess->>Subprocess: voxel filter + outlier removal
            Subprocess->>LCM: publish PointCloud2 (world frame)
            Subprocess->>LCM: publish Odometry
            opt Global Map Enabled
                Subprocess->>Subprocess: VoxelMap.insert(cloud)
                Subprocess->>Subprocess: VoxelMap.raycast_clear()
                Subprocess->>LCM: publish global_map PointCloud2
            end
        end
    end

    Python->>Subprocess: SIGTERM (on stop())
    Subprocess->>LivoxSDK: shutdown
    Subprocess->>Subprocess: exit
    Python->>Python: wait for exit + cleanup
Loading

Last reviewed commit: af4e732

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

15 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

leshy added 19 commits February 12, 2026 15:35
Integrates FAST-LIO-NON-ROS directly with Livox SDK2 as a dimos
NativeModule. The C++ binary feeds live LiDAR/IMU data into FAST-LIO's
EKF-LOAM SLAM and publishes aggregated world-frame point clouds and
odometry over LCM. Includes rate-limited output (pointcloud_freq,
odom_freq), Odometry.to_rerun() for visualization, and nix flake deps.
Add standalone nix flakes to build native C++ modules with all
dependencies (Livox SDK2, LCM, PCL, etc.) from nix, avoiding
glibc version conflicts. CMakeLists.txt now accepts FASTLIO_DIR
as an external parameter for hermetic nix builds.
…n flake

Move livox flake.nix into cpp/ for consistency with fastlio2. Add cmake
install() rules with default prefix=result so both nix and cmake produce
binaries at cpp/result/bin/. Remove livox-sdk and lidar deps from main
flake since native modules build via their own standalone flakes.
@leshy leshy changed the title WIP Feat/livox feat/livox Feb 13, 2026
# Conflicts:
#	dimos/core/__init__.py
#	dimos/robot/all_blueprints.py
#	dimos/robot/unitree_webrtc/unitree_go2_blueprints.py
- Add generic type param to NativeModule (Module[NativeModuleConfig])
- Add Any import and type annotations to NativeModule.__init__
- Fix ModuleConfig import in lidar module
- Fix FastLio2Module -> FastLio2 import in test
- Rename test_spec_compliance.py to test_spec.py across lidar modules
- Adjust fastlio2 voxel_size from 0.25 to 0.15
cd dimos/hardware/sensors/lidar/fastlio2/cpp
nix build .#fastlio2_native
```

Copy link
Member

@jeff-hykin jeff-hykin Feb 14, 2026

Choose a reason for hiding this comment

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

I know I've pushed towards dimos users to not being forced to use nix, but for C++ and similar individual modules I think its fair to say "Either use the prebuilt binary, use nix, or figure it out yourself". E.g. for future C++ modules I don't think we need to support the native install instructions.

Why I feel these are okay to force is cause its for a build of an exe instead of a devShell.

Copy link
Contributor Author

@leshy leshy Feb 14, 2026

Choose a reason for hiding this comment

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

yeah I agree, maintaining build systems would be horrible otherwise. nix can be used for wheel builds also

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually I added a build system it was just a few lines and makes it much more ergonomic

jeff-hykin
jeff-hykin previously approved these changes Feb 14, 2026
Copy link
Member

@jeff-hykin jeff-hykin left a comment

Choose a reason for hiding this comment

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

I think the only thing missing is docs (all my other feedback is super minor).

I can ask Jalaj to do the docs for this (after its merged) cause Jala already needs to add docs for his docker module, and this native module is way more simple.

That said, in a future I think it would be good for native modules to support automated build hook for a missing binary.

Resolved conflict in Odometry.py: kept dev's refactored class
structure and added to_rerun() method from feat/livox.
Resolved conflicts: kept to_rerun() in Odometry.py, took dev's
updated doc paths in modules.md.
@leshy
Copy link
Contributor Author

leshy commented Feb 14, 2026

I think the only thing missing is docs (all my other feedback is super minor).

I can ask Jalaj to do the docs for this (after its merged) cause Jala already needs to add docs for his docker module, and this native module is way more simple.

That said, in a future I think it would be good for native modules to support automated build hook for a missing binary.

true, this can host docker very easily.. wrote some docs, and yeah autobuild sounds good, just wanted to keep the PR smaller, pretty big already

  • edit added the build system

Comment on lines +106 to +116
class MyConfig(NativeModuleConfig):
executable: str = "./build/my_module" # relative or absolute path to your executable
host_ip: str = "192.168.1.5" # becomes --host_ip 192.168.1.5
frequency: float = 10.0 # becomes --frequency 10.0
enable_imu: bool = True # becomes --enable_imu true
filters: list[str] = field(default_factory=lambda: ["a", "b"]) # becomes --filters a,b
```

- `None` values are skipped.
- Booleans are lowercased (`true`/`false`).
- Lists are comma-joined.
Copy link
Contributor

Choose a reason for hiding this comment

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

It's probably safer to pass it as JSON.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah or ENV actually, given this would make it compatible with Docker modules, need to coordinate this a bit, IMO this is a POC, wanted to create an issue and discuss

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 162 to 165
self._io_threads = [
self._start_reader(self._process.stdout, "info"),
self._start_reader(self._process.stderr, "warning"),
]
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit wasteful to start two extra threads just to pipe two streams. You can do everything from the watchdog thread.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've put management of these threads in the watchdog, but the code gets really ugly if I try to process everything in the same thread, so keeping split for ease of undertanding now

paul-nechifor
paul-nechifor previously approved these changes Feb 15, 2026
Remove runtime assert_implements_protocol and test_spec files. Instead,
add TYPE_CHECKING instantiation at the bottom of each module file so mypy
catches missing or misnamed protocol ports statically.
Move reader thread spawning into the watchdog thread so only one thread
is externally managed. Use tmp_path pytest fixture instead of manual
tempfile. Use communicate() for build subprocess.
Use argparse in native_echo.py instead of env vars for output_file and
die_after. Add these as config fields on StubNativeConfig so they flow
through the normal to_cli_args() path.
Replace NATIVE_ECHO_OUTPUT/NATIVE_ECHO_DIE_AFTER env vars with
output_file/die_after config fields passed as CLI args. Add type
annotations to test functions. Remove stale type: ignore on
NativeModule.default_config.
# Conflicts:
#	dimos/robot/all_blueprints.py
paul-nechifor
paul-nechifor previously approved these changes Feb 19, 2026
@paul-nechifor paul-nechifor merged commit 7ba4e29 into dev Feb 19, 2026
30 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments