Time Override
The global time override of a robot arm is a scaling factor used to adjust the overall speed of the robot’s
movements. It ranges from a factor of 0.0 to 1.0 (or from 0 % to 100 %). A factor of 1.0 means the robot
operates at its maximum speed, whereas lower values slow down all movements proportionally. The time override should
be used to scale the velocity of a whole application, for example to minimize hazards during testing.
Warning
A time override of 0.0 halts all motion. A motion under 0.0 does not progress, and its join() waits until the
time override rises again. Do not use 0.0 to pause an application. Use pause_motion() from the PauseContinue
trait instead.
Note
If possible, refrain from using the time override for scaling the velocity during operation. Instead, use the
speed() modifier of the motion instructions. The effective speed of a motion is the time override times its speed.
Reading the Current Time Override
The current time override is read with time_override() from the GetTimeOverride trait. It returns a Factor;
Percent::from() converts it. The robot control reports factors from 0.0 to 1.0; time_override() returns an
error for anything else:
let current = robot.time_override()?;
info!(
"Current time override: {current} ({})",
Percent::from(current)
);
Setting the Time Override
The time override is set with set_time_override(). The desired value can be given as a Factor or as a
Percent. The method returns once the robot control reports the desired value, at most after the robot’s
command timeout. If the time override could not be set for any reason,
set_time_override() returns an error.
send_time_override() takes the same value but returns a CommandHandle as soon as the value is sent, so the
program can do other work while the robot control applies it. confirm() on the handle waits until the value is
applied, at most the command timeout. confirm_timeout() takes its own timeout.
The time override is one global value that other clients of the robot control and other threads can write as well. If
another writer changes the value while the confirmation is pending, set_time_override() or confirm() returns
RobotArmError::NotConfirmed although the robot control accepted the value. In that case read the current value with
time_override() to learn which value is in effect.
A typical use is to slow the whole application down in a test mode:
fn decrease_speed_if_in_test_mode(robot: &impl SetTimeOverride) -> Result<()> {
let in_test_mode = env::var("TEST_MODE")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
if in_test_mode {
robot.set_time_override(Factor(0.25))?;
} else {
robot.set_time_override(Percent(100.0))?;
}
Ok(())
}
Time Override for a Section
A time override that only applies to a section of the program is expressed with with_time_override(). It sets the
value and waits for the confirmation, runs the closure, and restores the previous value afterwards, also when the
closure returns an error or setting the value fails. The closure’s result is returned; if the previous value could not
be restored after a successful closure, that error is returned instead, so the robot never silently keeps the
section’s speed. The closure may return the application’s own error type, as long as RobotArmError converts into
it, so non-robot work in the section can propagate its errors with ?:
robot.with_time_override(Factor(0.25), |robot| -> Result<()> {
robot
.move_ptp(poses::home())
.move_ptp(poses::vertical())
.start()?
.join()?;
info!("Moved at 25 %.");
Ok(())
})?;
info!("Restored to {}.", robot.time_override()?);
Setting the Time Override with a Transition Time
The time override can also be changed gradually over a transition time with set_time_override_with_transition()
from the TimeOverrideTransition trait. It takes the desired value as Factor or Percent and the transition as a
Duration, which must be at least 250 ms.
If this method is called during a robot standstill, the time override is set immediately. If it is called during a robot motion, the time override transitions smoothly and reaches its value after the transition time. If the robot motion stops before the transition has ended, the transition continues until the target value is reached. A short transition time can lead to high accelerations and torques.
The transition can be observed while a motion is running:
fn run_transition_example(robot: &(impl TimeOverrideTransition + MovePtp)) -> Result<()> {
info!("--- Time Override with Transition ---");
robot.move_ptp(poses::home()).start()?.join()?;
// Slow down to 10%; the original time override is restored afterwards.
robot.with_time_override(Factor(0.1), |robot| {
// Start a slow motion.
let motion = robot.move_ptp(poses::vertical()).start()?;
// Wait for robot to start moving.
sleep(Duration::from_millis(500));
// Smoothly transition to 25% over 5 seconds. Returns once the
// transition is sent.
let transition =
robot.send_time_override_with_transition(Factor(0.25), Duration::from_secs(5))?;
// Monitor the changing time override while the motion completes.
while !motion.is_done() {
info!("Current time override: {:.2}", robot.time_override()?);
sleep(Duration::from_secs(1));
}
// Wait until the time override reaches the target, at most the
// transition time plus the command timeout.
transition.confirm()?;
motion.join()
})
}
The log then shows the value rising (timestamps and log levels left out):
Current time override: 0.10
Current time override: 0.11
Current time override: 0.14
Current time override: 0.20
Current time override: 0.23
Current time override: 0.25
set_time_override_with_transition() returns once the time override reaches the desired value, at most after the transition
time plus the command timeout. The example uses send_time_override_with_transition() instead, which returns a CommandHandle
once the transition is sent; confirm() on the handle does the same wait. Another call while a transition is active
overrides the previous one, so the previous one is not confirmed and returns RobotArmError::NotConfirmed. As the
confirmation only observes the global value, a concurrent write by another client or thread can confirm the call or
make it return RobotArmError::NotConfirmed; read the current value with time_override() to learn which value is in
effect.
Full Example
Time override example
//! Time override examples.
//!
//! Demonstrates how to read and set the global time override, both with
//! immediate effect and with a smooth transition time.
mod common;
use std::env;
use std::thread::sleep;
use std::time::Duration;
use common::poses;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// Example showing how to read and set the time override.
fn run_basic_example(robot: &(impl SetTimeOverride + MovePtp)) -> Result<()> {
info!("--- Time Override (basic) ---");
// Read the time override as a factor and log it in percent, too.
let current = robot.time_override()?;
info!(
"Current time override: {current} ({})",
Percent::from(current)
);
// Move with different per-instruction speeds. The effective speed is
// time override times speed.
robot
.move_ptp_relative(CartesianPose::new(
Meters(0.0),
Meters(0.0),
Meters(0.1),
Radians(0.0),
Radians(0.0),
Radians(0.0),
))
.speed(Factor(0.75))
.start()?
.join()?;
robot
.move_ptp_relative(CartesianPose::new(
Meters(0.0),
Meters(0.0),
Meters(-0.1),
Radians(0.0),
Radians(0.0),
Radians(0.0),
))
.speed(Factor(1.0))
.start()?
.join()?;
Ok(())
}
/// Decrease the overall speed when running in test mode.
///
/// `set_time_override()` returns once the control applies the value, at
/// most after the robot's command timeout.
fn decrease_speed_if_in_test_mode(robot: &impl SetTimeOverride) -> Result<()> {
let in_test_mode = env::var("TEST_MODE")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
if in_test_mode {
robot.set_time_override(Factor(0.25))?;
} else {
robot.set_time_override(Percent(100.0))?;
}
Ok(())
}
/// Time override for a section of the program: the previous value is
/// restored after the closure, also when it fails.
fn run_scoped_example(robot: &(impl SetTimeOverride + MovePtp)) -> Result<()> {
info!("--- Scoped time override ---");
robot.with_time_override(Factor(0.25), |robot| -> Result<()> {
robot
.move_ptp(poses::home())
.move_ptp(poses::vertical())
.start()?
.join()?;
info!("Moved at 25 %.");
Ok(())
})?;
info!("Restored to {}.", robot.time_override()?);
Ok(())
}
/// Example showing time override with transition time.
fn run_transition_example(robot: &(impl TimeOverrideTransition + MovePtp)) -> Result<()> {
info!("--- Time Override with Transition ---");
robot.move_ptp(poses::home()).start()?.join()?;
// Slow down to 10%; the original time override is restored afterwards.
robot.with_time_override(Factor(0.1), |robot| {
// Start a slow motion.
let motion = robot.move_ptp(poses::vertical()).start()?;
// Wait for robot to start moving.
sleep(Duration::from_millis(500));
// Smoothly transition to 25% over 5 seconds. Returns once the
// transition is sent.
let transition =
robot.send_time_override_with_transition(Factor(0.25), Duration::from_secs(5))?;
// Monitor the changing time override while the motion completes.
while !motion.is_done() {
info!("Current time override: {:.2}", robot.time_override()?);
sleep(Duration::from_secs(1));
}
// Wait until the time override reaches the target, at most the
// transition time plus the command timeout.
transition.confirm()?;
motion.join()
})
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
// Move to home.
robot.move_ptp(poses::home()).start()?.join()?;
decrease_speed_if_in_test_mode(&robot)?;
run_basic_example(&robot)?;
run_scoped_example(&robot)?;
run_transition_example(&robot)?;
info!("Done.");
Ok(())
}
Trait Definitions
GetTimeOverride
pub trait GetTimeOverride {
/// Get the global time override.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl GetTimeOverride) -> Result<()> {
/// let factor = robot.time_override()?;
/// println!("{factor} ({})", Percent::from(factor));
/// # Ok(())
/// # }
/// ```
///
/// # Returns
///
/// The current time override as a factor in `0.0..=1.0`.
///
/// # Errors
///
/// Returns [`RobotArmError::InvalidValue`](crate::error::RobotArmError::InvalidValue)
/// if the control reports a value outside `0.0..=1.0` by more than
/// rounding noise, or the error the read failed with.
fn time_override(&self) -> Result<Factor>;
}
SetTimeOverride
pub trait SetTimeOverride: GetTimeOverride {
/// Send the global time override.
///
/// Prefer [`set_time_override`](SetTimeOverride::set_time_override) or
/// [`send_time_override`](SetTimeOverride::send_time_override); implement
/// this method when adding a robot type.
///
/// # Arguments
///
/// * `request` - The new time override as a factor or percentage.
///
/// # Returns
///
/// The handle that confirms the command. [`CommandHandle::confirm`]
/// returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not apply the value within the command timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside `0.0..=1.0` (`0%..=100%`), or the error the
/// write failed with.
#[doc(hidden)]
fn apply_time_override(&self, request: TimeOverrideRequest) -> Result<CommandHandle<'_>>;
/// Set the global time override.
///
/// The call returns once the control reports the new value, at most
/// after the command timeout. To send the value and wait later, use
/// [`send_time_override`](SetTimeOverride::send_time_override).
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl SetTimeOverride) -> Result<()> {
/// robot.set_time_override(Factor(0.25))?;
/// robot.set_time_override(Percent(25.0))?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `value` - The new time override: a [`Factor`] in `0.0..=1.0` or a
/// [`Percent`](crate::types::Percent) in `0.0..=100.0`.
///
/// # Returns
///
/// `Ok(())` once the control reports the new value.
///
/// # Errors
///
/// Returns [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside its range, before anything is sent,
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not report the new value within the command
/// timeout, also when another writer changed it in the meantime, or the
/// error the write failed with.
fn set_time_override(&self, value: impl Into<Speed>) -> Result<()>
where
Self: Sized,
{
self.send_time_override(value)?.confirm()
}
/// Send the global time override.
///
/// The call returns once the value is sent. Call
/// [`confirm`](CommandHandle::confirm) on the returned handle to wait
/// until the control applies it.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl SetTimeOverride) -> Result<()> {
/// let command = robot.send_time_override(Factor(0.25))?;
/// // ... other work while the control applies the value ...
/// command.confirm()?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `value` - The new time override: a [`Factor`] in `0.0..=1.0` or a
/// [`Percent`](crate::types::Percent) in `0.0..=100.0`.
///
/// # Returns
///
/// The handle that confirms the new value. [`CommandHandle::confirm`]
/// returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not report the value within the command timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside its range, before anything is sent, or the
/// error the write failed with.
fn send_time_override(&self, value: impl Into<Speed>) -> Result<CommandHandle<'_>>
where
Self: Sized,
{
self.apply_time_override(TimeOverrideRequest {
value: value.into(),
})
}
/// Run `body` with the time override set to `value`, then restore the
/// previous value.
///
/// The set and the restore each wait for the confirmation of the
/// control.
///
/// The previous value is restored after `body` returns, whether `body`
/// succeeded, failed or panicked; a panic continues to unwind after the
/// restore. It is also restored when setting `value` fails, since the
/// control may have applied a write it did not confirm.
///
/// The time override is one global value of the robot, and this method
/// takes no lock. Nested sections on one thread restore in order, but
/// sections that overlap on several threads do not: each records the
/// value it found on entry, and the last restore wins. If thread A sets
/// `0.1` and thread B then sets `0.5`, B records `0.1`; when A ends
/// first and restores `1.0`, B restores `0.1` afterwards and the robot
/// stays at `0.1`. Keep scoped sections on one thread.
///
/// `body` may return its own error type, as long as a
/// [`RobotArmError`](crate::error::RobotArmError) converts into it, so it
/// can also propagate errors of non-robot work with `?`. A closure that
/// ends in `Ok(..)` names its error type, as in `|robot| -> Result<()>`.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &(impl SetTimeOverride + MovePtp), a: JointPose, b: JointPose) -> Result<()> {
/// robot.with_time_override(Factor(0.25), |robot| {
/// robot.move_ptp(a).move_ptp(b).start()?.join()
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `value` - The time override to apply while `body` runs: a
/// [`Factor`] in `0.0..=1.0` or a [`Percent`](crate::types::Percent)
/// in `0.0..=100.0`.
/// * `body` - The section of the program that runs under `value`.
///
/// # Returns
///
/// The value `body` returned.
///
/// # Errors
///
/// Returns, converted into `E`, the error of reading the current value;
/// nothing is set in this case. Returns the error of setting the new
/// value; `body` does not run, but the previous value is restored.
/// Otherwise the error of `body`, or the error of restoring the previous
/// value once `body` succeeded. A restore failure after a failed set or
/// a failed `body` is logged.
fn with_time_override<T, E: From<RobotArmError>>(
&self,
value: impl Into<Speed>,
body: impl FnOnce(&Self) -> Result<T, E>,
) -> Result<T, E>
where
Self: Sized,
{
let previous = self.time_override()?;
// The set runs inside the guarded section: a failed confirmation
// can follow a write the control already applied, so `previous` is
// restored whenever the set fails too.
restore_after(
|| {
self.set_time_override(value)?;
body(self)
},
|| self.set_time_override(previous),
format_args!("restore the time override to {previous}"),
)
}
}
TimeOverrideTransition
pub trait TimeOverrideTransition: SetTimeOverride {
/// Start a time override transition.
///
/// # Arguments
///
/// * `request` - The target time override and the transition time,
/// at least 250 ms.
///
/// # Returns
///
/// The handle that confirms the command. [`CommandHandle::confirm`]
/// returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the value does not reach the target within the transition time
/// plus the command timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside `0.0..=1.0`,
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// if the transition is shorter than 250 ms, or the error the write
/// failed with.
#[doc(hidden)]
fn apply_time_override_transition(
&self,
request: TimeOverrideTransitionRequest,
) -> Result<CommandHandle<'_>>;
/// Set the global time override with a transition time.
///
/// The call returns once the value reaches the target, at most after
/// the transition time plus the command timeout. To send the transition
/// and wait later, use
/// [`send_time_override_with_transition`](TimeOverrideTransition::send_time_override_with_transition).
///
/// # Example
///
/// ```rust
/// # use std::time::Duration;
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl TimeOverrideTransition) -> Result<()> {
/// robot.set_time_override_with_transition(Factor(0.25), Duration::from_secs(5))?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `value` - The target time override: a [`Factor`](crate::types::Factor)
/// in `0.0..=1.0` or a [`Percent`](crate::types::Percent) in `0.0..=100.0`.
/// * `transition` - The duration over which the value changes; at least 250 ms.
///
/// # Returns
///
/// `Ok(())` once the value reaches the target.
///
/// # Errors
///
/// Returns [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// if the transition is shorter than 250 ms or
/// [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside its range, both before anything is sent,
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the value does not reach the target within the transition time
/// plus the command timeout, or the error the write failed with.
fn set_time_override_with_transition(
&self,
value: impl Into<Speed>,
transition: Duration,
) -> Result<()>
where
Self: Sized,
{
self.send_time_override_with_transition(value, transition)?
.confirm()
}
/// Start a time override transition.
///
/// The call returns once the transition is sent. Call
/// [`confirm`](CommandHandle::confirm) on the returned handle to wait
/// until the value reaches the target.
///
/// # Example
///
/// ```rust
/// # use std::time::Duration;
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl TimeOverrideTransition) -> Result<()> {
/// let transition = robot.send_time_override_with_transition(Factor(0.25), Duration::from_secs(5))?;
/// // ... other work during the transition ...
/// transition.confirm()?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `value` - The target time override: a [`Factor`](crate::types::Factor)
/// in `0.0..=1.0` or a [`Percent`](crate::types::Percent) in `0.0..=100.0`.
/// * `transition` - The duration over which the value changes; at least 250 ms.
///
/// # Returns
///
/// The handle that confirms the target value. [`CommandHandle::confirm`]
/// returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the value does not reach the target within the transition time
/// plus the command timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// if the transition is shorter than 250 ms or
/// [`RobotArmError::UnitLimit`](crate::error::RobotArmError::UnitLimit)
/// if the value is outside its range, both before anything is sent, or
/// the error the write failed with.
fn send_time_override_with_transition(
&self,
value: impl Into<Speed>,
transition: Duration,
) -> Result<CommandHandle<'_>>
where
Self: Sized,
{
self.apply_time_override_transition(TimeOverrideTransitionRequest {
value: value.into(),
transition,
})
}
}