11 KiB
Lidarlib
Thư viện C++17 cho lidar OLEI (UDP) và SICK (TCP/UDP). Build bằng CMake
ra shared lib, hỗ trợ find_package(lidarlib). Mọi driver dùng chung một
interface lidarlib::Lidar và một hàm khởi tạo duy nhất lidarlib::make_lidar().
- Tự nhận diện họ giao thức OLEI (Family A/B/C) theo từng gói tin
- Tự dò model (
MODEL_AUTO) với Family B/C - Chạy nhiều lidar song song (mỗi instance độc lập, an toàn đa luồng)
- Output chuẩn ROS
sensor_msgs/LaserScan(radian, mét) - Không có UI — tự viết giao diện trên API này
Build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
Sinh ra build/liblidarlib.so (chỉ phụ thuộc pthread) và các binary demo
(example, test_dual, sick_example, nanoscan_example, lidar_app).
Tùy chọn: -DLIDARLIB_BUILD_EXAMPLES=OFF (tắt demo),
-DBUILD_SHARED_LIBS=OFF (static lib).
Cài đặt và dùng từ project khác:
cmake --install build --prefix "$HOME/.local" # hoặc sudo với /usr/local
find_package(lidarlib REQUIRED)
target_link_libraries(my_app PRIVATE lidarlib::lidarlib)
Quick start
#include "lidarlib/lidarlib.hpp" // toàn bộ API trong 1 include
lidarlib::LidarConfig c{"front", "192.168.1.10", 2368, "AUTO", false, "OLEI"};
std::unique_ptr<lidarlib::Lidar> lidar = lidarlib::make_lidar(c);
lidarlib::ErrorCode err = lidar->open();
if (err != lidarlib::ErrorCode::Ok) {
fprintf(stderr, "open that bai: %s\n", lidarlib::to_string(err));
return 1;
}
lidarlib::ScanResult r;
if (!lidar->recv_scan(r, 1000)) {
// Timeout / DeviceDisconnected / NotOpen — xem lidar->last_error()
}
// r.scan : LaserScan — format sensor_msgs/LaserScan của ROS, chung mọi lidar
// r.info : ExtraInfo — thông tin thêm tuỳ family/model
printf("%zu diem, model=%s\n", r.scan.ranges.size(), r.info.detected_model.c_str());
Có thể khởi tạo driver trực tiếp thay vì qua make_lidar():
lidarlib::Driver olei(lidarlib::MODEL_AUTO, "192.168.100.100", 2368);
lidarlib::SickDriver tim (lidarlib::MODEL_SICK_TIM571, "192.168.0.1", 2111);
lidarlib::NanoScanDriver nano(lidarlib::MODEL_SICK_NANOSCAN3, "0.0.0.0", 6060);
Ngoài recv_scan() blocking còn có callback: set_scan_callback() +
spin_once() trong vòng lặp riêng.
Error handling & lifecycle
open() trả về lidarlib::ErrorCode (header lidarlib/error.hpp,
to_string() để log). Các code chính:
| ErrorCode | Khi nào |
|---|---|
Ok |
Thành công |
AlreadyOpen |
Gọi open() khi đang mở — kết nối cũ giữ nguyên |
NotOpen |
Gọi recv_scan()/spin_once() khi chưa open() |
InvalidAddress |
Chuỗi IP không hợp lệ |
PortInUse |
Port local đã bị chiếm (bind EADDRINUSE/EACCES) |
BindFailed / SocketError |
Lỗi bind khác / không tạo được socket |
ConnectionRefused / ConnectionFailed / Timeout |
TCP connect (SICK TiM) bị từ chối / không tới được / quá 2s |
HandshakeFailed |
TCP nối được nhưng gửi sEN LMDscandata 1 thất bại |
DeviceDisconnected |
Thiết bị đóng kết nối / lỗi recv giữa chừng |
Trạng thái instance:
is_open()— socket đang mở hay không.last_error()— kết quả của lầnopen()/recv_scan()/spin_once()gần nhất (recv_scan()trảfalsethì gọi hàm này để biếtTimeouthayDeviceDisconnected).- Lifecycle chịu lỗi mọi thứ tự gọi:
close()trướcopen()hoặcclose()hai lần là no-op;open()hai lần trảAlreadyOpenvà không đụng kết nối đang chạy; sauclose()có thểopen()lại (state scan dở được reset).
Cấu hình (config.json)
lidar_app là app mẫu headless: đọc config.json, mở từng lidar bằng
make_lidar(), một thread mỗi con.
./build/lidar_app [my_config.json]
{
"lidars": [
{"name":"front", "ip":"192.168.100.100", "port":2368, "brand":"OLEI", "model":"AUTO", "inverted":false},
{"name":"rear", "ip":"192.168.100.100", "port":2369, "brand":"OLEI", "model":"AUTO", "inverted":true},
{"name":"sick1", "ip":"192.168.0.1", "port":2111, "brand":"SICK", "model":"SICK-TIM571"},
{"name":"nano1", "ip":"0.0.0.0", "port":6060, "brand":"SICK", "model":"SICK-nanoScan3"}
]
}
| Trường | Ý nghĩa |
|---|---|
brand |
"OLEI" (mặc định) hoặc "SICK" |
model |
Tên trong bảng model bên dưới; tên lạ → mặc định của hãng (AUTO / SICK-TIM571). Với SICK, "SICK-nanoScan3" → driver UDP, còn lại → driver TCP |
inverted |
Chỉ OLEI: true nếu lidar lắp úp ngược, driver tự đảo góc về hệ quy chiếu xe |
angle_min_deg / angle_max_deg |
Tuỳ chọn: remap tuyến tính góc output sang cửa sổ này (độ). Không cắt điểm nào, chỉ ghi lại angle_min/max/increment. Bỏ trống (±360) = tắt |
Đọc/ghi bằng lidarlib::load_config(path) / lidarlib::save_config(path, cfg).
Model
OLEI (brand = "OLEI", UDP, port mặc định 2368)
| Constant | FOV (°) | Range (m) | Ghi chú |
|---|---|---|---|
MODEL_VB |
-135…135 | 0.05…30 | 2D 270°, Family A |
MODEL_VF |
-180…180 | 0.05…30 | 2D 360°, Family A |
MODEL_LR1F |
-180…180 | 0.05…50 | Family A; 0° thô của máy chỉ về đuôi (offset +180°) |
MODEL_LR1FMI |
-180…180 | 0.05…30 | Family B, ~2400 điểm/vòng; 0° thô chỉ về đuôi (offset +180°) |
MODEL_LR1BS5 |
-180…180 | 0.05…30 | Family B |
MODEL_LR16F |
-135…135 | 0.05…30 | 3D 16-line |
MODEL_GS15 |
-180…180 | 0.05…30 | Family C/V3 — chưa verify phần cứng |
MODEL_AUTO |
-180…180 | 0.05…30 | Không biết trước model; tự dò với Family B (chuỗi tên) và C (magic). Family A không mang tên model nên giữ FOV rộng |
Driver nhận diện họ giao thức theo Frame ID mỗi gói:
Family A 0xFAF0 (header 20B, 3B/điểm, CRC32) ·
Family B 0xFEF0 (header 40B kèm tên model ASCII, 8B/điểm) ·
Family C/V3 0xFEAC (header 48B, 2/4B/điểm — port từ driver C#, chưa verify).
Tên model thật đọc từ packet xem qua detected_model() hoặc
result.info.detected_model.
SICK (brand = "SICK")
| Constant | FOV (°) | Range (m) | Transport |
|---|---|---|---|
MODEL_SICK_TIM5XX |
-135…135 | 0.05…10 | TCP/SOPAS (CoLa-A), port 2111 |
MODEL_SICK_TIM571 |
-135…135 | 0.05…25 | TCP/SOPAS, port 2111 |
MODEL_SICK_TIM7XX |
-135…135 | 0.05…25 | TCP/SOPAS, port 2111 |
MODEL_SICK_NANOSCAN3 |
-137.5…137.5 | 0.05…40 | UDP safety-data, port 6060 |
TiM (SickDriver) — open() tự gửi sEN LMDscandata 1 để bắt đầu stream.
Hệ góc trên dây đặt 90° = trước mặt nên preset có angle_offset_deg = -90,
output ra -135…135° với 0° = phía trước. Đã verify trên TiM781S thật
(811 điểm/scan, increment 0.333°, DIST1/RSSI1 đúng layout). Chưa verify:
encoder, kênh 8-bit, thông số TiM5xx/571.
nanoScan3 (NanoScanDriver) — UDP receiver thụ động: chỉ bind cổng và
parse datagram; đích UDP phải cấu hình sẵn trong SICK Safety Designer
(driver không bắt tay CoLa2). Layout port từ sick_safetyscanners (Apache-2.0).
Chưa verify phần cứng thật — mới test bằng gói tổng hợp qua loopback.
Output
ScanResult { LaserScan scan; ExtraInfo info; } mỗi vòng quét:
LaserScan— cùng field/đơn vị với ROS:angle_min/max/increment(rad, đã unwrap liên tục, không giới hạn ±π),ranges[](m),intensities[](0-255),timestamp_ms(đồng hồ thiết bị, 0 nếu family không có).range_min/maxlấy từModelConfig(đặt sẵn, không đo mỗi scan);time_increment/scan_timeluôn 0.ExtraInfo— field tuỳ family:detected_model,error_status(Family A),distance_scale_mm(A/B), và các trường raw của Family C (chưa verify). Field thiết bị không có giữstd::nullopt.
Cấu trúc source
| File | Vai trò |
|---|---|
include/lidarlib/error.hpp |
enum class ErrorCode + to_string() |
include/lidarlib/lidar.hpp |
Data model, interface Lidar, driver OLEI, các MODEL_* OLEI |
include/lidarlib/sick_lidar.hpp |
SickDriver, NanoScanDriver, các MODEL_SICK_* |
include/lidarlib/config.hpp |
LidarConfig, load/save JSON, make_lidar() |
src/olei_lidar.cpp |
Parse Family A/B/C, CRC, gom scan |
src/sick_lidar.cpp |
Parse CoLa-A (TiM) + safety-data UDP (nanoScan3) |
src/lidar_config.cpp |
Bảng model/brand, config JSON, factory |
examples/ |
Demo: 1 lidar, 2 lidar song song, SICK TiM, nanoScan3, app khung |
Ghi chú
- Nếu port UDP đã bị app khác giữ (không bật
SO_REUSEPORT),open()trảErrorCode::PortInUse. Kiểm tra:ss -lunp | grep 2368. invertedđã verify bằng sniff sống:falsegóc tăng dần,truegóc giảm dần cùng bước.
Changelog
2026-07-06 — Error handling & lifecycle API
- Header mới
lidarlib/error.hpp:enum class ErrorCode—Ok,AlreadyOpen,NotOpen,SocketError,InvalidAddress,PortInUse,BindFailed,ConnectionRefused,ConnectionFailed,HandshakeFailed,Timeout,DeviceDisconnected— kèmto_string()để log. open()đổi chữ kýbool→ErrorCodetrên cả 3 driver (Driver,SickDriver,NanoScanDriver). Lỗi phân loại từerrnothật: bindEADDRINUSE→PortInUse, connect TCP bị từ chối →ConnectionRefused, quá 2s →Timeout, gửi lệnh start-stream fail →HandshakeFailed, IP sai format →InvalidAddress.- API trạng thái mới trên interface
Lidar:is_open()— socket đang mở hay không (cả 3 driver implement);last_error()— kết quả lầnopen()/recv_scan()/spin_once()gần nhất;recv_scan()trảfalsethì gọi hàm này để biếtTimeouthayDeviceDisconnected.
- Lifecycle chịu lỗi mọi thứ tự gọi:
close()trướcopen()hoặc gọi hai lần là no-op;open()khi đang mở trảAlreadyOpenvà không đụng kết nối đang chạy;recv_scan()/spin_once()khi chưa mở trảfalse+NotOpen; sauclose()có thểopen()lại (state scan dở được reset). NanoScanDriverbỏSO_REUSEADDR: với UDP không có tác dụng (không có TIME_WAIT) mà còn che mất lỗi trùng port — giờPortInUsebáo được thật.- ⚠️ Breaking change: code cũ viết
if (!lidar->open())bị đảo ngược logic vìErrorCode::Ok == 0— phải đổi thànhif (lidar->open() != lidarlib::ErrorCode::Ok).