MRPT  2.0.4
CIMUXSens_MT4.cpp
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #include "hwdrivers-precomp.h" // Precompiled headers
11 
15 
16 #include <iostream>
17 #include <thread>
18 
19 #if !MRPT_HAS_xSENS
20 namespace mrpt::hwdrivers
21 {
23 {
24 };
25 } // namespace mrpt::hwdrivers
26 #else
27 
28 // Tell MSVC that we are not using MTSDK as DLL:
29 #define XDA_STATIC_LIB
30 
31 #include <xscontroller/xscallback.h>
32 #include <xscontroller/xscontrol_def.h>
33 #include <xscontroller/xsdevice_def.h>
34 #include <xscontroller/xsscanner.h>
35 #include <xstypes/xsdatapacket.h>
36 #include <xstypes/xsoutputconfigurationarray.h>
37 #include <xstypes/xsportinfo.h>
38 #include <xstypes/xsstatusflag.h>
39 
40 // MTSDK expects this global symbol as "extern":
41 Journaller* gJournal = 0;
42 
43 namespace mrpt::hwdrivers
44 {
45 class MyXSensCallback : public XsCallback
46 {
47  public:
48  CIMUXSens_MT4* me = nullptr;
49 
50  private:
51  // uint64_t m_timeStartUI = 0;
52  mrpt::system::TTimeStamp m_timeStartTT;
53 
54  protected:
55  void onLiveDataAvailable(XsDevice*, const XsDataPacket* ptrpacket) override
56  {
57  using namespace mrpt::obs;
58 
59  if (!me) return;
60  if (!ptrpacket) return;
61  const XsDataPacket& packet = *ptrpacket;
62 
63  // Data properly collected: extract data fields
64  // -------------------------------------------------
66  CObservationIMU::Ptr obs = std::make_shared<CObservationIMU>();
67 
68  if (packet.containsOrientation())
69  {
70  XsEuler euler = packet.orientationEuler();
71  obs->rawMeasurements[IMU_YAW] = DEG2RAD(euler.yaw());
72  obs->dataIsPresent[IMU_YAW] = true;
73  obs->rawMeasurements[IMU_PITCH] = DEG2RAD(euler.pitch());
74  obs->dataIsPresent[IMU_PITCH] = true;
75  obs->rawMeasurements[IMU_ROLL] = DEG2RAD(euler.roll());
76  obs->dataIsPresent[IMU_ROLL] = true;
77 
78  XsQuaternion quat = packet.orientationQuaternion();
79  obs->rawMeasurements[IMU_ORI_QUAT_X] = quat.x();
80  obs->dataIsPresent[IMU_ORI_QUAT_X] = true;
81  obs->rawMeasurements[IMU_ORI_QUAT_Y] = quat.y();
82  obs->dataIsPresent[IMU_ORI_QUAT_Y] = true;
83  obs->rawMeasurements[IMU_ORI_QUAT_Z] = quat.z();
84  obs->dataIsPresent[IMU_ORI_QUAT_Z] = true;
85  obs->rawMeasurements[IMU_ORI_QUAT_W] = quat.w();
86  obs->dataIsPresent[IMU_ORI_QUAT_W] = true;
87  }
88 
89  if (packet.containsCalibratedAcceleration())
90  {
91  XsVector acc_data = packet.calibratedAcceleration();
92  obs->rawMeasurements[IMU_X_ACC] = acc_data[0];
93  obs->dataIsPresent[IMU_X_ACC] = true;
94  obs->rawMeasurements[IMU_Y_ACC] = acc_data[1];
95  obs->dataIsPresent[IMU_Y_ACC] = true;
96  obs->rawMeasurements[IMU_Z_ACC] = acc_data[2];
97  obs->dataIsPresent[IMU_Z_ACC] = true;
98  }
99 
100  if (packet.containsCalibratedGyroscopeData())
101  {
102  XsVector gyr_data = packet.calibratedGyroscopeData();
103  obs->rawMeasurements[IMU_YAW_VEL] = gyr_data[2];
104  obs->dataIsPresent[IMU_YAW_VEL] = true;
105  obs->rawMeasurements[IMU_PITCH_VEL] = gyr_data[1];
106  obs->dataIsPresent[IMU_PITCH_VEL] = true;
107  obs->rawMeasurements[IMU_ROLL_VEL] = gyr_data[0];
108  obs->dataIsPresent[IMU_ROLL_VEL] = true;
109  }
110 
111  if (packet.containsCalibratedMagneticField())
112  {
113  XsVector mag_data = packet.calibratedMagneticField();
114  obs->rawMeasurements[IMU_MAG_X] = mag_data[0];
115  obs->dataIsPresent[IMU_MAG_X] = true;
116  obs->rawMeasurements[IMU_MAG_Y] = mag_data[1];
117  obs->dataIsPresent[IMU_MAG_Y] = true;
118  obs->rawMeasurements[IMU_MAG_Z] = mag_data[2];
119  obs->dataIsPresent[IMU_MAG_Z] = true;
120  }
121 
122  if (packet.containsVelocity())
123  {
124  XsVector vel_data = packet.velocity();
125  obs->rawMeasurements[IMU_X_VEL] = vel_data[0];
126  obs->dataIsPresent[IMU_X_VEL] = true;
127  obs->rawMeasurements[IMU_Y_VEL] = vel_data[1];
128  obs->dataIsPresent[IMU_Y_VEL] = true;
129  obs->rawMeasurements[IMU_Z_VEL] = vel_data[2];
130  obs->dataIsPresent[IMU_Z_VEL] = true;
131  }
132 
133  if (packet.containsTemperature())
134  {
135  obs->rawMeasurements[IMU_TEMPERATURE] = packet.temperature();
136  obs->dataIsPresent[IMU_TEMPERATURE] = true;
137  }
138 
139  if (packet.containsAltitude())
140  {
141  obs->rawMeasurements[IMU_ALTITUDE] = packet.altitude();
142  obs->dataIsPresent[IMU_ALTITUDE] = true;
143  }
144 
145  // TimeStamp
146 #if 0 // I can't find a generic conversion between sample time and seconds!
147  if (packet.containsSampleTime64())
148  {
149  dev->getDataPacketByIndex()
150  const uint64_t nowUI = packet.sampleTime64();
151  std::cout << "nowUI: " << nowUI << "\n";
152 
153  uint64_t AtUI = 0;
154  if (m_timeStartUI == 0)
155  {
156  m_timeStartUI = nowUI;
157  m_timeStartTT = mrpt::system::now();
158  }
159  else
160  AtUI = nowUI - m_timeStartUI; // ms
161 
162  obs->timestamp = m_timeStartTT + std::chrono::milliseconds(AtUI);
163  }
164  else
165  if (packet.containsUtcTime())
166  {
167  XsTimeInfo utc = packet.utcTime();
168 
170 
171  parts.day_of_week = 0;
172  parts.daylight_saving = 0;
173  parts.year = utc.m_year;
174  parts.month = utc.m_month;
175  parts.day = utc.m_day;
176  parts.hour = utc.m_hour;
177  parts.minute = utc.m_minute;
178  parts.second = utc.m_second + (utc.m_nano * 1000000000.0);
179 
180  obs->timestamp = mrpt::system::buildTimestampFromParts(parts);
181  }
182  else
183 #endif
184  obs->timestamp = mrpt::system::now();
185 
186  obs->sensorPose = me->m_sensorPose;
187  obs->sensorLabel = me->m_sensorLabel;
188 
189  me->appendObservation(obs);
190 
191  if (packet.containsLatitudeLongitude())
192  {
193  XsVector lla_data = packet.latitudeLongitude();
194 
195  CObservationGPS::Ptr obsGPS = CObservationGPS::Create();
198  rGPS.latitude_degrees = lla_data[0];
199  rGPS.longitude_degrees = lla_data[1];
200 
201  if (packet.containsStatus() && packet.status() & XSF_GpsValid)
202  rGPS.validity_char = 'A';
203  else
204  rGPS.validity_char = 'V';
205 
206  if (packet.containsUtcTime())
207  {
208  auto utc = packet.utcTime();
209  rGPS.UTCTime.hour = utc.m_hour;
210  rGPS.UTCTime.minute = utc.m_minute;
211  rGPS.UTCTime.sec = utc.m_second + (utc.m_nano * 1000000.0);
212  }
213  else
214  {
215  rGPS.UTCTime.hour =
216  ((obs->timestamp.time_since_epoch().count() /
217  (60 * 60 * ((uint64_t)1000000 / 100))) %
218  24);
219  rGPS.UTCTime.minute =
220  ((obs->timestamp.time_since_epoch().count() /
221  (60 * ((uint64_t)1000000 / 100))) %
222  60);
223  rGPS.UTCTime.sec = fmod(
224  obs->timestamp.time_since_epoch().count() /
225  (1000000.0 / 100),
226  60);
227  }
228 
229  if (packet.containsVelocity())
230  {
231  XsVector vel_data = packet.velocity();
232 
233  rGPS.speed_knots =
234  sqrt(vel_data[0] * vel_data[0] + vel_data[1] * vel_data[1]);
235  rGPS.direction_degrees = 0; // Could be worked out from
236  // velocity and magnatic field
237  // perhaps.
238  }
239  else
240  rGPS.speed_knots = rGPS.direction_degrees = 0;
241 
242  obsGPS->setMsg(rGPSs);
243  obsGPS->timestamp = obs->timestamp;
244  obsGPS->originalReceivedTimestamp = obs->timestamp;
245  obsGPS->has_satellite_timestamp = false;
246  obsGPS->sensorPose = me->m_sensorPose;
247  obsGPS->sensorLabel = me->m_sensorLabel;
248 
249  me->appendObservation(obsGPS);
250  }
251  }
252 };
253 
254 struct CIMUXSens_MT4::Impl
255 {
256  Impl() = default;
257 
258  XsControl* m_xscontrol = nullptr;
259  XsDevice* m_device = nullptr;
260  XsPortInfo m_port;
262  std::make_shared<MyXSensCallback>();
263 };
264 
265 } // namespace mrpt::hwdrivers
266 
267 #endif
268 
269 // Include libraries in linking:
270 #if MRPT_HAS_xSENS
271 #ifdef _WIN32
272 // WINDOWS:
273 #if defined(_MSC_VER)
274 #pragma comment(lib, "SetupAPI.lib")
275 #pragma comment(lib, "WinUsb.lib")
276 #endif
277 #endif // _WIN32
278 #endif // MRPT_HAS_xSENS
279 
281 
282 using namespace mrpt::obs;
283 using namespace mrpt::hwdrivers;
284 using namespace std;
285 
287 {
288  m_sensorLabel = "XSensMTi_MT4";
289 
290 #if MRPT_HAS_xSENS
291  m_impl->m_xscontrol = XsControl::construct();
292  m_impl->myCallback->me = this;
293 
294 #else
296  "MRPT has been compiled with 'BUILD_XSENS'=OFF, so this class "
297  "cannot be used.");
298 #endif
299 }
300 
302 {
303 #if MRPT_HAS_xSENS
304  close();
305  m_impl->m_xscontrol->destruct();
306 #endif
307 }
308 
310 {
311 #if MRPT_HAS_xSENS
312  if (m_impl->m_device != nullptr)
313  {
314  m_impl->m_device->stopRecording();
315  m_impl->m_device->closeLogFile();
316  m_impl->m_device->removeCallbackHandler(m_impl->myCallback.get());
317  }
318  m_impl->m_xscontrol->closePort(m_impl->m_port);
319 
320 #endif
321 }
322 
323 /*-------------------------------------------------------------
324  doProcess
325 -------------------------------------------------------------*/
327 {
328 #if MRPT_HAS_xSENS
329  if (m_state == ssError)
330  {
331  std::this_thread::sleep_for(200ms);
332  initialize();
333  }
334 
335  // Main processing happens asynchronously via callbacks.
336 
337 #else
339  "MRPT has been compiled with 'BUILD_XSENS'=OFF, so this class "
340  "cannot be used.");
341 #endif
342 }
343 
344 /*-------------------------------------------------------------
345  initialize
346 -------------------------------------------------------------*/
348 {
349 #if MRPT_HAS_xSENS
351 
352  try
353  {
354  // Try to open a specified device, or scan the bus?
355  XsPortInfo mtPort;
356 
357  if (m_portname.empty())
358  {
359  if (m_verbose)
360  cout << "[CIMUXSens_MT4] Scanning for USB devices...\n";
361 
362  XsPortInfoArray portInfoArray = XsScanner::scanPorts();
363 
364  for (const auto& portInfo : portInfoArray)
365  {
366  if (portInfo.deviceId().isMti() || portInfo.deviceId().isMtig())
367  {
368  mtPort = portInfo;
369  break;
370  }
371  }
372 
373  if (mtPort.empty())
375  "CIMUXSens_MT4: No 'portname' was specified and no "
376  "compatible XSens device was found in the system (%u "
377  "devices connected)",
378  portInfoArray.size());
379 
380  if (m_verbose)
381  cout << "[CIMUXSens_MT4] Found " << portInfoArray.size()
382  << " devices. Opening the first one.\n";
383  }
384  else
385  {
386  cout << "[CIMUXSens_MT4] Using user-supplied portname '"
387  << m_portname << "' at " << m_port_bauds << " baudrate.\n";
388 
389  mtPort = XsPortInfo(m_portname, XsBaud_numericToRate(m_port_bauds));
390  }
391 
392  if (mtPort.empty()) THROW_EXCEPTION("No MTi device found");
393 
394  // Use the first detected device or the specified one:
395  if (!m_deviceId.empty())
396  {
397  if (mtPort.deviceId().toString().c_str() != m_deviceId)
399  "Device with ID: %s not found", m_deviceId.c_str());
400  }
401 
402  std::cout << mrpt::format(
403  "[CIMUXSens_MT4] Found a device with ID: %s @ port: %s, baudrate: "
404  "%d",
405  mtPort.deviceId().toString().toStdString().c_str(),
406  mtPort.portName().toStdString().c_str(), mtPort.baudrate());
407 
408  if (!m_impl->m_xscontrol->openPort(
409  mtPort.portName().toStdString(), mtPort.baudrate()))
410  THROW_EXCEPTION("Could not open port");
411 
412  m_impl->m_device = m_impl->m_xscontrol->device(mtPort.deviceId());
413  ASSERT_(m_impl->m_device != nullptr);
414 
415  std::cout << mrpt::format(
416  "[CIMUXSens_MT4] Device: %s, with ID: %s opened.",
417  m_impl->m_device->productCode().toStdString().c_str(),
418  m_impl->m_device->deviceId().toString().c_str());
419 
420  m_impl->m_device->addCallbackHandler(m_impl->myCallback.get());
421 
422  // Put the device in configuration mode
423  if (m_verbose)
424  cout << "[CIMUXSens_MT4] Putting device into configuration "
425  "mode...\n";
426 
427  if (!m_impl->m_device->gotoConfig())
428  THROW_EXCEPTION("Could not go to config");
429 
430  // read EMTS and device config stored in .mtb file header.
431  if (!m_impl->m_device->readEmtsAndDeviceConfiguration())
432  THROW_EXCEPTION("Could not read device configuration");
433 
434  // Set configuration:
435  // if (mtPort.deviceId().isMti())
436  {
437  XsOutputConfigurationArray configArray;
438  configArray.push_back(
439  XsOutputConfiguration(XDI_SampleTime64, m_sampleFreq));
440  configArray.push_back(
441  XsOutputConfiguration(XDI_SampleTimeFine, m_sampleFreq));
442  configArray.push_back(
443  XsOutputConfiguration(XDI_SampleTimeCoarse, m_sampleFreq));
444  configArray.push_back(
445  XsOutputConfiguration(XDI_Quaternion, m_sampleFreq));
446  configArray.push_back(
447  XsOutputConfiguration(XDI_Temperature, m_sampleFreq));
448  configArray.push_back(
449  XsOutputConfiguration(XDI_Acceleration, m_sampleFreq));
450  configArray.push_back(
451  XsOutputConfiguration(XDI_RateOfTurn, m_sampleFreq));
452  configArray.push_back(
453  XsOutputConfiguration(XDI_MagneticField, m_sampleFreq));
454  configArray.push_back(
455  XsOutputConfiguration(XDI_VelocityXYZ, m_sampleFreq));
456 
457  configArray.push_back(
458  XsOutputConfiguration(XDI_StatusByte, m_sampleFreq));
459  configArray.push_back(
460  XsOutputConfiguration(XDI_LatLon, m_sampleFreq));
461  configArray.push_back(
462  XsOutputConfiguration(XDI_UtcTime, m_sampleFreq));
463  configArray.push_back(
464  XsOutputConfiguration(XDI_AltitudeEllipsoid, m_sampleFreq));
465 
466  if (!m_impl->m_device->setOutputConfiguration(configArray))
467  throw std::runtime_error(
468  "Could not configure MTi device. Aborting.");
469  }
470 
471  // Put the device in measurement mode
472  if (m_verbose)
473  cout << "[CIMUXSens_MT4] Putting device into measurement mode..."
474  << std::endl;
475 
476  if (!m_impl->m_device->gotoMeasurement())
477  THROW_EXCEPTION("Could not put device into measurement mode");
478 
479  if (!m_xsensLogFile.empty())
480  {
481  if (m_impl->m_device->createLogFile(m_xsensLogFile) != XRV_OK)
483  "Failed to create a log file! (%s)",
484  m_xsensLogFile.c_str());
485  else
486  printf(
487  "[CIMUXSens_MT4] Created a log file: %s",
488  m_xsensLogFile.c_str());
489 
490  if (!m_impl->m_device->startRecording())
491  THROW_EXCEPTION("Could not start recording");
492  }
493 
494  m_state = ssWorking;
495  }
496  catch (std::exception&)
497  {
498  m_state = ssError;
499  std::cerr << "[CIMUXSens_MT4] Error Could not initialize the device"
500  << std::endl;
501  throw;
502  }
503 
504 #else
506  "MRPT has been compiled with 'BUILD_XSENS'=OFF, so this class "
507  "cannot be used.");
508 #endif
509 }
510 
511 /*-------------------------------------------------------------
512  loadConfig_sensorSpecific
513 -------------------------------------------------------------*/
515  const mrpt::config::CConfigFileBase& c, const std::string& s)
516 {
518  c.read_float(s, "pose_x", 0, false),
519  c.read_float(s, "pose_y", 0, false),
520  c.read_float(s, "pose_z", 0, false),
521  DEG2RAD(c.read_float(s, "pose_yaw", 0, false)),
522  DEG2RAD(c.read_float(s, "pose_pitch", 0, false)),
523  DEG2RAD(c.read_float(s, "pose_roll", 0, false)));
524 
525  m_sampleFreq = c.read_int(s, "sampleFreq", m_sampleFreq, false);
526  m_port_bauds = c.read_int(s, "baudRate", m_port_bauds, false);
527  m_deviceId = c.read_string(s, "deviceId", m_deviceId, false);
528  m_xsensLogFile = c.read_string(s, "logFile", m_xsensLogFile, false);
529 
530 #ifdef _WIN32
531  m_portname = c.read_string(s, "portname_WIN", m_portname, false);
532 #else
533  m_portname = c.read_string(s, "portname_LIN", m_portname, false);
534 #endif
535 }
void initialize() override
Turns on the xSens device and configure it for getting orientation data.
content_t fields
Message content, accesible by individual fields.
double longitude_degrees
The measured longitude, in degrees (East:+ , West:-)
void appendObservation(const mrpt::serialization::CSerializable::Ptr &obj)
Like appendObservations() but for just one observation.
std::string read_string(const std::string &section, const std::string &name, const std::string &defaultValue, bool failIfNotFound=false) const
mrpt::poses::CPose3D m_sensorPose
Definition: CIMUXSens_MT4.h:91
#define THROW_EXCEPTION(msg)
Definition: exceptions.h:67
void doProcess() override
This method will be invoked at a minimum rate of "process_rate" (Hz)
std::string std::string format(std::string_view fmt, ARGS &&... args)
Definition: format.h:26
std::string m_sensorLabel
See CGenericSensor.
std::string m_deviceId
Device ID to open, or first one if empty string.
Definition: CIMUXSens_MT4.h:85
orientation pitch absolute value (global/navigation frame) (rad)
mrpt::system::TTimeStamp buildTimestampFromParts(const mrpt::system::TTimeParts &p)
Builds a timestamp from the parts (Parts are in UTC)
Definition: datetime.cpp:74
uint8_t day_of_week
Seconds (0.0000-59.9999)
Definition: datetime.h:57
mrpt::system::TTimeStamp now()
A shortcut for system::getCurrentTime.
Definition: datetime.h:86
Contains classes for various device interfaces.
temperature (degrees Celsius)
float read_float(const std::string &section, const std::string &name, float defaultValue, bool failIfNotFound=false) const
x magnetic field value (local/vehicle frame) (gauss)
y-axis acceleration (local/vehicle frame) (m/sec2)
STL namespace.
Orientation Quaternion X (global/navigation frame)
z-axis acceleration (local/vehicle frame) (m/sec2)
x-axis velocity (global/navigation frame) (m/sec)
int8_t validity_char
This will be: &#39;A&#39;=OK or &#39;V&#39;=void.
UTC_time UTCTime
The GPS sensor measured timestamp (in UTC time)
std::string m_portname
The USB or COM port name (if blank -> autodetect)
Definition: CIMUXSens_MT4.h:82
int m_port_bauds
Baudrate, only for COM ports.
Definition: CIMUXSens_MT4.h:80
int read_int(const std::string &section, const std::string &name, int defaultValue, bool failIfNotFound=false) const
#define ASSERT_(f)
Defines an assertion mechanism.
Definition: exceptions.h:120
mrpt::Clock::time_point TTimeStamp
A system independent time type, it holds the the number of 100-nanosecond intervals since January 1...
Definition: datetime.h:40
pitch angular velocity (local/vehicle frame) (rad/sec)
int daylight_saving
Day of week (1:Sunday, 7:Saturday)
Definition: datetime.h:58
This class allows loading and storing values and vectors of different types from a configuration text...
void loadConfig_sensorSpecific(const mrpt::config::CConfigFileBase &configSource, const std::string &iniSection) override
See the class documentation at the top for expected parameters.
constexpr double DEG2RAD(const double x)
Degrees to radians.
The parts of a date/time (it&#39;s like the standard &#39;tm&#39; but with fractions of seconds).
Definition: datetime.h:49
uint8_t day
Month (1-12)
Definition: datetime.h:53
This namespace contains representation of robot actions and observations.
Orientation Quaternion Y (global/navigation frame)
Orientation Quaternion Z (global/navigation frame)
z magnetic field value (local/vehicle frame) (gauss)
Orientation Quaternion W (global/navigation frame)
#define IMPLEMENTS_GENERIC_SENSOR(class_name, NameSpace)
This must be inserted in all CGenericSensor classes implementation files:
double second
Minute (0-59)
Definition: datetime.h:56
y magnetic field value (local/vehicle frame) (gauss)
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
uint8_t minute
Hour (0-23)
Definition: datetime.h:55
double direction_degrees
Measured speed direction (in degrees)
mrpt::pimpl< Impl > m_impl
Definition: CIMUXSens_MT4.h:76
void setFromValues(const double x0, const double y0, const double z0, const double yaw=0, const double pitch=0, const double roll=0)
Set the pose from a 3D position (meters) and yaw/pitch/roll angles (radians) - This method recomputes...
Definition: CPose3D.cpp:265
uint8_t month
The year.
Definition: datetime.h:52
A class for interfacing XSens 4th generation Inertial Measuring Units (IMUs): MTi 10-series...
Definition: CIMUXSens_MT4.h:55
pimpl< T > make_impl(Args &&... args)
Definition: pimpl.h:18
double latitude_degrees
The measured latitude, in degrees (North:+ , South:-)
uint8_t hour
Day (1-31)
Definition: datetime.h:54
orientation yaw absolute value (global/navigation frame) (rad)
y-axis velocity (global/navigation frame) (m/sec)
orientation roll absolute value (global/navigation frame) (rad)
yaw angular velocity (local/vehicle frame) (rad/sec)
roll angular velocity (local/vehicle frame) (rad/sec)
#define THROW_EXCEPTION_FMT(_FORMAT_STRING,...)
Definition: exceptions.h:69
x-axis acceleration (local/vehicle frame) (m/sec2)
z-axis velocity (global/navigation frame) (m/sec)
altitude from an altimeter (meters)



Page generated by Doxygen 1.8.14 for MRPT 2.0.4 Git: 33de1d0ad Sat Jun 20 11:02:42 2020 +0200 at sáb jun 20 17:35:17 CEST 2020