Force & Tactile Messages
HORUS provides message types for force/torque sensors, tactile arrays, impedance control, and haptic feedback systems commonly used in manipulation tasks.
All force types — WrenchStamped, TactileArray, ImpedanceParameters, ForceCommand, ContactInfo, ContactState, HapticFeedback — are re-exported through horus::prelude; use horus::prelude::*; is all you need.
WrenchStamped
6-DOF force and torque measurement from force/torque sensors. Implements PodMessage for zero-copy transfer.
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 against the previous sample
let previous_wrench = WrenchStamped::new(Vector3::new(9.8, 4.9, -2.1), torque);
let mut current = wrench;
current.filter(&previous_wrench, 0.1); // alpha = 0.1 (heavy filtering)
Fields:
| Field | Type | Description |
|---|---|---|
force | Vector3 | Force [fx, fy, fz] in Newtons |
torque | Vector3 | Torque [tx, ty, tz] in Nm |
point_of_application | Point3 | Force application point |
frame_id | [u8; 32] | Reference frame |
timestamp_ns | u64 | Nanoseconds since epoch |
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) |
TactileArray
Pressure/force array from tactile sensors (fingertip sensors, skin patches). Serde-serialized message (not POD — it holds a Vec<f32>, so it uses the serialization path rather than zero-copy shared memory).
use horus::prelude::*;
// Create 8x8 grid tactile array
let mut tactile = TactileArray::new(8, 8); // 8 rows x 8 cols
tactile.physical_size = [0.016, 0.016]; // 16mm x 16mm pad
// Set and get taxel readings
tactile.set_force(0, 0, 1.5);
tactile.set_force(0, 1, 2.0);
if let Some(reading) = tactile.get_force(0, 0) {
println!("Taxel (0,0): {:.1} N", reading);
}
// Get raw taxel readings
let readings = &tactile.forces; // row-major, len = rows * cols
// Total contact force
let total = tactile.total_force; // [fx, fy, fz] in Newtons
println!("Total force: [{:.2}, {:.2}, {:.2}] N", total[0], total[1], total[2]);
// Detect contact
if tactile.in_contact {
println!("Contact detected!");
}
// Center of pressure
let cop = tactile.center_of_pressure; // [row, col] normalized 0.0-1.0
println!("Center of pressure: ({:.2}, {:.2})", cop[0], cop[1]);
// Get contact pattern
let pattern: Vec<bool> = tactile.forces.iter().map(|f| *f > 0.5).collect();
Fields:
| Field | Type | Description |
|---|---|---|
rows | u32 | Number of taxel rows |
cols | u32 | Number of taxel columns |
forces | Vec<f32> | Taxel force readings in Newtons (row-major, len = rows × cols) |
total_force | [f32; 3] | Total contact force [fx, fy, fz] in Newtons |
center_of_pressure | [f32; 2] | Center of pressure, normalized 0.0-1.0 (row, col) |
in_contact | bool | Whether any contact is detected |
physical_size | [f32; 2] | Physical sensor size [width_m, height_m] |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new(rows, cols) | Allocate a rows × cols taxel grid |
get_force(row, col) | Get taxel reading as Option<f32> (None if out of bounds) |
set_force(row, col, force) | Set taxel reading (silently ignored if out of bounds) |
ImpedanceParameters
Impedance control parameters for compliant manipulation. Implements PodMessage for zero-copy transfer.
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 | Description |
|---|---|---|
stiffness | [f64; 6] | Stiffness [Kx, Ky, Kz, Krx, Kry, Krz] |
damping | [f64; 6] | Damping [Dx, Dy, Dz, Drx, Dry, Drz] |
inertia | [f64; 6] | Virtual inertia |
force_limits | [f64; 6] | Force/torque limits |
enabled | u8 | Impedance control active (0 = off, 1 = on) |
timestamp_ns | u64 | 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. Implements PodMessage for zero-copy transfer.
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 | Description |
|---|---|---|
target_force | Vector3 | Desired force [N] |
target_torque | Vector3 | Desired torque [Nm] |
force_mode | [u8; 6] | 1 = force control, 0 = position control per axis |
position_setpoint | Vector3 | Position target for position-controlled axes |
orientation_setpoint | Vector3 | Orientation target (Euler angles) |
max_deviation | Vector3 | Maximum position deviation |
gains | [f64; 6] | Control gains |
timeout_seconds | f64 | Command timeout (0 = no timeout) |
frame_id | [u8; 32] | Reference frame |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
force_only(target_force) | Create pure force command (force_mode = [1, 1, 1, 0, 0, 0] — the three translational axes are 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. Implements PodMessage for zero-copy transfer.
use horus::prelude::*;
// 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 | Description |
|---|---|---|
state | u8 | Contact state (use ContactState as u8 to set) |
contact_force | f64 | Contact force magnitude |
contact_normal | Vector3 | Contact normal vector (estimated) |
contact_point | Point3 | Contact point (estimated) |
stiffness | f64 | Contact stiffness (estimated) |
damping | f64 | Contact damping (estimated) |
confidence | f32 | Detection confidence (0.0 to 1.0) |
contact_start_time | u64 | Time contact was first detected (ns) |
frame_id | [u8; 32] | Reference frame |
timestamp_ns | u64 | 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. Implements PodMessage for zero-copy transfer.
use horus::prelude::*;
// 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 | Description |
|---|---|---|
vibration_intensity | f32 | Vibration intensity (0.0 to 1.0) |
vibration_frequency | f32 | Vibration frequency in Hz |
duration_seconds | f32 | Duration of feedback in seconds |
force_feedback | Vector3 | Force feedback vector |
pattern_type | u8 | Feedback pattern (see constants) |
enabled | u8 | Enable/disable feedback (0 = off, 1 = on) |
timestamp_ns | u64 | 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);
}
}
}
See Also
- Geometry Messages - Vector3, Point3
- Control Messages - Motor and actuator commands