- LidarManager facade (liblidar_manager.so): dlopen plugin discovery, available_drivers map<driver_id, PluginRegistry>, create_lidar_device, config.json load/save with legacy lidarlib migration - Common LidarDriverInterface + DriverInfo/DeviceConfig plugin ABI (extern C get_driver_info / create_driver_instance) - Plugins: driver_rplidar (ported from xlocd, Slamtec SDK), driver_olei, driver_sick_code (TiM CoLa-A), driver_sick_safety (nanoScan3), driver_espe - Diagnostics extended with rplidar health + firmware; FOV filter window, range override and legacy remap window unified in DeviceConfig - Rewritten README, diagnostics doc and examples (list_drivers, example, lidar_app) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xlidar-driver
SDK driver lidar 2D cho Linux, kiến trúc plugin nạp động: mỗi driver là
một file .so độc lập, cùng implement một interface chung
xlidar::LidarDriverInterface; host chỉ cần facade
xlidar::LidarManager (build ra liblidar_manager.so) để khám phá
plugin, đọc metadata và tạo instance thiết bị.
Driver đi kèm: Slamtec RPLIDAR (serial), OLEI (UDP), SICK TiM
(TCP/CoLa-A), SICK nanoScan3 (UDP safety), ESPE LGA60 (TCP/UDP).
Output thống nhất theo định dạng ROS sensor_msgs/LaserScan.
Kiến trúc
xlidar_driver/
├── CMakeLists.txt
├── include/ # API public — host chỉ include từ đây
│ ├── lidar_interface.hpp # LidarDriverInterface, DriverInfo, DeviceConfig,
│ │ # LaserScan/ScanResult, ErrorCode, plugin ABI
│ ├── lidar_diagnostics.hpp # Diagnostics + bit lỗi từng hãng
│ └── lidar_manager.hpp # LidarManager, PluginRegistry, config.json
├── src/
│ ├── CMakeLists.txt # → liblidar_manager.so
│ ├── lidar_manager.cpp # dlopen/dlsym + load/save config.json
│ └── json_mini.hpp
├── plugins/ # mỗi thư mục → một plugin .so
│ ├── CMakeLists.txt # helper xlidar_add_plugin()
│ ├── common/plugin_helpers.hpp
│ ├── driver_rplidar/ # → driver_rplidar.so (rplidar_c1_driver)
│ ├── driver_olei/ # → driver_olei.so (olei_lidar_driver)
│ ├── driver_sick_code/ # → driver_sick_code.so (sick_tim_driver)
│ ├── driver_sick_safety/ # → driver_sick_safety.so (sick_nanoscan3_driver)
│ └── driver_espe/ # → driver_espe.so (espe_lga60_driver)
├── examples/ # list_drivers, example, lidar_app
└── docs/diagnostics.md # nghiên cứu layout dữ liệu chẩn đoán
-
LidarManager quét thư mục plugin,
dlopentừng.so, resolve hai entry point C rồi đăng ký vàoavailable_drivers()— map<driver_id, PluginRegistry>trong đóPluginRegistry = {DriverInfo, file_path}. -
Plugin ABI — mỗi plugin export đúng hai symbol C:
XLIDAR_PLUGIN_EXPORT void get_driver_info(xlidar::DriverInfo* out); XLIDAR_PLUGIN_EXPORT xlidar::LidarDriverInterface* create_driver_instance(const xlidar::DeviceConfig* cfg); -
DriverInfo — plugin tự đăng ký danh tính:
vendor,model(dòng thiết bị driver phụ trách — một driver có thể cover cả series),driver_id(định danh duy nhất, ví dụrplidar_c1_driver),description(mô tả ngắn: thiết bị hỗ trợ, transport, ghi chú), kèm metadata cho UI:transport(serial/udp/tcp),transport_selectable,supported_models. -
Plugin được giữ nguyên trong bộ nhớ tới khi manager bị hủy — manager phải sống lâu hơn mọi instance nó tạo ra.
Cài đặt
Yêu cầu: Linux, CMake ≥ 3.16, C++17. Không có dependency ngoài (pthread, dl). Plugin rplidar cần thêm source SDK của Slamtec.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DXLIDAR_RPLIDAR_SDK_DIR=/path/to/rplidar_sdk # dir chứa include/ + src/
cmake --build build -j"$(nproc)"
Kết quả: build/src/liblidar_manager.so, plugin trong build/plugins/*.so,
demo trong build/examples/. Không truyền XLIDAR_RPLIDAR_SDK_DIR thì các
vị trí quen thuộc được tự dò (third_party/rplidar_sdk,
../rplidar_sdk/sdk, ../xloc-monorepo/xlocd/deps/rplidar_sdk); không thấy
SDK thì plugin rplidar bị bỏ qua, phần còn lại build bình thường.
Tùy chọn: -DXLIDAR_BUILD_EXAMPLES=OFF. Hỗ trợ cmake --install +
find_package(xlidar_driver) (target xlidar::lidar_manager).
Sử dụng
Khám phá driver và đọc scan
#include "lidar_manager.hpp" // kéo theo lidar_interface.hpp
xlidar::LidarManager manager("plugins");
manager.load_all_plugins();
for (const auto& [id, plugin] : manager.available_drivers())
printf("%s — %s\n", id.c_str(), plugin.info.description.c_str());
xlidar::DeviceConfig cfg;
cfg.driver_id = "sick_tim_driver";
cfg.model = "SICK-TIM7xx";
cfg.ip = "192.168.0.1"; // port 0 = port mặc định của driver
auto lidar = manager.create_lidar_device(cfg); // nullptr nếu driver_id lạ
if (lidar->open() != xlidar::ErrorCode::Ok) {
fprintf(stderr, "open: %s\n", xlidar::to_string(lidar->last_error()));
return 1;
}
xlidar::ScanResult r;
if (lidar->recv_scan(r, 1000)) {
// r.scan : LaserScan — điểm đo, format ROS
// r.info : ExtraInfo — metadata tuỳ model
}
Driver serial (rplidar) dùng cfg.serial_port + cfg.baudrate thay cho
ip/port. Xem examples/example.cpp.
Chế độ callback
lidar->set_scan_callback([](const xlidar::ScanResult& r) { /* mỗi vòng quét */ });
while (running) lidar->spin_once();
Callback chỉ phát từ spin_once() — chọn một kiểu bơm dữ liệu:
recv_scan() (poll) hoặc callback + spin_once().
Sẵn sàng & chẩn đoán thiết bị
if (!lidar->wait_ready(5000)) { /* chưa có scan sạch nào trong 5s */ }
xlidar::Diagnostics d = lidar->get_diagnostics();
if (d.has_fault()) {
d.monitor_fault(); // OLEI: motor/giám sát
d.voltage_fault(); // OLEI: điện áp
d.temperature_fault(); // OLEI: nhiệt độ
d.sick_error(); // SICK TiM: device error
d.pollution_error(); // SICK TiM: kính bẩn nặng
d.contamination_error(); // nanoScan3: kính bẩn nặng
d.manipulation(); // nanoScan3: nghi bị che/can thiệp
d.espe_fault(); // ESPE: từ lỗi thiết bị
d.rplidar_fault(); // RPLIDAR: health = Error (kèm rplidar_error_code)
printf("fault: %s\n", xlidar::to_string(d).c_str());
} else if (d.has_warning()) {
// pollution/contamination warning, rplidar health warning
}
d.healthy() = đã có dữ liệu và không fault. is_ready(max_age_ms) = đang
mở + healthy + scan mới nhất chưa quá hạn — diagnostics chỉ refresh qua
recv_scan()/spin_once(), nên gọi từ chính thread bơm dữ liệu. Layout
chẩn đoán từng giao thức: docs/diagnostics.md.
Xử lý lỗi & lifecycle
open() trả về ErrorCode; last_error() giữ kết quả gần nhất.
| ErrorCode | Ý nghĩa |
|---|---|
Ok |
Thành công |
AlreadyOpen |
open() khi đang mở — kết nối cũ giữ nguyên |
NotOpen |
recv_scan()/spin_once() khi chưa open() |
InvalidAddress |
Chuỗi IP không hợp lệ |
PortInUse / BindFailed / SocketError |
Lỗi bind/socket (driver UDP) |
ConnectionRefused / ConnectionFailed / Timeout |
TCP connect bị từ chối / không tới được / quá hạn |
HandshakeFailed |
Nối được nhưng lệnh start-stream/start-scan thất bại |
SerialError |
Cổng serial không tồn tại / không mở được (rplidar) |
DeviceError |
Thiết bị tự báo fault (rplidar health check lúc open()) |
DeviceDisconnected |
Thiết bị đóng kết nối / lỗi recv giữa chừng |
InvalidConfig |
DeviceConfig không dùng được với driver |
Lifecycle an toàn với mọi thứ tự gọi: close() idempotent, open() lặp trả
AlreadyOpen, sau close() mở lại được. Mỗi instance độc lập hoàn toàn —
mỗi lidar một thread, không cần khóa.
Cấu hình JSON
lidar_app (binary demo) đọc config.json, mở mỗi lidar một thread; API:
xlidar::load_config(path) / save_config(path, cfg) /
manager.create_from_config_file(path).
{
"lidars": [
{"name":"front", "driver_id":"olei_lidar_driver", "model":"AUTO", "ip":"192.168.100.100", "port":2368},
{"name":"sick1", "driver_id":"sick_tim_driver", "model":"SICK-TIM7xx", "ip":"192.168.0.1", "port":2111},
{"name":"nano1", "driver_id":"sick_nanoscan3_driver","model":"SICK-nanoScan3", "ip":"0.0.0.0", "port":6060},
{"name":"espe1", "driver_id":"espe_lga60_driver", "model":"ESPE-LGA60", "ip":"192.168.1.88", "port":8080},
{"name":"rp1", "driver_id":"rplidar_c1_driver", "model":"AUTO", "serial_port":"/dev/ttyUSB0", "baudrate":460800}
]
}
Các trường DeviceConfig (mọi trường có default, chỉ khai báo cái cần):
| Trường | Ý nghĩa |
|---|---|
driver_id |
Plugin phụ trách thiết bị (bắt buộc) |
model |
Một trong supported_models của driver; tên lạ → mặc định của driver |
ip / port |
Driver mạng: UDP = địa chỉ bind cục bộ, TCP = địa chỉ thiết bị; port 0 = mặc định của driver |
serial_port / baudrate |
Driver serial (rplidar); mặc định /dev/ttyUSB0 @ 460800 |
inverted |
true nếu lidar lắp úp ngược — driver tự đảo góc |
use_udp |
Chỉ driver transport_selectable (ESPE): chuyển sang UDP |
angle_min_deg / angle_max_deg |
Cửa sổ FOV hợp lệ (hệ góc có dấu, 0 = phía trước): điểm ngoài cửa sổ thành NaN. Bỏ trống = tắt |
range_min_m / range_max_m |
Ghi đè dải đo; 0 = theo model |
remap_angle_min_deg / remap_angle_max_deg |
(legacy) remap tuyến tính góc output, không cắt điểm |
extra |
map chuỗi→chuỗi cho tuỳ chọn riêng của driver |
File format cũ của lidarlib ({"brand": "OLEI", ...}) được migrate tự động
khi load: brand+model → driver_id, cặp angle_*_deg cũ → remap.
Driver đi kèm
| driver_id | Plugin | Vendor / dòng máy | Transport | Model hỗ trợ | Ghi chú |
|---|---|---|---|---|---|
rplidar_c1_driver |
driver_rplidar.so |
Slamtec RPLIDAR | serial | AUTO, C1 |
Build trên SDK vendor; default C1 @ 460800; A/S series dùng được với baud tương ứng. Health check lúc open(), model/firmware tự nhận |
olei_lidar_driver |
driver_olei.so |
OLEI 2D | udp | AUTO, VB, VF, LR-1F, LR-1FMI, LR-1BS5, LR-16F, GS1-5 |
Tự nhận diện giao thức Family A/B/C theo frame ID từng gói; AUTO tự dò model (Family B/C). Port mặc định 2368 |
sick_tim_driver |
driver_sick_code.so |
SICK TiM 5xx/7xx | tcp | SICK-TIM5xx, SICK-TIM571, SICK-TIM7xx |
SOPAS/CoLa-A; open() tự start stream. Port 2111. Verify trên TiM781S thật |
sick_nanoscan3_driver |
driver_sick_safety.so |
SICK nanoScan3/microScan3 | udp | SICK-nanoScan3 |
Receiver thụ động UDP safety-data; đích UDP cấu hình sẵn bằng Safety Designer. Port 6060. Chưa verify phần cứng |
espe_lga60_driver |
driver_espe.so |
ESPE LGA60 | tcp (+udp) | ESPE-LGA60 |
FOV 320°; open() gửi RAuto; tham số thiết bị theo tool Windows của hãng. Port 8080. Chưa verify phần cứng |
Chi tiết giao thức từng hãng (frame layout, offset góc, đơn vị) nằm trong comment đầu mỗi file plugin và docs/diagnostics.md.
Kiểu dữ liệu
ScanResult = { LaserScan scan; ExtraInfo info; }
LaserScan cùng field và đơn vị với ROS sensor_msgs/LaserScan:
| Field | Ý nghĩa |
|---|---|
angle_min / angle_max / angle_increment |
Góc (rad); góc điểm i = angle_min + i·increment. Driver mạng: hệ góc có dấu, 0 = phía trước; rplidar: hệ góc thiết bị [0, 2π) |
ranges |
Khoảng cách (m); NaN = điểm không hợp lệ (rplidar/FOV filter), ∞ = không có phản hồi (nanoScan3/ESPE) |
intensities |
Cường độ 0–255 |
range_min / range_max |
Dải đo hợp lệ (m) |
timestamp_ms |
Đồng hồ thiết bị (ms); 0 nếu giao thức không có |
time_increment / scan_time |
Chỉ rplidar đo được (chu kỳ grab thực); driver khác = 0 |
ExtraInfo: metadata thô tuỳ giao thức (detected_model, byte lỗi OLEI,
status SICK, health RPLIDAR, ...) — field thiết bị không có giữ
std::nullopt. Diagnostics (từ get_diagnostics() hoặc
decode_diagnostics(info)) là bản decode tiện dùng: has_fault() /
has_warning() / healthy() / to_string() , kèm model, firmware
(rplidar), device_timestamp_ms.
Viết một plugin mới
-
Tạo
plugins/driver_<tên>/với<tên>_driver.cpp+CMakeLists.txt(xlidar_add_plugin(driver_<tên> <tên>_driver.cpp)), thêmadd_subdirectoryvàoplugins/CMakeLists.txt. -
Implement class kế thừa
xlidar::LidarDriverInterface— đủopen/close/recv_scan/spin_once/set_scan_callback/detected_model/is_open/get_driver_info, và cập nhậtget_diagnostics()+mark_scan_decoded()mỗi vòng quét. -
Export hai entry point:
XLIDAR_PLUGIN_EXPORT void get_driver_info(xlidar::DriverInfo* out) { *out = kDriverInfo; } XLIDAR_PLUGIN_EXPORT xlidar::LidarDriverInterface* create_driver_instance(const xlidar::DeviceConfig* cfg) { return new MyDriver(...); } -
Chọn
driver_idduy nhất, mô tảdescriptionrõ driver phụ trách nhóm thiết bị nào; dùngapply_device_config()trongplugins/commonđể tôn trọng các cửa sổ góc/dải đo chung.
Plugin build với -fvisibility=hidden — chỉ hai entry point lộ ra ngoài.
Manager và plugin phải build cùng toolchain (chúng trao đổi kiểu C++).