LaserScan
A 2D laser scan message representing range measurements from a rotating LiDAR sensor. The scan stores up to 360 range readings in a fixed-size array, making it safe for shared memory transport with zero-copy semantics.
When to Use
Use LaserScan when your robot has a 2D LiDAR sensor (e.g., RPLiDAR, Hokuyo, SICK) and you need to publish range data for obstacle avoidance, SLAM, or safety zone monitoring.
ROS2 Equivalent
sensor_msgs/LaserScan -- same conceptual fields. HORUS uses a fixed [f32; 360] array (shared-memory safe) instead of a dynamic Vec<f32>.
Rust Example
use horus::prelude::*;
// Create a scan with default parameters (-PI to PI, 1-degree resolution)
let mut scan = LaserScan::new();
scan.range_min = 0.1;
scan.range_max = 12.0;
// Populate ranges from sensor driver
scan.ranges[0] = 2.5; // 2.5 meters at angle_min
scan.ranges[90] = 1.2; // 1.2 meters at 90 degrees
// Query the scan
let closest = scan.min_range(); // Nearest valid reading
let valid = scan.valid_count(); // Number of valid readings
let angle = scan.angle_at(90); // Angle for index 90
// Publish
let topic: Topic<LaserScan> = Topic::new("lidar.scan")?;
topic.send(&scan);
Python Example
from horus import LaserScan, Topic
scan = LaserScan(
angle_min=-3.14159,
angle_max=3.14159,
angle_increment=0.01745,
range_min=0.1,
range_max=12.0,
ranges=[1.0, 1.1, 1.2, 0.0, 2.5] # up to 360 values
)
topic = Topic(LaserScan)
topic.send(scan)
# Access fields
print(f"Ranges: {scan.ranges[:5]}")
print(f"Min range: {scan.range_min}")
Fields
| Field | Type | Unit | Description |
|---|---|---|---|
ranges | [f32; 360] | m | Range measurements. 0.0 = invalid reading |
angle_min | f32 | rad | Start angle. Default: -PI |
angle_max | f32 | rad | End angle. Default: PI |
range_min | f32 | m | Minimum valid range. Default: 0.1 |
range_max | f32 | m | Maximum valid range. Default: 30.0 |
angle_increment | f32 | rad | Angular resolution. Default: PI/180 (1 degree) |
time_increment | f32 | s | Time between measurements |
scan_time | f32 | s | Time to complete full scan. Default: 0.1 |
timestamp_ns | u64 | ns | Timestamp in nanoseconds since epoch |
Methods
| Method | Signature | Description |
|---|---|---|
new() | -> LaserScan | Create with default parameters and current timestamp |
angle_at(index) | (usize) -> f32 | Angle in radians for a given range index |
is_range_valid(index) | (usize) -> bool | True if range is within [range_min, range_max] and finite |
valid_count() | -> usize | Number of valid range readings |
min_range() | -> Option<f32> | Minimum valid range reading, or None if no valid readings |
Common Patterns
Typical pipeline:
LiDAR hardware --> LaserScan --> obstacle avoidance --> CmdVel
\-> SLAM algorithm --> OccupancyGrid + Pose2D
Simple obstacle avoidance:
use horus::prelude::*;
fn emergency_stop(scan: &LaserScan, safety_distance: f32) -> bool {
if let Some(closest) = scan.min_range() {
closest < safety_distance
} else {
true // No valid readings = assume danger
}
}
Fixed-size array: The [f32; 360] array means LaserScan is a POD (Plain Old Data) type that can be sent through shared memory in approximately 50 nanoseconds. If your LiDAR has fewer than 360 beams, leave unused indices at 0.0 (they will be filtered out by is_range_valid). If your LiDAR has more than 360 beams, downsample before publishing.