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