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

FieldTypeUnitDescription
ranges[f32; 360]mRange measurements. 0.0 = invalid reading
angle_minf32radStart angle. Default: -PI
angle_maxf32radEnd angle. Default: PI
range_minf32mMinimum valid range. Default: 0.1
range_maxf32mMaximum valid range. Default: 30.0
angle_incrementf32radAngular resolution. Default: PI/180 (1 degree)
time_incrementf32sTime between measurements
scan_timef32sTime to complete full scan. Default: 0.1
timestamp_nsu64nsTimestamp in nanoseconds since epoch

Methods

MethodSignatureDescription
new()-> LaserScanCreate with default parameters and current timestamp
angle_at(index)(usize) -> f32Angle in radians for a given range index
is_range_valid(index)(usize) -> boolTrue if range is within [range_min, range_max] and finite
valid_count()-> usizeNumber 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.