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
Jogging example
//! Jogging examples.
//!
//! Demonstrates the four jogging methods:
//!
//! - **Continuous joint jog** (`jog_joint()`, [`JogJoint`])
//! - **Continuous TCP jog** (`jog_tcp()`, [`JogTcp`])
//! - **Target-pose PTP jog** (`jog_ptp_relative()`, [`JogPtp`])
//! - **Target-pose linear jog** (`jog_linear()`, [`JogLinear`])
mod common;
use std::thread::sleep;
use std::time::Duration;
use std::time::Instant;
use common::poses;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// How long to jog in each sub-example.
const JOG_DURATION: Duration = Duration::from_secs(3);
/// Continuous joint jog, manual loop.
///
/// Every call is a fresh setpoint that expires after ~150 ms. Velocity and
/// direction may change from tick to tick. Each `start()` returns a plain
/// [`InstructionHandle`]; the last one resolves when the robot has stopped.
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(())
}
/// Continuous TCP jog using the library's setpoint loop.
///
/// `run_while` resends this setpoint at the library cadence as long as the
/// 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, as read from a joystick.
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(())
}
/// Target-pose PTP jog, manual keep-alive loop.
///
/// The target is fixed; `continue_jogging()` is a bare confirmation.
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(())
}
/// Target-pose linear jog using the library's keep-alive loop.
///
/// `keep_alive_while` confirms at the library cadence while the closure
/// returns true or until the target is reached, then waits for the robot
/// to stop and reports how the jog ended.
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(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
// Move to home first so all sub-examples start from a known pose.
robot.move_ptp(poses::home()).start()?.join()?;
jog_joint_example(&robot)?;
jog_tcp_example(&robot)?;
jog_ptp_example(&robot)?;
jog_linear_example(&robot)?;
info!("Done.");
Ok(())
}
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.
JogJoint
pub trait JogJoint {
/// Send one joint-jogging setpoint.
///
/// # Returns
///
/// An [`InstructionHandle`] that resolves when the jogging motion ends.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the setpoint
/// is rejected.
#[doc(hidden)]
fn execute_jog_joint(&self, request: JogJointRequest) -> Result<InstructionHandle>;
/// Jog one joint continuously.
///
/// The sign of `velocity` is the direction. Each setpoint is valid for at
/// most 150 ms. Send setpoints every
/// [`JOG_KEEP_ALIVE_INTERVAL`](crate::jogging_handle::JOG_KEEP_ALIVE_INTERVAL) to
/// keep the joint moving. Each setpoint can carry another velocity. Once
/// the setpoints stop, the joint decelerates to standstill.
///
/// Default: [`DEFAULT_JOG_JOINT_ACCELERATION`]. Change it with
/// [`acceleration`](JogJointMotion::acceleration), in rad/s² for a rotary
/// axis or m/s² for a linear axis. Send the jog with one
/// of these methods:
///
/// - [`run_while`](JogJointMotion::run_while) resends this setpoint while
/// a closure returns `true`.
/// - [`run_with`](JogJointMotion::run_with) takes the velocity of each
/// setpoint from a closure, for example from a joystick.
/// - [`start`](JogJointMotion::start) sends one setpoint. Your code
/// sends the next one.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogJoint, button_held: impl FnMut() -> bool) -> Result<()> {
/// robot
/// .jog_joint(Joint::J1, DegreesPerSecond(10.0))
/// .acceleration(RadiansPerSecondSquared(0.5))
/// .run_while(button_held)?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `joint` - The joint to jog.
/// * `velocity` - The signed joint velocity: a
/// [`RadiansPerSecond`](crate::types::units::RadiansPerSecond) or a
/// [`DegreesPerSecond`](crate::types::units::DegreesPerSecond) for a
/// rotary axis, a
/// [`MetersPerSecond`](crate::types::units::MetersPerSecond) for a
/// linear axis (see [`JointVelocity`]).
///
/// # Returns
///
/// A setpoint builder with the default acceleration; nothing is sent until
/// `start`, `run_while` or `run_with` is called.
fn jog_joint(
&self,
joint: Joint,
velocity: impl Into<JointVelocity>,
) -> JogJointMotion<'_, Self>
where
Self: Sized,
{
jog_joint_motion(self, joint, velocity.into())
}
}
JogTcp
pub trait JogTcp {
/// Send one TCP-jogging setpoint.
///
/// # Returns
///
/// An [`InstructionHandle`] that resolves when the jogging motion ends.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the setpoint
/// is rejected.
#[doc(hidden)]
fn execute_jog_tcp(&self, request: JogTcpRequest) -> Result<InstructionHandle>;
/// Jog the tool center point continuously.
///
/// The signs of the velocity components are the directions; any
/// combination of translational and rotational components is allowed.
/// Each setpoint is valid for at most 150 ms. Send setpoints every
/// [`JOG_KEEP_ALIVE_INTERVAL`](crate::jogging_handle::JOG_KEEP_ALIVE_INTERVAL) to
/// keep the TCP moving. Each setpoint can carry another velocity. Once
/// the setpoints stop, the robot decelerates to standstill.
///
/// Defaults: the robot coordinate system and
/// [`DEFAULT_JOG_TCP_ACCELERATION`]. Change them with
/// [`cs`](JogTcpMotion::cs) and
/// [`acceleration`](JogTcpMotion::acceleration). Send the jog with one
/// of these methods:
///
/// - [`run_while`](JogTcpMotion::run_while) resends this setpoint while
/// a closure returns `true`.
/// - [`run_with`](JogTcpMotion::run_with) takes the velocity of each
/// setpoint from a closure, for example from a joystick.
/// - [`start`](JogTcpMotion::start) sends one setpoint. Your code sends
/// the next one.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogTcp, joystick: impl FnMut() -> Option<CartesianVelocity>) -> Result<()> {
/// // Jog in the tool coordinate system while the joystick yields velocities.
/// robot
/// .jog_tcp(CartesianVelocity::default())
/// .cs(Cs::Tool)
/// .run_with(joystick)?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `velocity` - The signed Cartesian velocity components.
///
/// # Returns
///
/// A setpoint builder in the robot coordinate system with the default
/// acceleration; nothing is sent until `start`, `run_while` or `run_with` is called.
fn jog_tcp(&self, velocity: CartesianVelocity) -> JogTcpMotion<'_, Self>
where
Self: Sized,
{
jog_tcp_motion(self, velocity)
}
}
JogPtp
pub trait JogPtp {
/// Queue a fully specified PTP jog.
///
/// # Returns
///
/// A [`JoggingHandle`] to keep the jog alive and observe its outcome.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the jog
/// cannot be queued.
#[doc(hidden)]
fn execute_jog_ptp(&self, request: JogPtpRequest) -> Result<JoggingHandle<'_>>;
/// Jog to an absolute target in joint space.
///
/// The robot interpolates in joint space, as for
/// [`move_ptp`](crate::traits::MovePtp::move_ptp),
/// and the Cartesian path is not defined. The robot only moves while the
/// jog is confirmed. It decelerates and stops if no confirmation arrives
/// within 150 ms, even if the target has not been reached.
///
/// Default: full speed. Change it with [`speed`](JogPtpMotion::speed).
/// The control limits jogging to a TCP speed of 0.25 m/s. Send the jog
/// with [`start`](JogPtpMotion::start), then keep it alive with
/// [`keep_alive_while`](JoggingHandle::keep_alive_while) or
/// [`continue_jogging`](JoggingHandle::continue_jogging).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogPtp, target: JointPose) -> Result<()> {
/// let outcome = robot
/// .jog_ptp(target)
/// .speed(Factor(0.5))
/// .start()?
/// // A real HMI would check a button here.
/// .keep_alive_while(|| true)?;
/// assert_eq!(outcome, JogOutcome::TargetReached);
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `target` - The absolute target pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget)
/// (see [`PoseTarget`]).
///
/// # Returns
///
/// A jog builder at full speed. Nothing is sent until `start` is called.
fn jog_ptp(&self, target: impl Into<PoseTarget>) -> JogPtpMotion<'_, Self>
where
Self: Sized,
{
jog_ptp_motion(self, target.into(), false)
}
/// Jog by an offset from the current pose in joint space.
///
/// The robot interpolates in joint space, as for
/// [`jog_ptp`](JogPtp::jog_ptp), and the Cartesian
/// path is not defined. The robot only moves while the jog is confirmed.
/// It decelerates and stops if no confirmation arrives within 150 ms.
///
/// Default: full speed. Change it with [`speed`](JogPtpMotion::speed).
/// The control limits jogging to a TCP speed of 0.25 m/s. Send the jog
/// with [`start`](JogPtpMotion::start), then keep it alive with
/// [`keep_alive_while`](JoggingHandle::keep_alive_while) or
/// [`continue_jogging`](JoggingHandle::continue_jogging).
///
/// # Example
///
/// ```rust
/// # use std::thread::sleep;
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogPtp, button_held: impl Fn() -> bool) -> Result<()> {
/// // Jog 10 cm up while the button is held.
/// let mut jog = robot.jog_ptp_relative(z(Meters(0.1))).start()?;
/// while button_held() && !jog.is_done() {
/// jog.continue_jogging()?;
/// sleep(JOG_KEEP_ALIVE_INTERVAL);
/// }
/// // Without confirmations the robot stops before the target.
/// match jog.join() {
/// Ok(()) => println!("target reached"),
/// Err(e) => println!("jog ended: {e}"),
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `offset` - The offset from the current pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget)
/// (see [`PoseTarget`]).
///
/// # Returns
///
/// A jog builder at full speed. Nothing is sent until `start` is called.
fn jog_ptp_relative(&self, offset: impl Into<PoseTarget>) -> JogPtpMotion<'_, Self>
where
Self: Sized,
{
jog_ptp_motion(self, offset.into(), true)
}
}
JogLinear
pub trait JogLinear {
/// Queue a fully specified linear jog.
///
/// # Returns
///
/// A [`JoggingHandle`] to keep the jog alive and observe its outcome.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the jog
/// cannot be queued.
#[doc(hidden)]
fn execute_jog_linear(&self, request: JogLinearRequest) -> Result<JoggingHandle<'_>>;
/// Jog to an absolute target on a straight line.
///
/// The tool center point moves on a straight Cartesian line to the
/// target. The robot only moves while the jog is confirmed. It
/// decelerates and stops if no confirmation arrives within 150 ms, even
/// if the target has not been reached. The control limits jogging to a
/// TCP speed of 0.25 m/s and reduces a higher `speed` to that limit.
///
/// Default: the motion profile of the control. Change it with
/// [`parameters`](JogLinearMotion::parameters). Send the jog with
/// [`start`](JogLinearMotion::start), then keep it alive with
/// [`keep_alive_while`](JoggingHandle::keep_alive_while) or
/// [`continue_jogging`](JoggingHandle::continue_jogging).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogLinear, home: CartesianPose, button_held: impl FnMut() -> bool) -> Result<()> {
/// let outcome = robot
/// .jog_linear(home, MetersPerSecond(0.1))
/// .start()?
/// .keep_alive_while(button_held)?;
/// match outcome {
/// JogOutcome::TargetReached => println!("at home"),
/// JogOutcome::Released => println!("released before home"),
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `target` - The absolute target pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget)
/// (see [`PoseTarget`]).
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`] or a unit that converts into it; a negative or
/// non-finite value fails at `start`.
///
/// # Returns
///
/// A jog builder with the control's default motion profile; nothing is sent
/// until `start` is called.
fn jog_linear(
&self,
target: impl Into<PoseTarget>,
speed: impl Into<MetersPerSecond>,
) -> JogLinearMotion<'_, Self>
where
Self: Sized,
{
jog_linear_motion(self, target.into(), speed.into(), false)
}
/// Jog by an offset from the current pose on a straight line.
///
/// The tool center point moves on a straight Cartesian line, as for
/// [`jog_linear`](JogLinear::jog_linear). The robot
/// only moves while the jog is confirmed. It decelerates and stops if no
/// confirmation arrives within 150 ms. The control limits jogging to a
/// TCP speed of 0.25 m/s and reduces a higher `speed` to that limit.
///
/// Default: the motion profile of the control. Change it with
/// [`parameters`](JogLinearMotion::parameters). Send the jog with
/// [`start`](JogLinearMotion::start), then keep it alive with
/// [`keep_alive_while`](JoggingHandle::keep_alive_while) or
/// [`continue_jogging`](JoggingHandle::continue_jogging).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl JogLinear, button_held: impl FnMut() -> bool) -> Result<()> {
/// // Jog 5 cm along x while the button is held.
/// robot
/// .jog_linear_relative(x(Meters(0.05)), MetersPerSecond(0.05))
/// .start()?
/// .keep_alive_while(button_held)?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `offset` - The offset from the current pose: a
/// [`JointPose`](crate::types::JointPose), a
/// [`CartesianPose`](crate::types::CartesianPose) or a
/// [`CartesianTarget`](crate::types::CartesianTarget)
/// (see [`PoseTarget`]).
/// * `speed` - The maximum translational TCP speed, as
/// [`MetersPerSecond`] or a unit that converts into it; a negative or
/// non-finite value fails at `start`.
///
/// # Returns
///
/// A jog builder with the control's default motion profile; nothing is sent
/// until `start` is called.
fn jog_linear_relative(
&self,
offset: impl Into<PoseTarget>,
speed: impl Into<MetersPerSecond>,
) -> JogLinearMotion<'_, Self>
where
Self: Sized,
{
jog_linear_motion(self, offset.into(), speed.into(), true)
}
}