feat(config,diagnostics): explicit transport in DeviceConfig, vendor-neutral diagnostics
DeviceConfig now carries an optional transport (serial/udp/tcp) instead of
the ESPE-only use_udp bool. Plugins validate it in create_driver_instance:
a fixed-transport driver configured with the wrong transport fails open()
with InvalidConfig (via InvalidConfigDriver — the plugin ABI forbids
returning nullptr) rather than silently ignoring the setting. Selectable
drivers (ESPE) switch TCP/UDP through the same field. config.json
load/save round-trips "transport" for every transport, including serial,
and migrates legacy use_udp:true entries.
Diagnostics drops the per-vendor accessors (espe_fault, rplidar_fault,
monitor_fault, sick_error, pollution_*, contamination_*, manipulation) for
one common shape: a list of DiagnosticIssue{severity, code, detail} with
cross-vendor codes, plus a raw map of vendor passthrough values and
to_json() for hosts that prefer a string. Vendor bit decoding now lives in
one place (decode_diagnostics); has_fault/has_warning/healthy keep their
meaning, so is_ready()/wait_ready() are unchanged.
Also: README regains the model/protocol and ExtraInfo tables lost in the
lidarlib->xlidar refactor (verified against current code), and the empty
xlocd/ tree left by a stray sync run is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
133
README.md
133
README.md
@@ -122,30 +122,37 @@ Callback chỉ phát từ `spin_once()` — chọn một kiểu bơm dữ liệu
|
||||
|
||||
### Sẵn sàng & chẩn đoán thiết bị
|
||||
|
||||
API chẩn đoán **chung cho mọi hãng**: `Diagnostics` trả về danh sách
|
||||
`issues` (mỗi issue = `severity` fault/warning + `code` trung lập + `detail`
|
||||
người-đọc-được), không có hàm riêng từng hãng. Giá trị thô của hãng nằm
|
||||
trong map `raw` (VD `"sick.device_status"`, `"rplidar.error_code"`); cần
|
||||
dạng chuỗi thì dùng `to_json()`.
|
||||
|
||||
```cpp
|
||||
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
|
||||
for (const xlidar::DiagnosticIssue& issue : d.issues) {
|
||||
// issue.severity : DiagSeverity::Fault | DiagSeverity::Warning
|
||||
// issue.code : "motor" | "voltage" | "temperature" | "optics_dirty"
|
||||
// | "manipulation" | "device_error" | "device_warning"
|
||||
// issue.detail : mô tả kèm tên hãng + giá trị thô, VD "ESPE fault word 0x0004"
|
||||
printf("[%s] %s — %s\n", xlidar::to_string(issue.severity),
|
||||
issue.code.c_str(), issue.detail.c_str());
|
||||
}
|
||||
if (d.has_fault()) { /* thiết bị báo hỏng — dừng tin dữ liệu */ }
|
||||
if (d.has_warning()) { /* suy giảm (kính bẩn, ...) — lên lịch bảo trì */ }
|
||||
|
||||
printf("%s\n", xlidar::to_string(d).c_str()); // "ok" / "FAULT: voltage | WARN: optics_dirty"
|
||||
printf("%s\n", xlidar::to_json(d).c_str()); // JSON đầy đủ cho REST/telemetry
|
||||
```
|
||||
|
||||
`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](docs/diagnostics.md).
|
||||
`recv_scan()`/`spin_once()`, nên gọi từ chính thread bơm dữ liệu. Ý nghĩa
|
||||
từng `code`, key `raw` và layout chẩn đoán từng giao thức:
|
||||
[docs/diagnostics.md](docs/diagnostics.md) + comment trong
|
||||
`include/lidar_diagnostics.hpp`.
|
||||
|
||||
### Xử lý lỗi & lifecycle
|
||||
|
||||
@@ -181,8 +188,8 @@ mỗi lidar một thread, không cần khóa.
|
||||
{"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}
|
||||
{"name":"espe1", "driver_id":"espe_lga60_driver", "model":"ESPE-LGA60", "transport":"udp", "ip":"192.168.1.88", "port":8080},
|
||||
{"name":"rp1", "driver_id":"rplidar_c1_driver", "model":"AUTO", "transport":"serial", "serial_port":"/dev/ttyUSB0", "baudrate":460800}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -193,17 +200,18 @@ Các trường `DeviceConfig` (mọi trường có default, chỉ khai báo cái
|
||||
|---|---|
|
||||
| `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 |
|
||||
| `transport` | `"serial"` / `"udp"` / `"tcp"`. Bỏ trống = transport mặc định của driver. Driver cố định transport mà bị cấu hình sai → `open()` trả `InvalidConfig`; driver `transport_selectable` (ESPE) chuyển TCP↔UDP qua trường này |
|
||||
| `ip` / `port` | Transport 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` | Transport 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.
|
||||
File format cũ được migrate tự động khi load: `brand`+`model` (lidarlib) →
|
||||
`driver_id`, cặp `angle_*_deg` cũ → remap, `use_udp: true` → `transport:
|
||||
"udp"`.
|
||||
|
||||
## Driver đi kèm
|
||||
|
||||
@@ -215,6 +223,57 @@ khi load: `brand`+`model` → `driver_id`, cặp `angle_*_deg` cũ → remap.
|
||||
| `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 |
|
||||
|
||||
### Thông số model
|
||||
|
||||
FOV/range dưới đây là mặc định theo `ModelConfig` của từng driver — ghi đè
|
||||
bằng `range_min_m`/`range_max_m` và cửa sổ `angle_min_deg`/`angle_max_deg`.
|
||||
Driver mạng dùng hệ góc có dấu, 0° = phía trước; rplidar giữ hệ góc thiết bị
|
||||
[0, 2π).
|
||||
|
||||
**OLEI** (UDP, port 2368) — nhận diện họ giao thức theo frame ID từng gói:
|
||||
**Family A** `0xFAF0` (header 20 B, 3 B/điểm, CRC32) · **Family B** `0xFEF0`
|
||||
(header 40 B kèm tên model ASCII, 8 B/điểm) · **Family C/V3** `0xFEAC`
|
||||
(header 48 B, 2–4 B/điểm tuỳ kiểu dữ liệu). Tên model đọc từ packet
|
||||
(Family B/C): `detected_model()` hoặc `result.info.detected_model`.
|
||||
|
||||
| Model | FOV (°) | Range (m) | Giao thức |
|
||||
|---|---|---|---|
|
||||
| `AUTO` | −180…180 | 0.05…30 | Tự dò model từ dữ liệu (Family B/C) |
|
||||
| `VB` | −135…135 | 0.05…30 | Family A |
|
||||
| `VF` | −180…180 | 0.05…30 | Family A |
|
||||
| `LR-1F` | −180…180 | 0.05…50 | Family A — 0° thiết bị hướng đuôi (offset +180°) |
|
||||
| `LR-1FMI` | −180…180 | 0.05…30 | Family B — 0° thiết bị hướng đuôi |
|
||||
| `LR-1BS5` | −180…180 | 0.05…30 | Family B |
|
||||
| `LR-16F` | −135…135 | 0.05…30 | 3D 16-line |
|
||||
| `GS1-5` | −180…180 | 0.05…30 | Family C/V3 — chưa verify phần cứng |
|
||||
|
||||
**SICK** — TiM qua TCP/SOPAS (CoLa-A) port 2111: `open()` gửi
|
||||
`sEN LMDscandata 1` để start stream; frame wire đặt 90° ở phía trước nên
|
||||
driver offset −90° để 0° = phía trước. nanoScan3 là receiver UDP thụ động
|
||||
port 6060 — đích UDP phải cấu hình sẵn bằng SICK Safety Designer, driver
|
||||
không handshake CoLa2/TCP.
|
||||
|
||||
| Model | FOV (°) | Range (m) | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| `SICK-TIM5xx` | −135…135 | 0.05…10 | TiM551/561 — FOV/range theo datasheet, chưa verify |
|
||||
| `SICK-TIM571` | −135…135 | 0.05…25 | Chưa verify phần cứng |
|
||||
| `SICK-TIM7xx` | −135…135 | 0.05…25 | Verify trên TiM781S thật (FW V5.11) |
|
||||
| `SICK-nanoScan3` | −137.5…137.5 | 0.05…40 | Cả microScan3; layout port từ sick_safetyscanners, chưa verify |
|
||||
|
||||
**ESPE LGA60** (TCP mặc định / UDP, port 8080; IP mặc định của hãng
|
||||
192.168.1.88) — FOV 320°: thiết bị quét 20°→340° với 0° hướng đuôi (offset
|
||||
−180°), preset −160…160°, range 0.05…50 m. Frame đo `HISN` (header 16 B
|
||||
big-endian; điểm = distance mm + intensity, distance 50 000 mm = không có
|
||||
phản hồi → ∞); frame vùng `WSimu` (nếu thiết bị gửi) được đọc lấy từ lỗi.
|
||||
Độ phân giải (0.025–0.5°), tốc độ quay, mức lọc nhiễu theo cấu hình đã nạp
|
||||
bằng tool Windows của hãng — driver không tự đổi. Chưa verify phần cứng.
|
||||
|
||||
**RPLIDAR** (serial, mặc định `/dev/ttyUSB0` @ 460800) — preset C1: range
|
||||
0.05…16 m (datasheet 12 m trên nền trắng), ~10 Hz, ~400–500 điểm/vòng
|
||||
(DenseBoost); A/S series dùng được với baud tương ứng. `open()` chạy health
|
||||
check (Error → `DeviceError`) và tự nhận model/firmware. Riêng driver này
|
||||
đo được `time_increment`/`scan_time` thực.
|
||||
|
||||
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](docs/diagnostics.md).
|
||||
|
||||
@@ -233,12 +292,30 @@ comment đầu mỗi file plugin và [docs/diagnostics.md](docs/diagnostics.md).
|
||||
| `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`.
|
||||
`ExtraInfo`: metadata thô tuỳ giao thức — field thiết bị không có giữ
|
||||
`std::nullopt`:
|
||||
|
||||
| Field | Nguồn | Ý nghĩa |
|
||||
|---|---|---|
|
||||
| `detected_model` | mọi driver | Tên model thực đọc từ dữ liệu (hoặc tên cấu hình) |
|
||||
| `error_status` | OLEI Family A | Byte lỗi thiết bị: BIT0 monitor, BIT1 điện áp, BIT2 nhiệt độ |
|
||||
| `distance_scale_mm` | OLEI A/B | Hệ số mm/count của khoảng cách; 0 = không báo |
|
||||
| `rotation_raw` | OLEI Family A | Tốc độ motor (raw) |
|
||||
| `distance_ratio_raw`, `scan_frequency_raw`, `input_status`, `output_status`, `field_status`, `status_flags` | OLEI Family C/V3 | Passthrough raw — ý nghĩa bit chưa verify |
|
||||
| `sick_device_status` | SICK TiM | Cặp Device Status `(word0<<8)\|word1`: 0 ok · 1 error · 2 pollution warning · 4 pollution error |
|
||||
| `nano_general_state` | nanoScan3 | Byte 0 block General System State (bit `kNanoState*`) |
|
||||
| `espe_error_status` | ESPE LGA60 | Từ lỗi từ frame vùng `WSimu` — chỉ có khi thiết bị gửi area data |
|
||||
| `rplidar_health_status`, `rplidar_error_code` | RPLIDAR | Health từ SDK (0 ok · 1 warning · 2 error) + mã lỗi thiết bị đi kèm |
|
||||
|
||||
`Diagnostics` (từ `get_diagnostics()` hoặc `decode_diagnostics(info)`) là
|
||||
bản decode **trung lập hãng**: `issues` (danh sách `{severity, code,
|
||||
detail}` với code chung: `motor` / `voltage` / `temperature` /
|
||||
`optics_dirty` / `manipulation` / `device_error` / `device_warning`), map
|
||||
`raw` giữ giá trị thô theo key ổn định (`"olei.error_status"`,
|
||||
`"sick.device_status"`, `"nano.general_state"`, `"espe.error_status"`,
|
||||
`"rplidar.health_status"`, `"rplidar.error_code"`, ...), cùng `model`,
|
||||
`firmware` (rplidar), `device_timestamp_ms`, và helper `has_fault()` /
|
||||
`has_warning()` / `healthy()` / `to_string()` / `to_json()`.
|
||||
|
||||
## Viết một plugin mới
|
||||
|
||||
|
||||
Reference in New Issue
Block a user