Skip to content

Commit 46852c7

Browse files
committed
Validate stiffness and damping
Negative values could cause the controller to explode Default to reasonable new damping when updating stiffness from Tracker
1 parent 9672157 commit 46852c7

5 files changed

Lines changed: 92 additions & 27 deletions

File tree

examples/manual_guidance_joint_impedance.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,24 @@
2222
parser.add_argument(
2323
"--stiffness",
2424
type=float,
25-
default=6.0,
26-
help="Uniform joint stiffness used for manual guidance",
25+
default=2.0,
26+
help="Joint stiffness",
2727
)
2828
parser.add_argument(
2929
"--lock-joint6",
3030
action="store_true",
31-
help="Hold joint 6 at its starting angle while leaving the other joints compliant",
31+
help="Hold joint 6 at its starting angle",
3232
)
3333
parser.add_argument(
3434
"--lock-joint7",
3535
action="store_true",
36-
help="Hold joint 7 at its starting angle while leaving the other joints compliant",
36+
help="Hold joint 7 at its starting angle",
3737
)
3838
parser.add_argument(
3939
"--lock-stiffness",
4040
type=float,
4141
default=40.0,
42-
help="Extra stiffness used for locked joints [Nm/rad]",
42+
help="Stiffness used for locked joints [Nm/rad]",
4343
)
4444

4545
args = parser.parse_args()
@@ -80,7 +80,7 @@
8080
period=0.001,
8181
) as tracker:
8282
while tracker.tick():
83-
q_ref = list(robot.current_joint_positions)
83+
q_ref = robot.current_joint_positions
8484
if args.lock_joint6:
8585
q_ref[5] = locked_targets[5]
8686
if args.lock_joint7:

franky/tracker.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ def _is_premption_exception(exc: ControlException) -> bool:
2323
return "Move command preempted!" in str(exc)
2424

2525

26+
_DEFAULT_JOINT_STIFFNESS = np.full(7, 50.0)
27+
28+
29+
def _default_joint_damping(stiffness: np.ndarray) -> np.ndarray:
30+
return 2.0 * np.sqrt(stiffness)
31+
32+
33+
def _as_joint_gain(name: str, value) -> np.ndarray:
34+
vector = np.asarray(value, dtype=float)
35+
if vector.shape != (7,):
36+
raise ValueError(f"{name} must contain exactly 7 values")
37+
if not np.all(np.isfinite(vector)):
38+
raise ValueError(f"{name} must contain only finite values")
39+
if np.any(vector < 0.0):
40+
raise ValueError(f"{name} must contain only non-negative values")
41+
return vector.copy()
42+
43+
2644
class CartesianImpedanceTracker:
2745
"""A long-lived session for streaming Cartesian impedance tracking commands.
2846
@@ -277,16 +295,21 @@ def __init__(
277295
q = self._robot.current_joint_positions
278296
self._reference_handle.set(q)
279297

280-
# Seed gains handle with initial values.
281-
stiffness_init = (
282-
np.asarray(stiffness) if stiffness is not None else np.full(7, 50.0)
298+
# Seed gains handle with initial values. When damping is omitted, use
299+
# critical damping for unit inertia so zero stiffness implies zero damping.
300+
stiffness_init = _as_joint_gain(
301+
"stiffness", _DEFAULT_JOINT_STIFFNESS if stiffness is None else stiffness
302+
)
303+
damping_init = (
304+
_as_joint_gain("damping", damping)
305+
if damping is not None
306+
else _default_joint_damping(stiffness_init)
283307
)
284-
damping_init = np.asarray(damping) if damping is not None else np.full(7, 10.0)
285308
self._gains_handle.set(stiffness_init, damping_init)
286309

287310
kwargs = {
288-
"stiffness": stiffness,
289-
"damping": damping,
311+
"stiffness": stiffness_init,
312+
"damping": damping_init,
290313
"constant_torque_offset": constant_torque_offset,
291314
"compensate_coriolis": compensate_coriolis,
292315
"max_delta_tau": max_delta_tau,
@@ -351,18 +374,27 @@ def set_gains(
351374
) -> None:
352375
"""Update joint impedance gains. Smoothed in the RT loop via exponential interpolation.
353376
354-
Only the provided gains are changed; omitted gains keep their current target values.
377+
When stiffness is changed and damping is omitted, damping is updated to
378+
the critical damping heuristic 2*sqrt(stiffness). Otherwise, omitted
379+
gains keep their current target values.
355380
"""
356381
current = self._gains_handle.get() if self._gains_handle.has_gains else None
357-
k = (
358-
np.asarray(stiffness)
359-
if stiffness is not None
360-
else (current.stiffness if current else np.full(7, 50.0))
382+
k = _as_joint_gain(
383+
"stiffness",
384+
(
385+
stiffness
386+
if stiffness is not None
387+
else (current.stiffness if current else _DEFAULT_JOINT_STIFFNESS)
388+
),
361389
)
362390
d = (
363-
np.asarray(damping)
391+
_as_joint_gain("damping", damping)
364392
if damping is not None
365-
else (current.damping if current else np.full(7, 10.0))
393+
else (
394+
_default_joint_damping(k)
395+
if stiffness is not None or current is None
396+
else _as_joint_gain("damping", current.damping)
397+
)
366398
)
367399
self._gains_handle.set(k, d)
368400

include/franky/motion/impedance_gains_handle.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88

99
namespace franky {
1010

11+
inline Vector7d defaultJointImpedanceStiffness() { return Vector7d::Constant(50.0); }
12+
13+
inline Vector7d defaultJointImpedanceDamping(const Vector7d &stiffness) { return 2.0 * stiffness.cwiseSqrt(); }
14+
15+
inline Vector7d defaultJointImpedanceDamping() {
16+
return defaultJointImpedanceDamping(defaultJointImpedanceStiffness());
17+
}
18+
1119
/**
1220
* @brief Target gains for a Cartesian impedance controller.
1321
*/
@@ -45,8 +53,8 @@ class CartesianImpedanceGainsHandle {
4553
struct JointImpedanceGains {
4654
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
4755

48-
Vector7d stiffness{Vector7d::Constant(50.0)};
49-
Vector7d damping{Vector7d::Constant(10.0)};
56+
Vector7d stiffness{defaultJointImpedanceStiffness()};
57+
Vector7d damping{defaultJointImpedanceDamping()};
5058
};
5159

5260
/**

include/franky/motion/joint_impedance_motion.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ struct TorqueSafetyParams {
4545
*/
4646
struct JointImpedanceParams : public TorqueSafetyParams {
4747
/** Joint stiffness gains in [Nm/rad]. */
48-
Vector7d stiffness{Vector7d::Constant(50.0)};
48+
Vector7d stiffness{defaultJointImpedanceStiffness()};
4949

5050
/** Joint damping gains in [Nms/rad]. */
51-
Vector7d damping{Vector7d::Constant(10.0)};
51+
Vector7d damping{defaultJointImpedanceDamping()};
5252

5353
/** Constant torque offset added to every command in [Nm]. */
5454
Vector7d constant_torque_offset{Vector7d::Zero()};

python/bind_motion_torque.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include <pybind11/pybind11.h>
33
#include <pybind11/stl.h>
44

5+
#include <cmath>
6+
#include <string>
7+
58
#include "franky.hpp"
69

710
namespace py = pybind11;
@@ -26,15 +29,30 @@ JointReference toJointReference(
2629
return reference;
2730
}
2831

32+
void validateNonNegativeFinite(const Vector7d &values, const char *name) {
33+
for (int i = 0; i < values.size(); ++i) {
34+
if (!std::isfinite(values[i]) || values[i] < 0.0) {
35+
throw py::value_error(std::string(name) + " must contain only finite, non-negative values");
36+
}
37+
}
38+
}
39+
2940
JointImpedanceParams makeJointImpedanceParams(
3041
const std::optional<Vector7d> &stiffness, const std::optional<Vector7d> &damping,
3142
const std::optional<Vector7d> &constant_torque_offset, const std::optional<Vector7d> &lower_joint_limits,
3243
const std::optional<Vector7d> &upper_joint_limits, bool compensate_coriolis, double max_delta_tau,
3344
double joint_limit_activation_distance, double joint_limit_stiffness, double joint_limit_damping,
3445
double joint_limit_max_torque) {
3546
auto params = JointImpedanceParams{};
36-
if (stiffness.has_value()) params.stiffness = stiffness.value();
37-
if (damping.has_value()) params.damping = damping.value();
47+
if (stiffness.has_value()) {
48+
validateNonNegativeFinite(stiffness.value(), "stiffness");
49+
params.stiffness = stiffness.value();
50+
if (!damping.has_value()) params.damping = defaultJointImpedanceDamping(params.stiffness);
51+
}
52+
if (damping.has_value()) {
53+
validateNonNegativeFinite(damping.value(), "damping");
54+
params.damping = damping.value();
55+
}
3856
if (constant_torque_offset.has_value()) params.constant_torque_offset = constant_torque_offset.value();
3957
if (lower_joint_limits.has_value() && upper_joint_limits.has_value()) {
4058
params.joint_limit_repulsion_active = true;
@@ -130,8 +148,15 @@ void bind_motion_torque(py::module &m) {
130148
.def(
131149
py::init<>([](const std::optional<Vector7d> &stiffness, const std::optional<Vector7d> &damping) {
132150
JointImpedanceGains g;
133-
if (stiffness.has_value()) g.stiffness = stiffness.value();
134-
if (damping.has_value()) g.damping = damping.value();
151+
if (stiffness.has_value()) {
152+
validateNonNegativeFinite(stiffness.value(), "stiffness");
153+
g.stiffness = stiffness.value();
154+
if (!damping.has_value()) g.damping = defaultJointImpedanceDamping(g.stiffness);
155+
}
156+
if (damping.has_value()) {
157+
validateNonNegativeFinite(damping.value(), "damping");
158+
g.damping = damping.value();
159+
}
135160
return g;
136161
}),
137162
"stiffness"_a = std::nullopt,

0 commit comments

Comments
 (0)