# 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 ```bash 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: ```bash cmake --install build --prefix "$HOME/.local" # hoặc sudo với /usr/local ``` ```cmake find_package(lidarlib REQUIRED) target_link_libraries(my_app PRIVATE lidarlib::lidarlib) ``` ## Quick start ```cpp #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 lidar = lidarlib::make_lidar(c); lidar->open(); lidarlib::ScanResult r; lidar->recv_scan(r, 1000); // 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()`: ```cpp 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. ## 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. ```bash ./build/lidar_app [my_config.json] ``` ```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/max` lấy từ `ModelConfig` (đặt sẵn, không đo mỗi scan); `time_increment/scan_time` luô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/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()` sẽ thất bại. Kiểm tra: `ss -lunp | grep 2368`. - `inverted` đã verify bằng sniff sống: `false` góc tăng dần, `true` góc giảm dần cùng bước.