Fix Family B angle decode + add LR-1FMI model
parse_family_b() dùng sai hệ số góc 0.25°/LSB; theo spec Olei chính hãng (Olei.LidarSensor/LidarDataBlock.GetAngleDegrees) AngleRaw là 0.01°/LSB. Sai 25× khiến điểm bị gán nhầm góc → một phòng bị bôi thành vòng tròn trên RViz. Đã verify với thiết bị thật OLELR-1FMI: sau khi sửa ra 2400 điểm/vòng, 0–359.9°, đúng hình học môi trường. - Đổi hệ số góc 0.25° → 0.01° trong parse_family_b(). - Bỏ qua block invalid (AngleRaw >= 0xFF00) theo spec. - Dò ranh giới vòng quay PER-POINT thay vì per-packet (một gói có thể chứa >1 vòng), tránh gộp nhiều vòng vào một scan. - Thêm model LR-1FMI (360°, 0.01°/LSB, ~2400 pts/rev) vào bảng model + kModelTable, đặt "1FMI" trước "1F" để khớp đúng chuỗi tên. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
346
README.md
Normal file
346
README.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Lidarlib
|
||||
|
||||
Thư viện C++17 cho lidar (OLEI + SICK), build bằng CMake ra **shared lib `.so`**
|
||||
(`lidarlib::lidarlib`), hỗ trợ `find_package()` để link vào project khác. Tự nhận
|
||||
diện họ giao thức (Family A/B/C) theo từng gói, hỗ trợ chạy nhiều lidar song
|
||||
song, gộp cả OLEI (UDP) và SICK (TCP) sau **một hàm config duy nhất**
|
||||
`lidarlib::make_lidar()`. **Không có Web UI** — người dùng tự viết giao diện trên
|
||||
API C++ này (include header + link `.so`).
|
||||
|
||||
## Tóm tắt API (cho người viết giao diện)
|
||||
|
||||
```cpp
|
||||
#include "lidarlib/lidarlib.hpp" // gộp toàn bộ API trong 1 include
|
||||
|
||||
// 1) MỘT hàm config: từ LidarConfig -> handle chung (OLEI hoặc SICK)
|
||||
lidarlib::LidarConfig c{"front", "192.168.1.10", 2368, "AUTO", false, "OLEI"};
|
||||
std::unique_ptr<lidarlib::Lidar> lidar = lidarlib::make_lidar(c);
|
||||
lidar->open();
|
||||
|
||||
// 2) HAI loại dữ liệu mỗi vòng quét
|
||||
lidarlib::ScanResult r;
|
||||
lidar->recv_scan(r, 1000);
|
||||
// r.scan : lidarlib::LaserScan — chung cho mọi lidar, đúng format sensor_msgs/LaserScan của ROS
|
||||
// r.info : lidarlib::ExtraInfo — thông tin thêm, tuỳ family/model thực tế
|
||||
|
||||
// (tuỳ chọn) lưu/đọc danh sách lidar ra file:
|
||||
lidarlib::Config cfg = lidarlib::load_config("config.json");
|
||||
lidarlib::save_config("config.json", cfg);
|
||||
```
|
||||
|
||||
`lidarlib::Lidar` là interface chung; `lidarlib::Driver` (OLEI/UDP) và `lidarlib::SickDriver`
|
||||
(SICK/TCP) đều kế thừa nó, nên giao diện chỉ cần thao tác qua `lidarlib::Lidar*` mà
|
||||
không phải phân biệt hãng. Vẫn có thể `new` thẳng `Driver`/`SickDriver` nếu muốn.
|
||||
|
||||
## Kiến trúc
|
||||
|
||||
| File | Vai trò |
|
||||
|------|---------|
|
||||
| `include/lidarlib/lidarlib.hpp` | Header tổng hợp — include 1 dòng là có cả data model + 2 driver + config + factory |
|
||||
| `include/lidarlib/lidar.hpp` | API public: `LaserScan`, `ExtraInfo`, `ScanResult`, `ModelConfig`, interface `Lidar`, class `Driver` |
|
||||
| `src/olei_lidar.cpp` | Parse Family A (0xFAF0), Family B (0xFEF0), Family C/V3 (0xFEAC), CRC, gom scan |
|
||||
| `include/lidarlib/config.hpp` + `src/olei_config.cpp` | `Config`/`LidarConfig` (gồm `brand`: `"OLEI"`/`"SICK"`), load/save `config.json`, tra cứu `ModelConfig` theo tên/theo hãng, và **hàm config `make_lidar()`** |
|
||||
| `src/json_mini.hpp` | Parser/serializer JSON tối giản, chỉ dùng nội bộ cho `olei_config` (load/save `config.json`) |
|
||||
| `include/lidarlib/sick_lidar.hpp` + `src/sick_lidar.cpp` | Driver riêng cho lidar **SICK TiM5xx/7xx** — giao thức SOPAS/CoLa-A qua TCP (port 2111), khác hoàn toàn UDP binary của OLEI. **Đã verify bằng TiM781S thật** (xem mục riêng bên dưới) |
|
||||
| `examples/example.cpp` | Demo 1 lidar, `recv_scan()` blocking |
|
||||
| `examples/test_dual.cpp` | Demo 2 lidar song song (2 thread) |
|
||||
| `examples/sick_example.cpp` | Demo driver SICK TiM, `recv_scan()` blocking qua TCP |
|
||||
| `examples/lidar_app.cpp` | Khung app headless: đọc `config.json` → `make_lidar()` từng con → in scan. Thay vòng `printf` bằng giao diện của bạn |
|
||||
| `CMakeLists.txt` | Build `lidarlib` (OLEI + SICK + factory + config, chỉ phụ thuộc pthread) thành `.so`, cài `install()`/`find_package()` |
|
||||
|
||||
Ba họ giao thức được driver tự nhận diện theo Frame ID/magic trong từng gói:
|
||||
|
||||
- **Family A** (`0xFAF0`) — VB/VF/LR-1F. Header 20B + block 3B/điểm. Có CRC32.
|
||||
- **Family B** (`0xFEF0`) — LR-1BS5/LR-1BS2. Header 40B (preamble `0x010F` +
|
||||
frame id ở offset [2-3], chuỗi tên model ASCII ở offset [7-17)) + block
|
||||
8B/điểm.
|
||||
- **Family C / protocol V3** (`0xFEAC`) — GS1-5. Header 48B, block 2/4B/điểm
|
||||
tùy byte `Types`. **Port từ driver C# `OleiGS15Driver.cs`
|
||||
(RobotNet10.RobotApp), CHƯA verify bằng phần cứng GS1-5 thật** (không có
|
||||
thiết bị để sniff) — chỉ test bằng packet giả lập tự dựng theo đúng cấu trúc
|
||||
header.
|
||||
|
||||
## Output: 2 loại
|
||||
|
||||
`Driver::recv_scan()` (và callback `set_scan_callback`) trả về
|
||||
`ScanResult { LaserScan scan; ExtraInfo info; }` mỗi khi gom đủ 1 vòng quay:
|
||||
|
||||
```cpp
|
||||
lidarlib::Driver drv(lidarlib::MODEL_AUTO, "192.168.100.100", 2369);
|
||||
drv.open();
|
||||
lidarlib::ScanResult result;
|
||||
drv.recv_scan(result, 2000);
|
||||
printf("%zu diem, model=%s\n", result.scan.ranges.size(), result.info.detected_model.c_str());
|
||||
```
|
||||
|
||||
**`LaserScan`** — cùng tên field/đơn vị với `sensor_msgs/LaserScan` của ROS
|
||||
(radian, mét, giây):
|
||||
|
||||
| Field | Ý nghĩa |
|
||||
|---|---|
|
||||
| `timestamp_ms` | Đồng hồ thiết bị (ms từ lúc bật nguồn); = 0 nếu family không có (xem `ExtraInfo`) |
|
||||
| `angle_min`/`angle_max`/`angle_increment` | rad — đã unwrap liên tục, KHÔNG bị giới hạn `[-π,π]` |
|
||||
| `time_increment`/`scan_time` | Luôn = 0 — thiết bị không báo timing chi tiết đó |
|
||||
| `range_min`/`range_max` | m — lấy từ `ModelConfig` (giá trị đặt sẵn, **không đo được mỗi scan**) |
|
||||
| `ranges[]`/`intensities[]` | m / 0-255 (đọc lại thành float như ROS) |
|
||||
|
||||
**`ExtraInfo`** — thông tin thêm tuỳ family/model thực tế của packet, field
|
||||
nào thiết bị không có thì giữ `std::nullopt`:
|
||||
|
||||
| Field | Family | Verify hardware? |
|
||||
|---|---|---|
|
||||
| `detected_model` | mọi family (qua `MODEL_AUTO`) | Family B verify bằng sniff sống |
|
||||
| `error_status` | Family A | Verify |
|
||||
| `distance_scale_mm` | Family A/B | Verify |
|
||||
| `rotation_raw` | Family A | Raw, chưa decode ý nghĩa |
|
||||
| `distance_ratio_raw`, `scan_frequency_raw`, `input_status`, `output_status`, `field_status`, `status_flags` | Family C/GS1-5 | Raw, **chưa verify hardware thật** |
|
||||
|
||||
Vì sao cần "unwrap": góc từng điểm được lọc theo FOV ở hệ **có dấu**
|
||||
`[-180, 180]` (0 = phía trước, dương = bên trái) — nhưng hệ này gãy ở biên
|
||||
±180° đối với lidar quét 360°. Trước khi đưa vào `LaserScan`, driver unwrap
|
||||
lại thành một dải góc liên tục trong từng vòng quay (`Driver::push_point()`
|
||||
trong `src/olei_lidar.cpp`), nên `angle_min`/`angle_max`/`ranges[]` luôn đơn
|
||||
điệu — đúng kiểu mảng mà `sensor_msgs/LaserScan` kỳ vọng.
|
||||
|
||||
## Kết nối lidar
|
||||
|
||||
Mạng tham chiếu trên host này (`/home/robotics`):
|
||||
|
||||
```
|
||||
eth0: 192.168.100.100/24
|
||||
├─ front (scan_1): DeviceIp 192.168.100.11, DevicePort 2368
|
||||
└─ rear (scan_2): DeviceIp 192.168.100.12, DevicePort 2369
|
||||
```
|
||||
|
||||
(Khớp với `RobotApp/RobotNet10.RobotApp/appsettings.json`, các mục
|
||||
`Olei-front`/`Olei-rear`.)
|
||||
|
||||
Kiểm tra kết nối trước khi test:
|
||||
|
||||
```bash
|
||||
ip -4 addr show eth0 # phải thấy inet 192.168.100.100/24
|
||||
ping -c1 192.168.100.11 # front
|
||||
ping -c1 192.168.100.12 # rear
|
||||
```
|
||||
|
||||
**Lưu ý quan trọng:** nếu `RobotNet10.RobotApp` đang chạy, nó bind sẵn port
|
||||
2368/2369 (không bật `SO_REUSEPORT`) → driver standalone sẽ bind lỗi
|
||||
(`Khong mo duoc socket... interface khong ton tai?`). Kiểm tra ai đang giữ port:
|
||||
|
||||
```bash
|
||||
ss -lunp | grep -E '2368|2369'
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
```
|
||||
|
||||
Sinh ra `build/liblidarlib.so` (driver OLEI + SICK + factory + config, không
|
||||
phụ thuộc gì ngoài pthread) và 4 binary demo (`example`, `test_dual`,
|
||||
`sick_example`, `lidar_app`). Tắt build demo bằng `-DLIDARLIB_BUILD_EXAMPLES=OFF`.
|
||||
Muốn ra static lib `.a` thay vì `.so` thì thêm `-DBUILD_SHARED_LIBS=OFF`.
|
||||
|
||||
Cài vào hệ thống để dùng `find_package(lidarlib)` từ project khác — `/usr/local`
|
||||
chỉ root mới ghi được nên cần `sudo`, không thì `cmake --install` báo lỗi
|
||||
permission denied:
|
||||
|
||||
```bash
|
||||
sudo cmake --install build --prefix /usr/local
|
||||
```
|
||||
|
||||
Muốn cài không cần `sudo` thì đổi prefix sang thư mục riêng trong `$HOME` (vd
|
||||
`~/.local`), rồi thêm `-DCMAKE_PREFIX_PATH=~/.local` khi configure project nào
|
||||
gọi `find_package(lidarlib)`:
|
||||
|
||||
```bash
|
||||
cmake --install build --prefix "$HOME/.local"
|
||||
```
|
||||
|
||||
```cmake
|
||||
# trong CMakeLists.txt của project dùng thư viện này
|
||||
find_package(lidarlib REQUIRED)
|
||||
target_link_libraries(my_app PRIVATE lidarlib::lidarlib)
|
||||
```
|
||||
|
||||
Sau khi link, giao diện chỉ cần `#include "lidarlib/lidarlib.hpp"` rồi gọi
|
||||
`lidarlib::make_lidar()` — xem "Tóm tắt API" ở đầu README.
|
||||
|
||||
Vẫn có thể build từng file bằng g++ thuần nếu không muốn dùng CMake:
|
||||
|
||||
```bash
|
||||
g++ -std=c++17 -O2 -pthread -Wall -Wextra -Iinclude -o test_dual examples/test_dual.cpp src/olei_lidar.cpp
|
||||
g++ -std=c++17 -O2 -pthread -Wall -Wextra -Iinclude -o example examples/example.cpp src/olei_lidar.cpp
|
||||
g++ -std=c++17 -O2 -pthread -Wall -Wextra -Iinclude -o lidar_app examples/lidar_app.cpp src/olei_lidar.cpp src/sick_lidar.cpp src/olei_config.cpp
|
||||
g++ -std=c++17 -O2 -pthread -Wall -Wextra -Iinclude -o sick_example examples/sick_example.cpp src/sick_lidar.cpp
|
||||
```
|
||||
|
||||
## Test thử
|
||||
|
||||
### 2 lidar song song
|
||||
|
||||
```bash
|
||||
./build/test_dual
|
||||
```
|
||||
|
||||
In ra 5 scan mỗi bên, kèm số điểm, timestamp, error status, và **model đã tự
|
||||
dò được** (`model=...`).
|
||||
|
||||
### 1 lidar
|
||||
|
||||
Sửa model/IP/port trong `examples/example.cpp` rồi build lại, hoặc gọi trực tiếp:
|
||||
|
||||
```cpp
|
||||
lidarlib::Driver drv(lidarlib::MODEL_AUTO, "192.168.100.100", 2369);
|
||||
drv.open();
|
||||
lidarlib::ScanResult result;
|
||||
drv.recv_scan(result, 2000);
|
||||
```
|
||||
|
||||
### Lidar lắp úp ngược
|
||||
|
||||
Constructor có tham số thứ 4 `inverted` (mặc định `false`). Đặt `true` nếu
|
||||
thiết bị bị lắp lật 180° quanh trục hướng về phía trước — driver tự đảo dấu
|
||||
góc từng điểm (`angle = -angle`, chuẩn hóa lại về `-180..180`) để output luôn
|
||||
đúng theo hệ quy chiếu xe, không phụ thuộc hướng lắp vật lý:
|
||||
|
||||
```cpp
|
||||
lidarlib::Driver drv(lidarlib::MODEL_AUTO, "192.168.100.100", 2369, /*inverted=*/true);
|
||||
```
|
||||
|
||||
Đã verify bằng sniff sống: chạy `inverted=false` góc tăng dần theo thời gian,
|
||||
chạy `inverted=true` góc giảm dần với cùng bước góc — đúng chữ ký của đảo dấu.
|
||||
|
||||
## Cấu hình & chạy (lidar_app + config.json)
|
||||
|
||||
Không còn Web UI. Cấu hình là một file JSON đơn giản — `examples/lidar_app.cpp`
|
||||
đọc nó, mở từng lidar qua đúng **một hàm** `lidarlib::make_lidar()`, rồi đọc scan
|
||||
trên mỗi thread. Đây là khung mẫu để bạn thay vòng `printf` bằng giao diện
|
||||
riêng (Qt, ImGui, ROS node, v.v.).
|
||||
|
||||
```bash
|
||||
./build/lidar_app # đọc/tạo config.json cạnh chỗ chạy
|
||||
./build/lidar_app my_config.json # đường dẫn config khác
|
||||
```
|
||||
|
||||
`config.json` — danh sách lidar, không cố định số lượng. `brand` chọn loại
|
||||
driver (`"OLEI"` = UDP, `"SICK"` = TCP/SOPAS); `model` tra trong bảng
|
||||
`ModelConfig` (tên lạ → tự lùi về mặc định của hãng: `AUTO` cho OLEI,
|
||||
`SICK-TIM571` cho SICK); `inverted` chỉ có tác dụng với OLEI. Bỏ trống `brand`
|
||||
thì mặc định `"OLEI"` (tương thích file cũ).
|
||||
|
||||
```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", "inverted":false}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Trong code, đọc/ghi file bằng `lidarlib::load_config(path)` / `lidarlib::save_config(path, cfg)`
|
||||
(file hỏng → trả về mặc định, không crash). Giao diện của bạn tự quyết khi nào
|
||||
lưu — thư viện không tự bind port hay phục vụ HTTP gì cả.
|
||||
|
||||
### Sniff packet thô (debug khi nghi ngờ offset header)
|
||||
|
||||
```bash
|
||||
python3 - <<'EOF'
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.bind(("192.168.100.100", 2369)) # đổi port theo lidar cần xem
|
||||
data, addr = s.recvfrom(4096)
|
||||
print("from", addr, "len", len(data))
|
||||
print(data[:40].hex(' '))
|
||||
EOF
|
||||
```
|
||||
|
||||
Luôn ưu tiên capture thật hơn là tin comment trong code — comment mô tả ý
|
||||
định lúc viết, không phải offset đã verify trên thiết bị thật.
|
||||
|
||||
## Chọn `ModelConfig`
|
||||
|
||||
| Constant | FOV (deg, có dấu) | range_min/max (m) | Khi dùng |
|
||||
|---|---|---|---|
|
||||
| `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 | 2D 360° 50m, Family A |
|
||||
| `MODEL_LR1BS5` | -180…180 | 0.05…30 | 2D 360°, Family B |
|
||||
| `MODEL_LR16F` | -135…135 | 0.05…30 | 3D 16-line |
|
||||
| `MODEL_GS15` | -180…180 | 0.05…30 | 2D 360°, Family C/V3 — **chưa verify hardware thật** |
|
||||
| `MODEL_AUTO` | -180…180 (mặc định, có thể tự thu hẹp) | 0.05…30 | Không biết trước model |
|
||||
|
||||
`range_min_m`/`range_max_m` chỉ là giá trị đặt sẵn để điền vào
|
||||
`LaserScan::range_min/range_max` (không đọc từ packet) — chỉnh trong
|
||||
`include/lidarlib/lidar.hpp` theo datasheet thật của từng model nếu cần chính xác.
|
||||
|
||||
`MODEL_AUTO`: chỉ có tác dụng tự-dò với **Family B** (đọc chuỗi tên model
|
||||
trong header). Driver luôn lưu lại **tên thật** đọc từ packet (vd
|
||||
`"OLELR-1BS5"`, `"OLELR-1BS2"`) — gọi `drv.detected_model()` hoặc
|
||||
`result.info.detected_model` để xem (trả về `"AUTO"` nếu chưa nhận gói Family B
|
||||
nào). FOV chỉ tự thu hẹp khi tên đó khớp một entry trong `kModelTable`
|
||||
(`src/olei_lidar.cpp`); nếu không khớp, FOV giữ nguyên mặc định 360°
|
||||
(`-180..180`, không mất điểm) — an toàn nhưng có thể giữ lại điểm ngoài FOV
|
||||
thật của thiết bị nếu thiết bị đó không quét tròn.
|
||||
|
||||
Family A không mang chuỗi tên model trong packet, nên `MODEL_AUTO` trên thiết
|
||||
bị Family A cũng giữ nguyên FOV rộng — phải chỉ định model cụ thể (VD
|
||||
`MODEL_VB`) nếu muốn thu hẹp FOV cho thiết bị góc hẹp.
|
||||
|
||||
## Lidar SICK (TiM5xx/7xx) — driver riêng
|
||||
|
||||
`lidarlib::SickDriver` (`include/lidarlib/sick_lidar.hpp` + `src/sick_lidar.cpp`) là
|
||||
driver **độc lập** với `lidarlib::Driver` ở trên — không phải thêm 1 family vào
|
||||
driver OLEI, vì giao thức khác hẳn:
|
||||
|
||||
- Kết nối **TCP** (SOPAS, port mặc định 2111) tới lidar, không phải UDP
|
||||
broadcast như OLEI.
|
||||
- Telegram là **ASCII** (CoLa-A), đóng khung bằng `STX`(0x02)/`ETX`(0x03),
|
||||
không có CRC32 như Family A.
|
||||
- Thiết bị đứng im cho tới khi driver gửi lệnh `sEN LMDscandata 1` — `open()`
|
||||
tự làm việc này; `close()` gửi `sEN LMDscandata 0` trước khi đóng socket.
|
||||
|
||||
Output vẫn dùng chung `ScanResult`/`LaserScan`/`ExtraInfo` như driver OLEI nên
|
||||
gọi giống hệt:
|
||||
|
||||
```cpp
|
||||
lidarlib::SickDriver drv(lidarlib::MODEL_SICK_TIM571, "192.168.0.1", 2111);
|
||||
drv.open();
|
||||
lidarlib::ScanResult result;
|
||||
drv.recv_scan(result, 2000);
|
||||
```
|
||||
|
||||
| Constant | FOV (deg, có dấu) | range_min/max (m) | Khi dùng |
|
||||
|---|---|---|---|
|
||||
| `MODEL_SICK_TIM5XX` | -135…135 | 0.05…10 | TiM551/561, 270° |
|
||||
| `MODEL_SICK_TIM571` | -135…135 | 0.05…25 | TiM571, 270° |
|
||||
| `MODEL_SICK_TIM7XX` | -135…135 | 0.05…25 | TiM781, 270° |
|
||||
|
||||
**Đã verify bằng phần cứng thật**: chạy trực tiếp với 1 con **SICK TiM781S**
|
||||
(FW `V5.11-14.10.24`, DeviceIdent đọc qua `sRN DeviceIdent` trên cổng 2111)
|
||||
tại `192.168.100.22:2111` — `MODEL_SICK_TIM7XX`. Kết quả khớp đúng datasheet
|
||||
TiM781S: 811 điểm/scan trải từ -45°…225° (270° FOV), `angle_increment` =
|
||||
0.333° (1/3°, đúng độ phân giải góc của dòng 781), khoảng cách 0.3-1.5m ổn
|
||||
định qua nhiều scan liên tiếp, kênh `RSSI1` có giá trị intensity hợp lý
|
||||
(không phải toàn 0), `error_status` = 0x00. Điều này xác nhận layout
|
||||
`LMDscandata` trong `parse_lmdscandata()` (`src/sick_lidar.cpp`) — kênh
|
||||
`DIST1`/`RSSI1`, scaling factor IEEE-754, start angle/step width — đọc đúng
|
||||
trên hardware thật, không chỉ đúng theo tài liệu nữa.
|
||||
|
||||
**Vẫn chưa verify**: chiều quy ước góc 0° (thẳng phía trước thiết bị hay
|
||||
hướng khác — chưa đối chiếu với hướng lắp vật lý thật), thiết bị có encoder
|
||||
(`NumEncoders > 0`, nhánh `next()`×2 chưa từng chạy qua vì test thực tế không
|
||||
có encoder), và nhánh 8-bit channel (`Num8BitChannels`, thiết bị test chỉ
|
||||
dùng kênh 16-bit). `MODEL_SICK_TIM5XX`/`MODEL_SICK_TIM571` (FOV/range theo
|
||||
datasheet) cũng chưa test trên phần cứng — chỉ `MODEL_SICK_TIM7XX` đã chạy
|
||||
thật.
|
||||
|
||||
`SickDriver` nối vào `config.json` qua field `LidarConfig::brand` (`"OLEI"`
|
||||
hoặc `"SICK"`, mặc định `"OLEI"`): `lidarlib::make_lidar()` thấy `brand=="SICK"` thì
|
||||
trả về `lidarlib::SickDriver` (TCP/SOPAS) thay cho `lidarlib::Driver` (UDP), cùng kiểu
|
||||
trả về `std::unique_ptr<lidarlib::Lidar>` nên phía gọi không phải phân biệt. Xem
|
||||
`examples/lidar_app.cpp` (chạy theo config) hoặc `examples/sick_example.cpp`
|
||||
(dùng thẳng `SickDriver` qua API C++).
|
||||
Reference in New Issue
Block a user