Sensor Messages
HORUS provides standard sensor data formats for common robotics sensors including lidar, IMU, GPS, and battery monitoring. All sensor messages are fixed-size types with #[repr(C)] layout, enabling zero-copy shared memory transport at ~50ns latency.
LaserScan
Laser scan data from a 2D lidar sensor.
Fixed-size array (360 readings) for shared memory safety. Supports up to 360-degree scanning with 1-degree resolution.
use horus::prelude::*;
// Create a new laser scan
let mut scan = LaserScan::new();
// Set scan parameters
scan.angle_min = -std::f32::consts::PI; // -180 degrees
scan.angle_max = std::f32::consts::PI; // +180 degrees
scan.range_min = 0.1; // 10cm minimum
scan.range_max = 30.0; // 30m maximum
// Fill in range data (360 readings)
for i in 0..360 {
scan.ranges[i] = 2.5; // 2.5m reading at all angles
}
// Get angle for a specific reading
let angle = scan.angle_at(90); // 90th reading
println!("Angle at index 90: {:.2} rad", angle);
// Check if a reading is valid
if scan.is_range_valid(45) {
println!("Reading at 45 is valid: {:.2}m", scan.ranges[45]);
}
// Get statistics
let valid = scan.valid_count();
let min_dist = scan.min_range();
println!("Valid readings: {}, Min distance: {:?}", valid, min_dist);
Fields:
| Field | Type | Description |
|---|---|---|
ranges | [f32; 360] | Range measurements in meters (0 = invalid) |
angle_min | f32 | Start angle in radians |
angle_max | f32 | End angle in radians |
range_min | f32 | Minimum valid range in meters |
range_max | f32 | Maximum valid range in meters |
angle_increment | f32 | Angular resolution in radians |
time_increment | f32 | Time between measurements (seconds) |
scan_time | f32 | Time to complete full scan (seconds) |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new() | Create with default parameters |
angle_at(index) | Get angle for a specific range index |
is_range_valid(index) | Check if a reading is valid |
valid_count() | Count valid range readings |
min_range() | Get minimum valid range reading |
Imu
IMU (Inertial Measurement Unit) sensor data.
Provides orientation, angular velocity, and linear acceleration measurements.
use horus::prelude::*;
// Create new IMU message
let mut imu = Imu::new();
// Set orientation from Euler angles (roll, pitch, yaw)
imu.set_orientation_from_euler(0.0, 0.05, 1.57); // Slight pitch, 90° yaw
// Set angular velocity [x, y, z] in rad/s
imu.angular_velocity = [0.0, 0.0, 0.5]; // Rotating around Z-axis
// Set linear acceleration [x, y, z] in m/s²
imu.linear_acceleration = [0.0, 0.0, 9.81]; // Gravity pointing up
// Check data availability
if imu.has_orientation() {
println!("Orientation: {:?}", imu.orientation);
}
// Get as Vector3 for calculations
let angular_vel = imu.angular_velocity_vec();
let linear_acc = imu.linear_acceleration_vec();
println!("Angular velocity magnitude: {:.2} rad/s", angular_vel.magnitude());
// Validate data
assert!(imu.is_valid());
Fields:
| Field | Type | Description |
|---|---|---|
orientation | [f64; 4] | Quaternion [x, y, z, w] |
orientation_covariance | [f64; 9] | Orientation covariance (3x3, -1 = no data) |
angular_velocity | [f64; 3] | Angular velocity [x, y, z] in rad/s |
angular_velocity_covariance | [f64; 9] | Angular velocity covariance (3x3) |
linear_acceleration | [f64; 3] | Linear acceleration [x, y, z] in m/s² |
linear_acceleration_covariance | [f64; 9] | Acceleration covariance (3x3) |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new() | Create new IMU message |
set_orientation_from_euler(roll, pitch, yaw) | Set orientation from Euler angles |
has_orientation() | Check if orientation data available |
is_valid() | Check if all values are finite |
angular_velocity_vec() | Get angular velocity as Vector3 |
linear_acceleration_vec() | Get linear acceleration as Vector3 |
Odometry
Odometry data combining pose and velocity.
Typically computed from wheel encoders or visual odometry, provides the robot's estimated position and velocity.
use horus::prelude::*;
// Create odometry message
let mut odom = Odometry::new();
// Set coordinate frames
odom.set_frames("odom", "base_link");
// Update with current pose and velocity
let pose = Pose2D::new(5.0, 3.0, 0.785); // x, y, theta
let twist = Twist::new_2d(0.5, 0.1); // linear, angular
odom.update(pose, twist);
// Access pose and velocity
println!("Position: ({:.2}, {:.2})", odom.pose.x, odom.pose.y);
println!("Velocity: {:.2} m/s", odom.twist.linear[0]);
// Validate
assert!(odom.is_valid());
Fields:
| Field | Type | Description |
|---|---|---|
pose | Pose2D | Current pose estimate |
twist | Twist | Current velocity estimate |
pose_covariance | [f64; 36] | Pose covariance (6x6 row-major) |
twist_covariance | [f64; 36] | Twist covariance (6x6 row-major) |
frame_id | [u8; 32] | Pose reference frame (e.g., "odom") |
child_frame_id | [u8; 32] | Body frame (e.g., "base_link") |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new() | Create new odometry message |
set_frames(frame, child_frame) | Set coordinate frame names |
update(pose, twist) | Update pose and velocity with timestamp |
is_valid() | Check if pose and twist are valid |
RangeSensor
Single-point range sensor data (ultrasonic, infrared).
use horus::prelude::*;
// Create ultrasonic range reading
let ultrasonic = RangeSensor::new(RangeSensor::ULTRASONIC, 1.5); // 1.5m reading
// Create infrared range reading
let ir = RangeSensor::new(RangeSensor::INFRARED, 0.3); // 30cm reading
// Check if reading is valid (within sensor limits)
if ultrasonic.is_valid() {
println!("Distance: {:.2}m", ultrasonic.range);
}
// Access sensor parameters
println!("FOV: {:.2} rad", ultrasonic.field_of_view);
println!("Range: {:.2} - {:.2}m", ultrasonic.min_range, ultrasonic.max_range);
Fields:
| Field | Type | Description |
|---|---|---|
sensor_type | u8 | 0 = ultrasonic, 1 = infrared |
field_of_view | f32 | Field of view in radians |
min_range | f32 | Minimum valid range (meters) |
max_range | f32 | Maximum valid range (meters) |
range | f32 | Range reading (meters) |
timestamp_ns | u64 | Nanoseconds since epoch |
Constants:
| Constant | Value | Description |
|---|---|---|
RangeSensor::ULTRASONIC | 0 | Ultrasonic sensor |
RangeSensor::INFRARED | 1 | Infrared sensor |
Methods:
| Method | Description |
|---|---|
new(sensor_type, range) | Create with sensor type and reading |
is_valid() | Check if reading is within sensor limits |
NavSatFix
GPS/GNSS position data.
Standard GNSS position data from GPS, GLONASS, Galileo, or other satellite navigation systems.
use horus::prelude::*;
// Create GPS fix from coordinates
let fix = NavSatFix::from_coordinates(
37.7749, // Latitude (positive = North)
-122.4194, // Longitude (positive = East)
10.5 // Altitude in meters
);
// Check fix status
if fix.has_fix() {
println!("GPS Fix acquired!");
println!("Position: {:.6}°N, {:.6}°E", fix.latitude, fix.longitude);
println!("Altitude: {:.1}m", fix.altitude);
println!("Satellites: {}", fix.satellites_visible);
}
// Get accuracy estimate
let accuracy = fix.horizontal_accuracy();
println!("Horizontal accuracy: ±{:.1}m", accuracy);
// Calculate distance to another position
let destination = NavSatFix::from_coordinates(37.8044, -122.2712, 0.0);
let distance = fix.distance_to(&destination);
println!("Distance to destination: {:.0}m", distance);
// Validate coordinates
assert!(fix.is_valid());
Fields:
| Field | Type | Description |
|---|---|---|
latitude | f64 | Latitude in degrees (+ = North) |
longitude | f64 | Longitude in degrees (+ = East) |
altitude | f64 | Altitude in meters (WGS84 ellipsoid) |
position_covariance | [f64; 9] | Position covariance (3x3) |
position_covariance_type | u8 | Covariance type (see constants) |
status | u8 | Fix status (see constants) |
satellites_visible | u16 | Number of satellites visible |
hdop | f32 | Horizontal dilution of precision |
vdop | f32 | Vertical dilution of precision |
speed | f32 | Ground speed in m/s |
heading | f32 | Course/heading in degrees |
timestamp_ns | u64 | Nanoseconds since epoch |
Status Constants:
| Constant | Value | Description |
|---|---|---|
STATUS_NO_FIX | 0 | No GPS fix |
STATUS_FIX | 1 | Standard GPS fix |
STATUS_SBAS_FIX | 2 | SBAS-augmented fix |
STATUS_GBAS_FIX | 3 | GBAS-augmented fix |
Covariance Type Constants:
| Constant | Value | Description |
|---|---|---|
COVARIANCE_TYPE_UNKNOWN | 0 | Unknown covariance |
COVARIANCE_TYPE_APPROXIMATED | 1 | Approximated covariance |
COVARIANCE_TYPE_DIAGONAL_KNOWN | 2 | Diagonal elements known |
COVARIANCE_TYPE_KNOWN | 3 | Full covariance matrix known |
Methods:
| Method | Description |
|---|---|
new() | Create empty fix |
from_coordinates(lat, lon, alt) | Create from coordinates |
has_fix() | Check if valid GPS fix |
is_valid() | Check if coordinates are valid |
horizontal_accuracy() | Estimate accuracy from HDOP |
distance_to(&other) | Calculate distance using Haversine formula |
BatteryState
Battery status monitoring.
use horus::prelude::*;
// Create battery state
let mut battery = BatteryState::new(12.6, 85.0); // 12.6V, 85%
// Set additional fields
battery.current = -2.5; // Discharging at 2.5A
battery.temperature = 28.0;
battery.power_supply_status = BatteryState::STATUS_DISCHARGING;
// Check battery level
if battery.is_low(20.0) {
println!("Battery low!");
}
if battery.is_critical() {
println!("Battery critical (below 10%)!");
}
// Estimate remaining time
if let Some(remaining) = battery.time_remaining() {
println!("Estimated time remaining: {:.0} seconds", remaining);
}
println!("Voltage: {:.2}V", battery.voltage);
println!("Charge: {:.0}%", battery.percentage);
println!("Temperature: {:.1}°C", battery.temperature);
Fields:
| Field | Type | Description |
|---|---|---|
voltage | f32 | Voltage in volts |
current | f32 | Current in amperes (- = discharging) |
charge | f32 | Charge in amp-hours (NaN if unknown) |
capacity | f32 | Capacity in amp-hours (NaN if unknown) |
percentage | f32 | Charge percentage (0-100) |
power_supply_status | u8 | Status (see constants) |
temperature | f32 | Temperature in Celsius |
cell_voltages | [f32; 16] | Individual cell voltages |
cell_count | u8 | Number of valid cell readings |
timestamp_ns | u64 | Nanoseconds since epoch |
Status Constants:
| Constant | Value | Description |
|---|---|---|
STATUS_UNKNOWN | 0 | Unknown status |
STATUS_CHARGING | 1 | Charging |
STATUS_DISCHARGING | 2 | Discharging |
STATUS_FULL | 3 | Fully charged |
Methods:
| Method | Description |
|---|---|
new(voltage, percentage) | Create new battery state |
is_low(threshold) | Check if below threshold % |
is_critical() | Check if below 10% |
time_remaining() | Estimate remaining time (seconds) |
Sensor Fusion Example
use horus::prelude::*;
struct SensorFusionNode {
imu_sub: Topic<Imu>,
odom_sub: Topic<Odometry>,
gps_sub: Topic<NavSatFix>,
fused_pose_pub: Topic<Pose2D>,
// Extended Kalman Filter state
ekf_state: [f64; 6], // [x, y, theta, vx, vy, omega]
}
impl Node for SensorFusionNode {
fn name(&self) -> &str { "SensorFusion" }
fn tick(&mut self) {
// Process IMU at highest rate
if let Some(imu) = self.imu_sub.recv() {
if imu.is_valid() {
// Use angular velocity for heading prediction
let omega = imu.angular_velocity[2];
self.predict_state(omega);
}
}
// Process odometry
if let Some(odom) = self.odom_sub.recv() {
if odom.is_valid() {
// Update with wheel odometry
self.update_odometry(&odom);
}
}
// Process GPS (lower rate, absolute position)
if let Some(gps) = self.gps_sub.recv() {
if gps.has_fix() && gps.is_valid() {
// Update with GPS (when available)
self.update_gps(&gps);
}
}
// Publish fused pose
let pose = Pose2D::new(
self.ekf_state[0],
self.ekf_state[1],
self.ekf_state[2]
);
self.fused_pose_pub.send(pose);
}
}
impl SensorFusionNode {
fn predict_state(&mut self, omega: f64) {
// EKF prediction step using IMU
let dt = 0.01; // 100Hz
self.ekf_state[2] += omega * dt;
}
fn update_odometry(&mut self, odom: &Odometry) {
// EKF update with odometry measurement
// ... implementation
}
fn update_gps(&mut self, gps: &NavSatFix) {
// EKF update with GPS measurement
// ... implementation
}
}
See Also
- Geometry Messages - Pose2D, Twist, TransformStamped
- Navigation Messages - Goals, paths, occupancy grids
- Perception Messages - Point clouds, depth data