pub struct LiveTuning {
pub joint_stiffness: [f64; 7],
pub joint_damping: [f64; 7],
pub cartesian_stiffness: f64,
pub ik_damping: f64,
pub ik_nullspace_gain: f64,
pub velocity_feedforward_gain: f64,
pub velocity_feedforward_cutoff: f64,
pub budget: [f64; 3],
pub rotation_budget: [f64; 3],
}Expand description
The targets a running session will accept. These are targets, not the values in force: the
loop keeps its own current values and moves them here under each field’s
policy.
cartesian_stiffness is one number here and twelve in the law. The operator sets a single
translational stiffness; whoever owns the preset derives the six stiffnesses and six
dampings from it, which is what holds the damping ratio the preset was tuned for. Neither
the panel nor this struct should know that rule, and it is not duplicated here.
Deliberately not #[non_exhaustive], unlike FieldBound and TuningUpdate: a
consumer builds one of these — it is the session’s seed, and it is what an update is
validated against. Closing the struct expression would leave the positional
from_words as the only way in, which is the mis-indexing hazard this
module’s tests exist to catch, promoted to the sole public constructor.
Fields§
§joint_stiffness: [f64; 7]Kq, per joint, Nm/rad.
joint_damping: [f64; 7]Kqd, per joint, Nm s/rad.
cartesian_stiffness: f64The translational Cartesian stiffness the six-axis Kx and Kxd are derived from, N/m.
ik_damping: f64λ of the IK’s damped least squares.
ik_nullspace_gain: f64Gain, 1/s, of the IK’s nullspace pull toward the posture.
velocity_feedforward_gain: f64The weight on the goal velocity in the damping term, within [0, 1]; zero is the law with the feedforward off.
velocity_feedforward_cutoff: f64The low-pass cutoff on that goal velocity, Hz; the maximum is the filter off.
budget: [f64; 3]The translational budget as a norm: m/s, m/s^2, m/s^3.
rotation_budget: [f64; 3]The rotational budget as a norm: rad/s, rad/s^2, rad/s^3.
Implementations§
Source§impl LiveTuning
impl LiveTuning
Sourcepub const BOUNDS: &'static [FieldBound]
pub const BOUNDS: &'static [FieldBound]
How far each scalar may be moved, in the order of to_words.
Publish this; never copy the numbers, and publish
joint_damping_floor with it. The joint damping ceilings
are the reference joint-impedance stack’s, less the crate’s own velocity barrier gain,
which is added on top of the law’s damping and is what the stability bound applies to,
and lower still on the wrist; the budget ceilings sit just past where the robot’s own
joint-velocity guard can fire.
Sourcepub fn joint_damping_floor(stiffness: f64) -> f64
pub fn joint_damping_floor(stiffness: f64) -> f64
The least damping, Nm s/rad, a joint spring of stiffness Nm/rad is allowed to run
with. apply_update raises joint_damping[i] to this, and a
caller that publishes BOUNDS has to publish this rule beside it: it
is the one limit in this file that a per-field row cannot express.
Why it exists: the pair is the only one where both halves are exposed and independent,
and joint_damping’s own floor is zero, so without it one slider move reaches a spring
of up to 1200 Nm/rad with no damping at all. The
VELOCITY_BARRIER_GAIN the damping ceiling is derived
against is no help here — it only engages beyond
velocity_barrier_fraction of the
joint’s velocity limit, so at working speed it contributes nothing, and what is left is
an undamped spring behind a low-pass that adds lag rather than damping.
MIN_JOINT_DAMPING_RATIO is below every shipped preset’s own D / sqrt(K), so this
forbids the corner and no configuration anyone runs today; it is not a tuning opinion
and it is far enough under the ceiling never to fight it.
Source§impl LiveTuning
impl LiveTuning
Sourcepub fn slew_gains(dt: f64) -> SlewGains
pub fn slew_gains(dt: f64) -> SlewGains
The SlewGains of a loop running at dt seconds a cycle. Build it once, at the
loop’s construction, with the cycle time the loop runs at.
Sourcepub fn slew_toward(&mut self, target: &LiveTuning, gains: &SlewGains) -> bool
pub fn slew_toward(&mut self, target: &LiveTuning, gains: &SlewGains) -> bool
Moves every word one cycle toward target under gains, and reports whether any of them
moved. false means the values in force are the targets, bit for bit, so a caller can
skip the work of putting them into effect until the next change arrives.
A gated word – the budget’s velocity and acceleration – is not filtered at all: its target is raised to a floor one cycle of the next order’s limit below where it is, so raising it is a step and lowering it a ramp. The rate is read off the values in force at the top of the cycle, so the order the words are walked in cannot change the answer.
The realtime half of the tuning path: no allocation, no lock, no fallible call, and
Self::WORDS multiply-adds whatever the targets are. gains comes from
slew_gains so that no exp runs here.
It walks the word array rather than the named fields, which is what makes it impossible for a field to be slewed under another field’s policy, or left out.
use franka::robot::target_control::{LiveTuning, TuningPolicy};
let mut current =
LiveTuning::from_options(&impedance, options.limits, options.rotation_limits);
let target = LiveTuning {
ik_damping: current.ik_damping * 2.0,
..current
};
let gains = LiveTuning::slew_gains(0.001);
// One cycle of a 0.3 s time constant is a third of a percent of the gap, not the gap.
assert!(current.slew_toward(&target, &gains));
assert!(current.ik_damping < impedance.ik.damping * 1.01);
// Converged, it reports that it has nothing left to do.
for _ in 0..20_000 {
current.slew_toward(&target, &gains);
}
assert_eq!(current, target);
assert!(!current.slew_toward(&target, &gains));Source§impl LiveTuning
impl LiveTuning
Sourcepub fn from_words(words: &[f64; 25]) -> LiveTuning
pub fn from_words(words: &[f64; 25]) -> LiveTuning
The inverse of to_words.
Sourcepub fn from_options(
impedance: &ImpedanceOptions,
budget: OtgLimits,
rotation_budget: OtgLimits,
) -> LiveTuning
pub fn from_options( impedance: &ImpedanceOptions, budget: OtgLimits, rotation_budget: OtgLimits, ) -> LiveTuning
A session’s seed: the values impedance and the two budgets are already running with,
which is what the loop slews from, so that the first change an operator makes starts
where the arm is and not at a default.
The budgets are the arguments’; of what impedance supplies, two words are not one of
its fields read straight off.
cartesian_stiffness is the first axis of
ImpedanceGains::cartesian_stiffness,
because that is the number
scaled_cartesian scales, and
gains derives all twelve Cartesian words back from it. A gains set that
is not a scaled CARTESIAN therefore loses
whatever else it changed the moment anything is retuned: seeded from
DROID, the first retune moves the translational
damping from DROID’s 37 to the preset’s 50 and 90. That is the parity preset’s price for
one slider, and the reason gains is the only mapping.
velocity_feedforward_gain is zero when
velocity_feedforward is off, which is
the same law: the weighted goal velocity is all the switch gates. Carrying the switch in
the gain is what lets it be crossed continuously rather than thrown.
Sourcepub fn budgets(&self) -> (OtgLimits, OtgLimits)
pub fn budgets(&self) -> (OtgLimits, OtgLimits)
The two budgets as OtgLimits, which is the form a generator takes them in: the
other side of from_options’s reading of them, and the only
mapping, so that nobody has to remember which of the three words is which.
Still norms, as the fields are. Whoever hands them to a generator owes them the per-axis division the OTG’s first rule asks for.
Sourcepub fn gains(&self) -> ImpedanceGains
pub fn gains(&self) -> ImpedanceGains
The law’s gains these targets stand for: the joint springs as they are, and the six
Cartesian stiffnesses and six dampings
scaled_cartesian derives from the one
translational stiffness, which is where that rule lives and the only place it may.
The other side of from_options’s reading of the gains, and the
mapping the loop applies every cycle it moves.
Sourcepub fn apply_update(
&mut self,
update: &TuningUpdate,
) -> FrankaResult<Vec<&'static FieldBound>>
pub fn apply_update( &mut self, update: &TuningUpdate, ) -> FrankaResult<Vec<&'static FieldBound>>
Applies every field update carries and returns the bounds that had to clamp one,
leaving self at the accepted, clamped values. Runs on the arm thread: it allocates,
and must not be called from the loop.
All or nothing: the update is built on a scratch copy, and if any value it carries is
rejected — not finite, or zero where zero means something else
(reject_non_positive) — self is left bit for bit
as it was and nothing is clamped.
A field the update does not carry keeps its current value and is not re-checked, so a
partial update is a real partial update and cannot quietly retune a field nobody moved.
A value seeded outside BOUNDS therefore survives until someone moves
that field; whoever builds the seed has to check it against BOUNDS first, and the way
to do that is this method with an update carrying every field, never a second copy of
the table.
The pair rule is the one thing that reaches past the carried fields. An update carrying
joint_stiffness raises any of the seven joint_damping values that sit under
joint_damping_floor of the spring they damp — the pair,
not either half, is what has to stay damped. A damping the update does not carry is
only ever raised: its ceiling is not applied, because lowering a field nobody moved is
the thing this method exists not to do. The floor is read off the stiffness BOUNDS
allows rather than the raw seeded one, so a seed the table would never have accepted
cannot push the floor above the ceiling.
Clamping is deliberate rather than a refusal, and the clamped bounds come back so that
a caller can show the value it will actually get; that echo is also what would expose a
caller whose own idea of the limits has drifted from BOUNDS. Read the value out of
self, not out of the bound: where the pair rule bit, the value in force is the floor
and FieldBound::min is still zero.
§Errors
FrankaError::InvalidArgument naming the field, with self unchanged.
Trait Implementations§
Source§impl Clone for LiveTuning
impl Clone for LiveTuning
Source§fn clone(&self) -> LiveTuning
fn clone(&self) -> LiveTuning
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for LiveTuning
Source§impl Debug for LiveTuning
impl Debug for LiveTuning
Source§impl PartialEq for LiveTuning
impl PartialEq for LiveTuning
Source§fn eq(&self, other: &LiveTuning) -> bool
fn eq(&self, other: &LiveTuning) -> bool
self and other values to be equal, and is used by ==.impl StructuralPartialEq for LiveTuning
Auto Trait Implementations§
impl Freeze for LiveTuning
impl RefUnwindSafe for LiveTuning
impl Send for LiveTuning
impl Sync for LiveTuning
impl Unpin for LiveTuning
impl UnsafeUnpin for LiveTuning
impl UnwindSafe for LiveTuning
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
impl<T> Scalar for T
§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read more§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.