๐ซจ Impedance Controlยถ
In addition to the trajectory control modes, franky also provides client-side impedance controllers in torque mode.
If you want to command joint torques directly instead of using a spring-damper law, see Torque Control.
There are two variants for both joint-space and Cartesian impedance:
JointImpedanceMotionandCartesianImpedanceMotionare fixed-target motions. They interpret a target once at motion start and then regulate toward it.JointImpedanceTrackingMotionandCartesianImpedanceTrackingMotionkeep the same controller alive while consuming updated references online.
The tracking variants are useful when the desired reference changes every
control cycle, for example for manual guidance, teleoperation, or virtual
fixtures. In Python, the recommended interface for these use cases is
JointImpedanceTracker or CartesianImpedanceTracker.
When using the tracking motions directly, references and gains are updated at
runtime through set_reference() and set_gains(), which validate their
inputs and forward them to the control loop. The corresponding
get_reference() and get_gains() methods return copies of the last
commanded values, so mutating a returned object has no effect until it is
passed back to the setter:
gains = motion.get_gains()
gains.stiffness = [100.0] * 7
motion.set_gains(gains)
Joint Impedance Controlยถ
Joint-space impedance can be used either as a fixed posture controller
from franky import JointImpedanceMotion
motion = JointImpedanceMotion(
target=[0.0, -0.6, 0.0, -2.2, 0.0, 1.7, 0.7],
stiffness=[600.0] * 7,
damping=[50.0] * 7,
)
or as a tracking controller with online reference updates:
from franky import JointImpedanceTracker
with JointImpedanceTracker(
robot,
stiffness=[600.0] * 7,
damping=[50.0] * 7,
period=0.01,
) as tracker:
while tracker.tick():
tracker.set_target(
[0.0, -0.6, 0.0, -2.2, 0.0, 1.7, 0.7],
dq=[0.0] * 7,
)
Joint tracking targets can optionally include feedforward torques tau_ff. This is
added on top of any constant_torque_offset configured on the tracker itself.
All joint-space impedance motions (JointImpedanceMotion, JointImpedanceTrackingMotion,
and JointImpedanceTracker) support optional friction compensation. Coulomb and viscous
terms are added as feedforward terms to the commanded torque each cycle and clamped per joint:
from franky import FrictionCompensationParams
# Use friction compensation to get a smooth zero-g mode kinesthetic demonstrations
with JointImpedanceTracker(
robot,
stiffness=[0.0] * 7,
damping=[0.0] * 7,
friction=FrictionCompensationParams(
coulomb=[0.5, 0.5, 0.4, 0.4, 0.3, 0.3, 0.2], # [Nm]
viscous=[0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05], # [Nms/rad]
max_torque=[2.0] * 7, # per-joint clamp [Nm]
),
period=0.01,
) as tracker:
...
The Coulomb term uses a smooth sign approximation controlled by
FrictionCompensationParams.velocity_epsilon (default 0.03 rad/s). Either or both
terms can be omitted; unset terms default to zero. CartesianImpedanceTracker accepts
the same friction= object.
Joint impedance can additionally be shaped in Cartesian space. Passing
cartesian_stiffness (a 6-vector [x, y, z, rx, ry, rz] at the end effector,
expressed in the base frame) adds J^T diag(cartesian_stiffness) J on top of
the joint-space stiffness each cycle, with cartesian_damping handled likewise
(critical damping when omitted). This is useful to stay compliant about a joint
posture while stiffening a specific Cartesian direction:
with JointImpedanceTracker(
robot,
stiffness=[20.0] * 7,
cartesian_stiffness=[800.0, 800.0, 0.0, 0.0, 0.0, 0.0], # stiff in x/y only
period=0.01,
) as tracker:
...
Both trackers support updating impedance gains at runtime via set_gains().
Changes are smoothed in the RT loop using exponential interpolation, so abrupt
stiffness steps are avoided automatically.
All torque-mode motions accept a max_delta_tau parameter that limits the
commanded torque change per control cycle in Nm, which can help avoid
discontinuity errors from abrupt torque steps.
Cartesian Impedance Controlยถ
Cartesian impedance follows the same split:
from franky import Affine, CartesianImpedanceMotion, ReferenceType
motion = CartesianImpedanceMotion(
target=Affine([0.45, 0.0, 0.35]),
target_type=ReferenceType.Absolute,
translational_stiffness=1200.0,
rotational_stiffness=80.0,
)
from franky import (
Affine,
CartesianImpedanceTracker,
PostureTask,
Twist,
)
with CartesianImpedanceTracker(
robot,
translational_stiffness=1200.0,
rotational_stiffness=80.0,
posture_task=PostureTask([0.0, -0.6, 0.0, -2.2, 0.0, 1.7, 0.7], stiffness=10.0),
max_delta_tau=0.5,
period=0.01,
) as tracker:
while tracker.tick():
tracker.set_target(
Affine([0.45, 0.0, 0.35]),
Twist([0.0, 0.0, 0.05], [0.0, 0.0, 0.0]),
)
For Cartesian tracking, the twist argument to set_target is optional. When provided, it is
interpreted as the desired end-effector twist in the base frame, so the damping
term acts on twist error instead of damping all motion toward zero.
When a period is configured, tracker.tick() maintains that loop rate using
time.perf_counter() internally and compensates for the time spent in the loop
body. tracker.elapsed_time and tracker.tick_count are available for
time-based target generation.
By default, Cartesian damping is chosen internally as critically damped with
respect to the requested stiffness. When that is not enough โ high-stiffness
configurations often need more โ the damping can be set explicitly through
CartesianImpedanceGains, either isotropically or per axis:
from franky import CartesianImpedanceGains
# Isotropic stiffness with explicit (over-)damping:
tracker.motion.set_gains(
CartesianImpedanceGains.isotropic(
1200.0, 80.0, translational_damping=140.0, rotational_damping=18.0
)
)
# Or fully anisotropic diagonal gains, ordered [x, y, z, rx, ry, rz]:
tracker.motion.set_gains(
CartesianImpedanceGains.diagonal(
[1200, 1200, 1200, 80, 80, 80],
[140, 140, 140, 18, 18, 18],
)
)
The tracker also exposes damping directly: tracker.set_gains(damping=[...]) pins a full
6-vector, and tracker.set_gains(damping=franky.CRITICAL) unpins it. Damping is all-or-nothing
and omitting it means critical. A stiffness change re-criticals damping unless damping is
passed in the same call. JointImpedanceTracker.set_gains follows the same rule with 7-vectors.
Cartesian impedance motions also support optional secondary objectives projected
into the Jacobian nullspace, so they bias the redundant arm posture without
changing the Cartesian task to first order. Pass a posture_task (a
PostureTask) to regulate toward a joint posture, and/or a manipulability_task
(a ManipulabilityTask) to maximize manipulability; at most one of each is
supported. A PostureTaskโs per-joint stiffness can weight joints differently or
exclude them (zero stiffness). Keep in mind that the projection spreads the
resulting torque over all joints participating in the self-motion: pushing a
single joint biases the whole null-space posture rather than moving that joint
alone.
from franky import ManipulabilityTask, PostureTask
with CartesianImpedanceTracker(
robot,
translational_stiffness=200.0,
rotational_stiffness=10.0,
posture_task=PostureTask(robot.current_joint_state.position, stiffness=2.0),
manipulability_task=ManipulabilityTask(gain=5.0, max_torque=1.0),
period=0.01,
) as tracker:
...
The task set is fixed at construction, but the gains can be retuned online:
tracker.set_gains(posture_stiffness=...) (scalar or 7-vector) nudges just the
posture stiffness, or tracker.set_gains(nullspace_gains=NullspaceGains(...))
replaces the full posture + manipulability gains. Retuning a gain for a task that
was not configured at construction raises.
CartesianImpedanceTracker also accepts translational_error_clip and
rotational_error_clip (each a 3-vector in m and rad respectively) to hard-clip
the pose error fed into the spring law, which can prevent large torque spikes
when the reference jumps.
Ending a Torque Motionยถ
Unlike the trajectory-based motions, impedance (torque) motions never signal
completion on their own, and Robot.stop() preempts the control loop with a
ControlException rather than ramping down. To end a torque loop cleanly, use
TorqueStopMotion. It blends the last commanded torque into a zero-stiffness
joint-damping law โ so it yields to the armโs current pose instead of snapping
to a target โ brings the arm to rest, and then finishes. Enqueue it to preempt a
running impedance motion:
from franky import TorqueStopMotion
robot.move(TorqueStopMotion())
or attach it as a reaction so the arm damps out automatically once a condition is met (as with any reaction, the reaction motion must use the same control mode, so it must itself be a torque motion):
from franky import Measure, Reaction, TorqueStopMotion
motion.add_reaction(Reaction(Measure.FORCE_Z > 20.0, TorqueStopMotion()))
TorqueStopMotion also finishes after max_duration regardless of velocity, so
a sustained external push can never make the stop hang.