Jogging

Jogging instructions move a robot arm in small increments along a desired direction or to a desired pose instead of following a whole path in one go. They require continuous input or confirmation to keep the motion alive: as soon as the input stops, the robot decelerates and stops. This makes jogging a dead man’s switch, which is why it is used for maintenance interactions, for teaching poses, and for debugging in HMI applications.

Jogging instructions cannot be mixed with normal instructions. Each type of instruction can only be processed exclusively at any given time. Issuing normal instructions while jogging is active fails with an error, and vice versa.

Jogging comes in two shapes, and the API keeps them apart deliberately:

  • Continuous jogging (jog_joint(), jog_tcp()) is a stream of setpoints. Every call may carry a new velocity and direction, and each setpoint expires on its own.

  • Target jogging (jog_ptp(), jog_linear()) is a fixed motion that only proceeds while the application keeps confirming it.

Jogging of a Joint

To jog a single robot joint, use jog_joint() from the JogJoint trait. Supply the joint as a Joint (Joint::J1 is the first joint) and the maximum velocity. For a rotary axis, give the velocity as a RadiansPerSecond or a DegreesPerSecond. For a linear axis, give it as a MetersPerSecond. The sign of the velocity defines the direction. Optionally, chain a maximum acceleration as a RadiansPerSecondSquared for a rotary axis or a MetersPerSecondSquared for a linear axis. It defaults to DEFAULT_JOG_JOINT_ACCELERATION, which is 1.25 in the SI unit of the axis. Other limits might apply before the given maximum velocity or acceleration is reached.

As long as the call is repeated, the robot keeps jogging in the desired direction. Each setpoint expires after about 150 ms; when calls are absent, the joint decelerates and comes to a stop. Each start() returns a plain InstructionHandle. It resolves when the next setpoint supersedes it, and the handle of the last setpoint resolves when the robot has stopped moving. Like any handle, it can be dropped, or the last one joined to synchronize with the standstill.

To keep the motion alive, refresh the setpoint periodically. Calling faster than every few milliseconds causes undesired side effects on the control and fails with an error; the library constant JOG_KEEP_ALIVE_INTERVAL (100 ms) is the recommended cadence. The example jogs for a fixed duration by refreshing the setpoint in a loop and then waits for the last handle, which is done when the robot stands still:

fn jog_joint_example(robot: &impl JogJoint) -> Result<()> {
    info!("--- Jog Joint (continuous, manual loop) ---");

    let mut last = None;
    let start = Instant::now();
    while start.elapsed() < JOG_DURATION {
        last = Some(
            robot
                .jog_joint(Joint::J1, RadiansPerSecond(0.5))
                .acceleration(RadiansPerSecondSquared(0.5))
                .start()?,
        );
        sleep(JOG_KEEP_ALIVE_INTERVAL);
    }

    if let Some(handle) = last {
        handle.join()?;
    }
    info!("Joint jog finished.");
    Ok(())
}

Jogging the Tool Center Point in Cartesian Space

To jog in Cartesian space, use jog_tcp() from the JogTcp trait. Supply a CartesianVelocity, the maximum velocity in each direction of the coordinate system in meters per second for the translations and radians per second for the rotations. A single direction or several directions may be non-zero, and negative values jog in the opposite direction.

Optionally, chain the coordinate system to move in (cs(), default Cs::Robot) and a CartesianAccelerationMagnitude, the maximum translational and rotational acceleration (acceleration(), default DEFAULT_JOG_TCP_ACCELERATION). Other limits might apply before the given maxima are reached.

Like joint jogging, the tool center point keeps jogging as long as setpoints arrive and decelerates when they stop. Instead of refreshing the setpoint in your own loop, you can let the library own it: run_while() resends the setpoint at the library cadence as long as a closure returns true, then releases and returns once the robot has stopped. run_with() is the variant for a velocity that changes from tick to tick: its closure yields Some(velocity) to keep jogging and None to release. Both methods exist for joint jogging as well.

fn jog_tcp_example(robot: &impl JogTcp) -> Result<()> {
    info!("--- Jog TCP (continuous, run_while) ---");

    let start = Instant::now();
    robot
        .jog_tcp(CartesianVelocity {
            x: MetersPerSecond(0.1),
            y: MetersPerSecond(0.1),
            ..Default::default()
        })
        .cs(Cs::Robot)
        .acceleration(CartesianAccelerationMagnitude::new(
            MetersPerSecondSquared(1.0),
            RadiansPerSecondSquared(1.25),
        ))
        // A real HMI would check a button here.
        .run_while(|| start.elapsed() < JOG_DURATION)?;
    info!("TCP jog finished.");
    Ok(())
}

A pause ends a continuous jog rather than holding it, because every setpoint would move the robot on its own once motion is continued. Setpoints not sent yet when pause_motion() is called are withdrawn, and new ones are refused with JoggingRejectedError::Paused until continue_motion() is called; run_while() and run_with() then return that error once the robot has stopped. Target jogs are queued during a pause like any other instruction.

Jogging to a Pose

A jogging instruction to a target pose behaves like a normal movement such as move_ptp() or move_linear(), except that it only proceeds while the application confirms it.

Use jog_ptp() and jog_linear() for absolute motions and jog_ptp_relative() and jog_linear_relative() for relative motions. They accept the same targets as the corresponding motions. The PTP variant takes an optional .speed(), the linear variant a required velocity and an optional .parameters() profile. The robot control limits target jogging to a TCP speed of 0.25 m/s and reduces a higher speed to that limit.

start() returns a JoggingHandle rather than an InstructionHandle. In contrast to normal motions, and like continuous jogging, a keep-alive call is required via continue_jogging() on the handle to continue the execution. This guarantees that the jog stops if the user input is removed, or if the application crashes or loses its connection. The robot decelerates and stops if no confirmation arrives within 150 ms, even if the target has not been reached.

The first example jogs the first joint by 60 degrees, driving the cadence from its own loop:

fn jog_ptp_example(robot: &impl JogPtp) -> Result<()> {
    info!("--- Jog PTP (target-pose, manual loop) ---");

    let offset = JointPose::new(
        Degrees(60.0),
        Radians(0.0),
        Radians(0.0),
        Radians(0.0),
        Radians(0.0),
        Radians(0.0),
    );
    let mut jog = robot.jog_ptp_relative(offset).start()?;

    let start = Instant::now();
    while start.elapsed() < JOG_DURATION && !jog.is_done() {
        jog.continue_jogging()?;
        sleep(JOG_KEEP_ALIVE_INTERVAL);
    }

    match jog.join() {
        Ok(()) => info!("PTP jog reached its target."),
        Err(e) => info!("PTP jog ended: {e}"),
    }
    Ok(())
}

The JoggingHandle implements the Wait trait like an InstructionHandle to observe the overall result: it has is_done(), outcome(), join(), join_timeout(), and wait_timeout(), which blocks with a deadline but keeps the handle: it returns None on a timeout, after which the jog can still be continued or joined, and otherwise Some of the outcome. The result is Ok if the desired pose is reached. It is an error if there was a robot error, or if the keep-alive was missed, in which case the jog was aborted with StopTriggered. Calling continue_jogging() too rapidly (less than 5 ms apart) also fails with an error; JOG_KEEP_ALIVE_INTERVAL (100 ms) is the recommended cadence.

Dropping the handle stops confirming, so the robot decelerates and the outcome is no longer observed. The handle is therefore #[must_use].

The second example jogs linearly to a previously defined pose and lets the library own the cadence. keep_alive_while() confirms while the closure returns true or until the target is reached, then waits for the robot to stop and reports how the jog ended as a JogOutcome. Releasing early is a normal outcome here, not an error:

fn jog_linear_example(robot: &(impl JogLinear + MovePtp)) -> Result<()> {
    info!("--- Jog Linear (target-pose, keep_alive_while) ---");

    // First move away from home so the jog back is visible.
    robot
        .move_ptp(poses::home())
        .move_ptp_relative(JointPose::new(
            Radians(0.0),
            Radians(0.2),
            Radians(0.0),
            Radians(0.0),
            Radians(0.0),
            Radians(0.0),
        ))
        .start()?
        .join()?;

    let start = Instant::now();
    let outcome = robot
        .jog_linear(poses::home(), common::SLOW_TRAVEL_SPEED)
        .start()?
        .keep_alive_while(|| start.elapsed() < JOG_DURATION)?;
    info!("Linear jog ended: {outcome:?}");
    Ok(())
}

Full Example

Trait Definitions

The traits carry the entry points as default methods; their required methods are what a robot type implements. The entry points return builders (JogJointMotion, JogTcpMotion, JogPtpMotion, JogLinearMotion). The builders and the JoggingHandle are documented in the API reference.