JointState / JointCommand

Messages for multi-joint robots: manipulator arms, grippers, legged robots, and any system with named revolute or prismatic joints. JointState reports current positions/velocities/efforts; JointCommand sends target positions/velocities.

When to Use

Use JointState to publish feedback from joint encoders (position, velocity, effort). Use JointCommand to send target positions or velocities to a servo controller. Supports up to 16 joints per message.

ROS2 Equivalent

sensor_msgs/JointState and trajectory_msgs/JointTrajectoryPoint — similar structure.

JointState — Feedback

Rust

use horus::prelude::*;

let mut state = JointState::default();
state.joint_count = 6;  // 6-DOF arm

// Set joint names (null-terminated, max 31 chars)
state.names[0][..8].copy_from_slice(b"shoulder");
state.names[1][..5].copy_from_slice(b"elbow");
state.names[2][..5].copy_from_slice(b"wrist");

// Set current positions (radians)
state.positions[0] = 0.5;   // shoulder at 0.5 rad
state.positions[1] = -1.2;  // elbow at -1.2 rad
state.positions[2] = 0.0;   // wrist at home

let topic: Topic<JointState> = Topic::new("joint.state")?;
topic.send(state);

Fields

FieldTypeUnitDescription
names[[u8; 32]; 16]--Joint names (null-terminated strings)
joint_countu8--Number of active joints (max 16)
positions[f64; 16]rad / mPosition: radians (revolute) or meters (prismatic)
velocities[f64; 16]rad/s / m/sVelocity
efforts[f64; 16]Nm / NTorque (revolute) or force (prismatic)
timestamp_nsu64nsTimestamp

JointCommand — Control

Rust

use horus::prelude::*;

let mut cmd = JointCommand::default();
cmd.joint_count = 3;

// Target positions
cmd.positions[0] = 1.0;   // shoulder to 1.0 rad
cmd.positions[1] = -0.5;  // elbow to -0.5 rad
cmd.positions[2] = 0.3;   // wrist to 0.3 rad

// Velocity limits
cmd.velocities[0] = 0.5;  // max 0.5 rad/s
cmd.velocities[1] = 0.5;
cmd.velocities[2] = 1.0;

let topic: Topic<JointCommand> = Topic::new("joint.command")?;
topic.send(cmd);

Fields

FieldTypeUnitDescription
joint_names[[u8; 32]; 16]--Joint names (null-terminated strings)
joint_countu8--Number of active joints (max 16)
positions[f64; 16]rad / mTarget positions
velocities[f64; 16]rad/s / m/sVelocity limits or targets
efforts[f64; 16]Nm / NTorque/force limits or targets
timestamp_nsu64nsTimestamp

Common Patterns

Arm Controller Node

fn tick(&mut self) {
    // IMPORTANT: always recv() every tick
    if let Some(cmd) = self.cmd_sub.recv() {
        for i in 0..cmd.joint_count as usize {
            // SAFETY: clamp to joint limits
            let pos = cmd.positions[i].clamp(self.min_limits[i], self.max_limits[i]);
            self.write_servo(i, pos);
        }
    }

    // Read encoder feedback
    let mut state = JointState::default();
    state.joint_count = self.num_joints;
    for i in 0..self.num_joints as usize {
        state.positions[i] = self.read_encoder(i);
    }
    self.state_pub.send(state);
}

fn shutdown(&mut self) -> Result<()> {
    // SAFETY: move all joints to home position
    for i in 0..self.num_joints as usize {
        self.write_servo(i, 0.0);
    }
    Ok(())
}