Force & Haptic Messages

HORUS provides message types for force/torque sensors, impedance control, and haptic feedback systems commonly used in manipulation tasks.

Re-exported types (available via use horus::prelude::*): WrenchStamped, ImpedanceParameters, ForceCommand.

Non-re-exported types (require direct import): ContactInfo, ContactState, HapticFeedback, TactileArray — import from horus_library::messages::force::*.

WrenchStamped

6-DOF force and torque measurement from force/torque sensors.

use horus::prelude::*;

// Create wrench measurement
let force = Vector3::new(10.0, 5.0, -2.0);   // Newtons
let torque = Vector3::new(0.1, 0.2, 0.05);   // Newton-meters

let wrench = WrenchStamped::new(force, torque)
    .with_frame_id("tool0");

// Check magnitudes
println!("Force magnitude: {:.2} N", wrench.force_magnitude());
println!("Torque magnitude: {:.3} Nm", wrench.torque_magnitude());

// Safety check
let max_force = 50.0;   // N
let max_torque = 5.0;   // Nm
if wrench.exceeds_limits(max_force, max_torque) {
    println!("Safety limits exceeded!");
}

// Create from force only
let force_only = WrenchStamped::force_only(Vector3::new(0.0, 0.0, -10.0));

// Create from torque only
let torque_only = WrenchStamped::torque_only(Vector3::new(0.0, 0.0, 0.5));

// Low-pass filter noisy sensor readings
let mut current = wrench;
current.filter(&previous_wrench, 0.1);  // alpha = 0.1 (heavy filtering)

Fields:

FieldTypeUnitDescription
forceVector3NForce [fx, fy, fz]
torqueVector3NmTorque [tx, ty, tz]
point_of_applicationPoint3mForce application point
frame_id[u8; 32]Reference frame
timestamp_nsu64nsNanoseconds since epoch

ROS2 equivalent: geometry_msgs/msg/WrenchStamped

Methods:

MethodDescription
new(force, torque)Create a new wrench measurement
force_only(force)Create from force only (zero torque)
torque_only(torque)Create from torque only (zero force)
with_frame_id(frame_id)Set reference frame (builder pattern)
force_magnitude()Get force vector magnitude
torque_magnitude()Get torque vector magnitude
exceeds_limits(max_force, max_torque)Check if limits exceeded
filter(&prev_wrench, alpha)Apply low-pass filter (alpha 0.0-1.0)

ImpedanceParameters

Impedance control parameters for compliant manipulation.

use horus::prelude::*;

// Default impedance (moderate compliance)
let mut impedance = ImpedanceParameters::new();

// Compliant mode (low stiffness - for delicate tasks)
let compliant = ImpedanceParameters::compliant();
// stiffness: [100, 100, 100, 10, 10, 10]
// damping: [20, 20, 20, 2, 2, 2]

// Stiff mode (high stiffness - for precision tasks)
let stiff = ImpedanceParameters::stiff();
// stiffness: [5000, 5000, 5000, 500, 500, 500]
// damping: [100, 100, 100, 10, 10, 10]

// Enable/disable
impedance.enable();
impedance.disable();

// Custom parameters
impedance.stiffness = [500.0, 500.0, 200.0, 50.0, 50.0, 50.0];  // [Kx, Ky, Kz, Krx, Kry, Krz]
impedance.damping = [30.0, 30.0, 20.0, 3.0, 3.0, 3.0];          // [Dx, Dy, Dz, Drx, Dry, Drz]
impedance.force_limits = [50.0, 50.0, 30.0, 5.0, 5.0, 5.0];     // Safety limits

Fields:

FieldTypeUnitDescription
stiffness[f64; 6]N/m, Nm/radStiffness [Kx, Ky, Kz, Krx, Kry, Krz]
damping[f64; 6]Ns/m, Nms/radDamping [Dx, Dy, Dz, Drx, Dry, Drz]
inertia[f64; 6]kg, kgm²Virtual inertia
force_limits[f64; 6]N, NmForce/torque limits
enabledu8Impedance control active (0 = off, 1 = on)
timestamp_nsu64nsNanoseconds since epoch

Methods:

MethodDescription
new()Create with default moderate stiffness/damping
compliant()Create with low stiffness for delicate tasks
stiff()Create with high stiffness for precision tasks
enable()Enable impedance control
disable()Disable impedance control

ForceCommand

Hybrid force/position control command.

use horus::prelude::*;

// Pure force command
let force_cmd = ForceCommand::force_only(Vector3::new(0.0, 0.0, -5.0));  // 5N downward

// Hybrid force/position control
// Force control on Z axis, position control on X/Y
let force_axes: [u8; 6] = [0, 0, 1, 0, 0, 0];  // 1=force, 0=position per axis
let target_force = Vector3::new(0.0, 0.0, -10.0);
let target_position = Vector3::new(0.5, 0.3, 0.0);

let hybrid_cmd = ForceCommand::hybrid(force_axes, target_force, target_position);

// Surface contact following
let surface_normal = Vector3::new(0.0, 0.0, 1.0);  // Horizontal surface
let contact_force = 5.0;  // 5N contact force
let surface_cmd = ForceCommand::surface_contact(contact_force, surface_normal);

// Set timeout
let cmd_with_timeout = force_cmd.with_timeout(5.0);  // 5 second timeout

Fields:

FieldTypeUnitDescription
target_forceVector3NDesired force
target_torqueVector3NmDesired torque
force_mode[u8; 6]1 = force control, 0 = position control per axis
position_setpointVector3mPosition target for position-controlled axes
orientation_setpointVector3radOrientation target (Euler angles)
max_deviationVector3mMaximum position deviation
gains[f64; 6]Control gains
timeout_secondsf64sCommand timeout (0 = no timeout)
frame_id[u8; 32]Reference frame
timestamp_nsu64nsNanoseconds since epoch

ROS2 equivalent: geometry_msgs/msg/Wrench (force/torque portion)

Methods:

MethodDescription
force_only(target_force)Create pure force command (all axes force-controlled)
hybrid(force_axes, target_force, target_position)Create hybrid force/position command
surface_contact(normal_force, surface_normal)Create surface following command
with_timeout(seconds)Set command timeout (builder pattern)

ContactInfo

Contact detection and classification.

Note: ContactInfo and ContactState are not re-exported in the prelude. Import directly: use horus_library::messages::force::{ContactInfo, ContactState};

use horus_library::messages::force::{ContactInfo, ContactState};

// Create contact info
let contact = ContactInfo::new(ContactState::StableContact, 15.0);  // 15N force

// Check contact state
if contact.is_in_contact() {
    println!("Contact force: {:.1} N", contact.contact_force);
    println!("Duration: {:.2}s", contact.contact_duration_seconds());
    println!("Confidence: {:.0}%", contact.confidence * 100.0);
}

ContactState values:

StateValueDescription
NoContact0No contact detected (default)
InitialContact1First contact moment
StableContact2Established contact
ContactLoss3Contact being broken
Sliding4Sliding contact
Impact5Impact detected

Fields:

FieldTypeUnitDescription
stateu8Contact state (use ContactState as u8 to set)
contact_forcef64NContact force magnitude
contact_normalVector3Contact normal vector (estimated, unit vector)
contact_pointPoint3mContact point (estimated)
stiffnessf64N/mContact stiffness (estimated)
dampingf64Ns/mContact damping (estimated)
confidencef32Detection confidence (0.0 to 1.0)
contact_start_timeu64nsTime contact was first detected
frame_id[u8; 32]Reference frame
timestamp_nsu64nsNanoseconds since epoch

Methods:

MethodDescription
new(state, force_magnitude)Create new contact info (takes ContactState, stores as u8)
is_in_contact()Check if currently in contact (InitialContact, StableContact, or Sliding)
contact_duration_seconds()Get contact duration in seconds

HapticFeedback

Haptic feedback commands for user interfaces.

Note: HapticFeedback is not re-exported in the prelude. Import directly: use horus_library::messages::force::HapticFeedback;

use horus::prelude::*;
use horus_library::messages::force::HapticFeedback;

// Vibration feedback
let vibration = HapticFeedback::vibration(
    0.8,   // intensity (0-1)
    250.0, // frequency (Hz)
    0.5    // duration (seconds)
);

// Force feedback
let force = HapticFeedback::force(
    Vector3::new(1.0, 0.0, 0.0),  // Force direction
    1.0                           // Duration (seconds)
);

// Pulse pattern
let pulse = HapticFeedback::pulse(
    0.6,   // intensity
    100.0, // frequency
    0.3    // duration
);

Pattern Types:

ConstantValueDescription
PATTERN_CONSTANT0Constant intensity
PATTERN_PULSE1Pulsing pattern
PATTERN_RAMP2Ramping intensity

Fields:

FieldTypeUnitDescription
vibration_intensityf32Vibration intensity (0.0 to 1.0)
vibration_frequencyf32HzVibration frequency
duration_secondsf32sDuration of feedback
force_feedbackVector3NForce feedback vector
pattern_typeu8Feedback pattern (see constants)
enabledu8Enable/disable feedback (0 = off, 1 = on)
timestamp_nsu64nsNanoseconds since epoch

Methods:

MethodDescription
vibration(intensity, frequency, duration)Create vibration feedback (intensity clamped to 0-1)
force(force, duration)Create force feedback
pulse(intensity, frequency, duration)Create pulse pattern feedback

Force Control Node Example

use horus::prelude::*;

struct ForceControlNode {
    wrench_sub: Topic<WrenchStamped>,
    cmd_pub: Topic<ForceCommand>,
    impedance_pub: Topic<ImpedanceParameters>,
    target_force: f64,
    prev_wrench: Option<WrenchStamped>,
}

impl Node for ForceControlNode {
    fn name(&self) -> &str { "ForceControl" }

    fn tick(&mut self) {
        if let Some(mut wrench) = self.wrench_sub.recv() {
            // Apply low-pass filter
            if let Some(prev) = &self.prev_wrench {
                wrench.filter(prev, 0.2);
            }
            self.prev_wrench = Some(wrench);

            // Safety check
            if wrench.exceeds_limits(100.0, 10.0) {
                // Switch to compliant mode
                let compliant = ImpedanceParameters::compliant();
                self.impedance_pub.send(compliant);
                return;
            }

            // Force control to maintain target contact force
            let error = self.target_force - wrench.force.z;
            let correction = error * 0.001;  // Simple P control

            let cmd = ForceCommand::force_only(
                Vector3::new(0.0, 0.0, self.target_force + correction)
            );
            self.cmd_pub.send(cmd);
        }
    }
}

ContactState

Represents the phase of a contact event during force-controlled manipulation.

VariantValueDescription
NoContact0No contact detected
InitialContact1First moment of contact detected
StableContact2Stable, sustained contact
ContactLoss3Contact is being lost
Sliding4Sliding contact (tangential motion)
Impact5High-force impact detected
use horus::prelude::*;
use horus_library::messages::force::ContactState;

if let Some(contact) = contact_sub.recv() {
    match contact.state {
        s if s == ContactState::Impact as u8 => {
            // Emergency: zero out force immediately
            let zero_cmd = ForceCommand::force_only(Vector3::new(0.0, 0.0, 0.0));
            cmd_pub.send(zero_cmd);
        }
        s if s == ContactState::StableContact as u8 => {
            // Safe to apply task forces
        }
        _ => {}
    }
}

TactileArray

Tactile sensor array reading for contact-rich manipulation. Each taxel reports a force value in Newtons arranged in a row-major grid.

Note: TactileArray uses Vec<f32> internally, so it is not zero-copy POD — it goes through the serde serialization path. Import directly: use horus_library::messages::force::TactileArray;

use horus_library::messages::force::TactileArray;

// Create a 4x4 tactile sensor pad
let mut tactile = TactileArray::new(4, 4);

// Set individual taxel readings
tactile.set_force(1, 2, 3.5);  // row 1, col 2 = 3.5 N

// Read a taxel
if let Some(force) = tactile.get_force(1, 2) {
    println!("Force at (1,2): {:.2} N", force);
}

// Aggregate contact info
tactile.in_contact = true;
tactile.total_force = [0.0, 0.0, 12.5];           // Net force vector
tactile.center_of_pressure = [0.45, 0.52];         // Normalized [0..1]
tactile.physical_size = [0.04, 0.04];              // 4cm × 4cm sensor

Fields:

FieldTypeUnitDescription
rowsu32Number of taxel rows
colsu32Number of taxel columns
forcesVec<f32>NRow-major taxel readings (length = rows × cols)
total_force[f32; 3]NNet contact force [fx, fy, fz]
center_of_pressure[f32; 2]Normalized CoP [0.0, 1.0] (row, col)
in_contactboolWhether any contact is detected
physical_size[f32; 2]mSensor surface dimensions [width, height]
timestamp_nsu64nsNanoseconds since epoch

Methods:

MethodDescription
new(rows, cols)Create array with given dimensions, all forces zeroed
get_force(row, col) -> Option<f32>Read taxel at (row, col), None if out of bounds
set_force(row, col, force)Set taxel at (row, col), no-op if out of bounds

See Also