SparkCar ROS 2 workspace for the SparkCar robot platform. This repository groups the Hunter SE chassis driver, Livox MID360 perception/localization stack, Nav2 bringup, Hybrid A* planner demo, and map conversion tools.
The README style follows the lightweight structure commonly used by AgileX/Hunter ROS repositories: packages, communication setup, build, launch, and safety notes.
- Packages
- Environment
- CAN Interface Setup
- Build
- Basic Usage
- Start Hunter SE Chassis
- Start Livox MID360
- PGO Mapping And Save PCD
- Convert PCD To PGM For Nav2
- Start FAST-LIO2 Localizer And Nav2
- Nav2 Velocity Output
- Twist To Ackermann Converter
- Hybrid A* Demo
- Useful Topics And Checks
- Safety Notes
- License
SparkCar_ROS2_WS
+-- HunterSE_Driver
| +-- src
| +-- hunter_base # Hunter SE / Hunter 2 chassis ROS 2 node
| +-- hunter_msgs # Hunter status and command messages
| +-- ugv_sdk # Weston Robot / AgileX UGV SDK
+-- SparkCar_Perception
| +-- src # Livox MID360 and FAST-LIO2 related source tree
| +-- install # Installed perception packages: livox_ros_driver2, localizer, pgo, interface, etc.
+-- SparkCar_Navigation
| +-- src
| +-- sparkcar_nav_bringup
| | +-- launch # Nav2 launch files
| | +-- behavior_trees # Hunter SE Nav2 behavior trees
| | +-- config # Nav2 parameters and waypoints
| | +-- maps # Default map
| | +-- src # twist_to_ackermann and obstacle_cloud_filter nodes
| +-- hybrid_astar_planner
| +-- launch # Hybrid A* demo launch
| +-- config # Planner parameters
| +-- rviz # RViz config
+-- SparkCar_Tools
| +-- install/pcd2pgm # PCD to occupancy grid map conversion tool
+-- scripts
+-- ControlCommand.sh # Mapping helper script
+-- hunter_ros2_test.sh # Hunter SE Nav2 navigation helper script
+-- nav2_test.sh # Older navigation helper script
+-- process.md # Mapping workflow notes
Recommended runtime environment:
- Ubuntu 22.04
- ROS 2 Humble
- SocketCAN enabled CAN adapter for Hunter SE chassis
- Livox MID360 lidar
- Micro XRCE-DDS Agent if the lower controller uses serial XRCE-DDS
Install common ROS dependencies:
sudo apt update sudo apt install -y \ python3-colcon-common-extensions \ ros-humble-rclcpp \ ros-humble-geometry-msgs \ ros-humble-nav-msgs \ ros-humble-sensor-msgs \ ros-humble-ackermann-msgs \ ros-humble-tf2 \ ros-humble-tf2-ros \ ros-humble-tf2-geometry-msgs \ ros-humble-navigation2 \ ros-humble-nav2-bringup \ ros-humble-nav2-map-server \ ros-humble-rviz2 \ libasio-dev \ libeigen3-dev \ libgoogle-glog-dev
Hunter chassis communication uses SocketCAN. The default launch parameter is port_name:=can0.
Bring up CAN at 500 kbit/s:
sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up
Check CAN traffic:
candump can0
This repository is organized as several ROS 2 workspaces. Build each workspace from its own directory.
Build Hunter SE driver:
cd /home/jiaverso/Desktop/SparkCar_ROS2_WS/HunterSE_Driver source /opt/ros/humble/setup.bash colcon build --symlink-install
Build navigation packages:
cd /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation source /opt/ros/humble/setup.bash colcon build --symlink-install
If perception/tools source packages are present on the target machine, build them the same way:
cd /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Perception source /opt/ros/humble/setup.bash colcon build --symlink-install cd /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Tools source /opt/ros/humble/setup.bash colcon build --symlink-install
Source installed workspaces after build:
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/HunterSE_Driver/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Perception/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Tools/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation/install/setup.bash
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/HunterSE_Driver/install/setup.bash ros2 launch hunter_base hunter_base.launch.py port_name:=can0 robot_model:=hunter_se
Common launch arguments:
port_name: CAN interface, defaultcan0robot_model:hunter_seorhunter2, defaulthunter2odom_frame: odometry frame, defaultodombase_frame: base frame, defaultbase_linkodom_topic_name: odometry topic, defaultodom
The chassis node subscribes to /cmd_vel, publishes odometry on the configured odometry topic, publishes TF from odom_frame to base_frame, and publishes chassis status on /hunter_status.
Send a simple test command:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.2}, angular: {z: 0.0}}" -r 10Stop the robot:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.0}, angular: {z: 0.0}}" -1Start the Livox MID360 driver before mapping or localization:
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Perception/install/setup.bash ros2 launch livox_ros_driver2 msg_MID360_launch.py
Check lidar and IMU topics:
ros2 topic hz /livox/lidar ros2 topic hz /livox/imu
Expected raw sensor topics:
/livox/lidar/livox/imu
Run the mapping helper:
cd /home/jiaverso/Desktop/SparkCar_ROS2_WS
bash scripts/ControlCommand.shManual mapping sequence:
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Perception/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Tools/install/setup.bash # Terminal 1: start lidar ros2 launch livox_ros_driver2 msg_MID360_launch.py # Terminal 2: start PGO mapping ros2 launch pgo pgo_launch.py
pgo_launch.py starts both FAST-LIO2 and the PGO node:
livox_ros_driver2 -> /livox/lidar, /livox/imu
FAST-LIO2 -> /fastlio2/lio_odom, /fastlio2/body_cloud
PGO -> optimized map and /pgo/save_maps service
Current mapping frames:
FAST-LIO2 odom frame: lio
FAST-LIO2 body frame: body
PGO TF: map -> lio
Fast-LIO2 odometry topic: /fastlio2/lio_odom
Fast-LIO2 body cloud topic: /fastlio2/body_cloud
Optional rosbag recording during mapping:
mkdir -p ~/Desktop/bags ros2 bag record \ /livox/lidar \ /livox/imu \ /fastlio2/lio_odom \ /fastlio2/body_cloud \ -o ~/Desktop/bags/mapping_001
Save PGO map:
mkdir -p ~/Desktop/Save_Map ros2 service call /pgo/save_maps interface/srv/SaveMaps \ "{file_path: '$HOME/Desktop/Save_Map', save_patches: true}"
The PGO service saves the merged map as:
~/Desktop/Save_Map/main.pcd
Each save overwrites main.pcd, so no extra copy or rename step is needed.
If save_patches: true is used, PGO also saves patch files under:
~/Desktop/Save_Map/patches/
pcd2pgm converts a .pcd point cloud map into a 2D occupancy grid and publishes it on /map. Then map_saver_cli saves the Nav2 .pgm and .yaml map files.
The default pcd2pgm config currently reads:
~/Desktop/Save_Map/main.pcd
The source config is:
pcd2pgm: ros__parameters: pcd_file: /home/jiaverso/Desktop/Save_Map/main.pcd
Rebuild or use the installed config that matches your runtime environment. Then run:
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Tools/install/setup.bash ros2 launch pcd2pgm pcd2pgm_launch.py \ params_file:=/home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Tools/src/pcd2pgm/config/pcd2pgm.yaml
In another terminal, save the published /map as main.pgm and main.yaml:
source /opt/ros/humble/setup.bash
mkdir -p /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation/src/sparkcar_nav_bringup/maps
ros2 run nav2_map_server map_saver_cli \
-f /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation/src/sparkcar_nav_bringup/maps/mainThis creates:
SparkCar_Navigation/src/sparkcar_nav_bringup/maps/main.pgm
SparkCar_Navigation/src/sparkcar_nav_bringup/maps/main.yaml
Confirm that main.yaml points to main.pgm:
image: main.pgm resolution: 0.05 origin: [...]
Run the navigation helper:
cd /home/jiaverso/Desktop/SparkCar_ROS2_WS
bash scripts/hunter_ros2_test.shManual navigation sequence:
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Perception/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation/install/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/HunterSE_Driver/install/setup.bash # Terminal 1: start Hunter SE chassis ros2 launch hunter_base hunter_base.launch.py \ port_name:=can0 \ robot_model:=hunter_se \ odom_frame:=odom \ base_frame:=body \ odom_topic_name:=odom # Terminal 2: start Livox MID360 ros2 launch livox_ros_driver2 msg_MID360_launch.py # Terminal 3: start FAST-LIO2 localizer ros2 launch localizer localizer_launch.py use_rviz:=false # Terminal 4: start Nav2 obstacle cloud filter ros2 run sparkcar_nav_bringup obstacle_cloud_filter # Terminal 5: start Nav2 ros2 launch sparkcar_nav_bringup nav_bringup.launch.py
localizer_launch.py starts:
fastlio2/lio_node
localizer/localizer_node
optional localizer RViz when `use_rviz:=true`
nav_bringup.launch.py starts:
map_server
planner_server
controller_server
smoother_server
behavior_server
bt_navigator
lifecycle_manager_navigation
rviz2
Current navigation topic and TF usage:
Hunter wheel odometry: /odom
Nav2 odometry input: /odom
Fast-LIO2 body cloud: /fastlio2/body_cloud
Filtered obstacle cloud: /nav2/obstacle_cloud
Nav2 local costmap obstacle input: /nav2/obstacle_cloud
Expected TF chain: map -> odom -> body
The helper script waits for map -> body before starting Nav2. If it times out, check that the Hunter driver is publishing odom -> body and the localizer is publishing map -> odom.
If needed, start Micro XRCE-DDS Agent:
sudo MicroXRCEAgent serial --dev /dev/ttyUSB1 -b 115200 -v 6
Disable RViz:
ros2 launch sparkcar_nav_bringup nav_bringup.launch.py use_rviz:=false
Nav2 computes velocity commands from the planned path and publishes:
/cmd_vel
Check output:
ros2 topic echo /cmd_vel
ros2 topic hz /cmd_velIf using the Hunter SE ROS2 driver directly, hunter_base subscribes to /cmd_vel, so the control chain is:
Nav2 controller_server -> /cmd_vel -> hunter_base -> CAN -> Hunter SE
In this case, do not run twist_to_ackermann unless a separate lower-level controller is explicitly subscribing to /ackermann_cmd.
The converter subscribes to /cmd_vel and publishes /ackermann_cmd.
ros2 run sparkcar_nav_bringup twist_to_ackermann
Use this only when the lower-level controller expects ackermann_msgs/msg/AckermannDrive on /ackermann_cmd.
Parameters:
wheelbase: default0.60max_steering_angle: default0.52min_speed_for_steering: default0.05cmd_vel_topic: default/cmd_velackermann_topic: default/ackermann_cmd
source /opt/ros/humble/setup.bash source /home/jiaverso/Desktop/SparkCar_ROS2_WS/SparkCar_Navigation/install/setup.bash ros2 launch hybrid_astar_planner hybrid_astar_test.py
ros2 topic list ros2 topic echo /hunter_status ros2 topic echo /odom ros2 topic echo /cmd_vel ros2 topic echo /ackermann_cmd ros2 topic echo /fastlio2/body_cloud ros2 topic echo /nav2/obstacle_cloud ros2 topic echo /plan ros2 lifecycle get /map_server ros2 lifecycle get /planner_server ros2 lifecycle get /controller_server ros2 lifecycle get /bt_navigator ros2 run tf2_ros tf2_echo map body
- Always lift the robot wheels or keep the robot in an open area for the first motion test.
- Verify the CAN interface and emergency stop before sending
/cmd_vel. - Start with low linear speed, for example
0.1to0.2 m/s. - Keep a terminal ready to publish zero velocity or stop the launch process.
- Confirm TF frames and map alignment before enabling autonomous navigation.
This repository contains code from multiple components. See LICENSE and package-level license files for details.