1 /****************************************************************************
2 *
3 * ViSP, open source Visual Servoing Platform software.
4 * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5 *
6 * This software is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * See the file LICENSE.txt at the root directory of this source
11 * distribution for additional information about the GNU GPL.
12 *
13 * For using ViSP with software that can not be combined with the GNU
14 * GPL, please contact Inria about acquiring a ViSP Professional
15 * Edition License.
16 *
17 * See http://visp.inria.fr for more information.
18 *
19 * This software was developed at:
20 * Inria Rennes - Bretagne Atlantique
21 * Campus Universitaire de Beaulieu
22 * 35042 Rennes Cedex
23 * France
24 *
25 * If you have questions regarding the use of this file, please contact
26 * Inria at visp@inria.fr
27 *
28 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30 *
31 * Description:
32 * Trajectory generator for torque control.
33 *
34 * Interface for the Franka robot.
35 *
36 * Authors:
37 * Fabien Spindler
38 *
39 *****************************************************************************/
40
41 #include "vpForceTorqueGenerator_impl.h"
42
43 #include <visp3/core/vpConfig.h>
44
45 #ifdef VISP_HAVE_FRANKA
46 #include <cmath>
47 #include <iomanip>
48 #include <algorithm>
49
50 #include <franka/exception.h>
51 #include <franka/robot.h>
52 #include <franka/model.h>
53
54 #include <visp3/core/vpException.h>
55 #include <visp3/core/vpTime.h>
56 #include <visp3/core/vpMatrix.h>
57
58 #include <Eigen/Core> // TODO: Remove when initial_position var is removed
59
60 vpColVector ft_cart_des_prev(6,0);
61
control_thread(franka::Robot * robot,std::atomic_bool & stop,const std::string & log_folder,const vpRobot::vpControlFrameType & frame,const std::array<double,7> & tau_J_des,const vpColVector & ft_cart_des,franka::RobotState & robot_state,std::mutex & mutex,const double & filter_gain,const bool & activate_pi_controller)62 void vpForceTorqueGenerator::control_thread(franka::Robot *robot, std::atomic_bool &stop,
63 const std::string &log_folder,
64 const vpRobot::vpControlFrameType &frame,
65 const std::array<double, 7> &tau_J_des,
66 const vpColVector &ft_cart_des,
67 franka::RobotState &robot_state,
68 std::mutex &mutex, const double &filter_gain,
69 const bool &activate_pi_controller)
70 {
71 double time = 0.0;
72
73 Eigen::Vector3d initial_position;
74 franka::Model model = robot->loadModel();
75
76 Eigen::VectorXd initial_tau_ext(7), tau_error_integral(7);
77 double k_p = 1.0;
78 double k_i = 2.0;
79
80 // Bias torque sensor
81 std::array<double, 7> gravity_array = model.gravity(robot_state);
82 Eigen::Map<Eigen::Matrix<double, 7, 1> > initial_tau_measured(robot_state.tau_J.data());
83 Eigen::Map<Eigen::Matrix<double, 7, 1> > initial_gravity(gravity_array.data());
84 initial_tau_ext = initial_tau_measured - initial_gravity;
85
86 franka::Torques zero_torques{{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
87 Eigen::VectorXd tau_joint_d(7), tau_cart_d(6);
88 for(size_t i = 0; i < 7; i++) {
89 tau_joint_d[i] = 0;
90 }
91 for(size_t i = 0; i < 6; i++) {
92 tau_cart_d[i] = 0;
93 }
94
95 // init integrator
96 tau_error_integral.setZero();
97
98 if (! activate_pi_controller) {
99 k_p = 0.;
100 k_i = 0.;
101 }
102
103 if (! log_folder.empty()) {
104 std::cout << "Save franka logs in \"" << log_folder << "\" folder" << std::endl;
105 std::cout << "Use gnuplot tool to visualize logs:" << std::endl;
106 std::cout << "$ cd " << log_folder << std::endl;
107 std::cout << "$ gnuplot plot.gp" << std::endl;
108 std::cout << "<press return key in the terminal to view next plot>" << std::endl;
109
110 std::ofstream gnuplot;
111 gnuplot.open(log_folder + "/plot.gp");
112 gnuplot << "set st data li\n" << std::endl;
113 gnuplot << "plot ";
114 for (size_t i = 0; i < 7; i++) {
115 gnuplot << "'tau_cmd.log' u " << i+1 << " title \"tau_cmd" << i+1 << "\", ";
116 }
117 gnuplot << "\npause -1\n" << std::endl;
118 gnuplot << "plot ";
119 for (size_t i = 0; i < 7; i++) {
120 gnuplot << "'tau_d.log' u " << i+1 << " title \"tau_d" << i+1 << "\", ";
121 }
122 gnuplot << "\npause -1\n" << std::endl;
123 gnuplot << "plot ";
124 for (size_t i = 0; i < 7; i++) {
125 gnuplot << "'tau_mes.log' u " << i+1 << " title \"tau_mes" << i+1 << "\", ";
126 }
127 gnuplot << "\npause -1\n" << std::endl;
128
129 if (frame == vpRobot::CAMERA_FRAME || frame == vpRobot::REFERENCE_FRAME || frame == vpRobot::END_EFFECTOR_FRAME) {
130 for (size_t i = 0; i < 7; i++) {
131 gnuplot << "plot 'tau_d.log' u " << i+1 << " title \"tau_d" << i+1 << "\", 'tau_mes.log' u " << i+1 << " title \"tau_mes" << i+1 << "\"" << std::endl;
132 gnuplot << "\npause -1\n" << std::endl;
133 }
134 }
135
136 gnuplot.close();
137 }
138
139 std::ofstream log_time;
140 std::ofstream log_tau_cmd;
141 std::ofstream log_tau_d;
142 std::ofstream log_tau_mes;
143 std::ofstream log_tau_diff;
144 std::ofstream log_tau_diff_prev;
145
146 auto force_joint_control_callback = [=, &log_time, &log_tau_cmd, &log_tau_d, &log_tau_mes, &time, &model, &initial_position,
147 &stop, &zero_torques, &robot_state, &tau_joint_d, &tau_error_integral,
148 &mutex, &tau_J_des]
149 (const franka::RobotState& state, franka::Duration period) -> franka::Torques {
150 time += period.toSec();
151
152 if (time == 0.0) {
153 if (! log_folder.empty()) {
154 log_time.open(log_folder + "/time.log");
155 log_tau_cmd.open(log_folder + "/tau_cmd.log");
156 log_tau_d.open(log_folder + "/tau_d.log");
157 log_tau_mes.open(log_folder + "/tau_mes.log");
158 }
159 }
160
161 {
162 std::lock_guard<std::mutex> lock(mutex);
163 robot_state = state;
164 }
165
166 Eigen::Map<const Eigen::Matrix<double, 7, 1> > tau_measured(state.tau_J.data());
167 Eigen::Map<const Eigen::Matrix<double, 7, 1> > gravity(gravity_array.data());
168
169 Eigen::VectorXd tau_cmd(7), tau_ext(7), desired_tau(7);
170 tau_ext << tau_measured - gravity - initial_tau_ext;
171
172 tau_error_integral += period.toSec() * (tau_joint_d - tau_ext);
173
174 // Smoothly update the mass to reach the desired target value
175 for(size_t i = 0; i < 7; i++) {
176 tau_joint_d[i] = filter_gain * tau_J_des[i] + (1 - filter_gain) * tau_joint_d[i];
177 }
178
179 // FF + PI control
180 tau_cmd << tau_joint_d + k_p * (tau_joint_d - tau_ext) + k_i * tau_error_integral;
181
182 std::array<double, 7> tau_d_array{};
183 Eigen::VectorXd::Map(&tau_d_array[0], 7) = tau_cmd;
184
185 if (! log_folder.empty()) {
186 log_time << time << std::endl;
187 log_tau_cmd << std::fixed << std::setprecision(8) << tau_cmd[0] << " " << tau_cmd[1] << " " << tau_cmd[2] << " " << tau_cmd[3] << " " << tau_cmd[4] << " " << tau_cmd[5] << " " << tau_cmd[6] << std::endl;
188 log_tau_d << std::fixed << std::setprecision(8) << tau_joint_d[0] << " " << tau_joint_d[1] << " " << tau_joint_d[2] << " " << tau_joint_d[3] << " " << tau_joint_d[4] << " " << tau_joint_d[5] << " " << tau_joint_d[6] << std::endl;
189 log_tau_mes << std::fixed << std::setprecision(8) << tau_ext[0] << " " << tau_ext[1] << " " << tau_ext[2] << " " << tau_ext[3] << " " << tau_ext[4] << " " << tau_ext[5] << " " << tau_ext[6] << std::endl;
190 }
191
192 if (stop) {
193 if (! log_folder.empty()) {
194 log_time.close();
195 log_tau_cmd.close();
196 log_tau_d.close();
197 log_tau_mes.close();
198 }
199 return franka::MotionFinished(zero_torques);
200 }
201
202 return tau_d_array;
203 };
204
205 auto force_cart_control_callback = [=, &log_time, &log_tau_cmd, &log_tau_d, &log_tau_mes, &log_tau_diff, &log_tau_diff_prev, &time, &model, /*&initial_position, */
206 &stop, &zero_torques, &robot_state, &tau_cart_d, &tau_error_integral,
207 &mutex, &ft_cart_des]
208 (const franka::RobotState& state, franka::Duration period) -> franka::Torques {
209 time += period.toSec();
210
211 Eigen::VectorXd tau_d(7), tau_cmd(7), tau_ext(7), desired_tau(7);
212
213 if (time == 0.0) {
214 tau_d << 0, 0, 0, 0, 0, 0, 0;
215 tau_ext << 0, 0, 0, 0, 0, 0, 0;
216
217 if (! log_folder.empty()) {
218 log_time.open(log_folder + "/time.log");
219 log_tau_cmd.open(log_folder + "/tau_cmd.log");
220 log_tau_d.open(log_folder + "/tau_d.log");
221 log_tau_mes.open(log_folder + "/tau_mes.log");
222 log_tau_diff.open(log_folder + "/tau_diff.log");
223 log_tau_diff_prev.open(log_folder + "/tau_diff_prev.log");
224 }
225 }
226
227 {
228 std::lock_guard<std::mutex> lock(mutex);
229 robot_state = state;
230 }
231
232 // get state variables
233 std::array<double, 42> jacobian_array = model.zeroJacobian(franka::Frame::kEndEffector, state);
234
235 Eigen::Map<const Eigen::Matrix<double, 6, 7> > jacobian(jacobian_array.data());
236 Eigen::Map<const Eigen::Matrix<double, 7, 1> > tau_measured(state.tau_J.data());
237 Eigen::Map<const Eigen::Matrix<double, 7, 1> > gravity(gravity_array.data());
238
239 tau_ext << tau_measured - gravity - initial_tau_ext;
240
241 tau_error_integral += period.toSec() * (tau_d - tau_ext);
242
243 // Apply force with gradually increasing the force
244 for(size_t i = 0; i < 7; i++) {
245 tau_cart_d[i] = filter_gain * ft_cart_des[i] + (1 - filter_gain) * tau_cart_d[i];
246 }
247
248 tau_d << jacobian.transpose() * tau_cart_d;
249
250 // FF + PI control
251 tau_cmd << tau_d + k_p * (tau_d - tau_ext) + k_i * tau_error_integral;
252
253 std::array<double, 7> tau_d_array{};
254 Eigen::VectorXd::Map(&tau_d_array[0], 7) = tau_cmd;
255
256 if (! log_folder.empty()) {
257 log_time << time << std::endl;
258 log_tau_cmd << std::fixed << std::setprecision(8) << tau_cmd[0] << " " << tau_cmd[1] << " " << tau_cmd[2] << " " << tau_cmd[3] << " " << tau_cmd[4] << " " << tau_cmd[5] << " " << tau_cmd[6] << std::endl;
259 log_tau_d << std::fixed << std::setprecision(8) << tau_d[0] << " " << tau_d[1] << " " << tau_d[2] << " " << tau_d[3] << " " << tau_d[4] << " " << tau_d[5] << " " << tau_d[6] << std::endl;
260 log_tau_mes << std::fixed << std::setprecision(8) << tau_ext[0] << " " << tau_ext[1] << " " << tau_ext[2] << " " << tau_ext[3] << " " << tau_ext[4] << " " << tau_ext[5] << " " << tau_ext[6] << std::endl;
261 log_tau_diff << std::fixed << std::setprecision(8);
262 for (size_t i = 0; i < ft_cart_des.size(); i++) {
263 log_tau_diff << ft_cart_des[i] - tau_cart_d[i] << " ";
264 }
265 log_tau_diff << std::endl;
266
267 log_tau_diff_prev << std::fixed << std::setprecision(8);
268 for (size_t i = 0; i < ft_cart_des.size(); i++) {
269 log_tau_diff_prev << ft_cart_des[i] - ft_cart_des_prev[i] << " ";
270 }
271 log_tau_diff_prev << std::endl;
272 }
273
274 if (stop) {
275 if (! log_folder.empty()) {
276 log_time.close();
277 log_tau_cmd.close();
278 log_tau_d.close();
279 log_tau_mes.close();
280 log_tau_diff.close();
281 log_tau_diff_prev.close();
282 }
283 return franka::MotionFinished(zero_torques);
284 }
285
286 ft_cart_des_prev = ft_cart_des;
287
288 return tau_d_array;
289 };
290
291 #if !(VISP_HAVE_FRANKA_VERSION < 0x000500)
292 double cutoff_frequency = 10;
293 #endif
294
295 switch (frame) {
296 case vpRobot::JOINT_STATE: {
297 int nbAttempts = 10;
298 for (int attempt = 1; attempt <= nbAttempts; attempt++) {
299 try {
300 robot->control(force_joint_control_callback, true, cutoff_frequency);
301 break;
302 } catch (const franka::ControlException &e) {
303 std::cerr << "Warning: communication error: " << e.what() << "\nRetry attempt: " << attempt << std::endl;
304 robot->automaticErrorRecovery();
305 if (attempt == nbAttempts)
306 throw e;
307 }
308 }
309 break;
310 }
311 case vpRobot::CAMERA_FRAME:
312 case vpRobot::REFERENCE_FRAME:
313 case vpRobot::END_EFFECTOR_FRAME: {
314 int nbAttempts = 10;
315 for (int attempt = 1; attempt <= nbAttempts; attempt++) {
316 try {
317 robot->control(force_cart_control_callback, true, cutoff_frequency);
318 break;
319 } catch (const franka::ControlException &e) {
320 std::cerr << "Warning: communication error: " << e.what() << "\nRetry attempt: " << attempt << std::endl;
321 robot->automaticErrorRecovery();
322 if (attempt == nbAttempts)
323 throw e;
324 }
325 }
326 break;
327 }
328 case vpRobot::MIXT_FRAME: {
329 throw vpException(vpException::fatalError, "Force/torque controller not supported");
330 }
331 }
332 }
333
334 #elif !defined(VISP_BUILD_SHARED_LIBS)
335 // Work arround to avoid warning: libvisp_robot.a(vpForceTorqueGenerator.cpp.o) has no symbols
dummy_vpForceTorqueGenerator()336 void dummy_vpForceTorqueGenerator(){};
337 #endif // VISP_HAVE_FRANKA
338
339