The “lidar” observation isn’t a true lidar — it’s the aggregated output of the 3-rangefinder stack: one TF-Luna on a servo (sweeping 0-180°, 181 samples per sweep) plus a fixed array of six VL53L0X sensors at 60° spacing around the perimeter. From the policy’s perspective they look the same — a 1D vector of distances.
Input structure (current 2D coverage task)
distances: array of 7 floats
[0]: TF-Luna current reading (in heading direction or current servo angle)
[1-6]: VL53L0X array (0°, 60°, 120°, 180°, 240°, 300°)
servo_angle: float in [0, π]
All distances are normalized to [0, 1] by clipping at TF-Luna’s max range (8 m).
Why 1D and not 2D
A 2D scanning lidar would be the more conventional choice, but indoor obstacle avoidance doesn’t actually need 2D scan density. A handful of distance readings spaced around the drone is sufficient for “is there a wall in this direction?” — which is the only question the policy actually has to answer. The cost saving is substantial (sub-$80 for the full stack vs $300+ for a small 2D lidar), and the policy’s training time is faster because the observation is lower-dimensional.
Connection to sim-to-real
The sim version comes from Gazebo’s gpu_lidar plugin attached to each sensor link. Without noise injection, the sim observation is too clean compared to the real sensor — the policy overfits to clean distances and breaks on real-world ±6 cm jitter. The ToF sim-to-real page covers the noise-injection recipe (σ=0.05 m Gaussian).
Sweep timing
The TF-Luna sweep takes ~22 seconds for a full 0-180° pass (with settle_ms=120 per stop). The policy can’t wait for a full sweep every step — it consumes the most recent TF-Luna distance reading, plus a separate sweep_complete topic that publishes the full 181-sample LaserScan when available. SLAM uses the full scans; the policy uses the instantaneous reading.
Where to go next
- TF-Luna sensor — physical layer
- VL53L0X array — perimeter layer
- ToF sim-to-real — noise injection
- Observation space hub — sibling pages