docs(interface): document the CCW angle-convention contract on LaserScan

Every driver must normalize its device's native scale into ROS REP-103
right-handed CCW (0 = front, + = left). Spells out the three stackable
device quirks to absorb — shifted 0° reference, clockwise scale,
upside-down mounting — and warns that a CW scale decoded as CCW mirrors
the world invisibly on single-lidar devices: verify handedness against
the real room, not just against motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 11:17:08 +07:00
parent abf7bab917
commit 145a647d35

View File

@@ -79,6 +79,21 @@ inline const char* to_string(ErrorCode e) {
// ROS sensor_msgs/LaserScan-shaped output (radians, meters, seconds). // ROS sensor_msgs/LaserScan-shaped output (radians, meters, seconds).
// ranges[i] is at angle_min + i*angle_increment, in sweep order. // ranges[i] is at angle_min + i*angle_increment, in sweep order.
//
// ANGLE CONVENTION CONTRACT (ROS REP-103, right-handed): 0 = device front,
// positive = LEFT, increasing counter-clockwise viewed from the top. Every
// driver must NORMALIZE its device's native scale into this frame — three
// independent, stackable device quirks to absorb:
// 1. shifted 0° reference -> ModelConfig::angle_offset_deg
// (OLEI LR-1F/1FMI/1BS5: 0° at the rear; SICK TiM: front at 90°);
// 2. CLOCKWISE angle scale -> mirror the raw angle (θ -> −θ) BEFORE
// the offset (all Slamtec RPLIDARs per the Interface Protocol spec;
// OLEI LR-1BS5, field-verified). Decoding a CW scale as CCW mirrors the
// whole world left/right — undetectable with a single lidar (the SLAM
// map is self-consistently mirrored and heading checks still pass), so
// verify handedness against the real room, not just against motion;
// 3. upside-down mounting -> the physical flip reverses the
// apparent rotation, one more angle negation (the driver's `inverted`).
struct LaserScan { struct LaserScan {
uint32_t timestamp_ms = 0; // device clock (ms); 0 if not on the wire uint32_t timestamp_ms = 0; // device clock (ms); 0 if not on the wire
float angle_min = 0.f; // rad float angle_min = 0.f; // rad