icub-test
opticalEncodersDrift.cpp
1 /*
2  * iCub Robot Unit Tests (Robot Testing Framework)
3  *
4  * Copyright (C) 2015-2022 Istituto Italiano di Tecnologia (IIT)
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 
22 #include <math.h>
23 #include <robottestingframework/TestAssert.h>
24 #include <robottestingframework/dll/Plugin.h>
25 #include <yarp/os/Time.h>
26 #include <yarp/math/Math.h>
27 #include <yarp/os/Property.h>
28 #include <yarp/conf/environment.h>
29 #include <fstream>
30 #include <algorithm>
31 #include <cstdlib>
32 #include "opticalEncodersDrift.h"
33 #include <iostream>
34 #include <ctime>
35 #include <filesystem>
36 
37 //example -v -t OpticalEncodersDrift.dll -p "--robot icub --part head --joints ""(0 1 2)"" --home ""(0 0 0)" --speed "(20 20 20)" --max "(10 10 10)" --min "(-10 -10 -10)" --cycles 100 --tolerance 1.0 "
38 //example2 -v -t OpticalEncodersDrift.dll -p "--robot icub --part head --joints ""(2)"" --home ""(0)"" --speed "(20 )" --max "(10 )" --min "(-10)" --cycles 100 --tolerance 1.0 "
39 using namespace robottestingframework;
40 using namespace yarp::os;
41 using namespace yarp::dev;
42 using namespace yarp::math;
43 using namespace std;
44 using namespace std::filesystem;
45 
46 // prepare the plugin
47 ROBOTTESTINGFRAMEWORK_PREPARE_PLUGIN(OpticalEncodersDrift)
48 
49 OpticalEncodersDrift::OpticalEncodersDrift() : yarp::robottestingframework::TestCase("OpticalEncodersDrift") {
50  jointsList=0;
51  dd=0;
52  ipos=0;
53  icmd=0;
54  iimd=0;
55  ienc=0;
56  imot=0;
57  enc_jnt=0;
58  enc_mot=0;
59  home_enc_mot=0;
60  end_enc_mot=0;
61  err_enc_mot=0;
62  cycles=100;
63 }
64 
65 OpticalEncodersDrift::~OpticalEncodersDrift() { }
66 
67 bool OpticalEncodersDrift::setup(yarp::os::Property& property) {
68 
69  if(property.check("name"))
70  setName(property.find("name").asString());
71 
72  // updating parameters
73  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("robot"), "The robot name must be given as the test parameter!");
74  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("part"), "The part name must be given as the test parameter!");
75  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("joints"), "The joints list must be given as the test parameter!");
76  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("home"), "The home position must be given as the test parameter!");
77  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("max"), "The max position must be given as the test parameter!");
78  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("min"), "The min position must be given as the test parameter!");
79  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("speed"), "The positionMove reference speed must be given as the test parameter!");
80  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("cycles"), "The number of cycles of the control signal must be given as the test parameter!");
81  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("tolerance"), "The max error tolerance must be given as the test parameter!");
82 
83  robotName = property.find("robot").asString();
84 
85  partName = property.find("part").asString();
86 
87  Bottle* jointsBottle = property.find("joints").asList();
88  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(jointsBottle!=0,"unable to parse joints parameter");
89 
90  Bottle* homeBottle = property.find("home").asList();
91  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(homeBottle!=0,"unable to parse zero parameter");
92 
93  Bottle* maxBottle = property.find("max").asList();
94  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(maxBottle!=0,"unable to parse max parameter");
95 
96  Bottle* minBottle = property.find("min").asList();
97  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(minBottle!=0,"unable to parse min parameter");
98 
99  Bottle* speedBottle = property.find("speed").asList();
100  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(speedBottle!=0,"unable to parse speed parameter");
101 
102  tolerance = property.find("tolerance").asFloat64();
103  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(tolerance>=0,"invalid tolerance");
104 
105  cycles = property.find("cycles").asInt32();
106  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(cycles>=0,"invalid cycles");
107 
108  plot = property.find("plot_enabled").asBool();
109 
110  if(plot)
111  ROBOTTESTINGFRAMEWORK_TEST_REPORT("This test will run gnuplot utility at the end.");
112  else
113  ROBOTTESTINGFRAMEWORK_TEST_REPORT("This test will NOT run gnuplot utility at the end.");
114 
115 
116 
117  Property options;
118  options.put("device", "remote_controlboard");
119  options.put("remote", "/"+robotName+"/"+partName);
120  options.put("local", "/opticalEncodersDrift/"+robotName+"/"+partName);
121 
122  dd = new PolyDriver(options);
123  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->isValid(),"Unable to open device driver");
124  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->view(ienc),"Unable to open encoders interface");
125  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->view(ipos),"Unable to open position interface");
126  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->view(icmd),"Unable to open control mode interface");
127  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->view(iimd),"Unable to open interaction mode interface");
128  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(dd->view(imot),"Unable to open motor encoders interface");
129 
130  if (!ienc->getAxes(&n_part_joints))
131  {
132  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR("unable to get the number of joints of the part");
133  }
134 
135  int n_cmd_joints = jointsBottle->size();
136  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(n_cmd_joints>0 && n_cmd_joints<=n_part_joints,"invalid number of joints, it must be >0 & <= number of part joints");
137  for (int i=0; i <n_cmd_joints; i++) jointsList.push_back(jointsBottle->get(i).asInt32());
138 
139  enc_jnt.resize(n_part_joints);
140  enc_mot.resize(n_part_joints);
141  home_enc_mot.resize(n_part_joints);
142  end_enc_mot.resize(n_part_joints);
143  err_enc_mot.resize(n_part_joints);
144 
145  max.resize (n_cmd_joints); for (int i=0; i< n_cmd_joints; i++) max[i]=maxBottle->get(i).asFloat64();
146  min.resize (n_cmd_joints); for (int i=0; i< n_cmd_joints; i++) min[i]=minBottle->get(i).asFloat64();
147  home.resize (n_cmd_joints); for (int i=0; i< n_cmd_joints; i++) home[i]=homeBottle->get(i).asFloat64();
148  speed.resize(n_cmd_joints); for (int i=0; i< n_cmd_joints; i++) speed[i]=speedBottle->get(i).asFloat64();
149 
150  return true;
151 }
152 
153 void OpticalEncodersDrift::tearDown()
154 {
155  if (dd) {delete dd; dd =0;}
156 }
157 
158 void OpticalEncodersDrift::setMode(int desired_mode)
159 {
160  for (unsigned int i=0; i<jointsList.size(); i++)
161  {
162  icmd->setControlMode((int)jointsList[i],desired_mode);
163  iimd->setInteractionMode((int)jointsList[i],VOCAB_IM_STIFF);
164  yarp::os::Time::delay(0.010);
165  }
166 
167  int cmode;
168  yarp::dev::InteractionModeEnum imode;
169  int timeout = 0;
170 
171  while (1)
172  {
173  int ok=0;
174  for (unsigned int i=0; i<jointsList.size(); i++)
175  {
176  icmd->getControlMode ((int)jointsList[i],&cmode);
177  iimd->getInteractionMode((int)jointsList[i],&imode);
178  if (cmode==desired_mode && imode==VOCAB_IM_STIFF) ok++;
179  }
180  if (ok==jointsList.size()) break;
181  if (timeout>100)
182  {
183  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR("Unable to set control mode/interaction mode");
184  }
185  yarp::os::Time::delay(0.2);
186  timeout++;
187  }
188 }
189 
190 bool OpticalEncodersDrift::goHome()
191 {
192  for (unsigned int i=0; i<jointsList.size(); i++)
193  {
194  ipos->setRefSpeed((int)jointsList[i],speed[i]);
195  ipos->positionMove((int)jointsList[i],home[i]);
196  }
197 
198  int timeout = 0;
199  while (1)
200  {
201  int in_position=0;
202  for (unsigned int i=0; i<jointsList.size(); i++)
203  {
204  double tmp=0;
205  ienc->getEncoder((int)jointsList[i],&tmp);
206  if (fabs(tmp-home[i])<tolerance) in_position++;
207  }
208  if (in_position==jointsList.size()) break;
209  if (timeout>100)
210  {
211  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Timeout while reaching home position");
212  return false;
213  }
214  yarp::os::Time::delay(0.2);
215  timeout++;
216  }
217  return true;
218 }
219 
220 bool OpticalEncodersDrift::saveToFile(const std::string &filename, const yarp::os::Bottle &b)
221 {
222  std::fstream fs;
223  fs.open (filename.c_str(), std::fstream::out);
224 
225  if ( (fs.rdstate() & std::ifstream::failbit ) != 0 )
226  {
227  std::cerr << "Error opening " << filename << "\n";
228  return false;
229  }
230 
231  for (unsigned int i=0; i<b.size(); i++)
232  {
233  std::string s = b.get(i).toString();
234  std::replace(s.begin(), s.end(), '(', ' ');
235  std::replace(s.begin(), s.end(), ')', ' ');
236  fs << s << std::endl;
237  }
238 
239  fs.close();
240  return true;
241 }
242 
243 void OpticalEncodersDrift::run()
244 {
245  setMode(VOCAB_CM_POSITION);
246  ROBOTTESTINGFRAMEWORK_ASSERT_FAIL_IF_FALSE(goHome(), "Test can't run");
247 
248  bool go_to_max=false;
249  for (unsigned int i=0; i<jointsList.size(); i++)
250  {
251  ipos->positionMove((int)jointsList[i], min[i]);
252  }
253 
254  int curr_cycle=0;
255  double start_time = yarp::os::Time::now();
256  Bottle dataToPlot;
257 
258  imot->getMotorEncoders (home_enc_mot.data());
259  while(1)
260  {
261  double curr_time = yarp::os::Time::now();
262  double elapsed = curr_time-start_time;
263 
264  //get joint e motor encoders data for the whole robot part
265  ienc->getEncoders (enc_jnt.data());
266  imot->getMotorEncoders (enc_mot.data());
267  //extract only the joints of interest
268  yarp::sig::Vector enc_jnt_of_interest (jointsList.size());
269  yarp::sig::Vector enc_mot_of_interest (jointsList.size());
270  for (size_t i =0; i< jointsList.size(); i++)
271  {
272  enc_jnt_of_interest[i] = enc_jnt[jointsList[i]];
273  enc_mot_of_interest[i] = enc_mot[jointsList[i]];
274  }
275  //put the data into a Bottle
276  //this is the output format: n values for the motor encoders, then n values for the jnt encoders
277  Bottle& row = dataToPlot.addList();
278  Bottle& b_mot = row.addList();
279  Bottle& b_jnt = row.addList();
280  b_mot.read(enc_mot_of_interest);
281  b_jnt.read(enc_jnt_of_interest);
282 
283  bool reached= false;
284  int in_position=0;
285  for (unsigned int i=0; i<jointsList.size(); i++)
286  {
287  double curr_val=0;
288  if (go_to_max==false) curr_val = min[i];
289  else curr_val = max[i];
290  if (fabs(enc_jnt[i]-curr_val)<tolerance) in_position++;
291  }
292  if (in_position==jointsList.size()) reached=true;
293 
294  if (elapsed >= 20.0)
295  {
296  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR("Timeout while moving joint");
297  }
298 
299  if (reached)
300  {
301  if (go_to_max==false)
302  {
303  for (unsigned int i=0; i<jointsList.size(); i++)
304  ipos->positionMove(i,max[i]);
305  go_to_max=true;
306  curr_cycle++;
307  start_time = yarp::os::Time::now();
308  if (curr_cycle % 10 == 0) ROBOTTESTINGFRAMEWORK_TEST_REPORT(Asserter::format("Cycle %d/%d completed", curr_cycle, cycles));
309  }
310  else
311  {
312  for (unsigned int i=0; i<jointsList.size(); i++)
313  ipos->positionMove(i,min[i]);
314  go_to_max=false;
315  curr_cycle++;
316  start_time = yarp::os::Time::now();
317  if (curr_cycle % 10 == 0) ROBOTTESTINGFRAMEWORK_TEST_REPORT(Asserter::format("Cycle %d/%d completed", curr_cycle, cycles));
318  }
319  }
320 
321  if (curr_cycle>=cycles) break;
322 
323  yarp::os::Time::delay(0.010);
324  }
325 
326  bool isInHome = goHome();
327  yarp::os::Time::delay(2.0);
328 
329  //automatic check, not complete yet
330  {
331  imot->getMotorEncoders (end_enc_mot.data());
332  for (int i=0; i<n_part_joints; i++)
333  {
334  err_enc_mot[i]=home_enc_mot[i]-end_enc_mot[i];
335 
336  if (fabs(err_enc_mot[i]) > tolerance)
337  {
338  //...assert something
339  }
340  }
341  }
342 
343  time_t now = time(0);
344  tm *ltm = localtime(&now);
345 
346  char folder_time_buffer[80];
347  char file_time_buffer[80];
348 
349  strftime(folder_time_buffer, sizeof(folder_time_buffer), "%d%m%Y", ltm);
350  strftime(file_time_buffer, sizeof(file_time_buffer), "%d%m%Y_%H%M", ltm);
351 
352  string folder_time_str(folder_time_buffer);
353  string file_time_str(file_time_buffer); //This string contain also minutes
354 
355  // Create the filename with date and time
356  string filename = "encDrift_plot_";
357  filename += partName;
358  filename += "_";
359  filename += file_time_str;
360  filename += ".txt";
361 
362  constexpr char default_robot_name[] = "RobotName";
363 
364  string robot_str = yarp::conf::environment::get_string("YARP_ROBOT_NAME", default_robot_name);
365  string directory_tree = "results/" + robot_str + "/encoders-icub_" + folder_time_str + "/encDrift";
366  create_directories(directory_tree); // This function return false if there is an error or if the directories already exist
367 
368  string filename_with_path = directory_tree + "/" + filename;
369  bool saved_files = saveToFile(filename_with_path,dataToPlot);
370 
371  if(!saved_files)
372  {
373  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Error saving files to plot!");
374  }
375 
376  char plotstring[1000];
377  sprintf (plotstring, "gnuplot -e \" unset key; plot for [col=1:%d] '%s' using col with lines \" -persist", (int)jointsList.size(),filename_with_path.c_str());
378 
379  if(plot)
380  {
381  system (plotstring);
382  }
383  else
384  {
385  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Test is finished. Please check if collected date are ok, by using following command: ");
386  ROBOTTESTINGFRAMEWORK_TEST_REPORT(robottestingframework::Asserter::format("%s", plotstring));
387  }
388 
389  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(isInHome, "This part is not in home. Suite test will be terminated!");
390  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Test is finished. Your files are saved in: \n" + filename_with_path);
391 
392 }
This tests checks if the relative encoders measurements are consistent over time, by performing cycli...