Dynamixel  2.9.5
RoadNarrows Robotics Dynamixel Package
dynashell_cmd_train.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: Dynamixel
4 //
5 // Program: dynashell
6 //
7 // File: dynashell_train.c
8 //
9 /*! \file
10  *
11  * $LastChangedDate: 2015-01-12 10:56:06 -0700 (Mon, 12 Jan 2015) $
12  * $Rev: 3845 $
13  *
14  * \brief Dynamixel shell record and playback functions.
15  *
16  * \author Robin Knight (robin.knight@roadnarrows.com)
17  *
18  * \copyright
19  * \h_copy 2011-2017. RoadNarrows LLC.\n
20  * http://www.roadnarrows.com\n
21  * All Rights Reserved
22  */
23 /*
24  * @EulaBegin@
25  *
26  * Unless otherwise stated explicitly, all materials contained are copyrighted
27  * and may not be used without RoadNarrows LLC's written consent,
28  * except as provided in these terms and conditions or in the copyright
29  * notice (documents and software) or other proprietary notice provided with
30  * the relevant materials.
31  *
32  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY
33  * MEMBERS/EMPLOYEES/CONTRACTORS OF ROADNARROWS OR DISTRIBUTORS OF THIS SOFTWARE
34  * BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
35  * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
36  * DOCUMENTATION, EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN
37  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  *
39  * THE AUTHORS AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
40  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
41  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
42  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
43  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
44  *
45  * @EulaEnd@
46  */
47 ////////////////////////////////////////////////////////////////////////////////
48 
49 #include <sys/select.h>
50 #include <limits.h>
51 #include <time.h>
52 #include <ctype.h>
53 #include <cstring>
54 #include <iostream>
55 #include <fstream>
56 #include <map>
57 #include <vector>
58 
59 #include <math.h>
60 #include <gsl/gsl_bspline.h>
61 #include <gsl/gsl_multifit.h>
62 #include <gsl/gsl_rng.h>
63 #include <gsl/gsl_randist.h>
64 #include <gsl/gsl_statistics.h>
65 
66 #include "rnr/rnrconfig.h"
67 #include "rnr/log.h"
68 
69 #include "Dynamixel/Dynamixel.h"
70 #include "Dynamixel/DynaComm.h"
71 #include "Dynamixel/DynaPidSpeed.h"
72 #include "Dynamixel/DynaServo.h"
73 #include "Dynamixel/DynaChain.h"
74 #include "Dynamixel/DynaOlio.h"
75 
76 #include "dynashell.h"
77 #include "dynashell_cmd.h"
78 #include "dynashell_recording.h"
79 #include "dynashell_util.h"
80 
81 using namespace std;
82 
83 
84 //-----------------------------------------------------------------------------
85 // Private Interface
86 //-----------------------------------------------------------------------------
87 
88 // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89 // Helper Functions
90 // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91 
92 /*!
93  * \brief Block, waiting for either timeout or user keyboard press.
94  *
95  * No characters are read.
96  *
97  * \param nMSec Wait period (ms)
98  *
99  * \return Returns true if wait was interrupted by keyboard press. Else returns
100  * false on time out.
101  */
102 static bool_t waitkey(int nMSec)
103 {
104  int fno = fileno(stdin);
105  fd_set fdset;
106  uint_t usec = (uint_t)nMSec * 1000;
107  struct timeval timeout;
108  int rc;
109 
110  FD_ZERO(&fdset);
111  FD_SET(fno, &fdset);
112 
113  // timeout (gets munged after each select())
114  timeout.tv_sec = (time_t)(usec / 1000000);
115  timeout.tv_usec = (time_t)(usec % 1000000);
116 
117  if( (rc = select(fno+1, &fdset, NULL, NULL, &timeout)) > 0 )
118  {
119  fflush(stdin);
120  }
121 
122  return rc>0? true: false;
123 }
124 
125 /*!
126  * \brief Block, waiting for either timeout or user keyboard press.
127  *
128  * No characters are read.
129  *
130  * \param fSec Wait period (seconds)
131  *
132  * \return Returns true if wait was interrupted by keyboard press. Else returns
133  * false on time out.
134  */
135 static bool_t waitkey(double fSec)
136 {
137  int fno = fileno(stdin);
138  fd_set fdset;
139  struct timeval timeout;
140  int rc;
141 
142  FD_ZERO(&fdset);
143  FD_SET(fno, &fdset);
144 
145  // timeout (gets munged after each select())
146  timeout.tv_sec = (time_t)((int)fSec);
147  timeout.tv_usec = (time_t)(((fSec - (double)timeout.tv_sec)) * 1000000.0);
148 
149  if( (rc = select(fno+1, &fdset, NULL, NULL, &timeout)) > 0 )
150  {
151  fflush(stdin);
152  }
153 
154  return rc>0? true: false;
155 }
156 
157 
158 // -----------------------------------------------------------------------------
159 // DynaShellCmdGetPid Class
160 // -----------------------------------------------------------------------------
161 
162 /*!
163  * \brief Get the PID values for the given servos.
164  */
166 {
167 public:
168 
169  /*
170  * \brief Default constructor.
171  */
173  {
174  m_sCmdName = "pid";
175  m_sCmdHelpBrief = "Get the current servo PID values.";
176  m_sCmdHelpArgs = "<servo_id> [servo_id...]\nchain";
177  m_sCmdHelpDesc = "Get the current PID Kp, Ki, and Kd parameters for the "
178  "specified servos. "
179  "If the keyword 'chain' is specified, then the PID "
180  "parameters for all of the servos in the chain are "
181  "retrieved.\n"
182  " <servo_id> Servo id [0-253].";
183  }
184 
185  /*
186  * \brief Default destructor.
187  */
188  virtual ~DynaShellCmdGetPid() { }
189 
190 
191 protected:
192 
193  /*!
194  * \brief Get servo PID parameters.
195  *
196  * \param shell Dynamixel shell.
197  * \param pServo Pointer to servo.
198  */
199  virtual void doExec(DynaShell &shell, DynaServo *pServo)
200  {
201  printf("Servo %3d: Kp %9.3lf, Ki %9.3lf, Kd %9.3lf\n",
202  pServo->GetServoId(), pServo->GetSpeedPid().GetKp(),
203  pServo->GetSpeedPid().GetKi(), pServo->GetSpeedPid().GetKd());
204  }
205 };
206 
207 
208 // -----------------------------------------------------------------------------
209 // DynaShellCmdSetPid Class
210 // -----------------------------------------------------------------------------
211 /*!
212  * \brief Get the PID values for the given servos.
213  */
215 {
216 public:
217 
218  /*
219  * \brief Default constructor.
220  */
222  {
223  m_sCmdName = "pid";
224  m_sCmdHelpBrief = "Set PID constants for the given servo(s).";
225  m_sCmdHelpArgs = "<servo_id> <Kp> <Ki> <Kd>\nchain <Kp> <Ki> <Kd>";
226  m_sCmdHelpDesc = "Set the PID Kp, Ki, and Kd parameters for the "
227  "specified servo(s). "
228  "If the keyword 'chain' is specified, then the PID "
229  "parameters for all of the servos in the chain are "
230  "set.\n"
231  " <servo_id> Servo id [0-253].\n"
232  " <Kp> Proportional constant.\n"
233  " <Ki> Integral constant.\n"
234  " <Kd> Derivative constant.";
235  }
236 
237  /*
238  * \brief Default destructor.
239  */
240  virtual ~DynaShellCmdSetPid() { }
241 
242  /*!
243  * \brief Execute 'read-like' command on servos.
244  *
245  * \param shell Dynamixel shell.
246  * \param argc Command argument count.
247  * \param argv Array of arguments.
248  */
249  void Exec(DynaShell &shell, int argc, char *argv[])
250  {
251  bool bAll;
252  int nServoId;
253  double fKp;
254  double fKi;
255  double fKd;
256  int i;
257  int iter;
258  DynaServo *pServo;
259 
260  TRY( ChkArgCnt(shell, argc) );
261 
262  TRY( ChkComm(shell) );
263  TRY( ChkChainNotEmpty(shell) );
264 
265  for(i=0; i<argc; ++i)
266  {
267  switch( i )
268  {
269  case 0:
270  if( !strcmp(argv[i], "chain") )
271  {
272  bAll = true;
273  }
274  else
275  {
276  TRY( ToInt(shell, argv[i], &nServoId) );
277  TRY( ChkChainHasServo(shell, nServoId) );
278  bAll = false;
279  }
280  break;
281  case 1:
282  TRY( ToDouble(shell, argv[i], &fKp) );
283  break;
284  case 2:
285  TRY( ToDouble(shell, argv[i], &fKi) );
286  break;
287  case 3:
288  TRY( ToDouble(shell, argv[i], &fKd) );
289  break;
290  default:
291  shell.Error("Huh?");
292  return;
293  }
294  }
295 
296  if( bAll )
297  {
298  for(nServoId = shell.m_pDynaChain->IterStart(&iter);
299  nServoId != DYNA_ID_NONE;
300  nServoId = shell.m_pDynaChain->IterNext(&iter))
301  {
302  pServo = shell.m_pDynaChain->GetServo(nServoId);
303  doExec(shell, pServo, fKp, fKi, fKd);
304  }
305  }
306  else
307  {
308  pServo = shell.m_pDynaChain->GetServo(nServoId);
309  doExec(shell, pServo, fKp, fKi, fKd);
310  }
311  }
312 
313  /*!
314  * \brief Command tab completion generator.
315  *
316  * Completes NULL <servo_id> | chain [servo_id [servo_id ...]]
317  *
318  * \param shell Dynamixel shell.
319  * \param sText Partial text string to complete.
320  * \param uTextLen Length of text.
321  * \param nState Generator state. If FIRST, then initialize any statics.
322  * \param sContext Generator context (i.e. canonical command path).
323  *
324  * \return
325  * If a first/next match is made, return allocated completed match.\n
326  * Otherwise return NULL.
327  */
328  virtual char *TabCompletion(DynaShell &shell,
329  const char *sText,
330  size_t uTextLen,
331  int nState,
332  const char *sContext)
333  {
334  int nArgNum;
335  char buf[16];
336 
337  if( (shell.m_pDynaChain == NULL) ||
338  (shell.m_pDynaChain->GetNumberInChain() == 0) )
339  {
340  return NULL;
341  }
342 
343  // argument number of already (expanded) arguments
344  nArgNum = ReadLine::wc(sContext) - ReadLine::wc(m_sPubName);
345 
346  // only first argument can be expanded
347  if( nArgNum > 0 )
348  {
349  return NULL;
350  }
351 
352  //
353  // New command to complete - initialize.
354  //
355  if( nState == ReadLine::FIRST )
356  {
357  m_nTabServoId = shell.m_pDynaChain->IterStart(&m_nTabIter);
358  }
359 
360  while( m_nTabServoId != DYNA_ID_NONE )
361  {
362  snprintf(buf, sizeof(buf), "%d", m_nTabServoId);
363  buf[sizeof(buf)-1] = 0;
364 
365  m_nTabServoId = shell.m_pDynaChain->IterNext(&m_nTabIter);
366 
367  if( !strncmp(buf, sText, uTextLen) )
368  {
369  return ReadLine::dupstr(buf);
370  }
371  }
372 
373  if( !strncmp("chain", sText, uTextLen) )
374  {
375  return ReadLine::dupstr("chain");
376  }
377 
378  // no more matches
379  return NULL;
380  }
381 
382 protected:
383  int m_nTabIter; ///< tab completion: servo id iterator
384  int m_nTabServoId; ///< tab completion: current servo id
385 
386  /*!
387  * \brief Get servo PID parameters.
388  *
389  * \param shell Dynamixel shell.
390  * \param pServo Pointer to servo.
391  * \param fKp PID proportional constant.
392  * \param fKi PID integral constant.
393  * \param fKd PID derivative constant.
394  */
395  virtual void doExec(DynaShell &shell,
396  DynaServo *pServo,
397  double fKp,
398  double fKi,
399  double fKd)
400  {
401  pServo->GetSpeedPid().SetConstants(fKp, fKi, fKd);
402  }
403 };
404 
405 
406 // -----------------------------------------------------------------------------
407 // DynaShellCmdLoadRecording Class
408 // -----------------------------------------------------------------------------
409 
410 /*!
411  * \brief Load recording from file.
412  */
414 {
415 public:
416  /*!
417  * \brief Default constructor.
418  */
420  {
421  m_sCmdName = "recording";
422  m_sCmdHelpBrief = "Load recording from a file.";
423  m_sCmdHelpArgs = "<file>";
424  m_sCmdHelpDesc = "Load a recording from a file. The current recording in "
425  "memory is overwritten.\n"
426  " <file> Recording file name.";
427 
428  m_fp = NULL;
429  m_sFileName = NULL;
430  m_nLineNum = 0;
431  m_nColNum = 0;
432  m_sCursor = NULL;
433  m_sField = NULL;
434  m_sizeBuf = 0;
435  m_buf = NULL;
436 
437  m_sDate = NULL;
438  m_nNumServos = 0;
439  m_nNumRecords = 0;
440  m_nSamplePeriod = 0;
441  m_pRecording = NULL;
442  }
443 
444  /*!
445  * \brief Default destructor.
446  */
448 
449  /*!
450  * \brief Execute load recording.
451  *
452  * \param shell Dynamixel shell.
453  * \param argc Command argument count.
454  * \param argv Array of arguments.
455  */
456  virtual void Exec(DynaShell &shell, int argc, char *argv[])
457  {
458  FILE *fp;
459 
460  TRY( ChkArgCnt(shell, argc) );
461 
462  if( Init(shell, argv[0]) != DYNA_OK )
463  {
464  return;
465  }
466 
467  Load(shell);
468 
469  Cleanup(shell);
470  }
471 
472  /*!
473  * \brief Command tab completion generator.
474  *
475  * Completes <file>
476  *
477  * \param shell Dynamixel shell.
478  * \param sText Partial text string to complete.
479  * \param uTextLen Length of text.
480  * \param nState Generator state. If FIRST, then initialize any statics.
481  * \param sContext Generator context (i.e. canonical command path).
482  *
483  * \return
484  * If a first/next match is made, return allocated completed match.\n
485  * Otherwise return NULL.
486  */
487  virtual char *TabCompletion(DynaShell &shell,
488  const char *sText,
489  size_t uTextLen,
490  int nState,
491  const char *sContext)
492  {
493  return ReadLine::FileCompletionGenerator(sText, nState);
494  }
495 
496 protected:
497  FILE *m_fp; ///< open file pointer
498  char *m_sFileName; ///< file name
499  int m_nLineNum; ///< line number
500  int m_nColNum; ///< column number in line
501  char *m_sCursor; ///< current line parse cursor
502  char *m_sField; ///< current field being parsed
503  char *m_buf; ///< input buffer
504  size_t m_sizeBuf; ///< size of input buffer
505 
506  int m_nNumServos; ///< NUM_SERVOS value
507  int m_nNumRecords; ///< NUM_RECORDS value
508  int m_nSamplePeriod; ///< SAMPLE_PERIOD value
509  char *m_sDate; ///< DATE value
510  DynaRecording *m_pRecording; ///< working and new recording
511 
512  /*!
513  * \brief Load recording.
514  *
515  * \param shell Dynamixel shell.
516  */
517  void Load(DynaShell &shell)
518  {
519  int n;
520  int rc = DYNA_OK;
521 
522  while( (rc == DYNA_OK) && GetLine() )
523  {
524  m_sField = GetFirstWord();
525 
526  // empty line
527  if( m_sField == NULL )
528  {
529  continue;
530  }
531 
532  // comment
533  else if( *m_sField == '#' )
534  {
535  continue;
536  }
537 
538  // version - ignored for now
539  else if( !strcmp(m_sField, "VERSION") )
540  {
541  continue;
542  }
543 
544  // recording date
545  else if( !strcmp(m_sField, "DATE") )
546  {
547  SetDateField(shell, GetEolPhrase());
548  }
549 
550  // number of servos recorded
551  else if( !strcmp(m_sField, "NUM_SERVOS") )
552  {
553  if( ParseIntField(shell, GetNextWord(), &m_nNumServos) != DYNA_OK )
554  {
555  rc = -DYNA_ECODE_PARSE;
556  }
557  else if( (m_nNumServos <= 0) || (m_nNumServos > DYNA_ID_NUMOF) )
558  {
559  shell.Error("Line %dc%d: Field %s: Value %d: Out-of-range.",
560  m_nLineNum, m_nColNum, m_sField, m_nNumServos);
561  rc = -DYNA_ECODE_PARSE;
562  }
563  }
564 
565  // list of servo id, model number pairs
566  else if( !strcmp(m_sField, "SERVOS") )
567  {
568  rc = ParseServoList(shell) != DYNA_OK;
569  }
570 
571  // sample period (msec)
572  else if( !strcmp(m_sField, "SAMPLE_PERIOD") )
573  {
574  if( ParseIntField(shell, GetNextWord(), &m_nSamplePeriod) != DYNA_OK )
575  {
576  rc = -DYNA_ECODE_PARSE;
577  }
578  else if( m_nSamplePeriod <= 0 )
579  {
580  shell.Error("Line %dc%d: Field %s: Value %d: Out-of-range.",
581  m_nLineNum, m_nColNum, m_sField, m_nNumServos);
582  rc = -DYNA_ECODE_PARSE;
583  }
584  else
585  {
586  m_pRecording->SetSamplePeriod(m_nSamplePeriod);
587  }
588  }
589 
590  // number of records
591  else if( !strcmp(m_sField, "NUM_RECORDS") )
592  {
593  if( ParseIntField(shell, GetNextWord(), &m_nNumRecords) != DYNA_OK )
594  {
595  rc = -DYNA_ECODE_PARSE;
596  }
597  else if( (m_nNumRecords <= 0) ||
598  (m_nNumRecords > DynaRecording::MaxRecords) )
599  {
600  shell.Error("Line %dc%d: Field %s: Value %d: Out-of-range.",
601  m_nLineNum, m_nColNum, m_sField, m_nNumServos);
602  rc = -DYNA_ECODE_PARSE;
603  }
604  }
605 
606  // recorded servo data - last extended field
607  else if( !strcmp(m_sField, "DATA") )
608  {
609  //
610  // Final checks and parsing
611  //
612  if( m_nNumServos == 0 )
613  {
614  shell.Error("Field NUM_SERVOS: Not specified in file header.",
615  m_sField);
616  rc = -DYNA_ECODE_PARSE;
617  }
618 
619  else if( m_pRecording->GetNumOfServosInRecording() != m_nNumServos )
620  {
621  shell.Error(
622  "Field SERVOS lists %d servos != Field NUM_SERVOS value of %d.",
623  m_pRecording->GetNumOfServosInRecording(), m_nNumServos);
624  rc = -DYNA_ECODE_PARSE;
625  }
626 
627  else if( m_nNumRecords == 0 )
628  {
629  shell.Error("Field NUM_RECORDS: Not specified in file header.");
630  rc = -DYNA_ECODE_PARSE;
631  }
632 
633  else if( m_nSamplePeriod == 0 )
634  {
635  shell.Error("Field SAMPLE_PERIOD: Not specified in file header.");
636  rc = -DYNA_ECODE_PARSE;
637  }
638 
639  else
640  {
641  rc = ParseRecordedData(shell);
642  }
643 
644  break;
645  }
646 
647  else
648  {
649  shell.Warning("Line %d: Field %s: Unknown field - ignoring.",
650  m_nLineNum, m_sField);
651  }
652  }
653 
654  //
655  // If ok, then the recording was successfully loaded from the file.
656  //
657  if( rc == DYNA_OK )
658  {
659  if( m_sDate == NULL )
660  {
661  shell.Warning("Field DATE: Not specified in file header.");
662  SetDateField(shell, "Unknown File Date");
663  }
664 
665  shell.RecordingReplace(m_pRecording);
666  m_pRecording = NULL; // make sure the cleanup does not delete this
667 
668  shell.Response("Loaded recording from file %s\n", m_sFileName);
669  shell.Response(" Recording date: %s.\n",
670  shell.m_pRecording->GetDate());
671  shell.Response(" Number of servos: %d\n",
673  shell.Response(" Number of records: %d\n",
674  shell.m_pRecording->GetNumOfRecords());
675  shell.Response(" Sample period: %dms.\n",
676  shell.m_pRecording->GetSamplePeriod());
677  }
678  }
679 
680  /*!
681  * \brief Parse servo list and save in working recording.
682  *
683  * \param shell Dynamixel shell.
684  *
685  * \copydoc doc_return_std
686  */
688  {
689  char *sWord;
690  int nServoId;
691  uint_t uModelNum;
692  int rc = DYNA_OK;
693 
694  while( (rc == DYNA_OK) && ((sWord = GetNextWord()) != NULL) )
695  {
696  // 2-tuple servo_id, model_num
697  if( (rc = ParseIntField(shell, sWord, &nServoId)) == DYNA_OK )
698  {
699  rc = ParseIntField(shell, GetNextWord(), (int *)&uModelNum);
700  }
701 
702  if( rc != DYNA_OK )
703  {
704  }
705 
706  else if( (nServoId < DYNA_ID_MIN) || (nServoId > DYNA_ID_MAX) )
707  {
708  shell.Error("Line %dc%d: Field %s: Value %d': Out-of-range.",
709  m_nLineNum, m_nColNum, m_sField, nServoId);
710  rc = -DYNA_ECODE_PARSE;
711  }
712 
713  else
714  {
715  rc = m_pRecording->RegisterServoInfo(nServoId, uModelNum);
716 
717  if( rc != DYNA_OK )
718  {
719  shell.Error("Line %dc%d: Field %s: Servo %d: Duplicate.",
720  m_nLineNum, m_nColNum, m_sField, nServoId);
721  }
722  }
723  }
724 
725  return rc;
726  }
727 
728  /*!
729  * \brief Parse recorded data and save in working recording.
730  *
731  * \param shell Dynamixel shell.
732  *
733  * \copydoc doc_return_std
734  */
736  {
737  int nRecNum;
738  int nFldNum;
739  char *sWord;
740  int nServoId;
741  int nPos;
742  int nSpeed;
743  int rc;
744 
745  while( GetLine() )
746  {
747  if( LineHasNoData() )
748  {
749  continue;
750  }
751 
752  m_sField = (char *)"DATA"; // multiline parsing clobbers this
753 
754  nRecNum = m_pRecording->AddRecord();
755 
756  if( nRecNum == DynaRecording::END )
757  {
758  shell.Error(
759  "Line %d: Field DATA: Exceeded maximum number of %d records.",
760  m_nLineNum, m_sField, DynaRecording::MaxRecords);
761  return -DYNA_ECODE_PARSE;
762  }
763 
764  else if( nRecNum >= m_nNumRecords )
765  {
766  shell.Error(
767  "Line %d: Field DATA: Exceeded NUM_RECORDS value of %d records.",
768  m_nLineNum, m_sField, m_nNumRecords);
769  return -DYNA_ECODE_PARSE;
770  }
771 
772  for(sWord = GetFirstWord(), nFldNum=0;
773  sWord != NULL;
774  sWord = GetNextWord(), ++nFldNum)
775  {
776  if( nFldNum >= m_nNumServos )
777  {
778  shell.Error(
779  "Line %dc%d: Field %s: Exceeded NUM_SERVOS value of %d servos.",
780  m_nLineNum, m_sField, m_nColNum, m_nNumServos);
781  return -DYNA_ECODE_PARSE;
782  }
783 
784  // read 2-tuple position, speed
785  if( (rc = ParseIntField(shell, sWord, &nPos)) != DYNA_OK )
786  {
787  return -DYNA_ECODE_PARSE;
788  }
789 
790  if( (rc = ParseIntField(shell, GetNextWord(), &nSpeed)) != DYNA_OK )
791  {
792  return -DYNA_ECODE_PARSE;
793  }
794 
795  if( (nServoId = m_pRecording->GetServoId(nFldNum)) == DYNA_ID_NONE )
796  {
797  shell.Error("Line %dc%d: Field %s: No registered servo at pos %d.",
798  m_nLineNum, m_nColNum, m_sField, nFldNum);
799  return -DYNA_ECODE_PARSE;
800  }
801 
802  rc = m_pRecording->AddFieldTuple(nRecNum, nServoId, nPos, nSpeed);
803 
804  if( rc == DynaRecording::END )
805  {
806  shell.Error("Line %dc%d: Field %s: Cannot add any more fields.",
807  m_nLineNum, m_nColNum, m_sField);
808  return -DYNA_ECODE_PARSE;
809  }
810  }
811 
812  if( nFldNum < m_nNumServos )
813  {
814  shell.Error(
815  "Line %d: Field %s: Only %d (pos,speed) 2-tuples found, expected %d.",
816  m_nLineNum, m_sField, nFldNum, m_nNumServos);
817  return -DYNA_ECODE_PARSE;
818  }
819  }
820 
821  if( nRecNum+1 < m_nNumRecords )
822  {
823  shell.Error("Line %d: Field %s: Only %d records found, expected %d.",
824  m_nLineNum, m_sField, nRecNum+1, m_nNumRecords);
825  return -DYNA_ECODE_PARSE;
826  }
827 
828  return DYNA_OK;
829  }
830 
831  /*!
832  * \brief Set date value.
833  *
834  * \param shell Dynamixel shell.
835  * \param sDate Date string.
836  *
837  * \copydoc doc_return_std
838  */
839  void SetDateField(DynaShell &shell, const char *sDate)
840  {
841  if( m_sDate != NULL )
842  {
843  delete[] m_sDate;
844  m_sDate = NULL;
845  }
846  if( (sDate != NULL) && (*sDate != 0) )
847  {
848  m_sDate = newstr(sDate);
849  m_pRecording->SetDate(m_sDate);
850  }
851  }
852 
853  /*!
854  * \brief Parse integer field.
855  *
856  * String field can be specified in hexidecimal, decimal, or octal.
857  *
858  * \param shell Dynamixel shell.
859  * \param sWord Word to parse.
860  * \param [out] pFieldVal Converted value.
861  *
862  * \copydoc doc_return_std
863  */
864  int ParseIntField(DynaShell &shell, const char *sWord, int *pFieldVal)
865  {
866  if( sWord == NULL )
867  {
868  shell.Error("Line %dc%d: Field %s: No field value found.",
869  m_nLineNum, m_nColNum, m_sField);
870  return -DYNA_ECODE_PARSE;
871  }
872  else if( sscanf(sWord, "%i", pFieldVal) != 1 )
873  {
874  shell.Error("Line %dc%d: Field %s: Value %s: Not an integer.",
875  m_nLineNum, m_nColNum, m_sField, sWord);
876  return -DYNA_ECODE_PARSE;
877  }
878  else
879  {
880  return DYNA_OK;
881  }
882  }
883 
884  /*!
885  * \brief Get the first word in line buffer.
886  *
887  * A word is defined as characters separated by white space.
888  *
889  * \return
890  * Returns pointer to start of word if a word is found.\n
891  * Else returns NULL.
892  */
893  char *GetFirstWord()
894  {
895  m_sCursor = m_buf;
896  m_nColNum = 1;
897 
898  return GetNextWord();
899  }
900 
901  /*!
902  * \brief Get the next word in line buffer.
903  *
904  * A word is defined as characters separated by white space.
905  *
906  * \return
907  * Returns pointer to start of word if a word is found.\n
908  * Else returns NULL.
909  */
910  char *GetNextWord()
911  {
912  char *sWord;
913 
914  m_nColNum = (int)(m_sCursor - m_buf) + 1;
915 
916  while( *m_sCursor && isspace((int)*m_sCursor) )
917  {
918  ++m_sCursor;
919  ++m_nColNum;
920  }
921 
922  if( *m_sCursor == 0 )
923  {
924  return NULL;
925  }
926 
927  sWord = m_sCursor;
928 
929  while( *m_sCursor && !isspace((int)*m_sCursor) )
930  {
931  ++m_sCursor;
932  }
933 
934  if( *m_sCursor != 0 )
935  {
936  *m_sCursor++ = 0;
937  }
938 
939  //fprintf(stderr, "DBG: word=%s\n", sWord);
940 
941  return sWord;
942  }
943 
944  /*!
945  * \brief Get the end-of-line phrase
946  *
947  * The eol phrase is defined as a set of characters bracketed by non-white
948  * space characters.
949  *
950  * \return
951  * Returns pointer to start of phrase if a phrase is found.\n
952  * Else returns NULL.
953  */
954  char *GetEolPhrase()
955  {
956  char *sPhrase;
957  char *s;
958 
959  m_nColNum = (int)(m_sCursor - m_buf) + 1;
960 
961  while( *m_sCursor && isspace((int)*m_sCursor) )
962  {
963  ++m_sCursor;
964  ++m_nColNum;
965  }
966 
967  if( *m_sCursor == 0 )
968  {
969  return NULL;
970  }
971 
972  sPhrase = m_sCursor;
973 
974  while( *m_sCursor && (*m_sCursor != '\n') )
975  {
976  ++m_sCursor;
977  }
978 
979  if( *m_sCursor != 0 )
980  {
981  ++m_sCursor;
982  }
983 
984  s = m_sCursor - 1;
985 
986  while( (s > sPhrase) && isspace((int)*s) )
987  {
988  --s;
989  }
990 
991  *++s = 0;
992 
993  //fprintf(stderr, "DBG: phrase=%s\n", sPhrase);
994 
995  return sPhrase;
996  }
997 
998  /*!
999  * \brief Test if line is all white space or a comment.
1000  *
1001  * \return Returns true if line contains no data, false otherwise.
1002  */
1004  {
1005  char *s;
1006 
1007  for(s=m_buf; *s; ++s)
1008  {
1009  if( *s == '#' )
1010  {
1011  return true;
1012  }
1013  else if( !isspace((int)*s) )
1014  {
1015  return false;
1016  }
1017  }
1018  return true;
1019  }
1020 
1021  /*!
1022  * \brief Read the next line of data.
1023  *
1024  * \return Returns true if line found, false otherwise.
1025  */
1026  bool GetLine()
1027  {
1028  if( getline(&m_buf, &m_sizeBuf, m_fp) != -1 )
1029  {
1030  m_nLineNum++;
1031  m_nColNum = 0;
1032  return true;
1033  }
1034  else
1035  {
1036  return false;
1037  }
1038  }
1039 
1040  /*!
1041  * \brief Initialize data prior to file loading.
1042  *
1043  * \param shell Dynamixel shell.
1044  * \param sFileName File to open.
1045  *
1046  * \copydoc doc_return_std
1047  */
1048  int Init(DynaShell &shell, char *sFileName)
1049  {
1050  if( (m_fp = fopen(sFileName, "r")) == NULL )
1051  {
1052  shell.Error("%s: cannot open.", sFileName);
1053  return -DYNA_ECODE_BADF;
1054  }
1055 
1056  m_sFileName = newstr(sFileName);
1057  m_nLineNum = 0;
1058  m_nColNum = 0;
1059  m_sizeBuf = 4096;
1060  m_buf = new char[m_sizeBuf];
1061 
1062  m_sDate = NULL;
1063  m_nNumServos = 0;
1064  m_nNumRecords = 0;
1065  m_nSamplePeriod = 0;
1066  m_pRecording = new DynaRecording;
1067 
1068  return DYNA_OK;
1069  }
1070 
1071  /*!
1072  * \brief Clean up data after to file loading parsing.
1073  *
1074  * \param shell Dynamixel shell.
1075  */
1076  void Cleanup(DynaShell &shell)
1077  {
1078  if( m_fp != NULL)
1079  {
1080  fclose(m_fp);
1081  m_fp = NULL;
1082  }
1083 
1084  if( m_sFileName != NULL )
1085  {
1086  delete[] m_sFileName;
1087  m_sFileName = NULL;
1088  }
1089 
1090  if( m_buf != NULL )
1091  {
1092  delete[] m_buf;
1093  m_buf = NULL;
1094  }
1095 
1096  if( m_sDate != NULL )
1097  {
1098  delete[] m_sDate;
1099  m_sDate = NULL;
1100  }
1101 
1102  // load failed, delete associated recording
1103  if( m_pRecording != NULL )
1104  {
1105  delete m_pRecording;
1106  m_pRecording = NULL;
1107  }
1108 
1109  m_nLineNum = 0;
1110  m_nColNum = 0;
1111  m_sCursor = NULL;
1112  m_sField = NULL;
1113  m_nNumServos = 0;
1114  m_nNumRecords = 0;
1115  m_nSamplePeriod = 0;
1116  }
1117 };
1118 
1119 
1120 // -----------------------------------------------------------------------------
1121 // DynaShellCmdSaveRecording Class
1122 // -----------------------------------------------------------------------------
1123 
1124 /*!
1125  * \brief Save recording to file.
1126  */
1128 {
1129 public:
1130  /*!
1131  * \brief Default constructor.
1132  */
1134  {
1135  m_sCmdName = "recording";
1136  m_sCmdHelpBrief = "Save the current recording to a file.";
1137  m_sCmdHelpArgs = "<file>";
1138  m_sCmdHelpDesc = "Save the current recording to an ASCII file."
1139  "The current recording is the last 'train' or 'load' "
1140  "command executed during this shell's session.\n"
1141  " <file> Recording file name.";
1142  }
1143 
1144  /*!
1145  * \brief Default destructor.
1146  */
1148 
1149  /*!
1150  * \brief Execute save recording.
1151  *
1152  * \param shell Dynamixel shell.
1153  * \param argc Command argument count.
1154  * \param argv Array of arguments.
1155  */
1156  virtual void Exec(DynaShell &shell, int argc, char *argv[])
1157  {
1158  FILE *fp;
1159 
1160  TRY( ChkArgCnt(shell, argc) );
1161 
1162  if( (shell.m_pRecording == NULL) ||
1163  (shell.m_pRecording->GetNumOfRecords() == 0) )
1164  {
1165  shell.Error("No recording to save.");
1166  return;
1167  }
1168 
1169  if( (fp = fopen(argv[0], "w")) == NULL )
1170  {
1171  shell.Error("%s: cannot open.", argv[1]);
1172  return;
1173  }
1174 
1175  Save(shell, fp);
1176 
1177  fclose(fp);
1178 
1179  shell.Response("Saved recording to file %s\n", argv[0]);
1180  }
1181 
1182  /*!
1183  * \brief Command tab completion generator.
1184  *
1185  * Completes <file>
1186  *
1187  * \param shell Dynamixel shell.
1188  * \param sText Partial text string to complete.
1189  * \param uTextLen Length of text.
1190  * \param nState Generator state. If FIRST, then initialize any statics.
1191  * \param sContext Generator context (i.e. canonical command path).
1192  *
1193  * \return
1194  * If a first/next match is made, return allocated completed match.\n
1195  * Otherwise return NULL.
1196  */
1197  virtual char *TabCompletion(DynaShell &shell,
1198  const char *sText,
1199  size_t uTextLen,
1200  int nState,
1201  const char *sContext)
1202  {
1203  return ReadLine::FileCompletionGenerator(sText, nState);
1204  }
1205 
1206 protected:
1207 
1208  /*!
1209  * \brief Save recording.
1210  *
1211  * \param shell Dynamixel shell.
1212  * \param fp File pointer.
1213  */
1214  void Save(DynaShell &shell, FILE *fp)
1215  {
1216  DynaRecording *pRecording = shell.m_pRecording;
1217  int nRecNum;
1218  int nFldNum;
1219  int nServoId;
1220  DynaRecord::FieldTuple_T tupRec;
1221 
1222  fprintf(fp, "# .DYD v1.0 - Dynamixel Data file format\n");
1223  fprintf(fp, "VERSION 1.0\n");
1224  fprintf(fp, "DATE %s\n", pRecording->GetDate());
1225  fprintf(fp, "NUM_SERVOS %d\n", pRecording->GetNumOfServosInRecording());
1226  fprintf(fp, "SERVOS");
1227 
1228  for(nFldNum = pRecording->FirstField(0);
1229  nFldNum != DynaRecording::END;
1230  nFldNum = pRecording->NextField(0, nFldNum))
1231  {
1232  nServoId = pRecording->GetServoId(nFldNum);
1233  fprintf(fp, " %d 0x%04x",
1234  nServoId,
1235  pRecording->GetServoModelNumber(nServoId));
1236  }
1237  fprintf(fp, "\n");
1238 
1239  fprintf(fp, "SAMPLE_PERIOD %d\n", pRecording->GetSamplePeriod());
1240  fprintf(fp, "NUM_RECORDS %d\n", pRecording->GetNumOfRecords());
1241  fprintf(fp, "DATA ascii\n");
1242 
1243  for(nRecNum = pRecording->FirstRecord();
1244  nRecNum != DynaRecording::END;
1245  nRecNum = pRecording->NextRecord(nRecNum))
1246  {
1247  for(nFldNum = pRecording->FirstField(nRecNum);
1248  nFldNum != DynaRecording::END;
1249  nFldNum = pRecording->NextField(nRecNum, nFldNum))
1250  {
1251  tupRec = pRecording->GetField(nRecNum, nFldNum);
1252  fprintf(fp, " %4d %-4d", tupRec.m_nPos, tupRec.m_nSpeed);
1253  }
1254  fprintf(fp, "\n");
1255  }
1256  }
1257 };
1258 
1259 
1260 // -----------------------------------------------------------------------------
1261 // DynaShellCmdTrain Class
1262 // -----------------------------------------------------------------------------
1263 
1264 /*!
1265  * \brief Record dynamixel chain movements.
1266  */
1268 {
1269 public:
1270  /*!
1271  * \brief Default constructor.
1272  */
1274  {
1275  m_sCmdName = "train";
1276  m_sCmdHelpBrief = "Record a training session.";
1277  m_sCmdHelpArgs = "<start_delay> <sample_period>";
1278  m_sCmdHelpDesc = "Record a dynamixel chain training session. "
1279  "The current recording in memory is overwritten.\n"
1280  " <start_delay> Recording start delay (sec).\n"
1281  " <sample_period> Recording sample period (msec).";
1282  }
1283 
1284  /*!
1285  * \brief Default destructor.
1286  */
1287  virtual ~DynaShellCmdTrain() { }
1288 
1289  /*!
1290  * \brief Execute recording.
1291  *
1292  * \param shell Dynamixel shell.
1293  * \param argc Command argument count.
1294  * \param argv Array of arguments.
1295  */
1296  virtual void Exec(DynaShell &shell, int argc, char *argv[])
1297  {
1298  int nStartDelay;
1299  int nSamplePeriod;
1300  int iter;
1301  int nServoId;
1302  DynaServo *pServo;
1303 
1304  //
1305  // Parse arguments
1306  //
1307  TRY( ChkArgCnt(shell, argc) );
1308  TRY( ToInt(shell, argv[0], &nStartDelay) );
1309  TRY( ToInt(shell, argv[1], &nSamplePeriod) );
1310 
1311  //
1312  // Check state
1313  //
1314  TRY( ChkComm(shell) );
1315  TRY( ChkChainNotEmpty(shell) );
1316 
1317 
1318  // initialize recording data
1319  shell.RecordingInit(nSamplePeriod);
1320 
1321  // register servos in chain to be trained with recording header
1322  for(nServoId = shell.m_pDynaChain->IterStartMaster(&iter);
1323  nServoId != DYNA_ID_NONE;
1324  nServoId = shell.m_pDynaChain->IterNextMaster(&iter))
1325  {
1326  pServo = shell.m_pDynaChain->GetServo(nServoId);
1327  shell.m_pRecording->RegisterServoInfo(nServoId, pServo->GetModelNumber());
1328  }
1329 
1330  // record training session
1331  RecordTraining(shell, nStartDelay, nSamplePeriod);
1332  }
1333 
1334  /*!
1335  * \brief Command tab completion generator.
1336  *
1337  * Completes NULL NULL <loop>
1338  *
1339  * \param shell Dynamixel shell.
1340  * \param sText Partial text string to complete.
1341  * \param uTextLen Length of text.
1342  * \param nState Generator state. If FIRST, then initialize any statics.
1343  * \param sContext Generator context (i.e. canonical command path).
1344  *
1345  * \return
1346  * If a first/next match is made, return allocated completed match.\n
1347  * Otherwise return NULL.
1348  */
1349  virtual char *TabCompletion(DynaShell &shell,
1350  const char *sText,
1351  size_t uTextLen,
1352  int nState,
1353  const char *sContext)
1354  {
1355  // direction keyword table
1356  static const char *keywordsLoop[] = {"close", "open"};
1357 
1358  int nArgNum;
1359  const char *s;
1360  char buf[16];
1361 
1362  // argument number of already (expanded) arguments
1363  nArgNum = ReadLine::wc(sContext) - ReadLine::wc(m_sPubName);
1364 
1365  if( nArgNum != 2 )
1366  {
1367  return NULL;
1368  }
1369 
1370  //
1371  // New command argument to complete - initialize.
1372  //
1373  if( nState == ReadLine::FIRST )
1374  {
1375  m_nTabIndex = 0;
1376  }
1377 
1378  while( m_nTabIndex < arraysize(keywordsLoop) )
1379  {
1380  s = keywordsLoop[m_nTabIndex++];
1381 
1382  if( !strncmp(s, sText, uTextLen) )
1383  {
1384  return ReadLine::dupstr(s);
1385  }
1386  }
1387 
1388  // no more matches
1389  return NULL;
1390  }
1391 
1392 protected:
1393  int m_nTabIndex; ///< tab completion: keyword index
1394 
1395  /*!
1396  * \brief Record the movements of a dynamixel chain.
1397  *
1398  * \param shell Dynamixel shell.
1399  * \param nStartDelay Delay in seconds, before recording starts.
1400  * \param nSamplePeriod Recording sample period (ms)
1401  */
1403  int nStartDelay,
1404  int nSamplePeriod)
1405  {
1406  DynaRecording *pRecording = shell.m_pRecording;
1407  int nRecNum;
1408  int iter;
1409  int nServoId;
1410  DynaServo *pServo;
1411  int nCurPos;
1412  int nCurSpeed;
1413  int rc;
1414 
1415  if( nSamplePeriod < 1 )
1416  {
1417  nSamplePeriod = 1;
1418  }
1419 
1420  printf("Starting to record in %u seconds.\n", nStartDelay);
1421  printf(" Recording sample rate: %dms.\n", nSamplePeriod);
1422  printf("Press <CR> to stop recording.\n\n");
1423  fflush(stdout);
1424 
1425  // count down
1426  for(iter=0; iter<nStartDelay; ++iter)
1427  {
1428  printf("%d ", nStartDelay-iter);
1429  fflush(stdout);
1430  sleep(1);
1431  }
1432 
1433  printf("Go\n\n");
1434 
1435  //
1436  // Add record at sampling rate
1437  //
1438  while( (nRecNum = pRecording->AddRecord()) != DynaRecording::END )
1439  {
1440  printf("record[%d]: ", nRecNum);
1441 
1442  //
1443  // Record current position and speed for each master servo in chain.
1444  //
1445  for(nServoId = shell.m_pDynaChain->IterStartMaster(&iter);
1446  nServoId != DYNA_ID_NONE;
1447  nServoId = shell.m_pDynaChain->IterNextMaster(&iter))
1448  {
1449  pServo = shell.m_pDynaChain->GetServo(nServoId);
1450 
1451  // get current position
1452  rc = pServo->ReadCurPos(&nCurPos);
1453 
1454  if( rc != DYNA_OK )
1455  {
1456  shell.Error(rc, "Servo %d: ReadCurPos.", nServoId);
1457  return;
1458  }
1459 
1460  // RDK TODO check alarms for out-of-range servo positions
1461 
1462  // get current speed
1463  rc = pServo->ReadCurSpeed(&nCurSpeed);
1464 
1465  if( rc != DYNA_OK )
1466  {
1467  shell.Error(rc, "Servo %d: ReadCurSpeed.", nServoId);
1468  return;
1469  }
1470 
1471  // and field tuple to record
1472  pRecording->AddFieldTuple(nRecNum, nServoId, nCurPos, nCurSpeed);
1473 
1474  printf("%d %d %d ", nServoId, nCurPos, nCurSpeed);
1475  }
1476 
1477  printf("\n");
1478 
1479  // recording sample period wait and user <CR> stop
1480  if( waitkey(nSamplePeriod) )
1481  {
1482  break;
1483  }
1484  }
1485  }
1486 };
1487 
1488 
1489 // -----------------------------------------------------------------------------
1490 // DynaShellCmdPlay Class
1491 // -----------------------------------------------------------------------------
1492 
1493 /*!
1494  * \brief Record dynamixel chain movements.
1495  */
1497 {
1498 public:
1499  /*!
1500  * \brief Default constructor.
1501  */
1503  {
1504  m_sCmdName = "play";
1505  m_sCmdHelpBrief = "Playback the current dynamixel recording.";
1506  m_sCmdHelpArgs = "<sub_sample> [<speed> [<plot_file>]]";
1507  m_sCmdHelpDesc = "Playback the current recording. "
1508  "The current recording is the last 'train' or 'load' "
1509  "command executed during this shell's session.\n"
1510  " <sub_sample> Number of control sub-sample points.\n"
1511  " <speed> Playback at % of recorded speed.\n"
1512  " Default: 100%\n"
1513  " <loop> Do [not] loop recording.\n"
1514  " One of: loop true noloop false.\n"
1515  " Default: noloop.";
1516  " <plot_file> Output plot file in gnuplot format.\n"
1517  " Default: no file";
1518 
1519  m_nNumSubSamplePts = 0;
1520  m_fSpeedPct = 0.0;
1521  m_fpPlot = NULL;
1522  m_nPlotLineCnt = 0;
1523  }
1524 
1525  /*!
1526  * \brief Default destructor.
1527  */
1528  virtual ~DynaShellCmdPlay() { }
1529 
1530  /*!
1531  * \brief Execute playback of recording.
1532  *
1533  * \param shell Dynamixel shell.
1534  * \param argc Command argument count.
1535  * \param argv Array of arguments.
1536  */
1537  virtual void Exec(DynaShell &shell, int argc, char *argv[])
1538  {
1539  DynaRecording *pRecording = shell.m_pRecording;
1540  int i;
1541  char *sPlotFileName;
1542  int iter;
1543  int nServoId;
1544  DynaServo *pServo;
1545 
1546  //
1547  // Check and parse command-line arguments.
1548  //
1549  TRY( ChkArgCnt(shell, argc) );
1550 
1551  // Is there a recording?
1552  if( (pRecording == NULL) || (pRecording->GetNumOfRecords() == 0) )
1553  {
1554  shell.Error("No recording to play.");
1555  return;
1556  }
1557 
1558  // defaults
1559  m_fSpeedPct = 100.0;
1560  sPlotFileName = NULL;
1561  m_fpPlot = NULL;
1562 
1563  // RDK loop option
1564  //
1565  // Parse arguments
1566  //
1567  for(i=0; i<argc; ++i)
1568  {
1569  switch( i )
1570  {
1571  // sub-sample points
1572  case 0:
1573  TRY( ToInt(shell, argv[i], &m_nNumSubSamplePts) );
1574  // sub-sample period too big - adjust
1575  if( m_nNumSubSamplePts < 1 )
1576  {
1577  m_nNumSubSamplePts = 1;
1578  }
1579  break;
1580  // playback speed (optional)
1581  case 1:
1582  TRY( ToDouble(shell, argv[i], &m_fSpeedPct) );
1583  if( m_fSpeedPct < 0.0 )
1584  {
1585  m_fSpeedPct = 100.0;
1586  }
1587  break;
1588  case 2:
1589  // create gnuplot file (optional)
1590  sPlotFileName = argv[i];
1591  break;
1592  default:
1593  shell.Error("Huh?");
1594  break;
1595  }
1596  }
1597 
1598  //
1599  // Validate recording against chain.
1600  //
1601 
1602  // Does the recording have the same number of servos?
1603  if( pRecording->GetNumOfServosInRecording() !=
1605  {
1606  shell.Error("%d servos in recording != %d master servos in chain.",
1607  pRecording->GetNumOfServosInRecording(),
1609  return;
1610  }
1611 
1612  // Do the servos match in id and model number?
1613  for(nServoId = shell.m_pDynaChain->IterStartMaster(&iter);
1614  nServoId != DYNA_ID_NONE;
1615  nServoId = shell.m_pDynaChain->IterNextMaster(&iter))
1616  {
1617  if( !pRecording->HasServo(nServoId) )
1618  {
1619  shell.Error("Servo %d: Not in recording.");
1620  return;
1621  }
1622 
1623  pServo = shell.m_pDynaChain->GetServo(nServoId);
1624 
1625  if(pRecording->GetServoModelNumber(nServoId) != pServo->GetModelNumber())
1626  {
1627  shell.Error("Servo %d: Recording model number 0x%04x != 0x%04x.",
1628  pRecording->GetServoModelNumber(nServoId),
1629  pServo->GetModelNumber());
1630  return;
1631  }
1632  }
1633 
1634  //
1635  // Open gnuplot file
1636  //
1637  if( sPlotFileName != NULL )
1638  {
1639  if( (m_fpPlot = fopen(sPlotFileName, "w")) == NULL )
1640  {
1641  shell.Error("%s: cannot open.", sPlotFileName);
1642  return;
1643  }
1644  }
1645 
1646  // initialiize
1647  Init(shell);
1648 
1649  // play back the recording
1650  Play(shell);
1651 
1652  // clean up any resources
1653  Cleanup(shell);
1654  }
1655 
1656  /*!
1657  * \brief Command tab completion generator.
1658  *
1659  * Completes NULL 100 <plot_file>
1660  *
1661  * \param shell Dynamixel shell.
1662  * \param sText Partial text string to complete.
1663  * \param uTextLen Length of text.
1664  * \param nState Generator state. If FIRST, then initialize any statics.
1665  * \param sContext Generator context (i.e. canonical command path).
1666  *
1667  * \return
1668  * If a first/next match is made, return allocated completed match.\n
1669  * Otherwise return NULL.
1670  */
1671  virtual char *TabCompletion(DynaShell &shell,
1672  const char *sText,
1673  size_t uTextLen,
1674  int nState,
1675  const char *sContext)
1676  {
1677  int nArgNum;
1678 
1679  // argument number of already (expanded) arguments
1680  nArgNum = ReadLine::wc(sContext) - ReadLine::wc(m_sPubName);
1681 
1682  //
1683  // New command argument to complete - initialize.
1684  //
1685  if( nState == ReadLine::FIRST )
1686  {
1687  m_nTabIndex = 0;
1688  }
1689 
1690  // only match default playback <speed>
1691  if( nArgNum == 1 )
1692  {
1693  if( (m_nTabIndex++ == 0) && !strncmp("100", sText, uTextLen) )
1694  {
1695  return ReadLine::dupstr("100");
1696  }
1697  }
1698 
1699  // <plot_file>
1700  else if( nArgNum == 2 )
1701  {
1702  return ReadLine::FileCompletionGenerator(sText, nState);
1703  }
1704 
1705  return NULL;
1706  }
1707 
1708 protected:
1709  typedef vector< vector<uint_t> > VecCurves;
1710 
1711  int m_nNumSubSamplePts; ///< sub-sample playback control period (msec)
1712  double m_fSpeedPct; ///< playback speed as a % of recorded speed
1713  double m_fSamplePeriod; ///< playback sample period (seconds)
1714  double m_fSubSamplePeriod; ///< control sub-sample period (seconds)
1715  double m_dt; ///< playback delta time (seconds)
1716  VecCurves m_vecCurves; ///< vector of fitted smooth curves
1717  double m_fTAccum; ///< accumulated time
1718  FILE *m_fpPlot; ///< plot data file pointer
1719  int m_nPlotLineCnt; ///< plotted data line count
1720  int m_nTabIndex; ///< tab completion: keyword index
1721 
1722  /*!
1723  * \brief Play back the previously recorded Dynamixel chain motion sequence.
1724  *
1725  * \param shell Dynamixel shell.
1726  */
1727  void Play(DynaShell &shell)
1728  {
1729  DynaRecording *pRecording = shell.m_pRecording;
1730  double dt;
1731  double t;
1732  int nRecNum;
1733  int rc;
1734 
1735  shell.Response("\nPlaying back recording made on %s.\n",
1736  pRecording->GetDate());
1737  shell.Response(" Number of records: %d\n",
1738  pRecording->GetNumOfRecords());
1739  shell.Response(" Playback sample period: %.1lfms.\n", m_fSamplePeriod);
1740  shell.Response(" Sub-sample period: %.3lfms.\n", dt);
1741  shell.Response("Press <CR> to abort recording.\n\n");
1742  fflush(stdout);
1743 
1744  m_fTAccum = 0.0;
1745 
1746  // move to the starting position
1747  MoveToStart(shell);
1748 
1749  //
1750  // Play back the recording.
1751  //
1752  for(nRecNum = pRecording->FirstRecord();
1753  nRecNum != DynaRecording::END;
1754  nRecNum = pRecording->NextRecord(nRecNum))
1755  {
1756  // set record goals
1757  rc = SetRecordGoals(shell, nRecNum);
1758 
1759  if( rc != DYNA_OK )
1760  {
1761  return;
1762  }
1763 
1764  //
1765  // Now control the speed profiles at the sub-sampled rate.
1766  //
1767  for(t=0.0; t<m_fSamplePeriod; t+=dt)
1768  {
1769  // control servos to goals
1770  rc = ControlToGoals(shell, nRecNum, dt);
1771 
1772  if( rc != DYNA_OK )
1773  {
1774  return;
1775  }
1776 
1777  // wait for next time interval or user keypress to abort
1778  if( waitkey(dt) )
1779  {
1780  // RDK could be that user aborted because of badness SecureArm(shell);
1781  return;
1782  }
1783 
1784  m_fTAccum += dt;
1785  }
1786  }
1787  }
1788 
1789  /*!
1790  * \brief Set record goals.
1791  *
1792  * \li Set new PID setpoints
1793  * \li Initiate synchronous move of all servos to the recorded end position.
1794  *
1795  * \param shell Dynamixel shell.
1796  * \param nRecNum Record number in the recording.
1797  *
1798  * \copydoc doc_return_std
1799  */
1800  int SetRecordGoals(DynaShell &shell, int nRecNum)
1801  {
1802  DynaRecord::FieldTuple_T tupRec;
1803  int nFldNum;
1804  int nServoId;
1805  DynaPosTuple_T tupPos[DYNA_ID_NUMOF];
1806  uint_t uCount;
1807  int rc;
1808 
1809  printf("\n goalpos_%d: ", nRecNum);
1810 
1811  //
1812  // Build synchronous write tuple while setting new pid setpoints.
1813  //
1814  for(nFldNum = shell.m_pRecording->FirstField(nRecNum), uCount=0;
1815  nFldNum != DynaRecording::END;
1816  nFldNum = shell.m_pRecording->NextField(nRecNum, nFldNum), ++uCount)
1817  {
1818  nServoId = shell.m_pRecording->GetServoId(nFldNum);
1819 
1820  tupRec = shell.m_pRecording->GetField(nRecNum, nFldNum);
1821 
1822  tupPos[nFldNum].m_nServoId = nServoId;
1823  tupPos[nFldNum].m_nPos = tupRec.m_nPos;
1824 
1825  rc = PidSetPoint(shell, nServoId, tupRec.m_nPos, tupRec.m_nSpeed);
1826 
1827  if( rc < 0 )
1828  {
1829  return rc;
1830  }
1831 
1832  printf("%d %d ", nServoId, tupRec.m_nPos);
1833  }
1834 
1835  printf("\n");
1836 
1837  //
1838  // Move synchronously to new position.
1839  //
1840  rc = shell.m_pDynaChain->SyncMoveTo(tupPos, uCount);
1841 
1842  if( rc < 0 )
1843  {
1844  printf("Error: SyncMoveTo: %s\n", DynaStrError(rc));
1845  return rc;
1846  }
1847 
1848  return DYNA_OK;
1849  }
1850 
1851  /*!
1852  * \brief Control the servos speed to reach the goal positions, hopefully at
1853  * the end of the sampled period and with the sampled speed.
1854  *
1855  * \param shell Dynamixel shell.
1856  * \param nRecNum Record number in the recording.
1857  * \param dt Delta time (seconds).
1858  *
1859  * \copydoc doc_return_std
1860  */
1861  int ControlToGoals(DynaShell &shell, int nRecNum, double dt)
1862  {
1863  int nFldNum;
1864  int nServoId;
1865  DynaServo *pServo;
1866  int nCurPos;
1867  int nCurSpeed;
1868  int nGoalSpeed;
1869  DynaSpeedTuple_T tupSpeed[DYNA_ID_NUMOF];
1870  uint_t uCount;
1871  int rc;
1872 
1873  printf(" speed_%d: ", nRecNum);
1874 
1875  //
1876  // Determine new speed goals and set.
1877  //
1878  for(nFldNum = shell.m_pRecording->FirstField(nRecNum), uCount=0;
1879  nFldNum != DynaRecording::END;
1880  nFldNum = shell.m_pRecording->NextField(nRecNum, nFldNum), ++uCount)
1881  {
1882  nServoId = shell.m_pRecording->GetServoId(nFldNum);
1883 
1884  pServo = shell.m_pDynaChain->GetServo(nServoId);
1885 
1886  // read current servo position
1887  rc = pServo->ReadCurPos(&nCurPos);
1888 
1889  if( rc != DYNA_OK )
1890  {
1891  printf("Error: Servo %d: ReadCurPos: %s\n", nServoId, DynaStrError(rc));
1892  return rc;
1893  }
1894 
1895  // read current speed
1896  rc = pServo->ReadCurSpeed(&nCurSpeed);
1897 
1898  if( rc != DYNA_OK )
1899  {
1900  printf("Error: Servo %d: ReadCurSpeed: %s\n", nServoId,
1901  DynaStrError(rc));
1902  return rc;
1903  }
1904 
1905  // pid control
1906  nGoalSpeed = pServo->GetSpeedPid().Control(nCurSpeed, dt);
1907 
1908  tupSpeed[nFldNum].m_nServoId = nServoId;
1909  tupSpeed[nFldNum].m_nSpeed = nGoalSpeed;
1910 
1911  if( m_fpPlot != NULL )
1912  {
1913  PlotWriteData(shell, nRecNum, dt, nServoId, nCurPos);
1914  }
1915 
1916  printf("%d %d ", nServoId, nGoalSpeed);
1917  }
1918 
1919  printf("\n");
1920 
1921  //
1922  // Synchronously write new goal speeds.
1923  //
1924  rc = shell.m_pDynaChain->SyncWriteGoalSpeed(tupSpeed, uCount);
1925 
1926  if( rc != DYNA_OK )
1927  {
1928  printf("Error: Servo %d: SyncWriteGoalSpeed: %s\n",
1929  nServoId, DynaStrError(rc));
1930  return rc;
1931  }
1932 
1933  return DYNA_OK;
1934  }
1935 
1936  void Init(DynaShell &shell)
1937  {
1938  DynaRecording *pRecording = shell.m_pRecording;
1939  int nNumPidCtlPts = 1; // may become an argument
1940 
1941  // recorded sample period, converted to seconds and scaled to play speed
1942  m_fSamplePeriod = ((double)pRecording->GetSamplePeriod() / 1000.0 ) *
1943  100.0 / m_fSpeedPct;
1944 
1945  // 1 millisecond sample period is as good as it gets
1946  if( m_fSamplePeriod < 0.001 )
1947  {
1948  m_fSamplePeriod = 0.001;
1949  }
1950 
1951  // control sub-sample period (seconds)
1952  m_fSubSamplePeriod = m_fSamplePeriod / (double)m_nNumSubSamplePts;
1953 
1954  if( m_fSubSamplePeriod < 0.001 )
1955  {
1956  m_fSubSamplePeriod = 0.001;
1957  }
1958 
1959  // sub-sample control delta time per step (ms)
1960  m_dt = m_fSubSamplePeriod / (double)nNumPidCtlPts;
1961 
1962  if( m_dt < 0.001 )
1963  {
1964  m_dt = 0.001;
1965  }
1966 
1967  // Transform recording to basis spline curves
1968  SmoothRecordingCurves(shell);
1969 
1970  // initialize speed PIDs
1971  PidInit(shell);
1972 
1973  // initialize any plot output
1974  PlotInit(shell);
1975  }
1976 
1977  void SmoothRecordingCurves(DynaShell &shell)
1978  {
1979  }
1980 
1981  void BSplineCurve(DynaShell &shell, int nFldNum)
1982  {
1983  DynaRecording *pRecording = shell.m_pRecording;
1984  size_t uNumRecs;
1985  double fTLen;
1986  size_t uOrder;
1987  size_t uNumCoeffs;
1988  size_t uNumBreaks;
1989  size_t i, j;
1990  double xi, yi;
1991  double yerr;
1992  double chisq;
1993 
1994  uNumRecs = (size_t)pRecording->GetNumOfRecords();
1995  fTLen = (double)uNumRecs * m_fSamplePeriod - m_fSamplePeriod;
1996 
1997  // TODO coeff + order < numrecs
1998 
1999  uOrder = 4; // cubic b-spline order
2000  uNumCoeffs = 15; // number of bases
2001  uNumBreaks = uNumCoeffs + 2 + uOrder; // number of break points
2002 
2003  // allocate cubic (k=4) bspline workspace
2004  gsl_bspline_workspace *pWsBspline = gsl_bspline_alloc(uOrder, uNumBreaks);
2005 
2006  // allocate working vectors and matrices
2007  gsl_vector *B = gsl_vector_alloc(uNumCoeffs);
2008  gsl_vector *y = gsl_vector_alloc(uNumRecs);
2009  gsl_matrix *X = gsl_matrix_alloc(uNumRecs, uNumCoeffs);
2010  gsl_vector *c = gsl_vector_alloc(uNumCoeffs);
2011  gsl_vector *w = gsl_vector_alloc(uNumRecs);
2012  gsl_matrix *Cov = gsl_matrix_alloc(uNumCoeffs, uNumCoeffs);
2013 
2014  // allocate x workspace
2015  gsl_multifit_linear_workspace *pWsMFitLinear =
2016  gsl_multifit_linear_alloc(uNumRecs, uNumCoeffs);
2017 
2018  // use uniform breakpoints on [0, len]
2019  gsl_bspline_knots_uniform(0.0, fTLen, pWsBspline);
2020 
2021  // construct the fit matrix X
2022  for(i=0, xi=0.0; i<uNumRecs; ++i, xi+=m_fSamplePeriod)
2023  {
2024  gsl_vector_set(y, i, (*pRecording)[i][nFldNum].m_nPos);
2025 
2026  // compute B_j(xi) for all j
2027  gsl_bspline_eval(xi, B, pWsBspline);
2028 
2029  /* fill in row i of X */
2030  for(j=0; j<uNumCoeffs; ++j)
2031  {
2032  double Bj = gsl_vector_get(B, j);
2033  gsl_matrix_set(X, i, j, Bj);
2034  }
2035  }
2036 
2037  // fit
2038  gsl_multifit_wlinear(X, w, y, c, Cov, &chisq, pWsMFitLinear);
2039 
2040  // smooth curve
2041 
2042  for(xi=0.0; xi<fTLen; xi+=m_fSubSamplePeriod)
2043  {
2044  gsl_bspline_eval(xi, B, pWsBspline);
2045  gsl_multifit_linear_est(B, c, Cov, &yi, &yerr);
2046  }
2047 
2048  // free
2049  gsl_bspline_free(pWsBspline);
2050  gsl_vector_free(B);
2051  gsl_vector_free(y);
2052  gsl_matrix_free(X);
2053  gsl_vector_free(c);
2054  gsl_vector_free(w);
2055  gsl_matrix_free(Cov);
2056  gsl_multifit_linear_free(pWsMFitLinear);
2057  }
2058 
2059  /*!
2060  * \brief Initialize servo PIDs.
2061  *
2062  * \param shell Dynamixel shell.
2063  */
2064  void PidInit(DynaShell &shell)
2065  {
2066  int iter;
2067  int nServoId;
2068  DynaServo *pServo;
2069 
2070  for(nServoId = shell.m_pDynaChain->IterStartMaster(&iter);
2071  nServoId != DYNA_ID_NONE;
2072  nServoId = shell.m_pDynaChain->IterNextMaster(&iter))
2073  {
2074  pServo = shell.m_pDynaChain->GetServo(nServoId);
2075  pServo->GetSpeedPid().InitControl();
2076  }
2077  }
2078 
2079  /*!
2080  * \brief Specify (new) PID goal position setpoint.
2081  *
2082  * \param shell Dynamixel shell.
2083  * \param nServoId Servo Id.
2084  * \param uGoalPos Goal odometer position.
2085  * \param nGoalSpeed Goal speed. Direction is important since a position may
2086  * be obtained in two rotation directions.
2087  *
2088  * \copydoc doc_return_std
2089  */
2091  int nServoId,
2092  int nGoalPos,
2093  int nGoalSpeed)
2094  {
2095  int nDir = nGoalSpeed < 0? DYNA_DIR_CW: DYNA_DIR_CCW;
2096  DynaServo *pServo;
2097  int nCurPos;
2098  int nOdPos;
2099  int rc;
2100 
2101  pServo = shell.m_pDynaChain->GetServo(nServoId);
2102 
2103  // read current servo position
2104  rc = pServo->ReadCurPos(&nCurPos);
2105 
2106  if( rc != DYNA_OK )
2107  {
2108  printf("Error: Servo %d: ReadCurPos: %s\n", nServoId, DynaStrError(rc));
2109  return rc;
2110  }
2111 
2112  // set new setpoint
2113  pServo->GetSpeedPid().SpecifySetPoint(nOdPos);
2114 
2115  return DYNA_OK;
2116  }
2117 
2118  /*!
2119  * \brief Clean up any allocated playback resources.
2120  *
2121  * \param shell Dynamixel shell.
2122  */
2123  void Cleanup(DynaShell &shell)
2124  {
2125  if( m_fpPlot != NULL )
2126  {
2127  fclose(m_fpPlot);
2128  m_fpPlot = NULL;
2129  }
2130 
2131  m_vecCurves.clear();
2132  }
2133 
2134  bool MoveToStart(DynaShell &shell)
2135  {
2136  return true;
2137  }
2138 
2139  /*!
2140  * \brief Secure arm in safe postition if posible.
2141  *
2142  * \param pDynaChain Pointer to Dynamixel chain handle.
2143  */
2144  void SecureArm(DynaShell &shell)
2145  {
2146  shell.m_pDynaChain->Freeze();
2147  }
2148 
2149  /*!
2150  * \brief Initialize plot data output.
2151  *
2152  * \param shell Dynamixel shell.
2153  */
2154  void PlotInit(DynaShell &shell)
2155  {
2156  if( m_fpPlot != NULL )
2157  {
2158  m_nPlotLineCnt = 0;
2159 
2160  fprintf(m_fpPlot, "#\n");
2161  fprintf(m_fpPlot, "# Dynamixel Play Back Plot Data\n");
2162  fprintf(m_fpPlot, "# Recorded: %s\n",
2163  shell.m_pRecording->GetDate());
2164  fprintf(m_fpPlot, "# Number of records: %d\n",
2165  shell.m_pRecording->GetNumOfRecords());
2166  fprintf(m_fpPlot, "# Recording sample period: %dms\n",
2167  shell.m_pRecording->GetSamplePeriod());
2168  fprintf(m_fpPlot, "# Playback sample period: %.3f\n",
2169  m_fSamplePeriod);
2170  fprintf(m_fpPlot, "# Playback delta time step: %.4lfs\n", m_dt);
2171  fprintf(m_fpPlot, "#\n");
2172  fprintf(m_fpPlot, "#Fields:\n");
2173  fprintf(m_fpPlot, "# time servo goal_pos goal_speed cur_pos "
2174  "cur_speed\n");
2175  fprintf(m_fpPlot, "#\n");
2176  }
2177  }
2178 
2179  /*!
2180  * \brief Write plot data to plot file.
2181  *
2182  * \param shell Dynamixel shell.
2183  * \param nRecNum Record number.
2184  * \param dt Playback delta time step (seconds).
2185  * \param nServoid Servo Id.
2186  * \param nCurPos Servo current position.
2187  */
2189  int nRecNum,
2190  double dt,
2191  int nServoId,
2192  int nCurPos)
2193  {
2194  DynaServo *pServo;
2195 
2196  if( m_fpPlot != NULL )
2197  {
2198  pServo = shell.m_pDynaChain->GetServo(nServoId);
2199  fprintf(m_fpPlot, "%.4f %d %u %d %u\n",
2200  m_fTAccum,
2201  nServoId,
2202  (uint_t)pServo->GetSpeedPid().GetSP(),
2203  nCurPos,
2204  (uint_t)pServo->GetSpeedPid().GetOutput());
2205  }
2206  }
2207 
2208  /*!
2209  * \brief Wait until all servos in chain have stopped moving utility function.
2210  *
2211  * \note Currently this member function is not used. But could be promoted to
2212  * a usefull shell command.
2213  *
2214  * \param shell Dynamixel shell.
2215  */
2216  void WaitStop(DynaShell &shell)
2217  {
2218  int nNumServos;
2219  int nMoving;
2220  int iter;
2221  int nServoId;
2222  DynaServo *pServo;
2223  bool bIsMoving;
2224  int rc;
2225 
2226  nNumServos = (int)shell.m_pDynaChain->GetNumberInChain();
2227  nMoving = nNumServos;
2228 
2229  while( nMoving > 0 )
2230  {
2231  //printf(".");
2232 
2233  for(nServoId = shell.m_pDynaChain->IterStart(&iter);
2234  nServoId != DYNA_ID_NONE;
2235  nServoId = shell.m_pDynaChain->IterNext(&iter))
2236  {
2237  pServo = shell.m_pDynaChain->GetServo(nServoId);
2238 
2239  if( ((rc = pServo->ReadIsMoving(&bIsMoving)) == DYNA_OK) && bIsMoving )
2240  {
2241  ++nMoving;
2242  }
2243  }
2244  }
2245 
2246  //printf("\n");
2247  }
2248 };
2249 
2250 
2251 //-----------------------------------------------------------------------------
2252 // Public Interface
2253 //-----------------------------------------------------------------------------
2254 
2255 /*!
2256  * \brief Publish shell servo commands to shell.
2257  *
2258  * \brief shell Dynamixel shell.
2259  */
2261 {
2262  shell.PublishCommand("get", new DynaShellCmdGetPid());
2263  shell.PublishCommand("set", new DynaShellCmdSetPid());
2264  shell.PublishCommand("load", new DynaShellCmdLoadRecording());
2265  shell.PublishCommand("save", new DynaShellCmdSaveRecording());
2266  shell.PublishCommand(NULL, new DynaShellCmdTrain());
2267  shell.PublishCommand(NULL, new DynaShellCmdPlay());
2268 }
RoadNarrows Dynamixel Bus Communications Abstract Base Class Interface.
bool GetLine()
Read the next line of data.
int m_nSamplePeriod
SAMPLE_PERIOD value.
virtual int GetNumOfRecords()
Get the number of records in the recording.
char * newstr(const char *s)
Allocate new duplicated string.
static bool_t waitkey(int nMSec)
Block, waiting for either timeout or user keyboard press.
int ParseRecordedData(DynaShell &shell)
Parse recorded data and save in working recording.
Load recording from file.
Synchronous Write Speed Tuple Structure.
Definition: DynaTypes.h:314
double m_fTAccum
accumulated time
void WaitStop(DynaShell &shell)
Wait until all servos in chain have stopped moving utility function.
virtual int SyncWriteGoalSpeed(DynaSpeedTuple_T tuplesSpeed[], uint_t uCount)
Synchronous write new goal speed for servos.
Definition: DynaChain.cxx:940
virtual void Exec(DynaShell &shell, int argc, char *argv[])
Execute save recording.
#define DYNA_OK
not an error, success
Definition: Dynamixel.h:78
int m_nNumServos
NUM_SERVOS value.
virtual int SyncMoveTo(DynaPosTuple_T tuplesPos[], uint_t uCount)
Synchronous move servos to new goal positions in tuple list.
Definition: DynaChain.cxx:513
char * GetNextWord()
Get the next word in line buffer.
virtual int Freeze()
Freeze all servos at current position.
Definition: DynaChain.cxx:751
Record Field Tuple Structure Type.
int m_nTabIndex
tab completion: keyword index
DynaShellCmdSaveRecording()
Default constructor.
virtual int AddRecord()
Add a new empty record to the recording.
virtual ~DynaShellCmdTrain()
Default destructor.
char * GetFirstWord()
Get the first word in line buffer.
Get the PID values for the given servos.
Dynamixel shell utilities.
Definition: t.py:1
virtual void Exec(DynaShell &shell, int argc, char *argv[])
Execute recording.
void RecordingReplace(DynaRecording *pNewRecording)
Replace recording.
Definition: dynashell.h:397
The Dynamixel Speed PID Class.
virtual int AddFieldTuple(int nRecNum, int nServoId, int nPos, int nSpeed)
Add new recording field tuple.
void PublishShellTrainCommands(DynaShell &shell)
Publish shell servo commands to shell.
Save recording to file.
void Save(DynaShell &shell, FILE *fp)
Save recording.
DynaRecording * m_pRecording
working and new recording
virtual uint_t GetNumberOfMastersInChain()
Get the number of servos currently in chain.
Definition: DynaChain.cxx:182
RoadNarrows Dynamixel Servo Chain Container Base Class Interface.
double m_dt
playback delta time (seconds)
void SecureArm(DynaShell &shell)
Secure arm in safe postition if posible.
double m_fSpeedPct
playback speed as a % of recorded speed
Record dynamixel chain movements.
int PidSetPoint(DynaShell &shell, int nServoId, int nGoalPos, int nGoalSpeed)
Specify (new) PID goal position setpoint.
static const int FIRST
first state
virtual ~DynaShellCmdPlay()
Default destructor.
size_t m_sizeBuf
size of input buffer
virtual const int GetServoId(int nFldNum)
Get the servo id associated with the given field number.
#define DYNA_ID_MIN
minimum servo id
Definition: Dynamixel.h:146
#define DYNA_DIR_CW
clockwise direction
Definition: Dynamixel.h:191
static char * dupstr(const string &str)
Duplicate string.
Dynamixel shell command abstract base class.
Definition: dynashell_cmd.h:80
virtual int SetSamplePeriod(int nSamplePeriod)
Set the sample period of the recording.
#define DYNA_ECODE_PARSE
Shell parse error.
Definition: Dynamixel.h:101
virtual int IterStartMaster(int *pIter)
Start iteration master servos over of entire servo chain.
Definition: DynaChain.cxx:1036
#define DYNA_ID_NUMOF
number of unique servo id&#39;s
Definition: Dynamixel.h:148
Miscellaneous collection of useful utilities.
virtual int IterNextMaster(int *pIter)
Next iteration of master servos over of entire servo chain.
Definition: DynaChain.cxx:1058
virtual uint_t GetNumberInChain() const
Get the number of servos currently in chain.
Definition: DynaChain.h:140
int Init(DynaShell &shell, char *sFileName)
Initialize data prior to file loading.
DynaRecording * m_pRecording
dynamixel recording
Definition: dynashell.h:362
Dynamixel Servo Abstract Base Class.
Definition: DynaServo.h:78
virtual uint_t GetServoModelNumber(int nServoId)
Get the registered servo model number.
void Cleanup(DynaShell &shell)
Clean up any allocated playback resources.
const char * DynaStrError(int ecode)
Get the error string describing the Dynamixel error code.
Definition: DynaError.cxx:141
void Cleanup(DynaShell &shell)
Clean up data after to file loading parsing.
static const int END
past-the-end mark
int m_nTabServoId
tab completion: current servo id
DynaShellCmdPlay()
Default constructor.
#define DYNA_ID_MAX
maximum servo id
Definition: Dynamixel.h:147
virtual int GetSamplePeriod() const
Get the sample period of the recording.
virtual void SetDate(const char *sDate)
static int wc(const string &str)
Count the words in the string.
void Response(const char *sFmt,...)
Print formatted success response.
Definition: dynashell.cxx:763
char * m_sField
current field being parsed
int m_nTabIndex
tab completion: keyword index
bool LineHasNoData()
Test if line is all white space or a comment.
char * m_sCursor
current line parse cursor
virtual const char * GetDate() const
Get the recording data.
FILE * m_fp
open file pointer
#define DYNA_DIR_CCW
counterclockwise direction
Definition: Dynamixel.h:193
void Warning(const char *sFmt,...)
Issue warning.
Definition: dynashell.cxx:781
The Dynamixel Recording Class.
DynaShellCmdLoadRecording()
Default constructor.
virtual void doExec(DynaShell &shell, DynaServo *pServo, double fKp, double fKi, double fKd)
Get servo PID parameters.
int ParseServoList(DynaShell &shell)
Parse servo list and save in working recording.
virtual int IterStart(int *pIter)
Start iteration over of entire servo chain.
Definition: DynaChain.cxx:1008
#define DYNA_ECODE_BADF
no comm object or not open
Definition: Dynamixel.h:88
virtual DynaServo * GetServo(int nServoId)
Definition: DynaChain.h:129
char * GetEolPhrase()
Get the end-of-line phrase.
int ParseIntField(DynaShell &shell, const char *sWord, int *pFieldVal)
Parse integer field.
virtual int FirstRecord()
Get the first record number in the recording.
void PlotWriteData(DynaShell &shell, int nRecNum, double dt, int nServoId, int nCurPos)
Write plot data to plot file.
double m_fSamplePeriod
playback sample period (seconds)
VecCurves m_vecCurves
vector of fitted smooth curves
virtual ~DynaShellCmdLoadRecording()
Default destructor.
virtual void doExec(DynaShell &shell, DynaServo *pServo)
Get servo PID parameters.
int m_nColNum
column number in line
void PlotInit(DynaShell &shell)
Initialize plot data output.
int m_nSpeed
speed
Definition: DynaTypes.h:317
int m_nPlotLineCnt
plotted data line count
int m_nServoId
servo id
Definition: DynaTypes.h:306
virtual void Exec(DynaShell &shell, int argc, char *argv[])
Execute load recording.
RoadNarrows Dynamixel Archetype Servo Abstract Base Class.
Dynamixel chain input command abstract base class.
virtual void Exec(DynaShell &shell, int argc, char *argv[])
Execute playback of recording.
Record dynamixel chain movements.
int m_nServoId
servo id
Definition: DynaTypes.h:316
virtual int NextField(int nRecNum, int nFldNum)
Get the next field number in the record after the given field number.
virtual int GetNumOfServosInRecording()
Get the number of servos in the recording.
virtual int NextRecord(int nRecNum)
Get the next record number in the recording after the given record number.
int m_nPos
position
Definition: DynaTypes.h:307
DynaShellCmdTrain()
Default constructor.
#define TRY(boolexpr,...)
Try boolean expression.
Definition: dynashell_cmd.h:89
Get the PID values for the given servos.
void Exec(DynaShell &shell, int argc, char *argv[])
Execute &#39;read-like&#39; command on servos.
void PublishCommand(const char *sParent, DynaShellCmd *pNewCmd)
Publish new command to shell.
Definition: dynashell.cxx:192
RoadNarrows Dynamixel Top-Level Package Header File.
virtual uint_t GetModelNumber() const
Get servo model number.
Definition: DynaServo.h:125
virtual const DynaRecord::FieldTuple_T & GetField(const int nRecNum, const int nFldNum) const
Get the given recorded field tuple.
The dynashell Command Class Interface.
virtual uint_t GetServoId() const
Get servo id.
Definition: DynaServo.h:155
virtual bool HasServo(int nServoId)
Check if the given servo is in the list of registered servos in the recording header.
virtual char * TabCompletion(DynaShell &shell, const char *sText, size_t uTextLen, int nState, const char *sContext)
Command tab completion generator.
void RecordTraining(DynaShell &shell, int nStartDelay, int nSamplePeriod)
Record the movements of a dynamixel chain.
#define DYNA_ID_NONE
no servo id
Definition: Dynamixel.h:145
virtual int FirstField(int nRecNum)
Get the first field number in the given record.
virtual int RegisterServoInfo(int nServoId, uint_t uModelNum)
Register servo information in recording header.
virtual char * TabCompletion(DynaShell &shell, const char *sText, size_t uTextLen, int nState, const char *sContext)
Command tab completion generator.
double m_fSubSamplePeriod
control sub-sample period (seconds)
virtual int IterNext(int *pIter)
Next iteration over of entire servo chain.
Definition: DynaChain.cxx:1020
DynaChain * m_pDynaChain
dynamixel chain
Definition: dynashell.h:360
void Error(int rc, const char *sFmt,...)
Raise error on dynamixel error code.
Definition: dynashell.cxx:808
int ControlToGoals(DynaShell &shell, int nRecNum, double dt)
Control the servos speed to reach the goal positions, hopefully at the end of the sampled period and ...
int m_nNumRecords
NUM_RECORDS value.
static char * FileCompletionGenerator(const char *sText, int nState)
File name tab completion generator.
void SetDateField(DynaShell &shell, const char *sDate)
Set date value.
Position Tuple Structure.
Definition: DynaTypes.h:304
FILE * m_fpPlot
plot data file pointer
virtual char * TabCompletion(DynaShell &shell, const char *sText, size_t uTextLen, int nState, const char *sContext)
Command tab completion generator.
virtual char * TabCompletion(DynaShell &shell, const char *sText, size_t uTextLen, int nState, const char *sContext)
Command tab completion generator.
int m_nTabIter
tab completion: servo id iterator
void PidInit(DynaShell &shell)
Initialize servo PIDs.
void Load(DynaShell &shell)
Load recording.
virtual char * TabCompletion(DynaShell &shell, const char *sText, size_t uTextLen, int nState, const char *sContext)
Command tab completion generator.
void Play(DynaShell &shell)
Play back the previously recorded Dynamixel chain motion sequence.
void RecordingInit(int nSamplePeriod, const char *sDate=NULL)
(Re)Initialize recording.
Definition: dynashell.h:383
int SetRecordGoals(DynaShell &shell, int nRecNum)
Set record goals.
The simple dynashell declarations.
int m_nNumSubSamplePts
sub-sample playback control period (msec)
static const int MaxRecords
maximum number of records
virtual ~DynaShellCmdSaveRecording()
Default destructor.