Custom lidar-inertial SLAM for the Clubcapra Rove rover. C++17 front-end (KISS-ICP + GTSAM), Python tooling for the rosbag converter and viewer. No ROS dependency in the C++ tree — sensors arrive over UDP at runtime, or from file-backed recordings offline.
Architectural shape ported from a prior DJI M400 + L2 deployment; phase plan and platform-specific deviations are written up in docs/phase-0.md.
Phase 3.0 — GTSAM ISAM2 pose-graph wiring (byte-identical to Phase 1).
| Phase | What | Branch | Gate |
|---|---|---|---|
| 0 | ISensorSource, ReplaySource, LiveSource, wire format | phase-0 |
— |
| 1 | KISS-ICP front-end, TUM + PCD output | phase-1 |
ROVE_SLAM_USE_KISS_ICP=ON (default) |
| 3.0 | ISAM2 + BetweenFactor pose graph, byte-identical to P1 | phase-3 |
ROVE_SLAM_USE_GTSAM=ON (default OFF, ~6 min compile) |
Validation:
- docs/phase-1-validation.md — KISS-ICP across the bag fleet.
- docs/phase-3-validation.md — pose-graph byte-identity vs Phase 1.
scripts/smoke.sh runs end-to-end (build, convert, replay, live, SLAM) in <15 s on x86_64.
$ scripts/smoke.sh
── convert
LIDAR 335 vs rosbag 335
IMU 1757 vs rosbag 1757
GPS 1759 vs rosbag 1759
── live (fake VN-300)
slam_live: vn_pkts=149 imu_emitted=149 gps_emitted=149 …
── slam (KISS-ICP)
poses 335 vs rosbag 335 lidar frames
drift 0.039 m (expect <1 m on a clean loop)
[smoke] PASS
KISS-ICP processes ~335 Livox MID-360 scans in ~0.7 s wall on 8 threads;
the moving_short_bag2 loop closes at 0.17 % drift end-to-start.
- Compute: Jetson-class aarch64 Linux on the rover; x86_64 Linux for dev/replay.
- Lidar: Livox MID-360, read straight off raw UDP — no Livox SDK link. Per-point timestamps preserved when the source provides them (Livox
CustomMsg, orPointCloud2with a per-pointtimestampfield). - INS: VectorNav VN-300 (accel + gyro + lat/lon/alt + heading) over UDP via the
rover's
rove_sensor_apion data port5000. GPS is bundled in the VN data packet, not a separate driver. - Bag input: ROS 2 rosbag2 SQLite (Humble), topics
/livox/lidar,/livox/imu,/imu/data,/fix. Converter is a one-shot Python tool using therosbagspip package — no ROS install needed.
rove_slam/
├── include/rove_slam/ # ISensorSource, sinks, wire format
├── src/ # ReplaySource, LiveSource, SlamPipeline
├── apps/ # slam_offline, slam_live CLIs
├── tools/ # Python: bag2recording.py, rerun viewer
├── tests/ # fixtures + smoke scripts
└── docs/ # phase-0.md, frame conventions, wire format
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j8
ctest --test-dir build --output-on-failureSystem deps: libeigen3-dev. Python deps for the converter:
python3 -m venv .venv && .venv/bin/pip install rosbagsKISS-ICP is on by default (-DROVE_SLAM_USE_KISS_ICP=OFF to skip — Phase 0
build only). Pulled via FetchContent at v1.2.3 — newer tags require
CMake 3.24+. GTSAM lands in Phase 3 behind ROVE_SLAM_USE_GTSAM=ON.
Run SLAM on a converted recording:
slam_offline /tmp/rec --slam --out /tmp/out --map
# writes /tmp/out/trajectory.tum and /tmp/out/map.pcd