Control Messages

HORUS provides comprehensive message types for controlling motors, servos, and multi-joint robotic systems. All control messages are fixed-size types with #[repr(C)] layout, enabling zero-copy shared memory transport at ~50ns latency.

MotorCommand

Direct motor control with multiple control modes.

use horus::prelude::*; // Provides control::MotorCommand;

// Velocity control
let mut vel_cmd = MotorCommand::velocity(0, 1.5);  // motor_id=0, 1.5 rad/s

// Position control with max velocity
let pos_cmd = MotorCommand::position(0, 3.14, 2.0);  // motor_id=0, 3.14 rad, max 2 rad/s

// Stop motor
let stop_cmd = MotorCommand::stop(0);

// Check validity — is_valid() requires target, max_velocity, max_acceleration and
// feed_forward to all be finite. velocity()/position()/stop() leave the limits at
// f64::INFINITY, so set finite limits before validating.
vel_cmd.max_velocity = 10.0;
vel_cmd.max_acceleration = 5.0;
if vel_cmd.is_valid() {
    println!("Target: {:.2}", vel_cmd.target);
}

Control Modes:

ConstantValueDescription
MODE_VELOCITY0Velocity control (rad/s)
MODE_POSITION1Position control (rad)
MODE_TORQUE2Torque control (Nm)
MODE_VOLTAGE3Direct voltage control

Fields:

FieldTypeDescription
motor_idu8Motor identifier
modeu8Control mode
targetf64Target value
max_velocityf64Velocity limit (position mode)
max_accelerationf64Acceleration limit
feed_forwardf64Feed-forward term
enableu8Motor enabled (1=enabled, 0=disabled)
timestamp_nsu64Nanoseconds since epoch

DifferentialDriveCommand

Commands for differential drive robots.

use horus::prelude::*; // Provides control::DifferentialDriveCommand;

// Direct wheel velocities
let cmd = DifferentialDriveCommand::new(1.0, 1.2);  // left=1.0, right=1.2 rad/s

// From linear and angular velocities
let wheel_base = 0.5;   // 50cm between wheels
let wheel_radius = 0.1; // 10cm wheels
let cmd = DifferentialDriveCommand::from_twist(
    0.5,  // 0.5 m/s forward
    0.2,  // 0.2 rad/s rotation
    wheel_base,
    wheel_radius
);

// Stop
let stop = DifferentialDriveCommand::stop();

Fields:

FieldTypeDescription
left_velocityf64Left wheel velocity (rad/s)
right_velocityf64Right wheel velocity (rad/s)
max_accelerationf64Acceleration limit (rad/s²)
enableu8Motors enabled (1=enabled, 0=disabled)
timestamp_nsu64Nanoseconds since epoch

Methods:

MethodDescription
new(left, right)Create with wheel velocities (rad/s)
from_twist(linear, angular, wheel_base, wheel_radius)Create from linear/angular velocity
stop()Stop both motors
is_valid()Check if all values are finite

ServoCommand

Position-controlled servo commands.

use horus::prelude::*; // Provides control::ServoCommand;

// Position command (radians)
let cmd = ServoCommand::new(0, 1.57);  // servo_id=0, 90 degrees

// With specific speed (0-1)
let cmd = ServoCommand::with_speed(0, 1.57, 0.3);  // 30% speed

// From degrees
let cmd = ServoCommand::from_degrees(0, 90.0);

// Disable servo (release torque)
let disable = ServoCommand::disable(0);

Fields:

FieldTypeDescription
servo_idu8Servo identifier
positionf32Target position (radians)
speedf32Movement speed (0-1, 0=max speed)
enableu8Torque enabled (1=enabled, 0=disabled)
timestamp_nsu64Nanoseconds since epoch

Note: HORUS has no PWM or stepper message type. Drive DC motors and steppers with MotorCommand for a single actuator (MODE_VOLTAGE for direct voltage control) or JointCommand for multi-axis systems.

PidConfig

PID controller configuration.

use horus::prelude::*; // Provides control::PidConfig;

// Full PID
let pid = PidConfig::new(1.0, 0.1, 0.05);  // Kp, Ki, Kd

// P-only controller
let p_only = PidConfig::proportional(2.0);

// PI controller
let pi = PidConfig::pi(1.5, 0.2);

// PD controller
let pd = PidConfig::pd(1.0, 0.1);

// With limits
let pid_limited = PidConfig::new(1.0, 0.1, 0.05)
    .with_limits(10.0, 100.0);  // integral_limit, output_limit

// Validation — is_valid() requires finite integral_limit/output_limit, so it
// only passes after with_limits(); PidConfig::new() leaves both at f64::INFINITY.
if pid_limited.is_valid() {
    println!("Kp={}, Ki={}, Kd={}", pid_limited.kp, pid_limited.ki, pid_limited.kd);
}

Fields:

FieldTypeDescription
controller_idu8Controller identifier
kpf64Proportional gain
kif64Integral gain
kdf64Derivative gain
integral_limitf64Integral windup limit
output_limitf64Output saturation limit
anti_windupu8Anti-windup enabled (1=enabled, 0=disabled)
timestamp_nsu64Nanoseconds since epoch

TrajectoryPoint

Single point in a trajectory.

use horus::prelude::*; // Provides control::TrajectoryPoint;

// Simple 2D trajectory point
let point = TrajectoryPoint::new_2d(
    1.0, 2.0,   // x, y position
    0.5, 0.3,   // vx, vy velocity
    1.5         // time from start (seconds)
);

// Stationary point (x, y, z)
let waypoint = TrajectoryPoint::stationary(1.0, 2.0, 0.0);

Fields:

FieldTypeDescription
position[f64; 3]Position [x, y, z]
velocity[f64; 3]Velocity [vx, vy, vz]
acceleration[f64; 3]Acceleration [ax, ay, az]
orientation[f64; 4]Quaternion [x, y, z, w]
angular_velocity[f64; 3]Angular velocity [wx, wy, wz]
time_from_startf64Time offset (seconds)

JointCommand

Multi-joint command for robot arms and manipulators.

use horus::prelude::*; // Provides control::JointCommand;

let mut cmd = JointCommand::new();

// These return `Result<(), &'static str>` — the error is the 16-joint capacity
// limit, not a HorusError, so `?` alone will not compile inside a function
// returning `Result<T>`. Convert it:
cmd.add_position("shoulder", 0.5).map_err(|e| Error::node("arm", e))?;
cmd.add_position("elbow", 1.0).map_err(|e| Error::node("arm", e))?;
cmd.add_position("wrist", -0.3).map_err(|e| Error::node("arm", e))?;

cmd.add_velocity("gripper", 0.2).map_err(|e| Error::node("arm", e))?;

add_position and add_velocity fail only when the command already holds 16 joints ("Maximum 16 joints supported"). If you would rather not convert, match on the Result or use if let Err(e) = ....

Control Modes:

ConstantValueDescription
MODE_POSITION0Position control (rad)
MODE_VELOCITY1Velocity control (rad/s)
MODE_EFFORT2Torque/effort control (Nm)

Fields:

FieldTypeDescription
joint_names[[u8; 32]; 16]Joint name strings
joint_countu8Number of joints (max 16)
positions[f64; 16]Position commands (rad)
velocities[f64; 16]Velocity commands (rad/s)
efforts[f64; 16]Effort commands (Nm)
modes[u8; 16]Control mode per joint
timestamp_nsu64Nanoseconds since epoch

Motor Control Node Example

use horus::prelude::*;

struct MotorDriverNode {
    cmd_sub: Topic<CmdVel>,
    left_pub: Topic<MotorCommand>,
    right_pub: Topic<MotorCommand>,
    wheel_base: f64,
    wheel_radius: f64,
    max_speed: f64,
}

impl Node for MotorDriverNode {
    fn name(&self) -> &str { "MotorDriver" }

    fn tick(&mut self) {
        if let Some(cmd) = self.cmd_sub.recv() {
            // Convert CmdVel to differential drive
            let diff = DifferentialDriveCommand::from_twist(
                cmd.linear as f64,
                cmd.angular as f64,
                self.wheel_base,
                self.wheel_radius
            );

            // Clamp wheel velocities to the drive limit (rad/s)
            let left = diff.left_velocity.clamp(-self.max_speed, self.max_speed);
            let right = diff.right_velocity.clamp(-self.max_speed, self.max_speed);

            // Send per-motor velocity commands
            self.left_pub.send(MotorCommand::velocity(0, left));
            self.right_pub.send(MotorCommand::velocity(1, right));
        }
    }
}

See Also