Standard Messages

Standard message types for robotics communication. Most messages are designed for zero-copy shared memory transport — see POD Types and Zero-Copy for the exceptions.

use horus::prelude::*;

Need to name a type rather than glob it in? Every message on this page also has a path: horus::msg::Twist, horus::msg::Imu, horus::msg::CmdVel. That is the same spelling C++ uses (horus::msg::Twist out of <horus/msg/geometry.hpp>), and it is deliberate — the crate a message is filed under (horus_types, horus_robotics) is a packaging decision that used to leak into every use line and differ per type. horus::msg re-exports them all under one path; the crate paths still resolve, so nothing that already compiles breaks. Image, PointCloud, DepthImage and Transform are buffer and frame types rather than wire messages, but they are re-exported under horus::msg too — along with the element types you need to fill them (ImageEncoding, PointXYZ, PointXYZI, PointXYZRGB) — so horus::msg::Image resolves right next to horus::msg::LaserScan. All of them stay on horus::prelude:: as well.


Geometry

Spatial primitives for position, orientation, and motion.

Twist

3D velocity command with linear and angular components.

pub struct Twist {
    pub linear: [f64; 3],       // [x, y, z] in m/s
    pub angular: [f64; 3],      // [roll, pitch, yaw] in rad/s
    pub timestamp_ns: u64,      // Timestamp in nanoseconds since epoch
}

Constructors

// Full 3D velocity
let twist = Twist::new([1.0, 0.0, 0.0], [0.0, 0.0, 0.5]);

// 2D velocity (forward + rotation)
let twist = Twist::new_2d(1.0, 0.5);  // 1 m/s forward, 0.5 rad/s rotation

// Stop command
let twist = Twist::stop();

Methods

MethodDescription
is_valid()Returns true if all values are finite

Pose2D

2D pose (position and orientation) for planar robots.

pub struct Pose2D {
    pub x: f64,            // X position in meters
    pub y: f64,            // Y position in meters
    pub theta: f64,        // Orientation in radians
    pub timestamp_ns: u64, // Timestamp in nanoseconds since epoch
}

Constructors

let pose = Pose2D::new(1.0, 2.0, 0.5);  // x=1m, y=2m, theta=0.5rad
let pose = Pose2D::origin();             // (0, 0, 0)

Methods

MethodDescription
distance_to(&other)Euclidean distance to another pose
normalize_angle()Normalize theta to [-π, π]
is_valid()Returns true if all values are finite

TransformStamped

Timestamped 3D transformation (translation + quaternion rotation) for message passing.

pub struct TransformStamped {
    pub translation: [f64; 3],  // [x, y, z] in meters
    pub rotation: [f64; 4],     // Quaternion [x, y, z, w]
    pub timestamp_ns: u64,
}

Constructors

let tf = TransformStamped::identity();
let tf = TransformStamped::new([1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]);
let tf = TransformStamped::from_pose_2d(&pose);

Methods

MethodDescription
is_valid()Check if quaternion is normalized
normalize_rotation()Normalize the quaternion

Note: For coordinate frame management (lookups, chains, interpolation), see Transform Frame which uses its own Transform type.


Point3

3D point in space.

pub struct Point3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

Methods

let p1 = Point3::new(1.0, 2.0, 3.0);
let p2 = Point3::origin();
let dist = p1.distance_to(&p2);

Vector3

3D vector for representing directions and velocities.

pub struct Vector3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

Methods

MethodDescription
magnitude()Vector length
normalize()Normalize to unit vector
dot(&other)Dot product
cross(&other)Cross product

Quaternion

Quaternion for 3D rotation representation.

pub struct Quaternion {
    pub x: f64,
    pub y: f64,
    pub z: f64,
    pub w: f64,
}

Constructors

let q = Quaternion::identity();
let q = Quaternion::new(0.0, 0.0, 0.0, 1.0);
let q = Quaternion::from_euler(roll, pitch, yaw);

Sensor

Standard sensor data formats.

LaserScan

2D LiDAR scan data with 360 range measurements.

pub struct LaserScan {
    pub ranges: [f32; 360],      // Range measurements in meters
    pub angle_min: f32,          // Start angle in radians
    pub angle_max: f32,          // End angle in radians
    pub range_min: f32,          // Minimum valid range
    pub range_max: f32,          // Maximum valid range
    pub angle_increment: f32,    // Angular resolution
    pub time_increment: f32,     // Time between measurements
    pub scan_time: f32,          // Full scan time
    pub timestamp_ns: u64,
}

Constructors

let scan = LaserScan::new();
let scan = LaserScan::default();

Methods

MethodDescription
angle_at(index)Get angle for range index
is_range_valid(index)Check if reading is valid
valid_count()Count valid readings
min_range()Get minimum valid range

Example

if let Some(scan) = scan_sub.recv() {
    if let Some(min_dist) = scan.min_range() {
        if min_dist < 0.5 {
            // Obstacle detected!
        }
    }
}

Imu

IMU sensor data (orientation, angular velocity, acceleration).

pub struct Imu {
    pub orientation: [f64; 4],              // Quaternion [x, y, z, w]
    pub orientation_covariance: [f64; 9],   // 3x3 covariance (-1 = no data)
    pub angular_velocity: [f64; 3],         // [x, y, z] in rad/s
    pub angular_velocity_covariance: [f64; 9],
    pub linear_acceleration: [f64; 3],      // [x, y, z] in m/s²
    pub linear_acceleration_covariance: [f64; 9],
    pub timestamp_ns: u64,
}

Constructors

let imu = Imu::new();

Methods

MethodDescription
set_orientation_from_euler(roll, pitch, yaw)Set orientation from Euler angles
has_orientation()Check if orientation data is 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

Combined pose and velocity estimate.

pub struct Odometry {
    pub pose: Pose2D,
    pub twist: Twist,
    pub pose_covariance: [f64; 36],    // 6x6 covariance
    pub twist_covariance: [f64; 36],
    pub frame_id: [u8; 32],            // e.g., "odom"
    pub child_frame_id: [u8; 32],      // e.g., "base_link"
    pub timestamp_ns: u64,
}

Methods

MethodDescription
set_frames(frame, child)Set frame IDs
update(pose, twist)Update pose and velocity
is_valid()Check validity

GPS/GNSS position data.

pub struct NavSatFix {
    pub latitude: f64,           // Degrees (+ North, - South)
    pub longitude: f64,          // Degrees (+ East, - West)
    pub altitude: f64,           // Meters above WGS84
    pub position_covariance: [f64; 9],
    pub position_covariance_type: u8,
    pub status: u8,              // Fix status
    pub satellites_visible: u16,
    pub hdop: f32,
    pub vdop: f32,
    pub speed: f32,              // m/s
    pub heading: f32,            // degrees
    pub timestamp_ns: u64,
}

Constants

NavSatFix::STATUS_NO_FIX    // 0
NavSatFix::STATUS_FIX       // 1
NavSatFix::STATUS_SBAS_FIX  // 2
NavSatFix::STATUS_GBAS_FIX  // 3

Methods

MethodDescription
from_coordinates(lat, lon, alt)Create from coordinates
has_fix()Check if GPS has fix
is_valid()Check coordinate validity
horizontal_accuracy()Estimated accuracy in meters
distance_to(&other)Distance to another position (Haversine)

BatteryState

Battery status information.

pub struct BatteryState {
    pub voltage: f32,            // Volts
    pub current: f32,            // Amperes (negative = discharging)
    pub charge: f32,             // Amp-hours
    pub capacity: f32,           // Amp-hours
    pub percentage: f32,         // 0-100
    pub power_supply_status: u8,
    pub temperature: f32,        // Celsius
    pub cell_voltages: [f32; 16],
    pub cell_count: u8,
    pub timestamp_ns: u64,
}

Constants

BatteryState::STATUS_UNKNOWN     // 0
BatteryState::STATUS_CHARGING    // 1
BatteryState::STATUS_DISCHARGING // 2
BatteryState::STATUS_FULL        // 3

Methods

MethodDescription
new(voltage, percentage)Create new battery state
is_low(threshold)Check if below threshold
is_critical()Check if below 10%
time_remaining()Estimated time in seconds

RangeSensor

Single-point distance measurement (ultrasonic, IR).

pub struct RangeSensor {
    pub sensor_type: u8,     // 0=ultrasonic, 1=infrared
    pub field_of_view: f32,  // radians
    pub min_range: f32,      // meters
    pub max_range: f32,      // meters
    pub range: f32,          // meters
    pub timestamp_ns: u64,
}

Vision

Image and camera data types.

Image

Pool-backed RAII camera image type with zero-copy shared memory transport. Fields are private — use accessor methods.

// Image is an RAII type, not a plain struct.
// Create with Image::new(width, height, encoding)?
// Access data with .data(), .pixel(), etc.
// See Vision Messages for full API.
let mut img = Image::new(640, 480, ImageEncoding::Rgb8)?;
img.copy_from(&pixel_data);

ImageEncoding

pub enum ImageEncoding {
    Rgb8,
    Bgr8,
    Rgba8,
    Bgra8,
    Mono8,
    Mono16,
    Yuv422,
    Mono32F,
    Rgb32F,
    BayerRggb8,
    Depth16,
}

CameraInfo

Camera calibration information.

pub struct CameraInfo {
    pub width: u32,
    pub height: u32,
    pub distortion_model: [u8; 16],
    pub distortion_coefficients: [f64; 8],  // [k1, k2, p1, p2, k3, k4, k5, k6]
    pub camera_matrix: [f64; 9],            // Intrinsic matrix (3x3)
    pub rectification_matrix: [f64; 9],     // Rectification (3x3)
    pub projection_matrix: [f64; 12],       // Projection (3x4)
    pub frame_id: [u8; 32],                 // Camera identifier
    pub timestamp_ns: u64,
}

Detection

Object detection result.

pub struct Detection {
    pub bbox: BoundingBox2D,
    pub confidence: f32,
    pub class_id: u32,
    pub class_name: [u8; 32],
    pub instance_id: u32,
}

Control

Actuator command messages.

MotorCommand

Motor control command.

pub struct MotorCommand {
    pub motor_id: u8,
    pub mode: u8,             // 0=velocity, 1=position, 2=torque, 3=voltage
    pub target: f64,
    pub max_velocity: f64,
    pub max_acceleration: f64,
    pub feed_forward: f64,
    pub enable: u8,
    pub timestamp_ns: u64,
}

ServoCommand

Servo position command.

pub struct ServoCommand {
    pub servo_id: u8,
    pub position: f32,       // radians
    pub speed: f32,          // 0-1 (0 = max speed)
    pub enable: u8,
    pub timestamp_ns: u64,
}

PidConfig

PID controller configuration.

pub struct PidConfig {
    pub controller_id: u8,
    pub kp: f64,
    pub ki: f64,
    pub kd: f64,
    pub integral_limit: f64,
    pub output_limit: f64,
    pub anti_windup: u8,
    pub timestamp_ns: u64,
}

GenericMessage

Dynamic message type for cross-language communication. Uses a fixed-size buffer (4KB max payload) with MessagePack serialization, making it safe for shared memory transport.

pub struct GenericMessage {
    inline_data: [u8; 256],    // First 256 bytes (inline)
    overflow_data: [u8; 3840], // Overflow up to 3840 more bytes
    metadata: [u8; 256],       // Optional metadata
    // ... internal length tracking fields
}

Fields are private — use data(), metadata() and to_value::<T>().

Total maximum payload: 4,096 bytes (inline_data + overflow_data).

Constructors

// From raw bytes (returns Err if > 4KB)
let msg = GenericMessage::new(data_vec)?;

// From any serializable type (uses MessagePack)
let msg = GenericMessage::from_value(&my_struct)?;

// With metadata string (max 255 chars)
let msg = GenericMessage::with_metadata(data, "my_type".to_string())?;

Methods

MethodReturn TypeDescription
data()Vec<u8>Get payload bytes
metadata()Option<String>Get metadata string if present
to_value::<T>()HorusResult<T> (i.e. Result<T, HorusError>)Deserialize from MessagePack to typed value

Example

use std::collections::HashMap;

// Send any serializable data
let mut data = HashMap::new();
data.insert("x", 1.0);
data.insert("y", 2.0);
let msg = GenericMessage::from_value(&data)?;
topic.send(msg);

// Receive and deserialize
if let Some(msg) = topic.recv() {
    let data: HashMap<String, f64> = msg.to_value()?;
    println!("x: {}", data["x"]);
}

Use typed messages (e.g., Twist, Pose2D) instead of GenericMessage whenever possible — they are far smaller on the wire, skip the MessagePack encode/decode, and are type-safe.


POD Types and Zero-Copy

Most HORUS message types are POD (Plain Old Data) — they are fixed-size and need no Drop, so HORUS can hand them straight to shared memory at ~50ns latency, with no serialization step.

POD types (the large majority of message types): Fixed-size, no heap allocation, zero-copy capable. Non-POD types: Either pool-backed RAII handles (Image, PointCloud, DepthImage), which send a small fixed-size descriptor while the payload stays in shared memory, or types holding a Vec for variable-size data, which are serialized with bincode.

CategoryPOD / TotalNon-POD Types
Geometry6/6
Sensor6/6
Control8/8
Diagnostics8/8
Force/Haptics5/6TactileArray (Vec)
Detection4/4
Navigation7/9OccupancyGrid, CostMap
Vision3/5Image (pool-backed RAII), CompressedImage (Vec)
Perception2/4PointCloud (pool-backed RAII), DepthImage (pool-backed RAII)

POD detection is automatic — you don't need to configure anything. HORUS checks at topic creation time whether your type is POD and selects the optimal backend.

Size Reference

MessageSizePOD
Twist56 bytesYes
Pose2D32 bytesYes
LaserScan~1.5 KBYes
Imu304 bytesYes
ImagePool-backed RAIIDescriptor is Pod
GenericMessage~4.3 KB (4364 bytes)Yes — fixed-size buffer, but each send copies all 4364 bytes plus a MessagePack encode/decode

Detailed Message Documentation

For comprehensive documentation of specialized message types, see:

CategoryDescription
Vision MessagesImage, CameraInfo, Detection, CompressedImage
Perception MessagesPointCloud, DepthImage, BoundingBox3D, PlaneDetection
Control MessagesMotorCommand, ServoCommand, DifferentialDriveCommand, PidConfig, JointCommand
Force & Tactile MessagesWrenchStamped, TactileArray, ImpedanceParameters, ForceCommand
ML MessagesSegmentationMask, and how tensors travel by pool handle rather than as a message struct
Navigation MessagesGoal, Path, OccupancyGrid, CostMap, VelocityObstacle
Diagnostics MessagesHeartbeat, DiagnosticStatus, EmergencyStop, ResourceUsage, SafetyStatus
TensorPool APIZero-copy tensor memory management for CPU/GPU