💪 Torque Control¶
The impedance controllers compute joint torques from a
spring-damper law. If you want to command joint torques directly instead — for
example because you run your own controller, a learned policy, or a teleoperation
scheme — use SimpleTorqueMotion. It applies the torques it is given, without any
feedback law on top.
Like the impedance controllers, SimpleTorqueMotion keeps the same motion alive
while the commanded torque is updated online. It is therefore started once, and
new torques are published with set_torque():
import time
import numpy as np
from franky import Robot, SimpleTorqueMotion, TorqueStopMotion
robot = Robot("10.90.90.1")
motion = SimpleTorqueMotion()
robot.move(motion, asynchronous=True)
start_time = time.time()
while time.time() - start_time < 5.0:
# A gentle sinusoidal torque on the last joint.
torque = np.zeros(7)
torque[6] = 0.5 * np.sin(2.0 * np.pi * (time.time() - start_time))
motion.set_torque(torque)
time.sleep(0.01)
robot.move(TorqueStopMotion())
As with the impedance controllers, the commanded torques are added on top of the robot’s internal gravity compensation, so a torque of zero leaves the arm floating.
The Signal Watchdog¶
In between two calls to set_torque(), the robot keeps applying the last commanded
torque. If the process that produces the torques stalls or dies, the arm would keep
accelerating under a torque that is no longer intended. SimpleTorqueMotion therefore
runs a watchdog: if no new torque arrives within signal_timeout seconds, the motion
terminates with a TorqueSignalTimeoutException.
The default timeout is 50 ms, so set_torque() has to be called at least every 50 ms,
even if the torque does not change. As the watchdog is armed the moment the motion
starts, it also bounds how long the initial torque is applied. Loosen it for slower
control loops:
motion = SimpleTorqueMotion(signal_timeout=0.2)
or disable it entirely with signal_timeout=None, in which case the last commanded
torque is held indefinitely.
Note that the watchdog fires in the robot’s control thread, so, like any other error
during an asynchronous motion, the exception is stored and rethrown by the next call
to Robot.join_motion or Robot.move (see Real-Time Motions).
Initial Torque¶
Until the first call to set_torque(), the motion applies its initial torque, which
is zero by default. Set it to start from something else than a floating arm:
motion = SimpleTorqueMotion(initial_torque=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5])
The initial torque can also be combined with a disabled watchdog to apply a constant torque without any further signals:
motion = SimpleTorqueMotion(
initial_torque=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5],
signal_timeout=None,
)
robot.move(motion, asynchronous=True)
Safety Limits¶
SimpleTorqueMotion applies the same safety mechanisms as the impedance controllers:
max_delta_tau(1 Nm by default) limits how much the commanded torque may change per control cycle, so a large step in the commanded torque is ramped in over multiple cycles rather than jerking the arm.lower_joint_limitsandupper_joint_limits(unset by default) enable a soft joint-limit repulsion that pushes the joints back into their range.frictionacceptsFrictionCompensationParamsto add a friction feedforward term.compensate_coriolis(off by default here, as the commanded torque is otherwise applied verbatim) adds the Coriolis torques of the robot model.
Ending the Motion¶
Like the impedance motions, SimpleTorqueMotion never finishes on its own. Use
TorqueStopMotion to bring the arm to rest and end the control loop cleanly, as
described in Ending a Torque Motion.