Laelaps  2.3.5
RoadNarrows Robotics Small Outdoor Mobile Robot Project
laeXmlTune.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: Laelaps
4 //
5 // Library: liblaelaps
6 //
7 // File: laeXmlTune.cxx
8 //
9 /*! \file
10  *
11  * \brief \h_laelaps XML tuning class implementation.
12  *
13  * \author Robin Knight (robin.knight@roadnarrows.com)
14  *
15  * \par Copyright
16  * \h_copy 2015-2017. RoadNarrows LLC.\n
17  * http://www.roadnarrows.com\n
18  * All Rights Reserved
19  */
20 /*
21  * @EulaBegin@
22  *
23  * Unless otherwise stated explicitly, all materials contained are copyrighted
24  * and may not be used without RoadNarrows LLC's written consent,
25  * except as provided in these terms and conditions or in the copyright
26  * notice (documents and software) or other proprietary notice provided with
27  * the relevant materials.
28  *
29  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY
30  * MEMBERS/EMPLOYEES/CONTRACTORS OF ROADNARROWS OR DISTRIBUTORS OF THIS SOFTWARE
31  * BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
32  * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
33  * DOCUMENTATION, EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN
34  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  *
36  * THE AUTHORS AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
37  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
38  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
39  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
40  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
41  *
42  * @EulaEnd@
43  */
44 ////////////////////////////////////////////////////////////////////////////////
45 
46 #include <unistd.h>
47 
48 #include <string>
49 #include <sstream>
50 #include <vector>
51 
52 #include "rnr/rnrconfig.h"
53 #include "rnr/log.h"
54 #include "rnr/appkit/Xml.h"
55 
56 #include "Laelaps/laelaps.h"
57 #include "Laelaps/laeUtils.h"
58 #include "Laelaps/laeMotor.h"
59 #include "Laelaps/laeTune.h"
60 #include "Laelaps/laeDesc.h"
61 #include "Laelaps/laeXmlTune.h"
62 
63 using namespace std;
64 using namespace rnr;
65 using namespace laelaps;
66 
67 //------------------------------------------------------------------------------
68 // LaeXmlTune Class
69 //------------------------------------------------------------------------------
70 
71 int LaeXmlTune::load(LaeTunes &tunes,
72  const string &strSearchPath,
73  const string &strXmlFileName,
74  bool bAllInstances)
75 {
76  vector<string> vecPath; // vector of search paths
77  string fqname; // fully qualified file name
78  bool bFoundInstance; // [not] found instance
79  size_t i; // working index
80  int rc; // return code
81 
82  split(strSearchPath, ':', vecPath);
83 
84  bFoundInstance = false;
85  rc = LAE_OK;
86 
87  for(i=0; i<vecPath.size(); ++i)
88  {
89  fqname = vecPath[i] + '/' + strXmlFileName;
90  if( access(fqname.c_str(), F_OK) == 0 )
91  {
92  LOGDIAG3("Loading tune XML file: %s.", fqname.c_str());
93 
94  bFoundInstance = true;
95 
96  if( (rc = Xml::loadFile(fqname)) < 0 )
97  {
98  LOGERROR("Parse of tune parameters from XML file %s failed.",
99  fqname.c_str());
100  rc = -LAE_ECODE_XML;
101  }
102  else
103  {
104  rc = setTunesFromDOM(tunes);
105  }
106 
107  if( rc == LAE_OK )
108  {
109  LOGDIAG2("Loaded tuning parameters from XML file %s.", fqname.c_str());
110  }
111 
112  if( !bAllInstances )
113  {
114  break;
115  }
116  }
117  }
118 
119  if( !bFoundInstance )
120  {
121  LOGDIAG2("Optional XML file %s not found - ignoring.",
122  strXmlFileName.c_str());
123  }
124 
125  return rc;
126 }
127 
128 int LaeXmlTune::loadFile(const std::string &strXmlFileName)
129 {
130  int rc;
131 
132  // test existence of optional tuning file
133  if( access(strXmlFileName.c_str(), F_OK) < 0 )
134  {
135  LOGDIAG2("Optional XML file %s does not exist - ignoring.",
136  strXmlFileName.c_str());
137  rc = -LAE_ECODE_XML;
138  }
139 
140  // parse
141  else if( (rc = Xml::loadFile(strXmlFileName)) < 0 )
142  {
143  LOGERROR("Parse of tune parameters from XML file %s failed - ignoring.",
144  strXmlFileName.c_str());
145  rc = -LAE_ECODE_XML;
146  }
147 
148  // success
149  else
150  {
151  rc = LAE_OK;
152  }
153 
154  return rc;
155 }
156 
157 int LaeXmlTune::loadFile(LaeTunes &tunes, const std::string &strXmlFileName)
158 {
159  int rc;
160 
161  if( (rc = loadFile(strXmlFileName)) == LAE_OK )
162  {
163  rc = setTunesFromDOM(tunes);
164  }
165 
166  return rc < 0? -LAE_ECODE_XML: LAE_OK;
167 }
168 
169 int LaeXmlTune::createTemplateFile(const string &strXmlFileName)
170 {
171  FILE *fp; // opened file pointer
172 
173  if( strXmlFileName.empty() )
174  {
175  setErrorMsg("No file name.");
176  LOGERROR("%s", m_bufErrMsg);
177  return -LAE_ECODE_XML;
178  }
179 
180  m_strXmlFileName = strXmlFileName;
181 
182  // open file
183  if( (fp = fopen(m_strXmlFileName.c_str(), "w+")) == NULL )
184  {
185  setErrorMsg("%s: %s(errno=%d).",
186  m_strXmlFileName.c_str(), strerror(errno), errno);
187  LOGERROR("%s", m_bufErrMsg);
188  return -LAE_ECODE_XML;
189  }
190 
191  makeXmlHead();
192  makeXmlTail();
193 
194  //
195  // XML head.
196  //
197  fprintf(fp, " <!-- RoadNarrows Laelaps Tuning Configuration -->\n");
198  fprintf(fp, "%s", m_strXmlHead.c_str());
199 
200  //
201  // Robotic base major element.
202  //
203  fprintf(fp, " <!-- Laelaps tuning -->\n");
204  fprintf(fp, " <%s>\n", m_strMajElemTuning.c_str());
205 
206  fprintf(fp, " <!-- TUNING ELEMENTS HERE -->\n");
207 
208  fprintf(fp, " </%s>\n\n", m_strMajElemTuning.c_str());
209 
210  //
211  // XML tail
212  //
213  fprintf(fp, "%s", m_strXmlTail.c_str());
214 
215  fclose(fp);
216 
217  LOGDIAG3("Created file %s.", m_strXmlFileName.c_str());
218 
219  return LAE_OK;
220 }
221 
222 int LaeXmlTune::setTunesFromDOM(LaeTunes &tunes)
223 {
224  TiXmlElement *pElem1, *pElem2; // working xml elements
225  const char *sValue; // working xml element name
226  int rc; // return code
227 
228  // root element
229  if( m_pElemRoot == NULL )
230  {
231  setErrorMsg("Missing DOM and/or <%s> root element missing.",
232  m_strRootElemName.c_str());
233  LOGERROR("%s", m_bufErrMsg);
234  return -LAE_ECODE_XML;
235  }
236 
237  // search for major element
238  for(pElem1 = m_pElemRoot->FirstChildElement(), rc = LAE_OK;
239  (pElem1 != NULL) && (rc == LAE_OK);
240  pElem1 = pElem1->NextSiblingElement())
241  {
242  // element name
243  if( (sValue = pElem1->Value()) == NULL )
244  {
245  continue;
246  }
247 
248  //
249  // Tuning major element. Walk through child elements and convert.
250  //
251  // <tuning> ... </tuning>
252  //
253  else if( !strcasecmp(sValue, m_strMajElemTuning.c_str()) )
254  {
255  // search for section elements
256  for(pElem2 = pElem1->FirstChildElement();
257  (pElem2 != NULL) && (rc == LAE_OK);
258  pElem2 = pElem2->NextSiblingElement())
259  {
260  // no child element name
261  if( (sValue = pElem2->Value()) == NULL )
262  {
263  continue;
264  }
265 
266  // <global> ... </global> section
267  else if( !strcasecmp(sValue, m_strSecElemGlobal.c_str()) )
268  {
269  rc = setGlobalTunes(pElem2, tunes);
270  }
271 
272  // <battery> ... </battery> section
273  else if( !strcasecmp(sValue, m_strSecElemBattery.c_str()) )
274  {
275  rc = setBatteryTunes(pElem2, tunes);
276  }
277 
278  // <powertrains location=LOC> ... <powertrains> section
279  else if( !strcasecmp(sValue, m_strSecElemPowertrains.c_str()) )
280  {
281  rc = setPowertrainTunes(pElem2, tunes);
282  }
283 
284  // <range_sensor type=TYPE location=LOC> ... <range_sensor> section
285  else if( !strcasecmp(sValue, m_strSecElemRangeSensor.c_str()) )
286  {
287  rc = setRangeSensorTunes(pElem2, tunes);
288  }
289 
290  // unknown
291  else
292  {
293  warnUnknownElem(sValue);
294  }
295  }
296  }
297  }
298 
299  return LAE_OK;
300 }
301 
302 int LaeXmlTune::setDOMFromTunes(const LaeTunes &tunes)
303 {
304  // TODO
305  return -LAE_ECODE_GEN;
306 }
307 
308 
309 // .............................................................................
310 // Global XML turning section
311 // .............................................................................
312 
313 int LaeXmlTune::setGlobalTunes(TiXmlElement *pElemSec, LaeTunes &tunes)
314 {
315  // sub-elements
316  string strSubSecElemThreads("threads");
317  string strSubSecElemTraj("trajectory");
318  string strElemVelDerate("velocity_derate");
319  string strElemWdTimeout("watchdog_timeout");
320 
321  TiXmlElement *pElem; // working xml element
322  const char *sValue; // working xml element name
323  int rc; // return code
324 
325  //
326  // Walk through child elements and convert.
327  //
328  for(pElem = pElemSec->FirstChildElement(), rc = LAE_OK;
329  (pElem != NULL) && (rc == LAE_OK);
330  pElem = pElem->NextSiblingElement())
331  {
332  // no element name
333  if( (sValue = pElem->Value()) == NULL )
334  {
335  continue;
336  }
337 
338  // <threads> ... </threads> subsection
339  else if( !strcasecmp(sValue, strSubSecElemThreads.c_str()) )
340  {
341  rc = setGlobalThreadTunes(pElem, tunes);
342  }
343 
344  // <velocity_derate> ... </velocity_derate>
345  else if( !strcasecmp(sValue, strElemVelDerate.c_str()) )
346  {
347  rc = strToDoubleWithinRange(strElemVelDerate, elemText(pElem),
348  LaeTuneVelDerateMin, LaeTuneVelDerateMax,
349  tunes.m_fVelDerate);
350 
351  // xml units are percentages - normalize
352  if( rc == LAE_OK )
353  {
354  tunes.m_fVelDerate /= 100.0;
355  }
356  }
357 
358  // <trajectory> ... </trajectory subsection
359  else if( !strcasecmp(sValue, strSubSecElemTraj.c_str()) )
360  {
361  rc = setGlobalTrajTunes(pElem, tunes);
362  }
363 
364  // <watchdog_timeout> ... </watchdog_timeout>
365  else if( !strcasecmp(sValue, strElemWdTimeout.c_str()) )
366  {
367  rc = strToDoubleWithinRange(strElemWdTimeout, elemText(pElem),
368  LaeTuneWdTimeoutMin, LaeTuneWdTimeoutMax,
369  tunes.m_fWatchDogTimeout);
370 
371  // xml units are percentages - normalize
372  if( rc == LAE_OK )
373  {
374  tunes.m_fVelDerate /= 100.0;
375  }
376  }
377  // unknown
378  else
379  {
380  warnUnknownElem(sValue);
381  }
382  }
383 
384  if( rc == LAE_OK )
385  {
386  LOGDIAG3("%s: Laelaps global tune parameters set.",
387  m_strXmlFileName.c_str());
388  }
389 
390  return rc;
391 }
392 
393 int LaeXmlTune::setGlobalThreadTunes(TiXmlElement *pElemSubSec, LaeTunes &tunes)
394 {
395  // sub-elements
396  string strElemImuHz("imu_hz");
397  string strElemKinHz("kinematics_hz");
398  string strElemRangeHz("range_hz");
399 
400  TiXmlElement *pElem; // working xml element
401  const char *sValue; // working xml element name
402  int rc; // return code
403 
404  //
405  // Walk through child elements and convert.
406  //
407  for(pElem = pElemSubSec->FirstChildElement(), rc = LAE_OK;
408  (pElem != NULL) && (rc == LAE_OK);
409  pElem = pElem->NextSiblingElement())
410  {
411  // no element name
412  if( (sValue = pElem->Value()) == NULL )
413  {
414  continue;
415  }
416 
417  // <imu_hz> ... </imu_hz>
418  else if( !strcasecmp(sValue, strElemImuHz.c_str()) )
419  {
420  rc = strToDoubleWithMinimum(strElemImuHz, elemText(pElem),
421  LaeTuneThreadHzMin, tunes.m_fImuHz);
422  }
423 
424  // <kinematics_hz> ... </kinematics_hz>
425  else if( !strcasecmp(sValue, strElemKinHz.c_str()) )
426  {
427  rc = strToDoubleWithMinimum(strElemKinHz, elemText(pElem),
428  LaeTuneThreadHzMin, tunes.m_fKinematicsHz);
429  }
430 
431  // <range_hz> ... </range_hz>
432  else if( !strcasecmp(sValue, strElemRangeHz.c_str()) )
433  {
434  rc = strToDoubleWithMinimum(strElemRangeHz, elemText(pElem),
435  LaeTuneThreadHzMin, tunes.m_fRangeHz);
436  }
437 
438  // unknown
439  else
440  {
441  warnUnknownElem(sValue);
442  }
443  }
444 
445  if( rc == LAE_OK )
446  {
447  LOGDIAG3("%s: Laelaps global thread tune parameters set.",
448  m_strXmlFileName.c_str());
449  }
450 
451  return rc;
452 }
453 
454 int LaeXmlTune::setGlobalTrajTunes(TiXmlElement *pElemSubSec, LaeTunes &tunes)
455 {
456  // sub-elements
457  string strElemTrajNorm("norm");
458  string strElemTrajEpsilon("epsilon");
459 
460  TiXmlElement *pElem; // working xml element
461  const char *sValue; // working xml element name
462  int rc; // return code
463 
464  //
465  // Walk through child elements and convert.
466  //
467  for(pElem = pElemSubSec->FirstChildElement(), rc = LAE_OK;
468  (pElem != NULL) && (rc == LAE_OK);
469  pElem = pElem->NextSiblingElement())
470  {
471  // no element name
472  if( (sValue = pElem->Value()) == NULL )
473  {
474  continue;
475  }
476 
477  // <norm> ... </norm>
478  else if( !strcasecmp(sValue, strElemTrajNorm.c_str()) )
479  {
480  strToNorm(strElemTrajNorm, elemText(pElem), tunes.m_eTrajNorm);
481  }
482 
483  // <epsilon> ... </epsilon>
484  else if( !strcasecmp(sValue, strElemTrajEpsilon.c_str()) )
485  {
486  rc = strToDoubleWithMinimum(strElemTrajEpsilon, elemText(pElem),
487  LaeTuneTrajEpsilonMin, tunes.m_fTrajEpsilon);
488 
489  // xml units are degrees - convert to radians
490  if( rc == LAE_OK )
491  {
492  tunes.m_fTrajEpsilon = degToRad(tunes.m_fTrajEpsilon);
493  }
494  }
495 
496  // unknown
497  else
498  {
499  warnUnknownElem(sValue);
500  }
501  }
502 
503  if( rc == LAE_OK )
504  {
505  LOGDIAG3("%s: Laelaps global trajectory tune parameters set.",
506  m_strXmlFileName.c_str());
507  }
508 
509  return rc;
510 }
511 
512 
513 // .............................................................................
514 // Battery XML Tuning Section
515 // .............................................................................
516 
517 int LaeXmlTune::setBatteryTunes(TiXmlElement *pElemSec, LaeTunes &tunes)
518 {
519  // sub-elements
520  string strElemType("type");
521  string strElemChem("chemistry");
522  string strElemCap("capacity");
523  string strElemCells("cells");
524  string strElemMax("max");
525  string strElemNominal("nominal");
526  string strElemMin("min");
527 
528  TiXmlElement *pElem;
529  const char *sValue;
530  string str;
531  int iVal;
532  double fVal;
533  int rc;
534 
535  //
536  // Walk through child elements and convert.
537  //
538  for(pElem = pElemSec->FirstChildElement(), rc = LAE_OK;
539  (pElem != NULL) && (rc == LAE_OK);
540  pElem = pElem->NextSiblingElement())
541  {
542  // no element name
543  if( (sValue = pElem->Value()) == NULL )
544  {
545  continue;
546  }
547 
548  // <type> ... </type>
549  else if( !strcasecmp(sValue, strElemType.c_str()) )
550  {
551  // Fixed
552  // tunes.m_battery.strType = elemText(pElem);
553  }
554 
555  // <chemistry> ... </chemistry>
556  else if( !strcasecmp(sValue, strElemChem.c_str()) )
557  {
558  // Fixed
559  // tunes.m_battery.strChem = elemText(pElem);
560  }
561 
562  // <capacity> ... </capacity>
563  else if( !strcasecmp(sValue, strElemCap.c_str()) )
564  {
565  str = elemText(pElem);
566  if( !str.empty() )
567  {
568  if( (rc = strToDouble(str, fVal)) < 0 )
569  {
570  setErrorMsg("%s: Element <%s> text \"%s\" not an number.",
571  m_strXmlFileName.c_str(), strElemCap.c_str(),
572  str.c_str());
573  LOGERROR("%s", m_bufErrMsg);
574  rc = -LAE_ECODE_XML;
575  }
576  else if( fVal != LaeTuneBattCapAh )
577  {
578  setErrorMsg("%s: Element <%s> value is fixed at %.1lfAh.",
579  m_strXmlFileName.c_str(), strElemCap.c_str(), LaeTuneBattCapAh);
580  LOGWARN("%s", m_bufErrMsg);
581  }
582  }
583  }
584 
585  // <cells> ... </cells>
586  else if( !strcasecmp(sValue, strElemCells.c_str()) )
587  {
588  str = elemText(pElem);
589  if( !str.empty() )
590  {
591  if( (rc = strToInt(str, iVal)) < 0 )
592  {
593  setErrorMsg("%s: Element <%s> text \"%s\" not an number.",
594  m_strXmlFileName.c_str(), strElemCells.c_str(),
595  str.c_str());
596  LOGERROR("%s", m_bufErrMsg);
597  rc = -LAE_ECODE_XML;
598  }
599  else if( iVal != LaeTuneBattCells )
600  {
601  setErrorMsg("%s: Element <%s> value is fixed at %d cells.",
602  m_strXmlFileName.c_str(), strElemCells.c_str(),
603  LaeTuneBattCells);
604  LOGWARN("%s", m_bufErrMsg);
605  }
606  }
607  }
608 
609  // <nominal> ... </nominal>
610  else if( !strcasecmp(sValue, strElemNominal.c_str()) )
611  {
612  str = elemText(pElem);
613  if( !str.empty() )
614  {
615  if( (rc = strToDouble(str, fVal)) < 0 )
616  {
617  setErrorMsg("%s: Element <%s> text \"%s\" not an number.",
618  m_strXmlFileName.c_str(), strElemNominal.c_str(),
619  str.c_str());
620  LOGERROR("%s", m_bufErrMsg);
621  rc = -LAE_ECODE_XML;
622  }
623  else if( fVal != LaeTuneBattNominalV )
624  {
625  setErrorMsg("%s: Element <%s> value is fixed at %.1lfV.",
626  m_strXmlFileName.c_str(), strElemNominal.c_str(),
627  LaeTuneBattNominalV);
628  LOGERROR("%s", m_bufErrMsg);
629  return -LAE_ECODE_XML;
630  }
631  }
632  }
633 
634  // <max> ... </max>
635  else if( !strcasecmp(sValue, strElemMax.c_str()) )
636  {
637  rc = strToDoubleWithinRange(strElemMax, elemText(pElem),
638  LaeTuneBattNominalV, LaeTuneBattMaxVMax,
639  tunes.m_battery.m_fMaxV);
640  }
641 
642  // <min> ... </min>
643  else if( !strcasecmp(sValue, strElemMin.c_str()) )
644  {
645  rc = strToDoubleWithinRange(strElemMin, elemText(pElem),
646  LaeTuneBattMinVMin, LaeTuneBattNominalV,
647  tunes.m_battery.m_fMinV);
648  }
649 
650  else
651  {
652  warnUnknownElem(sValue);
653  }
654  }
655 
656  if( rc == LAE_OK )
657  {
658  LOGDIAG3("%s: Laelaps battery tune parameters set.",
659  m_strXmlFileName.c_str());
660  }
661 
662  return rc;
663 }
664 
665 
666 // .............................................................................
667 // Powertrains XML Tuning Section
668 // .............................................................................
669 
670 int LaeXmlTune::setPowertrainTunes(TiXmlElement *pElemSec, LaeTunes &tunes)
671 {
672  // sub-elements
673  string strAttrLoc("location");
674  string strSubSecElemVelPid("velocity_pid");
675  string strSubSecElemTires("tires");
676 
677  string strLoc; // powertrain location (and key)
678  size_t i; // working index
679  bool bFound; // [not] found
680  TiXmlElement *pElem; // working xml element
681  const char *sValue; // working xml element name
682  int rc; // return code
683 
684  strLoc = elemAttr(pElemSec, strAttrLoc);
685 
686  if( strLoc.empty() )
687  {
688  setErrorMsg("%s: No %s attribute of <%s> found.",
689  m_strXmlFileName.c_str(),
690  strAttrLoc.c_str(),
691  m_strSecElemPowertrains.c_str());
692  LOGERROR("%s", m_bufErrMsg);
693  return -LAE_ECODE_XML;
694  }
695 
696  for(i = 0, bFound = false; i < LaeNumMotorCtlrs; ++i)
697  {
698  if( !strcasecmp(strLoc.c_str(), LaeDesc::KeyMotorCtlr[i]) )
699  {
700  bFound = true;
701  break;
702  }
703  }
704 
705  if( !bFound )
706  {
707  setErrorMsg("%s: Bad %s=\"%s\" attribute value of <%s> found.",
708  m_strXmlFileName.c_str(),
709  strAttrLoc.c_str(),
710  strLoc.c_str(),
711  m_strSecElemPowertrains.c_str());
712  LOGERROR("%s", m_bufErrMsg);
713  return -LAE_ECODE_XML;
714  }
715 
716  // create entry
717  if( tunes.m_mapPtp.find(strLoc) == tunes.m_mapPtp.end() )
718  {
719  tunes.m_mapPtp[strLoc] = LaeTunesPowertrain();
720  }
721 
722  //
723  // Walk through child elements and convert.
724  //
725  for(pElem = pElemSec->FirstChildElement(), rc = LAE_OK;
726  (pElem != NULL) && (rc == LAE_OK);
727  pElem = pElem->NextSiblingElement())
728  {
729  // no element name
730  if( (sValue = pElem->Value()) == NULL )
731  {
732  continue;
733  }
734 
735  // <velocity_pid> ... </velocity_pid>
736  else if( !strcasecmp(sValue, strSubSecElemVelPid.c_str()) )
737  {
738  rc = setPowertrainVelPidTunes(strLoc, pElem, tunes);
739  }
740 
741  // <tires> ... </tires>
742  else if( !strcasecmp(sValue, strSubSecElemTires.c_str()) )
743  {
744  rc = setPowertrainTireTunes(strLoc, pElem, tunes);
745  }
746 
747  // unknown
748  else
749  {
750  warnUnknownElem(sValue);
751  }
752  }
753 
754  if( rc == LAE_OK )
755  {
756  LOGDIAG3("%s: Laelaps %s powertrain tune parameters set.",
757  m_strXmlFileName.c_str(), strLoc.c_str());
758  }
759 
760  return rc;
761 }
762 
763 int LaeXmlTune::setPowertrainVelPidTunes(const string &strLoc,
764  TiXmlElement *pElemSubSec,
765  LaeTunes &tunes)
766 {
767  // sub-elements
768  string strElemVelPidKp("Kp");
769  string strElemVelPidKi("Ki");
770  string strElemVelPidKd("Kd");
771 
772  LaeTunesPowertrain tunesPowertrain; // powertrain tuning
773  TiXmlElement *pElem; // working xml element
774  const char *sValue; // working xml element name
775  int rc; // return code
776 
777  // tuning parameters
778  if( tunes.m_mapPtp.find(strLoc) != tunes.m_mapPtp.end() )
779  {
780  tunesPowertrain = tunes.m_mapPtp[strLoc];
781  }
782  else
783  {
784  LOGERROR("Bug: Cannot find \%s\" powertrain tuning parameters.",
785  strLoc.c_str());
786  return -LAE_ECODE_INTERNAL;
787  }
788 
789  //
790  // Walk through child elements and convert.
791  //
792  for(pElem = pElemSubSec->FirstChildElement(), rc = LAE_OK;
793  (pElem != NULL) && (rc == LAE_OK);
794  pElem = pElem->NextSiblingElement())
795  {
796  // no element name
797  if( (sValue = pElem->Value()) == NULL )
798  {
799  continue;
800  }
801 
802  // <Kp> .. </Kp>
803  else if( !strcasecmp(sValue, strElemVelPidKp.c_str()) )
804  {
805  rc = strToDoubleWithMinimum(strElemVelPidKp, elemText(pElem),
806  LaeTuneVelPidKMin,
807  tunesPowertrain.m_fVelPidKp);
808  }
809 
810  // <Ki> ... </Ki>
811  else if( !strcasecmp(sValue, strElemVelPidKi.c_str()) )
812  {
813  rc = strToDoubleWithMinimum(strElemVelPidKi, elemText(pElem),
814  LaeTuneVelPidKMin,
815  tunesPowertrain.m_fVelPidKi);
816  }
817 
818  // <Kd> ... </Kd>
819  else if( !strcasecmp(sValue, strElemVelPidKd.c_str()) )
820  {
821  rc = strToDoubleWithMinimum(strElemVelPidKd, elemText(pElem),
822  LaeTuneVelPidKMin,
823  tunesPowertrain.m_fVelPidKd);
824  }
825 
826  // unknown
827  else
828  {
829  warnUnknownElem(sValue);
830  }
831  }
832 
833  if( rc == LAE_OK )
834  {
835  // add back powertrain with new tuning parameters
836  tunes.m_mapPtp[strLoc] = tunesPowertrain;
837 
838  LOGDIAG3("%s: Laelaps %s powertrain velocity PID tune parameters set.",
839  m_strXmlFileName.c_str(), strLoc.c_str());
840  }
841 
842  return rc;
843 }
844 
845 int LaeXmlTune::setPowertrainTireTunes(const string &strLoc,
846  TiXmlElement *pElemSubSec,
847  LaeTunes &tunes)
848 {
849  // sub-elements
850  string strElemTireRadius("radius");
851  string strElemTireWidth("width");
852 
853  LaeTunesPowertrain tunesPowertrain; // powertrain tuning
854  TiXmlElement *pElem; // working xml element
855  const char *sValue; // working xml element name
856  int rc; // return code
857 
858  // tuning parameters
859  if( tunes.m_mapPtp.find(strLoc) != tunes.m_mapPtp.end() )
860  {
861  tunesPowertrain = tunes.m_mapPtp[strLoc];
862  }
863  else
864  {
865  LOGERROR("Bug: Cannot find \%s\" powertrain tuning parameters.",
866  strLoc.c_str());
867  return -LAE_ECODE_INTERNAL;
868  }
869 
870  //
871  // Walk through child elements and convert.
872  //
873  for(pElem = pElemSubSec->FirstChildElement(), rc = LAE_OK;
874  (pElem != NULL) && (rc == LAE_OK);
875  pElem = pElem->NextSiblingElement())
876  {
877  // no element name
878  if( (sValue = pElem->Value()) == NULL )
879  {
880  continue;
881  }
882 
883  // <radius> .. </radius>
884  else if( !strcasecmp(sValue, strElemTireRadius.c_str()) )
885  {
886  rc = strToDoubleWithMinimum(strElemTireRadius, elemText(pElem),
887  LaeTuneTireDimMin,
888  tunesPowertrain.m_fTireRadius);
889  }
890 
891  // <width> ... </width>
892  else if( !strcasecmp(sValue, strElemTireWidth.c_str()) )
893  {
894  rc = strToDoubleWithMinimum(strElemTireWidth, elemText(pElem),
895  LaeTuneTireDimMin,
896  tunesPowertrain.m_fTireWidth);
897  }
898 
899  // unknown
900  else
901  {
902  warnUnknownElem(sValue);
903  }
904  }
905 
906  if( rc == LAE_OK )
907  {
908  // add back powertrain with new tuning parameters
909  tunes.m_mapPtp[strLoc] = tunesPowertrain;
910 
911  LOGDIAG3("%s: Laelaps %s powertrain tire dimension tune parameters set.",
912  m_strXmlFileName.c_str(), strLoc.c_str());
913  }
914 
915  return rc;
916 }
917 
918 
919 // .............................................................................
920 // Range Sensors XML Tuning Section
921 // .............................................................................
922 
923 int LaeXmlTune::setRangeSensorTunes(TiXmlElement *pElemSec, LaeTunes &tunes)
924 {
925  // sub-elements
926  string strAttrType("type");
927  string strAttrLoc("location");
928 
929  string strType; // range sensor type (and key)
930  string strLoc; // range sensor location (and key)
931 
932  strType = elemAttr(pElemSec, strAttrType);
933  strLoc = elemAttr(pElemSec, strAttrLoc);
934 
935  if( strType.empty() )
936  {
937  setErrorMsg("%s: No %s attribute of <%s> found.",
938  m_strXmlFileName.c_str(),
939  strAttrLoc.c_str(),
940  m_strSecElemRangeSensor.c_str());
941  LOGERROR("%s", m_bufErrMsg);
942  return -LAE_ECODE_XML;
943  }
944 
945  else if( strcasecmp(strType.c_str(), "vl6180") )
946  {
947  setErrorMsg("%s: Bad %s=\"%s\" attribute value of <%s> found.",
948  m_strXmlFileName.c_str(),
949  strAttrType.c_str(),
950  strType.c_str(),
951  m_strSecElemRangeSensor.c_str());
952  LOGERROR("%s", m_bufErrMsg);
953  return -LAE_ECODE_XML;
954  }
955 
956  else
957  {
958  return setVL6180Tunes(pElemSec, strType, strLoc, tunes);
959  }
960 }
961 
962 int LaeXmlTune::setVL6180Tunes(TiXmlElement *pElemSec,
963  string &strType,
964  string &strLoc,
965  LaeTunes &tunes)
966 {
967  // sub-elements
968  string strAttrLoc("location");
969  string strElemTofOffset("tof_offset");
970  string strElemTofCrossTalk("tof_crosstalk");
971  string strElemAlsGain("als_gain");
972  string strElemAlsIntPeriod("als_int_period");
973  string strValFactory("factory");
974 
975  LaeTunesVL6180 tunesSensor; // VL6180 sensor tuning defaults
976  size_t i; // working index
977  bool bFound; // [not] found
978  TiXmlElement *pElem; // working xml element
979  const char *sValue; // working xml element name
980  string strText; // working element text
981  int rc; // return code
982 
983  if( strLoc.empty() )
984  {
985  setErrorMsg("%s: No %s attribute of <%s> found.",
986  m_strXmlFileName.c_str(),
987  strAttrLoc.c_str(),
988  m_strSecElemRangeSensor.c_str());
989  LOGERROR("%s", m_bufErrMsg);
990  return -LAE_ECODE_XML;
991  }
992 
993  for(i = 0, bFound = false; i < ToFSensorMaxNumOf; ++i)
994  {
995  if( !strcasecmp(strLoc.c_str(), LaeDesc::KeyRangeSensorMax[i]) )
996  {
997  bFound = true;
998  break;
999  }
1000  }
1001 
1002  if( !bFound )
1003  {
1004  setErrorMsg("%s: Bad %s=\"%s\" attribute value of <%s> found.",
1005  m_strXmlFileName.c_str(),
1006  strAttrLoc.c_str(),
1007  strLoc.c_str(),
1008  m_strSecElemRangeSensor.c_str());
1009  LOGERROR("%s", m_bufErrMsg);
1010  return -LAE_ECODE_XML;
1011  }
1012 
1013  // tuning parameters
1014  if( tunes.m_mapVL6180.find(strLoc) == tunes.m_mapVL6180.end() )
1015  {
1016  tunes.m_mapVL6180[strLoc] = tunesSensor; // new sensor tunes
1017  }
1018  else
1019  {
1020  tunesSensor = tunes.m_mapVL6180[strLoc]; // existing sensor tunes
1021  }
1022 
1023  //
1024  // Walk through child elements and convert.
1025  //
1026  for(pElem = pElemSec->FirstChildElement(), rc = LAE_OK;
1027  (pElem != NULL) && (rc == LAE_OK);
1028  pElem = pElem->NextSiblingElement())
1029  {
1030  // no element name
1031  if( (sValue = pElem->Value()) == NULL )
1032  {
1033  continue;
1034  }
1035 
1036  // <tof_offset> ... </tof_offset>
1037  else if( !strcasecmp(sValue, strElemTofOffset.c_str()) )
1038  {
1039  strText = elemText(pElem);
1040  if( !strcasecmp(strText.c_str(), strValFactory.c_str()) )
1041  {
1042  tunesSensor.m_nTofOffset = LaeTuneVL6180TofOffsetDft;
1043  }
1044  else
1045  {
1046  rc = strToIntWithinRange(strElemTofOffset, strText,
1047  LaeTuneVL6180TofOffsetMin, LaeTuneVL6180TofOffsetMax,
1048  tunesSensor.m_nTofOffset);
1049  }
1050  }
1051 
1052  // <tof_crosstalk> ... </tof_crosstalk>
1053  else if( !strcasecmp(sValue, strElemTofCrossTalk.c_str()) )
1054  {
1055  strText = elemText(pElem);
1056  if( !strcasecmp(strText.c_str(), strValFactory.c_str()) )
1057  {
1058  tunesSensor.m_nTofCrossTalk = LaeTuneVL6180TofXTalkDft;
1059  }
1060  else
1061  {
1062  rc = strToIntWithinRange(strElemTofCrossTalk, strText,
1063  LaeTuneVL6180TofXTalkMin, LaeTuneVL6180TofXTalkMax,
1064  tunesSensor.m_nTofCrossTalk);
1065  }
1066  }
1067 
1068  // <als_gain> ... </als_gain>
1069  else if( !strcasecmp(sValue, strElemAlsGain.c_str()) )
1070  {
1071  strText = elemText(pElem);
1072  if( !strcasecmp(strText.c_str(), strValFactory.c_str()) )
1073  {
1074  tunesSensor.m_fAlsGain = LaeTuneVL6180AlsGainDft;
1075  }
1076  else
1077  {
1078  rc = strToDoubleWithinRange(strElemAlsGain, strText,
1079  LaeTuneVL6180AlsGainMin, LaeTuneVL6180AlsGainMax,
1080  tunesSensor.m_fAlsGain);
1081  }
1082  }
1083 
1084  // <als_int_period> ... </als_int_period>
1085  else if( !strcasecmp(sValue, strElemAlsIntPeriod.c_str()) )
1086  {
1087  strText = elemText(pElem);
1088  if( !strcasecmp(strText.c_str(), strValFactory.c_str()) )
1089  {
1090  tunesSensor.m_nAlsIntPeriod = LaeTuneVL6180AlsIntPeriodDft;
1091  }
1092  else
1093  {
1094  rc = strToIntWithinRange(strElemAlsIntPeriod, strText,
1095  LaeTuneVL6180AlsIntPeriodMin, LaeTuneVL6180AlsIntPeriodMax,
1096  tunesSensor.m_nAlsIntPeriod);
1097  }
1098  }
1099 
1100  // unknown
1101  else
1102  {
1103  warnUnknownElem(sValue);
1104  }
1105  }
1106 
1107  if( rc == LAE_OK )
1108  {
1109  tunes.m_mapVL6180[strLoc] = tunesSensor; // update
1110  LOGDIAG3("%s: Laelaps %s VL6180 tune parameters set.",
1111  m_strXmlFileName.c_str(), strLoc.c_str());
1112  }
1113 
1114  return rc;
1115 }
1116 
1117 int LaeXmlTune::strToDoubleWithMinimum(const string &strElem,
1118  const string &strText,
1119  const double fMin,
1120  double &fVal)
1121 {
1122  int rc = LAE_OK; // return code
1123 
1124  if( !strText.empty() )
1125  {
1126  if( (rc = strToDouble(strText, fVal)) < 0 )
1127  {
1128  setErrorMsg("%s: Element <%s> text \"%s\" not a FPN.",
1129  m_strXmlFileName.c_str(), strElem.c_str(), strText.c_str());
1130  LOGERROR("%s", m_bufErrMsg);
1131  rc = -LAE_ECODE_XML;
1132  }
1133 
1134  else if( fVal < fMin )
1135  {
1136  setErrorMsg("%s: Element <%s> value %lf < than minimum of %lf.",
1137  m_strXmlFileName.c_str(), strElem.c_str(), fVal, fMin);
1138  LOGWARN("%s", m_bufErrMsg);
1139  fVal = fMin;
1140  }
1141  }
1142  return rc;
1143 }
1144 
1145 int LaeXmlTune::strToDoubleWithinRange(const string &strElem,
1146  const string &strText,
1147  const double fMin,
1148  const double fMax,
1149  double &fVal)
1150 {
1151  int rc = LAE_OK; // return code
1152 
1153  if( !strText.empty() )
1154  {
1155  if( (rc = strToDouble(strText, fVal)) < 0 )
1156  {
1157  setErrorMsg("%s: Element <%s> text \"%s\" not a FPN.",
1158  m_strXmlFileName.c_str(), strElem.c_str(), strText.c_str());
1159  LOGERROR("%s", m_bufErrMsg);
1160  rc = -LAE_ECODE_XML;
1161  }
1162 
1163  else if( fVal < fMin )
1164  {
1165  setErrorMsg("%s: Element <%s> value %lf < than minimum of %lf.",
1166  m_strXmlFileName.c_str(), strElem.c_str(), fVal, fMin);
1167  LOGWARN("%s", m_bufErrMsg);
1168  fVal = fMin;
1169  }
1170 
1171  else if( fVal > fMax )
1172  {
1173  setErrorMsg("%s: Element <%s> value %lf > than maximum of %lf.",
1174  m_strXmlFileName.c_str(), strElem.c_str(), fVal, fMax);
1175  LOGWARN("%s", m_bufErrMsg);
1176  fVal = fMax;
1177  }
1178  }
1179 
1180  return rc;
1181 }
1182 
1183 int LaeXmlTune::strToIntWithinRange(const string &strElem,
1184  const string &strText,
1185  const int nMin,
1186  const int nMax,
1187  int &nVal)
1188 {
1189  int rc = LAE_OK; // return code
1190 
1191  if( !strText.empty() )
1192  {
1193  if( (rc = strToInt(strText, nVal)) < 0 )
1194  {
1195  setErrorMsg("%s: Element <%s> text \"%s\" Not a Number.",
1196  m_strXmlFileName.c_str(), strElem.c_str(), strText.c_str());
1197  LOGERROR("%s", m_bufErrMsg);
1198  rc = -LAE_ECODE_XML;
1199  }
1200 
1201  else if( nVal < nMin )
1202  {
1203  setErrorMsg("%s: Element <%s> value %d < than minimum of %d.",
1204  m_strXmlFileName.c_str(), strElem.c_str(), nVal, nMin);
1205  LOGWARN("%s", m_bufErrMsg);
1206  nVal = nMin;
1207  }
1208 
1209  else if( nVal > nMax )
1210  {
1211  setErrorMsg("%s: Element <%s> value %d > than maximum of %d.",
1212  m_strXmlFileName.c_str(), strElem.c_str(), nVal, nMax);
1213  LOGWARN("%s", m_bufErrMsg);
1214  nVal = nMax;
1215  }
1216  }
1217 
1218  return rc;
1219 }
1220 
1221 int LaeXmlTune::strToNorm(const string &strElem,
1222  const string &strText,
1223  LaeNorm &eNorm)
1224 {
1225  if( strText.empty() )
1226  {
1227  return LAE_OK;
1228  }
1229  else if( !strcasecmp(strText.c_str(), "L1") )
1230  {
1231  eNorm = LaeNormL1;
1232  return LAE_OK;
1233  }
1234  else if( !strcasecmp(strText.c_str(), "L2") )
1235  {
1236  eNorm = LaeNormL2;
1237  return LAE_OK;
1238  }
1239  else if( !strcasecmp(strText.c_str(), "Linf") )
1240  {
1241  eNorm = LaeNormLinf;
1242  return LAE_OK;
1243  }
1244  else
1245  {
1246  setErrorMsg("%s: Element <%s> text \"%s\" not a recognized norm.",
1247  m_strXmlFileName.c_str(), strElem.c_str(), strText.c_str());
1248  LOGERROR("%s", m_bufErrMsg);
1249  return -LAE_ECODE_XML;
1250  }
1251 }
double m_fMinV
minimum operation voltage
Definition: laeTune.h:431
double m_fImuHz
kinematic thread rate (hertz)
Definition: laeTune.h:576
double m_fKinematicsHz
kinematic thread rate (hertz)
Definition: laeTune.h:577
Laelaps tuning data class.
Definition: laeTune.h:566
double degToRad(double d)
Convert degrees to radians.
Definition: laeUtils.h:124
LaeTunesMapVL6180 m_mapVL6180
range sensors tuning
Definition: laeTune.h:587
double m_fVelPidKd
motor velocity PID derivative constant
Definition: laeTune.h:476
int m_nTofOffset
ToF part-to-part offset.
Definition: laeTune.h:522
double m_fVelPidKp
motor velocity PID proportional const
Definition: laeTune.h:474
int m_nAlsIntPeriod
ALS integration period.
Definition: laeTune.h:525
std::vector< std::string > & split(const std::string &s, char delim, std::vector< std::string > &elems)
Split string at the delimiter character.
double m_fTireRadius
tire radius (meters)
Definition: laeTune.h:477
<b><i>Laelaps</i></b> XML tuning class interface.
LaeNorm m_eTrajNorm
trajectory distanct norm
Definition: laeTune.h:581
The <b><i>Laelaps</i></b> namespace encapsulates all <b><i>Laelaps</i></b> related constructs...
Definition: laeAlarms.h:64
Laelaps robotic base mobile platform description class interface.
double m_fWatchDogTimeout
watchdog timeout (seconds)
Definition: laeTune.h:579
Laelaps common utilities.
LaeTunesMapPtp m_mapPtp
powertrain pair tuning
Definition: laeTune.h:586
int m_nTofCrossTalk
ToF cross-talk compensation.
Definition: laeTune.h:523
double m_fVelPidKi
motor velocity PID integral constant
Definition: laeTune.h:475
Laelaps tuning.
double m_fTrajEpsilon
trajectory epsilon distance (radians)
Definition: laeTune.h:582
double m_fMaxV
maximum operating voltage
Definition: laeTune.h:429
double m_fTireWidth
tire width (meters)
Definition: laeTune.h:478
double m_fVelDerate
velocity derate (fraction)
Definition: laeTune.h:580
Laelaps motors, encoder, and controllers hardware abstraction interfaces.
double m_fRangeHz
kinematic thread rate (hertz)
Definition: laeTune.h:578
Laelaps powertrain tuning data class.
Definition: laeTune.h:519
Laelaps powertrain tuning data class.
Definition: laeTune.h:471
LaeTunesBattery m_battery
battery tuning
Definition: laeTune.h:585
double m_fAlsGain
ALS analog gain.
Definition: laeTune.h:524
Top-level package include file.