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:
| Field | Type | Unit | Description |
|---|---|---|---|
force | Vector3 | N | Force [fx, fy, fz] |
torque | Vector3 | Nm | Torque [tx, ty, tz] |
point_of_application | Point3 | m | Force application point |
frame_id | [u8; 32] | — | Reference frame |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
ROS2 equivalent:
geometry_msgs/msg/WrenchStamped
Methods:
| Method | Description |
|---|---|
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:
| Field | Type | Unit | Description |
|---|---|---|---|
stiffness | [f64; 6] | N/m, Nm/rad | Stiffness [Kx, Ky, Kz, Krx, Kry, Krz] |
damping | [f64; 6] | Ns/m, Nms/rad | Damping [Dx, Dy, Dz, Drx, Dry, Drz] |
inertia | [f64; 6] | kg, kgm² | Virtual inertia |
force_limits | [f64; 6] | N, Nm | Force/torque limits |
enabled | u8 | — | Impedance control active (0 = off, 1 = on) |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
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:
| Field | Type | Unit | Description |
|---|---|---|---|
target_force | Vector3 | N | Desired force |
target_torque | Vector3 | Nm | Desired torque |
force_mode | [u8; 6] | — | 1 = force control, 0 = position control per axis |
position_setpoint | Vector3 | m | Position target for position-controlled axes |
orientation_setpoint | Vector3 | rad | Orientation target (Euler angles) |
max_deviation | Vector3 | m | Maximum position deviation |
gains | [f64; 6] | — | Control gains |
timeout_seconds | f64 | s | Command timeout (0 = no timeout) |
frame_id | [u8; 32] | — | Reference frame |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
ROS2 equivalent:
geometry_msgs/msg/Wrench(force/torque portion)
Methods:
| Method | Description |
|---|---|
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:
ContactInfoandContactStateare 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:
| State | Value | Description |
|---|---|---|
NoContact | 0 | No contact detected (default) |
InitialContact | 1 | First contact moment |
StableContact | 2 | Established contact |
ContactLoss | 3 | Contact being broken |
Sliding | 4 | Sliding contact |
Impact | 5 | Impact detected |
Fields:
| Field | Type | Unit | Description |
|---|---|---|---|
state | u8 | — | Contact state (use ContactState as u8 to set) |
contact_force | f64 | N | Contact force magnitude |
contact_normal | Vector3 | — | Contact normal vector (estimated, unit vector) |
contact_point | Point3 | m | Contact point (estimated) |
stiffness | f64 | N/m | Contact stiffness (estimated) |
damping | f64 | Ns/m | Contact damping (estimated) |
confidence | f32 | — | Detection confidence (0.0 to 1.0) |
contact_start_time | u64 | ns | Time contact was first detected |
frame_id | [u8; 32] | — | Reference frame |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
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:
HapticFeedbackis 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:
| Constant | Value | Description |
|---|---|---|
PATTERN_CONSTANT | 0 | Constant intensity |
PATTERN_PULSE | 1 | Pulsing pattern |
PATTERN_RAMP | 2 | Ramping intensity |
Fields:
| Field | Type | Unit | Description |
|---|---|---|---|
vibration_intensity | f32 | — | Vibration intensity (0.0 to 1.0) |
vibration_frequency | f32 | Hz | Vibration frequency |
duration_seconds | f32 | s | Duration of feedback |
force_feedback | Vector3 | N | Force feedback vector |
pattern_type | u8 | — | Feedback pattern (see constants) |
enabled | u8 | — | Enable/disable feedback (0 = off, 1 = on) |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
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.
| Variant | Value | Description |
|---|---|---|
NoContact | 0 | No contact detected |
InitialContact | 1 | First moment of contact detected |
StableContact | 2 | Stable, sustained contact |
ContactLoss | 3 | Contact is being lost |
Sliding | 4 | Sliding contact (tangential motion) |
Impact | 5 | High-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:
TactileArrayusesVec<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:
| Field | Type | Unit | Description |
|---|---|---|---|
rows | u32 | — | Number of taxel rows |
cols | u32 | — | Number of taxel columns |
forces | Vec<f32> | N | Row-major taxel readings (length = rows × cols) |
total_force | [f32; 3] | N | Net contact force [fx, fy, fz] |
center_of_pressure | [f32; 2] | — | Normalized CoP [0.0, 1.0] (row, col) |
in_contact | bool | — | Whether any contact is detected |
physical_size | [f32; 2] | m | Sensor surface dimensions [width, height] |
timestamp_ns | u64 | ns | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
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
- Geometry Messages - Vector3, Point3
- Control Messages - Motor and actuator commands