icub-test
imu.cpp
1 #include <iostream>
2 #include <cmath>
3 #include <numeric>
4 
5 #include <robottestingframework/TestAssert.h>
6 #include <robottestingframework/dll/Plugin.h>
7 
8 #include <yarp/os/Property.h>
9 #include <yarp/os/Stamp.h>
10 #include <yarp/os/ResourceFinder.h>
11 #include <yarp/os/LogStream.h>
12 
13 #include <robometry/BufferConfig.h>
14 #include <robometry/BufferManager.h>
15 
16 #include "imu.h"
17 
18 using namespace robottestingframework;
19 ROBOTTESTINGFRAMEWORK_PREPARE_PLUGIN(Imu)
20 
21 Imu::Imu() : TestCase("Imu") { }
22 
23 Imu::~Imu() { }
24 
25 bool Imu::setup(yarp::os::Property& property)
26 {
27  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("robot"), "The robot name must be given as the test parameter!");
28  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("port"), "The port name must be given as the test parameter!");
29  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("remoteControlBoards"), "Please, provide the controlboards name.");
30  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("axesNames"), "Please, provide the controlled joints name.");
31  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("model"), "Please, provide the urdf model path.");
32  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("maxError"), "Please, provide the threshold error.");
33  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(property.check("sensorsList"), "Please, provide the list of the sensors you want to check or 'all' if you want to test all the IMUs.");
34 
35  robotName = property.find("robot").asString(); // robot name
36  portName = property.find("port").asString(); // name of the port from which the data are streamed
37  errorMax = property.find("maxError").asFloat64(); //error mean
38  modelName = property.find("model").asString(); // urdf model path
39  yarp::os::ResourceFinder &rf = yarp::os::ResourceFinder::getResourceFinderSingleton();
40  std::string modelAbsolutePath = rf.findFileByName(modelName);
41 
42  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Running IMU test on "+robotName+"...");
43 
44  yarp::os::Property controlBoardOptions;
45  controlBoardOptions.put("device", "remotecontrolboardremapper");
46 
47  yarp::os::Bottle remoteControlBoards;
48  yarp::os::Bottle & remoteControlBoardsList = remoteControlBoards.addList();
49  yarp::os::Bottle *inputControlBoards;
50  inputControlBoards = property.find("remoteControlBoards").asList();
51  for(int ctrlBoard = 0; ctrlBoard < inputControlBoards->size(); ctrlBoard++)
52  {
53  partsList.push_back(inputControlBoards->get(ctrlBoard).asString());
54  remoteControlBoardsList.addString("/"+robotName+"/"+inputControlBoards->get(ctrlBoard).asString());
55  }
56 
57  yarp::os::Bottle axesNames;
58  yarp::os::Bottle & axesList = axesNames.addList();
59  yarp::os::Bottle *inputJoints;
60  inputJoints = property.find("axesNames").asList();
61  for(int ctrlJoint = 0; ctrlJoint < inputJoints->size(); ctrlJoint++)
62  {
63  axesList.addString(inputJoints->get(ctrlJoint).asString());
64  }
65 
66  controlBoardOptions.put("remoteControlBoards", remoteControlBoards.get(0));
67  controlBoardOptions.put("localPortPrefix", "/test");
68  controlBoardOptions.put("axesNames", axesNames.get(0));
69 
70  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(controlBoardDriver.open(controlBoardOptions), "Unable to open the controlBoard driver");
71  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(controlBoardDriver.isValid(), "Device driver cannot be opened");
72  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(controlBoardDriver.view(ipos), "Unable to open position control interface");
73  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(controlBoardDriver.view(ienc), "Unable to open encoder interface");
74  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(controlBoardDriver.view(iaxes), "Unable to open axes interface");
75 
76  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(ienc->getAxes(&axes), "Cannot get number of controlled axes");
77  std::string axisName;
78 
79  for (int axIndex = 0; axIndex < axes; axIndex++)
80  {
81  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iaxes->getAxisName(axIndex, axisName), "Cannot get the name of controlled axes");
82  axesVec.push_back(axisName);
83  }
84 
85  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(model.loadReducedModelFromFile(modelAbsolutePath.c_str(), axesVec), Asserter::format("Cannot load model from %s", modelAbsolutePath.c_str()));
86  kinDynComp.loadRobotModel(model.model());
87 
88  yarp::os::Property MASclientOptions;
89  MASclientOptions.put("device", "multipleanalogsensorsclient");
90  MASclientOptions.put("remote", portName);
91  MASclientOptions.put("local", "/imuTest"+portName);
92 
93  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASclientDriver.open(MASclientOptions), "Unable to open the MAS client driver");
94  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASclientDriver.isValid(), "Device driver cannot be opened");
95 
96  yarp::os::Bottle & sensorsNamesList = sensorsList.addList();
97  yarp::os::Bottle *inputSensorsList;
98  inputSensorsList = property.find("sensorsList").asList();
99 
100  if(inputSensorsList->size() == 0 || inputSensorsList->get(0).asString() == "all")
101  {
102  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Testing all the IMUs available...");
103  std::string sensorName{""};
104  yarp::dev::IOrientationSensors* ior;
105  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASclientDriver.view(ior), "Unable to open the orientation interface");
106  for(int sensorIndex = 0; sensorIndex < ior->getNrOfOrientationSensors(); sensorIndex++)
107  {
108  ior->getOrientationSensorName(sensorIndex, sensorName);
109  sensorsNamesList.addString(sensorName);
110  }
111  }
112 
113  else
114  {
115  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Testing the list of provided IMUs...");
116  for(int sensorIndex = 0; sensorIndex < inputSensorsList->size(); sensorIndex++)
117  {
118  sensorsNamesList.addString(inputSensorsList->get(sensorIndex).asString());
119  }
120  }
121 
122  yarp::os::Property MASremapperOptions;
123  MASremapperOptions.put("device", "multipleanalogsensorsremapper");
124  MASremapperOptions.put("OrientationSensorsNames", sensorsList.get(0));
125  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASremapperDriver.open(MASremapperOptions), "Unable to open the MAS remapper driver");
126  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASremapperDriver.isValid(), "Device driver cannot be opened");
127 
128  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASremapperDriver.view(imultiwrap), "Unable to open multiple wrapper interface");
129 
130  yarp::dev::PolyDriverList driverList;
131  driverList.push(&MASclientDriver, "alljoints_inertials");
132  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(imultiwrap->attachAll(driverList), "Unable to do the attach");
133  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(MASremapperDriver.view(iorientation), "Unable to open orientation interface");
134 
135  iDynTree::Vector3 baseLinkOrientationRad;
136  baseLinkOrientationRad.zero();
137 
138  baseLinkOrientation = iDynTree::Rotation::RPY(baseLinkOrientationRad[0], baseLinkOrientationRad[1], baseLinkOrientationRad[2]);
139  I_T_base = iDynTree::Transform(baseLinkOrientation, iDynTree::Position::Zero());
140  iDynTree::Twist baseVelocity = iDynTree::Twist::Zero();
141 
142  unsigned int dofs = kinDynComp.model().getNrOfDOFs();
143  s.resize(dofs);
144  ds.resize(dofs);
145 
146  positions.resize(axes);
147  velocities.resize(axes);
148  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(ienc->getEncoders(positions.data()), "Cannot get joint positions");
149  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(ienc->getEncoderSpeeds(velocities.data()), "Cannot get joint velocities");
150 
151  for(auto axIndex = 0; axIndex < axes; axIndex++)
152  {
153  s.setVal(axIndex, iDynTree::deg2rad(positions[axIndex]));
154  ds.setVal(axIndex, iDynTree::deg2rad(velocities[axIndex]));
155  }
156 
157  gravity.zero();
158  gravity(2) = -9.81;
159 
160  kinDynComp.setRobotState(
161  I_T_base,
162  s,
163  baseVelocity,
164  ds,
165  gravity
166  );
167 
168  setupRobometry();
169  localBroker.resize(remoteControlBoards.get(0).asList()->size());
170 
171  return true;
172 }
173 
174 void Imu::tearDown()
175 {
176  outputPort.interrupt();
177  outputPort.close();
178 
179  controlBoardDriver.close();
180  MASclientDriver.close();
181  MASremapperDriver.close();
182 
183  for(int i = 0; i < localBroker.size(); i++)
184  {
185  localBroker[i].stop();
186  }
187 
188  scriptBroker.stop();
189 }
190 
191 void Imu::run()
192 {
193  ROBOTTESTINGFRAMEWORK_TEST_REPORT("Starting reading IMU orientation values...");
194  rpyValues.resize(sensorsList.get(0).asList()->size());
195  I_R_I_IMU.resize(sensorsList.get(0).asList()->size());
196 
197  for (int sensorIndex = 0; sensorIndex < sensorsList.get(0).asList()->size(); sensorIndex++)
198  {
199  std::string sensorName{""};
200  std::string frameName{""};
201  double timestamp;
202  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorName(sensorIndex, sensorName), "Unable to obtain sensor name.");
203  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorMeasureAsRollPitchYaw(sensorIndex, rpyValues[sensorIndex], timestamp), "Unable to obtain rpy measurements.");
204  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorFrameName(sensorIndex, frameName), "Unable to obtain frame name.");
205  iDynTree::Rotation I_R_FK = kinDynComp.getWorldTransform(frameName).getRotation();
206  I_R_I_IMU[sensorIndex] = (I_R_FK * ((iDynTree::Rotation::RPY(iDynTree::deg2rad(rpyValues[sensorIndex][0]), iDynTree::deg2rad(rpyValues[sensorIndex][1]), iDynTree::deg2rad(rpyValues[sensorIndex][2]))).inverse()));
207  }
208 
209  setupBrokers();
210  startMove();
211 }
212 
213 bool Imu::startMove()
214 {
215  bool done = false;
216  iDynTree::GeomVector3 error;
217  std::vector<std::vector<double>> errorTot(sensorsList.get(0).asList()->size());
218 
219  yarp::os::Time::delay(.1);
220 
221  while(!done)
222  {
223  for (int sensorIndex = 0; sensorIndex < sensorsList.get(0).asList()->size(); sensorIndex++)
224  {
225  std::string frameName{""};
226  std::string sensorName{""};
227 
228  double timestamp;
229  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorName(sensorIndex, sensorName), "Unable to obtain sensor name.");
230  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorMeasureAsRollPitchYaw(sensorIndex, rpyValues[sensorIndex], timestamp), "Unable to obtain rpy measurements.");
231  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(iorientation->getOrientationSensorFrameName(sensorIndex, frameName), "Unable to obtain frame name.");
232 
233  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(ienc->getEncoders(positions.data()), "Cannot get joint positions");
234  ROBOTTESTINGFRAMEWORK_ASSERT_ERROR_IF_FALSE(ienc->getEncoderSpeeds(velocities.data()), "Cannot get joint velocities");
235 
236  for (auto axIndex = 0; axIndex < axes; axIndex++)
237  {
238  s.setVal(axIndex, iDynTree::deg2rad(positions[axIndex]));
239  ds.setVal(axIndex, iDynTree::deg2rad(velocities[axIndex]));
240  }
241 
242  kinDynComp.setRobotState(
243  I_T_base,
244  s,
245  baseVelocity,
246  ds,
247  gravity);
248 
249  iDynTree::Rotation expectedImuSignal = kinDynComp.getWorldTransform(frameName).getRotation();
250  iDynTree::Rotation imuSignal = (I_R_I_IMU[sensorIndex] * iDynTree::Rotation::RPY(iDynTree::deg2rad(rpyValues[sensorIndex][0]), iDynTree::deg2rad(rpyValues[sensorIndex][1]), iDynTree::deg2rad(rpyValues[sensorIndex][2])));
251  error = (expectedImuSignal * imuSignal.inverse()).log();
252 
253  bufferManager.push_back(positions, "joints_state::positions");
254  bufferManager.push_back(velocities, "joints_state::velocities");
255  bufferManager.push_back({expectedImuSignal.asRPY()[0], expectedImuSignal.asRPY()[1], expectedImuSignal.asRPY()[2]}, "orientations::" + sensorName + "::expected");
256  bufferManager.push_back({imuSignal.asRPY()[0], imuSignal.asRPY()[1], imuSignal.asRPY()[2]}, "orientations::" + sensorName + "::measured");
257 
258  double sumOfSquares = std::accumulate(error.begin(), error.end(), 0.0,
259  [](double accumulator, double element) {
260  return accumulator + element * element;
261  });
262 
263  double mag = std::sqrt(sumOfSquares);
264  bufferManager.push_back(mag, "orientations::" + sensorName + "::error");
265 
266  errorTot[sensorIndex].push_back(mag);
267  }
268 
269  ipos->checkMotionDone(&done);
270  }
271 
272  for(int sensorIndex = 0; sensorIndex < sensorsList.get(0).asList()->size(); sensorIndex++)
273  {
274  auto sensorName = sensorsList.get(0).asList()->get(sensorIndex).asString();
275  auto maxError = std::max_element(errorTot[sensorIndex].begin(), errorTot[sensorIndex].end());
276  ROBOTTESTINGFRAMEWORK_TEST_CHECK(*maxError < errorMax, Asserter::format("Testing sensor %s: the max rotation angle error is %f rad!", sensorName.c_str(), *maxError));
277  }
278 
279  return true;
280 }
281 
282 bool Imu::setupRobometry()
283 {
284  robometry::BufferConfig bufferConfig;
285  bufferConfig.auto_save = true;
286  bufferConfig.yarp_robot_name = std::getenv("YARP_ROBOT_NAME");
287  bufferConfig.filename = "test_imu";
288  bufferConfig.file_indexing = "%Y_%m_%d_%H_%M_%S";
289  bufferConfig.n_samples = 100000;
290 
291  bufferManager.addChannel({"joints_state::positions", {axesVec.size(), 1}, axesVec});
292  bufferManager.addChannel({"joints_state::velocities", {axesVec.size(), 1}, axesVec});
293 
294  for(auto sensorIndex = 0; sensorIndex < sensorsList.get(0).asList()->size(); sensorIndex++)
295  {
296  bufferManager.addChannel({"orientations::" + sensorsList.get(0).asList()->get(sensorIndex).asString() + "::expected", {3, 1}, {"r", "p", "y"}});
297  bufferManager.addChannel({"orientations::" + sensorsList.get(0).asList()->get(sensorIndex).asString() + "::measured", {3, 1}, {"r", "p", "y"}});
298  bufferManager.addChannel({"orientations::" + sensorsList.get(0).asList()->get(sensorIndex).asString() + "::error", {1, 1}, {"error"}});
299  }
300 
301  return bufferManager.configure(bufferConfig);
302 }
303 
304 void Imu::setupBrokers()
305 {
306  strCmd = "ctpService";
307  for(int i = 0; i < localBroker.size(); i++)
308  {
309  strParam = "--robot " + robotName + " --part " + partsList[i];
310  localBroker[i].init(strCmd.c_str(), strParam.c_str(), nullptr, nullptr, nullptr, nullptr);
311  localBroker[i].start();
312  strParam.clear();
313  }
314 
315  for (auto part : partsList)
316  {
317  if(part.find("leg") != std::string::npos)
318  strParam = "";
319 
320  else
321  strParam = "no_legs";
322  }
323 
324  strCmd.clear();
325  strCmd = "move.sh";
326 
327  scriptBroker.init(strCmd.c_str(), strParam.c_str(), nullptr, nullptr, nullptr, nullptr);
328  scriptBroker.start();
329 }
The purpose of this test is to evaluate the accuracy of the IMU Euler angles measurements.
Definition: imu.h:39