Skip to content

Commit 38a04a0

Browse files
authored
Merge pull request #3 from jabasai/copilot/fix-logging-and-race-conditions
Single-node dual MID360 driver: eliminate SDK race condition + logging/thread-safety fixes + unified livox CLI
2 parents 6b9356c + f3b3a24 commit 38a04a0

42 files changed

Lines changed: 4505 additions & 466 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
FROM ros:humble
2+
3+
ARG DEBIAN_FRONTEND=noninteractive
4+
5+
# Install system and ROS2 dependencies
6+
RUN apt-get update && apt-get install -y \
7+
build-essential \
8+
cmake \
9+
git \
10+
wget \
11+
curl \
12+
python3-colcon-common-extensions \
13+
python3-rosdep \
14+
python3-vcstool \
15+
libpcl-dev \
16+
libapr1-dev \
17+
libaprutil1-dev \
18+
ros-humble-rclcpp \
19+
ros-humble-rclcpp-components \
20+
ros-humble-std-msgs \
21+
ros-humble-sensor-msgs \
22+
ros-humble-rcl-interfaces \
23+
ros-humble-pcl-conversions \
24+
ros-humble-pcl-ros \
25+
ros-humble-rosbag2 \
26+
ros-humble-rosidl-default-generators \
27+
ros-humble-rosidl-default-runtime \
28+
ros-humble-ament-lint-auto \
29+
ros-humble-ament-lint-common \
30+
&& rm -rf /var/lib/apt/lists/*
31+
32+
RUN \
33+
echo "Installing livox_sdk2..." && \
34+
REPO_DIR="/tmp/livox_sdk2_repo" && \
35+
mkdir -p "$REPO_DIR" && \
36+
git clone --branch v1.2.5 --depth 1 https://github.com/Livox-SDK/Livox-SDK2.git "$REPO_DIR/Livox-SDK2" && \
37+
cd "$REPO_DIR/Livox-SDK2" && \
38+
mkdir -p build && \
39+
cd build && \
40+
rm -rf * && \
41+
cmake .. && \
42+
make -j$(nproc) && \
43+
make install && \
44+
echo "livox_sdk2 installed successfully." && \
45+
cd "/" && \
46+
rm -rf "$REPO_DIR" && \
47+
ldconfig
48+
49+
50+
# Source ROS2 in every new shell
51+
RUN echo "source /opt/ros/humble/setup.bash" >> /etc/bash.bashrc
52+
53+
# Set up workspace directory
54+
WORKDIR /ros2_ws

.devcontainer/devcontainer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "livox_ros_driver2 (ROS2 Humble)",
3+
"dockerComposeFile": "docker-compose.yml",
4+
"service": "devcontainer",
5+
"workspaceFolder": "/ros2_ws/src/livox_ros_driver2",
6+
"remoteEnv": {
7+
"ROS_DISTRO": "humble",
8+
"AMENT_PREFIX_PATH": "/opt/ros/humble",
9+
"ROS_DOMAIN_ID": "0"
10+
},
11+
"customizations": {
12+
"vscode": {
13+
"extensions": [
14+
"ms-vscode.cpptools",
15+
"ms-vscode.cmake-tools",
16+
"ms-python.python"
17+
],
18+
"settings": {
19+
"terminal.integrated.defaultProfile.linux": "bash",
20+
"terminal.integrated.profiles.linux": {
21+
"bash": {
22+
"path": "/bin/bash",
23+
"args": ["--login"]
24+
}
25+
},
26+
"cmake.buildDirectory": "/ros2_ws/build/livox_ros_driver2",
27+
"C_Cpp.default.includePath": [
28+
"/opt/ros/humble/include/**",
29+
"/ros2_ws/src/livox_ros_driver2/src/**",
30+
"/ros2_ws/src/livox_ros_driver2/3rdparty/**"
31+
]
32+
}
33+
}
34+
},
35+
"postCreateCommand": "cd /ros2_ws && bash -c 'source /opt/ros/humble/setup.bash && cp src/livox_ros_driver2/package_ROS2.xml src/livox_ros_driver2/package.xml'",
36+
"postStartCommand": ". /opt/ros/humble/setup.sh",
37+
"features": {}
38+
}

.devcontainer/docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: '3.8'
2+
3+
services:
4+
devcontainer:
5+
build:
6+
context: ..
7+
dockerfile: .devcontainer/Dockerfile
8+
volumes:
9+
- ..:/ros2_ws/src/livox_ros_driver2:cached
10+
networks:
11+
default: {}
12+
livox_net:
13+
ipv4_address: 192.168.1.10
14+
command: sleep infinity
15+
16+
networks:
17+
livox_net:
18+
driver: bridge
19+
ipam:
20+
driver: default
21+
config:
22+
- subnet: 192.168.1.0/24
23+
gateway: 192.168.1.1

.github/copilot-instructions.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Livox ROS Driver 2 — Copilot Instructions
2+
3+
## Project Overview
4+
5+
This is a ROS2 device driver for Livox 3D LiDAR sensors (MID360, HAP). It targets **ROS2 Humble or later**. The codebase also retains partial ROS1 compatibility gated behind `#ifdef BUILDING_ROS1` preprocessor guards. But only ever work on the ROS2 code.
6+
7+
The driver supports running a **single node that manages multiple lidars** (the recommended approach for dual MID360 setups): one SDK instance handles both lidars, eliminating cross-instance SDK race conditions. See `launch/dual_mid360_launch.py`.
8+
9+
## Build and Test
10+
11+
```bash
12+
# From the workspace root (/ros2_ws)
13+
colcon build
14+
15+
```
16+
17+
## Architecture
18+
19+
- **`src/driver_node.*`** — ROS2 node entry point, lifecycle management; includes watchdog timer and Race-3 reconnect timer
20+
- **`src/lddc.*`** — Lidar Data Distribution Center; routes point cloud and IMU data to ROS publishers
21+
- **`src/lds_lidar.*` / `src/lds.*`** — Lidar Device Scheduler; manages sensor connections and data pipelines
22+
- **`src/comm/`** — Low-level communication utilities: queues, semaphores, caching, and the central `PubHandler` (with per-handle allowlist)
23+
- **`src/call_back/`** — SDK callback handlers for incoming lidar/IMU frames
24+
- **`src/parse_cfg_file/`** — JSON config file parsing (uses RapidJSON from `3rdparty/`)
25+
- **`src/include/`** — Shared headers; `ros_headers.h` abstracts ROS1/ROS2 includes; `livox_log.h` provides node-free logging macros
26+
- **`config/`** — Per-sensor JSON configuration files
27+
- **`launch/`** — ROS2 Python launch files; `dual_mid360_launch.py` launches ONE node for BOTH MID360 lidars
28+
- **`src/tools/`** — Standalone CLI tools: `livox_scan` (discover lidars) and `livox_set_ip` (configure lidar IPs)
29+
30+
## Code Conventions
31+
32+
- **Language**: C++14, namespace `livox_ros`
33+
- **Naming**: `PascalCase` for classes, `snake_case` for functions and member variables
34+
- **Header guards**: `#ifndef LIVOX_<MODULE>_H` / `#define LIVOX_<MODULE>_H`
35+
- **ROS abstraction**: Use the wrappers in `src/include/ros_headers.h` rather than including ROS headers directly; guard any ROS1-specific code with `#ifdef BUILDING_ROS1`
36+
- **Logging**: Use `LIVOX_INFO` / `LIVOX_WARN` / `LIVOX_ERROR` / `LIVOX_DEBUG` macros from `src/include/livox_log.h` for node-free logging; they route to `RCLCPP_*` in ROS2 and `ROS_*` in ROS1
37+
- **Thread safety**: `connect_state` in `LidarDevice` is `std::atomic<LidarConnectState>`; use `memory_order_acquire` / `memory_order_release` at all access sites
38+
- **JSON parsing**: Use the bundled RapidJSON library in `3rdparty/rapidjson/`
39+
- **License header**: All new source files must include the MIT license block present in existing files
40+
41+
## Dependencies
42+
43+
Key runtime deps: `rclcpp`, `rclcpp_components`, `sensor_msgs`, `pcl_conversions`, `livox_lidar_sdk`.
44+
Do **not** add new third-party libraries without updating both `CMakeLists.txt` and `package.xml`.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Copilot Setup Steps
2+
3+
on:
4+
workflow_dispatch:
5+
6+
push:
7+
branches:
8+
- master
9+
paths:
10+
- .github/workflows/copilot-setup-steps.yml
11+
12+
pull_request:
13+
paths:
14+
- .github/workflows/copilot-setup-steps.yml
15+
16+
jobs:
17+
copilot-setup-steps:
18+
runs-on: ubuntu-22.04
19+
permissions:
20+
contents: read
21+
22+
steps:
23+
- name: setup workspace
24+
run: |
25+
mkdir -p ${GITHUB_WORKSPACE}/src
26+
27+
- uses: actions/checkout@v6
28+
with:
29+
path: src/livox_ros_driver2
30+
31+
- name: setup ROS environment
32+
uses: ros-tooling/setup-ros@v0.7
33+
34+
- name: install workspace dependencies
35+
run: |
36+
sudo apt-get update
37+
sudo apt-get install -y build-essential cmake ros-humble-ros-base
38+
source /opt/ros/humble/setup.bash
39+
rosdep update --rosdistro humble
40+
rosdep install --from-paths ${GITHUB_WORKSPACE}/src --ignore-src -r -y
41+
42+
- name: install Livox SDK2
43+
run: |
44+
REPO_DIR="/tmp/livox_sdk2_repo"
45+
mkdir -p "$REPO_DIR"
46+
git clone --branch v1.2.5 --depth 1 https://github.com/Livox-SDK/Livox-SDK2.git "$REPO_DIR/Livox-SDK2"
47+
cd "$REPO_DIR/Livox-SDK2"
48+
mkdir -p build && cd build
49+
cmake ..
50+
make -j$(nproc)
51+
sudo make install
52+
sudo ldconfig
53+
rm -rf "$REPO_DIR"
54+
55+
- name: build workspace
56+
run: |
57+
cd ${GITHUB_WORKSPACE}
58+
source /opt/ros/humble/setup.bash
59+
colcon build --symlink-install --continue-on-error || true
60+
source install/setup.bash

.github/workflows/ros-ci.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: ros CI
2+
3+
on:
4+
push:
5+
# you may want to configure the branches that this should be run on here.
6+
branches: [ "master" ]
7+
pull_request:
8+
9+
jobs:
10+
test_docker: # On Linux, iterates on all ROS 1 and ROS 2 distributions.
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
strategy:
15+
matrix:
16+
ros_distribution:
17+
# - noetic
18+
- humble
19+
# - iron
20+
21+
# Define the Docker image(s) associated with each ROS distribution.
22+
# The include syntax allows additional variables to be defined, like
23+
# docker_image in this case. See documentation:
24+
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-including-configurations-in-a-matrix-build
25+
#
26+
# Platforms are defined in REP 3 and REP 2000:
27+
# https://ros.org/reps/rep-0003.html
28+
# https://ros.org/reps/rep-2000.html
29+
include:
30+
# Noetic Ninjemys (May 2020 - May 2025)
31+
# - docker_image: ubuntu:focal
32+
# ros_distribution: noetic
33+
# ros_version: 1
34+
35+
# Humble Hawksbill (May 2022 - May 2027)
36+
- docker_image: ubuntu:jammy
37+
ros_distribution: humble
38+
ros_version: 2
39+
40+
# Iron Irwini (May 2023 - November 2024)
41+
# - docker_image: ubuntu:jammy
42+
# ros_distribution: iron
43+
# ros_version: 2
44+
45+
# # Rolling Ridley (No End-Of-Life)
46+
# - docker_image: ubuntu:jammy
47+
# ros_distribution: rolling
48+
# ros_version: 2
49+
50+
container:
51+
image: ${{ matrix.docker_image }}
52+
steps:
53+
- uses: actions/checkout@v6
54+
with:
55+
path: src/livox_ros_driver2
56+
- name: setup ROS environment
57+
uses: ros-tooling/setup-ros@v0.7
58+
- name: install build dependencies
59+
run: |
60+
apt-get update
61+
apt-get install -y build-essential cmake
62+
- name: install Livox SDK2
63+
run: |
64+
REPO_DIR="/tmp/livox_sdk2_repo"
65+
mkdir -p "$REPO_DIR"
66+
git clone --branch v1.2.5 --depth 1 https://github.com/Livox-SDK/Livox-SDK2.git "$REPO_DIR/Livox-SDK2"
67+
cd "$REPO_DIR/Livox-SDK2"
68+
mkdir -p build && cd build
69+
cmake ..
70+
make -j$(nproc)
71+
make install
72+
ldconfig
73+
rm -rf "$REPO_DIR"
74+
- name: build and test ROS 2
75+
if: ${{ matrix.ros_version == 2 }}
76+
uses: ros-tooling/action-ros-ci@v0.3
77+
with:
78+
import-token: ${{ github.token }}
79+
target-ros2-distro: ${{ matrix.ros_distribution }}
80+
skip-tests: true

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
.vscode
22
build
3-
package.xml
3+
#package.xml
44
__pycache__

CMakeLists.txt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
# Set defaults if not specified, using ROS environment variables when available
2+
if(NOT DEFINED ROS_EDITION)
3+
if(DEFINED ENV{ROS_VERSION})
4+
if("$ENV{ROS_VERSION}" STREQUAL "1")
5+
set(ROS_EDITION "ROS1" CACHE STRING "ROS edition to build for (ROS1 or ROS2)" FORCE)
6+
else()
7+
set(ROS_EDITION "ROS2" CACHE STRING "ROS edition to build for (ROS1 or ROS2)" FORCE)
8+
endif()
9+
else()
10+
set(ROS_EDITION "ROS2" CACHE STRING "ROS edition to build for (ROS1 or ROS2)" FORCE)
11+
endif()
12+
endif()
13+
14+
if(NOT DEFINED HUMBLE_ROS)
15+
if(DEFINED ENV{ROS_DISTRO})
16+
set(HUMBLE_ROS "$ENV{ROS_DISTRO}" CACHE STRING "ROS2 distro name" FORCE)
17+
else()
18+
set(HUMBLE_ROS "humble" CACHE STRING "ROS2 distro name" FORCE)
19+
endif()
20+
endif()
21+
122
# judge which cmake codes to use
223
if(ROS_EDITION STREQUAL "ROS1")
324

@@ -317,6 +338,19 @@ else(ROS_EDITION STREQUAL "ROS2")
317338
EXECUTABLE ${PROJECT_NAME}_node
318339
)
319340

341+
# livox: unified command-line tool (search, setup, reboot, reset, firmware_upgrade)
342+
# Replaces the former livox_set_ip, livox_scan, and livox_reboot tools.
343+
add_executable(livox src/tools/livox.cpp)
344+
target_include_directories(livox PRIVATE
345+
${LIVOX_LIDAR_SDK_INCLUDE_DIR}
346+
)
347+
target_link_libraries(livox
348+
${LIVOX_LIDAR_SDK_LIBRARY}
349+
)
350+
install(TARGETS livox
351+
DESTINATION lib/${PROJECT_NAME}
352+
)
353+
320354
if(BUILD_TESTING)
321355
find_package(ament_lint_auto REQUIRED)
322356
# the following line skips the linter which checks for copyrights
@@ -330,7 +364,7 @@ else(ROS_EDITION STREQUAL "ROS2")
330364

331365
ament_auto_package(INSTALL_TO_SHARE
332366
config
333-
launch_ROS2
367+
launch
334368
)
335369

336370
endif()

0 commit comments

Comments
 (0)