RTXI  2.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
data_recorder.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 Georgia Institute of Technology, University of Utah, Weill Cornell Medical College
3 
4  This program is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program. If not, see <http://www.gnu.org/licenses/>.
16 
17 */
18 
19 #include <rtxi_config.h>
20 #include <cstring>
21 #include <string>
22 #include <unistd.h>
23 #include <compiler.h>
24 #include <debug.h>
25 #include <main_window.h>
26 #include <sstream>
27 #include <workspace.h>
28 #include <data_recorder.h>
29 #include <iostream>
30 #include <pthread.h>
31 
32 #define QFileExistsEvent (QEvent::User+0)
33 #define QSetFileNameEditEvent (QEvent::User+1)
34 #define QDisableGroupsEvent (QEvent::User+2)
35 #define QEnableGroupsEvent (QEvent::User+3)
36 
37 #define TAG_SIZE 1024
38 
40 {
41  long long index;
42  double value;
43 };
44 
45 // Debug for event handling
46 QDebug operator<<(QDebug str, const QEvent * ev)
47 {
48  static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type");
49  str << "QEvent";
50  if (ev)
51  {
52  QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
53  if (!name.isEmpty())
54  str << name;
55  else
56  str << ev->type();
57  }
58  else
59  str << (void*)ev;
60  return str.maybeSpace();
61 }
62 
63 struct find_daq_t
64 {
65  int index;
67 };
68 
69 static void findDAQDevice(DAQ::Device *dev,void *arg)
70 {
71  struct find_daq_t *info = static_cast<struct find_daq_t *>(arg);
72  if(!info->index)
73  info->device = dev;
74  info->index--;
75 }
76 
77 namespace
78 {
79 void buildBlockPtrList(IO::Block *block, void *arg)
80 {
81  std::vector<IO::Block *> *list = reinterpret_cast<std::vector<IO::Block *> *> (arg);
82  list->push_back(block);
83 };
84 
85 struct FileExistsEventData
86 {
87  QString filename;
88  int response;
89  QWaitCondition done;
90 };
91 
92 struct SetFileNameEditEventData
93 {
94  QString filename;
95  QWaitCondition done;
96 };
97 
98 class InsertChannelEvent: public RT::Event
99 {
100 public:
101  InsertChannelEvent(bool &, RT::List<DataRecorder::Channel> &,
103  ~InsertChannelEvent(void);
104  int callback(void);
105 
106 private:
107  bool &recording;
110  DataRecorder::Channel &channel;
111 }; // class InsertChannelEvent
112 
113 class RemoveChannelEvent: public RT::Event
114 {
115 public:
116  RemoveChannelEvent(bool &, RT::List<DataRecorder::Channel> &,
118  ~RemoveChannelEvent(void);
119  int callback(void);
120 
121 private:
122  bool &recording;
124  DataRecorder::Channel &channel;
125 }; // class RemoveChannelEvent
126 
127 class OpenFileEvent: public RT::Event
128 {
129 public:
130  OpenFileEvent(QString &, AtomicFifo &);
131  ~OpenFileEvent(void);
132  int callback(void);
133 
134 private:
135  QString &filename;
136  AtomicFifo &fifo;
137 }; // class OpenFileEvent
138 
139 class StartRecordingEvent: public RT::Event
140 {
141 public:
142  StartRecordingEvent(bool &, AtomicFifo &);
143  ~StartRecordingEvent(void);
144  int callback(void);
145 
146 private:
147  bool &recording;
148  AtomicFifo &fifo;
149 }; // class StartRecordingEvent
150 
151 class StopRecordingEvent: public RT::Event
152 {
153 public:
154  StopRecordingEvent(bool &, AtomicFifo &);
155  ~StopRecordingEvent(void);
156  int callback(void);
157 
158 private:
159  bool &recording;
160  AtomicFifo &fifo;
161 }; //class StopRecordingEvent
162 
163 class AsyncDataEvent: public RT::Event
164 {
165 public:
166  AsyncDataEvent(const double *, size_t, AtomicFifo &);
167  ~AsyncDataEvent(void);
168  int callback(void);
169 
170 private:
171  const double *data;
172  size_t size;
173  AtomicFifo &fifo;
174 }; // class AsyncDataEvent
175 
176 class DoneEvent: public RT::Event
177 {
178 public:
179  DoneEvent(AtomicFifo &);
180  ~DoneEvent(void);
181  int callback(void);
182 
183 private:
184  AtomicFifo &fifo;
185 }; // class DoneEvent
186 }; // namespace
187 
188 InsertChannelEvent::InsertChannelEvent(bool &r, RT::List<DataRecorder::Channel> & l,
190  recording(r), channels(l), end(e), channel(c)
191 {
192 }
193 
194 InsertChannelEvent::~InsertChannelEvent(void)
195 {
196 }
197 
198 int InsertChannelEvent::callback(void)
199 {
200  if(recording)
201  return -1;
202  channels.insertRT(end, channel);
203  return 0;
204 }
205 
206 RemoveChannelEvent::RemoveChannelEvent(bool &r, RT::List<DataRecorder::Channel> & l,
208  recording(r), channels(l), channel(c)
209 {
210 }
211 
212 RemoveChannelEvent::~RemoveChannelEvent(void)
213 {
214 }
215 
216 int RemoveChannelEvent::callback(void)
217 {
218  if (recording)
219  return -1;
220  channels.removeRT(channel);
221  return 0;
222 }
223 
224 OpenFileEvent::OpenFileEvent(QString &n, AtomicFifo &f) :
225  filename(n), fifo(f)
226 {
227 }
228 
229 OpenFileEvent::~OpenFileEvent(void)
230 {
231 }
232 
233 int OpenFileEvent::callback(void)
234 {
236  token.type = DataRecorder::OPEN;
237  token.size = filename.length() + 1;
238  token.time = RT::OS::getTime();
239  fifo.write(&token, sizeof(token));
240  fifo.write(filename.toLatin1().constData(), token.size);
241  return 0;
242 }
243 
244 StartRecordingEvent::StartRecordingEvent(bool &r, AtomicFifo &f) :
245  recording(r), fifo(f)
246 {
247 }
248 
249 StartRecordingEvent::~StartRecordingEvent(void)
250 {
251 }
252 
253 int StartRecordingEvent::callback(void)
254 {
256  recording = true;
257  token.type = DataRecorder::START;
258  token.size = 0;
259  token.time = RT::OS::getTime();
260  fifo.write(&token, sizeof(token));
261  return 0;
262 }
263 
264 StopRecordingEvent::StopRecordingEvent(bool &r, AtomicFifo &f) :
265  recording(r), fifo(f)
266 {
267 }
268 
269 StopRecordingEvent::~StopRecordingEvent(void)
270 {
271 }
272 
273 int StopRecordingEvent::callback(void)
274 {
276  recording = false;
277  token.type = DataRecorder::STOP;
278  token.size = 0;
279  token.time = RT::OS::getTime();
280  fifo.write(&token, sizeof(token));
281  return 0;
282 }
283 
284 AsyncDataEvent::AsyncDataEvent(const double *d, size_t s, AtomicFifo &f) :
285  data(d), size(s), fifo(f)
286 {
287 }
288 
289 AsyncDataEvent::~AsyncDataEvent(void)
290 {
291 }
292 
293 int AsyncDataEvent::callback(void)
294 {
296  token.type = DataRecorder::ASYNC;
297  token.size = size * sizeof(double);
298  token.time = RT::OS::getTime();
299  fifo.write(&token, sizeof(token));
300  fifo.write(data, token.size);
301  return 1;
302 }
303 
304 DoneEvent::DoneEvent(AtomicFifo &f) :
305  fifo(f)
306 {
307 }
308 
309 DoneEvent::~DoneEvent(void)
310 {
311 }
312 
313 int DoneEvent::callback(void)
314 {
316  token.type = DataRecorder::DONE;
317  token.size = 0;
318  token.time = RT::OS::getTime();
319  fifo.write(&token, sizeof(token));
320  return 0;
321 }
322 
323 DataRecorder::CustomEvent::CustomEvent(QEvent::Type type) : QEvent(type)
324 {
325  data = 0;
326 }
327 
329 {
330  data = ptr;
331 }
332 
334 {
335  return data;
336 }
337 
339 {
341  if (RT::OS::isRealtime())
343  else
345 }
346 
348 {
350  if (RT::OS::isRealtime())
352  else
354 }
355 
356 void DataRecorder::openFile(const QString& filename)
357 {
359  event.setParam("filename", const_cast<char *> (filename.toLatin1().constData()));
360  if (RT::OS::isRealtime())
362  else
364 }
365 
366 void DataRecorder::postAsyncData(const double *data, size_t size)
367 {
369  event.setParam("data", const_cast<double *> (data));
370  event.setParam("size", &size);
371  if (RT::OS::isRealtime())
373  else
375 }
376 
378 {
379 }
380 
382 {
383 }
384 
385 DataRecorder::Panel::Panel(QWidget *parent, size_t buffersize) :
386  QWidget(parent), RT::Thread(RT::Thread::MinimumPriority), fifo(buffersize), recording(false)
387 {
388  setWhatsThis(
389  "<p><b>Data Recorder:</b><br>The Data Recorder writes data to an HDF5 file format "
390  "All available signals for saving to file are automatically detected. Currently "
391  "loaded user modules are listed in the \"Block\" drop-down box. Available DAQ cards "
392  "are listed here as /proc/analogy/devices. Use the \"Type\" and \"Channel\" drop-down boxes "
393  "to select the signals that you want to save. Use the left and right arrow buttons to "
394  "add these signals to the file. You may select a downsampling rate that is applied "
395  "to the real-time period for execution (set in the System Control Panel). The real-time "
396  "period and the data downsampling rate are both saved as metadata in the HDF5 file "
397  "so that you can reconstruct your data correctly. The current recording status of "
398  "the Data Recorder is shown at the bottom.</p>");
399 
400  // Make Mdi
401  subWindow = new QMdiSubWindow;
402  subWindow->setWindowIcon(QIcon("/usr/local/share/rtxi/RTXI-widget-icon.png"));
403  subWindow->setAttribute(Qt::WA_DeleteOnClose);
404  subWindow->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint |
405  Qt::WindowMinimizeButtonHint);
407 
408  // Create main layout
409  QGridLayout *layout = new QGridLayout;
410 
411  // Create child widget and layout for channel selection
412  channelGroup = new QGroupBox(tr("Channel Selection"));
413  QVBoxLayout *channelLayout = new QVBoxLayout;
414 
415  // Create elements for channel box
416  channelLayout->addWidget(new QLabel(tr("Block:")));
417  blockList = new QComboBox;
418  channelLayout->addWidget(blockList);
419  QObject::connect(blockList,SIGNAL(activated(int)), this, SLOT(buildChannelList(void)));
420 
421  channelLayout->addWidget(new QLabel(tr("Type:")));
422  typeList = new QComboBox;
423  channelLayout->addWidget(typeList);
424  typeList->addItem("Input");
425  typeList->addItem("Output");
426  typeList->addItem("Parameter");
427  typeList->addItem("State");
428  typeList->addItem("Event");
429  QObject::connect(typeList,SIGNAL(activated(int)),this,SLOT(buildChannelList(void)));
430 
431  channelLayout->addWidget(new QLabel(tr("Channel:")));
432  channelList = new QComboBox;
433  channelLayout->addWidget(channelList);
434 
435  // Attach layout to child widget
436  channelGroup->setLayout(channelLayout);
437 
438  // Create elements for arrow
439  rButton = new QPushButton("Add");
440  channelLayout->addWidget(rButton);
441  QObject::connect(rButton,SIGNAL(released(void)),this,SLOT(insertChannel(void)));
442  rButton->setEnabled(false);
443  lButton = new QPushButton("Remove");
444  channelLayout->addWidget(lButton);
445  QObject::connect(lButton,SIGNAL(released(void)),this,SLOT(removeChannel(void)));
446  lButton->setEnabled(false);
447 
448  // Timestamp
449  stampGroup = new QGroupBox(tr("Tag Data"));
450  QHBoxLayout *stampLayout = new QHBoxLayout;
451 
452  // Add timestamp elements
453  timeStampEdit = new QLineEdit;
454  stampLayout->addWidget(timeStampEdit);
455  addTag = new QPushButton(tr("Tag"));
456  stampLayout->addWidget(addTag);
457  QObject::connect(addTag,SIGNAL(released(void)),this,SLOT(addNewTag(void)));
458 
459  // Attach layout to child widget
460  stampGroup->setLayout(stampLayout);
461 
462  // Create child widget and layout
463  sampleGroup = new QGroupBox(tr("Trial Metadata"));
464  QHBoxLayout *sampleLayout = new QHBoxLayout;
465 
466  // create elements for sample box
467  trialNumLbl = new QLabel;
468  trialNumLbl->setText("Trial #:");
469  trialNumLbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
470  sampleLayout->addWidget(trialNumLbl);
471  trialNum = new QLabel;
472  trialNum->setText("0");
473  trialNum->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
474  sampleLayout->addWidget(trialNum);
475 
476  trialLengthLbl = new QLabel;
477  trialLengthLbl->setText("Trial Length (s):");
478  sampleLayout->addWidget(trialLengthLbl);
479  trialLength = new QLabel;
480  trialLength->setText("No data recorded");
481  trialLength->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
482  sampleLayout->addWidget(trialLength);
483 
484  fileSizeLbl = new QLabel;
485  fileSizeLbl->setText("File Size (MB):");
486  sampleLayout->addWidget(fileSizeLbl);
487  fileSize = new QLabel;
488  fileSize->setText("No data recorded");
489  sampleLayout->addWidget(fileSize);
490  fileSize->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
491 
492  // Attach layout to child widget
493  sampleGroup->setLayout(sampleLayout);
494 
495  // Create child widget and layout for file control
496  fileGroup = new QGroupBox(tr("File Control"));
497  QHBoxLayout *fileLayout = new QHBoxLayout;
498 
499  // Create elements for file control
500  fileLayout->addWidget(new QLabel(tr("File Name:")));
501  fileNameEdit = new QLineEdit;
502  fileNameEdit->setReadOnly(true);
503  fileLayout->addWidget(fileNameEdit);
504  QPushButton *fileChangeButton = new QPushButton("Choose File");
505  fileLayout->addWidget(fileChangeButton);
506  QObject::connect(fileChangeButton,SIGNAL(released(void)),this,SLOT(changeDataFile(void)));
507 
508  fileLayout->addWidget(new QLabel(tr("Downsample \nRate:")));
509  downsampleSpin = new QSpinBox(this);
510  downsampleSpin->setMinimum(1);
511  downsampleSpin->setMaximum(500);
512  fileLayout->addWidget(downsampleSpin);
513  QObject::connect(downsampleSpin,SIGNAL(valueChanged(int)),this,SLOT(updateDownsampleRate(int)));
514 
515  // Attach layout to child
516  fileGroup->setLayout(fileLayout);
517 
518  // Create child widget and layout
519  listGroup = new QGroupBox(tr("Currently Recording"));
520  QGridLayout *listLayout = new QGridLayout;
521 
522  // Create elements for box
523  selectionBox = new QListWidget;
524  listLayout->addWidget(selectionBox,1,1,4,5);
525 
526  // Attach layout to child
527  listGroup->setLayout(listLayout);
528 
529  // Creat child widget and layout for buttons
530  buttonGroup = new QGroupBox;
531  QHBoxLayout *buttonLayout = new QHBoxLayout;
532 
533  // Create elements for box
534  startRecordButton = new QPushButton("Start Recording");
535  QObject::connect(startRecordButton,SIGNAL(released(void)),this,SLOT(startRecordClicked(void)));
536  buttonLayout->addWidget(startRecordButton);
537  startRecordButton->setEnabled(false);
538  stopRecordButton = new QPushButton("Stop Recording");
539  QObject::connect(stopRecordButton,SIGNAL(released(void)),this,SLOT(stopRecordClicked(void)));
540  buttonLayout->addWidget(stopRecordButton);
541  stopRecordButton->setEnabled(false);
542  closeButton = new QPushButton("Close");
543  QObject::connect(closeButton,SIGNAL(released(void)),subWindow,SLOT(close()));
544  buttonLayout->addWidget(closeButton);
545  recordStatus = new QLabel;
546  buttonLayout->addWidget(recordStatus);
547  recordStatus->setText("Not ready.");
548  recordStatus->setFrameStyle(QFrame::Panel | QFrame::Sunken);
549  recordStatus->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
550 
551  // Attach layout to group
552  buttonGroup->setLayout(buttonLayout);
553 
554  // Attach child widgets to parent
555  layout->addWidget(channelGroup, 0, 0, 1, 2);
556  layout->addWidget(listGroup, 0, 2, 1, 4);
557  layout->addWidget(stampGroup, 2, 0, 2, 6);
558  layout->addWidget(fileGroup, 4, 0, 1, 6);
559  layout->addWidget(sampleGroup, 5, 0, 1, 6);
560  layout->addWidget(buttonGroup, 6, 0, 1, 6);
561 
562  setLayout(layout);
563  setWindowTitle(QString::number(getID()) + " Data Recorder");
564 
565  // Set layout to Mdi
566  subWindow->setWidget(this);
567  subWindow->setFixedSize(subWindow->minimumSizeHint());
568  show();
569 
570  // Register custom QEvents
571  QEvent::registerEventType(QFileExistsEvent);
572  QEvent::registerEventType(QSetFileNameEditEvent);
573  QEvent::registerEventType(QDisableGroupsEvent);
574  QEvent::registerEventType(QEnableGroupsEvent);
575 
576  // Build initial block list
577  IO::Connector::getInstance()->foreachBlock(buildBlockPtrList, &blockPtrList);
578  for (std::vector<IO::Block *>::const_iterator i = blockPtrList.begin(), end = blockPtrList.end(); i != end; ++i)
579  blockList->addItem(QString::fromStdString((*i)->getName()) + " " + QString::number((*i)->getID()));
580 
581  // Setup thread sleep
582  sleep.tv_sec = 0;
583  sleep.tv_nsec = RT::System::getInstance()->getPeriod();
584 
585  // Check if FIFO is truly atomic for hardware arcstartecture
586  if(!fifo.isLockFree())
587  ERROR_MSG("DataRecorder::Panel: WARNING: Atomic FIFO is not lock free\n");
588 
589  // Build initial channel list
591 
592  // Launch Recording Thread
593  pthread_create(&thread, 0, bounce, this);
594  counter = 0;
595  downsample_rate = 1;
596  prev_input = 0.0;
597  count = 0;
598  setActive(true);
599 }
600 
601 // Destructor for Panel
603 {
605  setActive(false);
606  DoneEvent RTevent(fifo);
607  while (RT::System::getInstance()->postEvent(&RTevent));
608  pthread_join(thread, 0);
609  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i!= end;)
610  delete &*(i++);
611 }
612 
613 // Execute loop
615 {
616  if (recording && !counter++)
617  {
618  data_token_t token;
619  double data[channels.size()];
620 
621  size_t n = 0;
622  token.type = SYNC;
623  token.size = channels.size() * sizeof(double);
624  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i != end; ++i)
625  if (i->block)
626  data[n++] = i->block->getValue(i->type, i->index);
627 
628  fifo.write(&token, sizeof(token));
629  fifo.write(data, sizeof(data));
630  }
631  count++;
632  counter %= downsample_rate;
633 }
634 
635 // Event handler
637 {
638  if (event->getName() == Event::IO_BLOCK_INSERT_EVENT)
639  {
640  IO::Block *block = reinterpret_cast<IO::Block *> (event->getParam("block"));
641  blockPtrList.push_back(block);
642  blockList->addItem(QString::fromStdString(block->getName()) + " " + QString::number(block->getID()));
643  buildChannelList();
644  }
645  else if (event->getName() == Event::IO_BLOCK_REMOVE_EVENT)
646  {
647  IO::Block *block = reinterpret_cast<IO::Block *> (event->getParam("block"));
648  QString name = QString::fromStdString(block->getName()) + " " + QString::number(block->getID());
649  int n = 0;
650  for (; n < blockList->count() && blockList->itemText(n) != name; ++n) ;
651  if (n < blockList->count())
652  blockList->removeItem(n);
653  blockPtrList.erase(blockPtrList.begin() + n);
654 
655  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i != end; ++i)
656  if (i->block == block) {
657  if (recording) i->block = 0;
658  RemoveChannelEvent RTevent(recording, channels, *i);
659  if (!RT::System::getInstance()->postEvent(&RTevent)) {
660  QList<QListWidgetItem*> channelItems = selectionBox->findItems(i->name, Qt::MatchExactly);
661  if (!channelItems.isEmpty()) {
662  /* Use takeItem(row) to remove the channel item. */
663  selectionBox->takeItem(selectionBox->row(channelItems.takeFirst()));
664  }
665  }
666  }
667  buildChannelList();
668  }
669  else if (event->getName() == Event::OPEN_FILE_EVENT)
670  {
671  QString filename(reinterpret_cast<char*> (event->getParam("filename")));
672  OpenFileEvent RTevent(filename, fifo);
673  RT::System::getInstance()->postEvent(&RTevent);
674  }
675  else if (event->getName() == Event::START_RECORDING_EVENT)
676  {
677  StartRecordingEvent RTevent(recording, fifo);
678  RT::System::getInstance()->postEvent(&RTevent);
679  }
680  else if (event->getName() == Event::STOP_RECORDING_EVENT)
681  {
682  StopRecordingEvent RTevent(recording, fifo);
683  RT::System::getInstance()->postEvent(&RTevent);
684  }
685  else if (event->getName() == Event::ASYNC_DATA_EVENT)
686  {
687  AsyncDataEvent RTevent(reinterpret_cast<double *> (event->getParam("data")),*reinterpret_cast<size_t *> (event->getParam("size")), fifo);
688  RT::System::getInstance()->postEvent(&RTevent);
689  }
690  else if( event->getName() == Event::RT_POSTPERIOD_EVENT )
691  {
692  sleep.tv_nsec = RT::System::getInstance()->getPeriod(); // Update recording thread sleep time
693  buildChannelList();
694  }
695 }
696 
697 // RT Event Handler
699 {
700  if (event->getName() == Event::OPEN_FILE_EVENT)
701  {
702  QString filename = QString(reinterpret_cast<char*> (event->getParam("filename")));
703  data_token_t token;
704  token.type = DataRecorder::OPEN;
705  token.size = filename.length() + 1;
706  token.time = RT::OS::getTime();
707  fifo.write(&token, sizeof(token));
708  fifo.write(filename.toLatin1().constData(), token.size);
709  }
710  else if (event->getName() == Event::START_RECORDING_EVENT)
711  {
712  data_token_t token;
713  recording = true;
714  token.type = DataRecorder::START;
715  token.size = 0;
716  token.time = RT::OS::getTime();
717  fifo.write(&token, sizeof(token));
718  }
719  else if (event->getName() == Event::STOP_RECORDING_EVENT)
720  {
721  data_token_t token;
722  recording = false;
723  token.type = DataRecorder::STOP;
724  token.size = 0;
725  token.time = RT::OS::getTime();
726  fifo.write(&token, sizeof(token));
727  }
728  else if (event->getName() == Event::ASYNC_DATA_EVENT)
729  {
730  size_t size = *reinterpret_cast<size_t *> (event->getParam("size"));
731  data_token_t token;
732  token.type = DataRecorder::ASYNC;
733  token.size = size * sizeof(double);
734  token.time = RT::OS::getTime();
735  fifo.write(&token, sizeof(token));
736  fifo.write(event->getParam("data"), token.size);
737  }
739  {
740  data_token_t token;
741  token.type = DataRecorder::PARAM;
742  token.size = sizeof(param_change_t);
743  token.time = RT::OS::getTime();
744  param_change_t data;
745  data.id = reinterpret_cast<Settings::Object::ID> (event->getParam("object"));
746  data.index = reinterpret_cast<size_t> (event->getParam("index"));
747  data.step = file.idx;
748  data.value = *reinterpret_cast<double *> (event->getParam("value"));
749  fifo.write(&token, sizeof(token));
750  fifo.write(&data, sizeof(data));
751  }
752  else if( event->getName() == Event::RT_POSTPERIOD_EVENT )
753  {
754  sleep.tv_nsec = RT::System::getInstance()->getPeriod(); // Update recording thread sleep time
755  }
756 }
757 
758 // Populate list of blocks and channels
760 {
761  channelList->clear();
762  if (!blockList->count())
763  return;
764 
765  // Get block
766  IO::Block *block = blockPtrList[blockList->currentIndex()];
767 
768  // Get type
769  IO::flags_t type;
770  switch (typeList->currentIndex())
771  {
772  case 0:
773  type = Workspace::INPUT;
774  break;
775  case 1:
776  type = Workspace::OUTPUT;
777  break;
778  case 2:
779  type = Workspace::PARAMETER;
780  break;
781  case 3:
782  type = Workspace::STATE;
783  break;
784  case 4:
785  type = Workspace::EVENT;
786  break;
787  default:
788  ERROR_MSG("DataRecorder::Panel::buildChannelList : invalid type selection\n");
789  typeList->setCurrentIndex(0);
790  type = Workspace::INPUT;
791  }
792 
793  for (size_t i = 0; i < block->getCount(type); ++i)
794  channelList->addItem(QString::fromStdString(block->getName(type, i)));
795 
796  if(channelList->count())
797  rButton->setEnabled(true);
798  else
799  rButton->setEnabled(false);
800 }
801 
802 // Slot for changing data file
804 {
805  QFileDialog fileDialog(this);
806  fileDialog.setFileMode(QFileDialog::AnyFile);
807  fileDialog.setWindowTitle("Select Data File");
808 
809  QSettings userprefs;
810  userprefs.setPath(QSettings::NativeFormat, QSettings::SystemScope, "/usr/local/share/rtxi/");
811  fileDialog.setDirectory(userprefs.value("/dirs/data", getenv("HOME")).toString());
812 
813  QStringList filterList;
814  filterList.push_back("HDF5 files (*.h5)");
815  filterList.push_back("All files (*.*)");
816  fileDialog.setNameFilters(filterList);
817  fileDialog.selectNameFilter("HDF5 files (*.h5)");
818 
819  QStringList files;
820  if(fileDialog.exec())
821  files = fileDialog.selectedFiles();
822 
823  QString filename;
824  if(files.isEmpty() || files[0] == NULL || files[0] == "/" )
825  return;
826  else
827  filename = files[0];
828 
829  if (!filename.toLower().endsWith(QString(".h5")))
830  filename += ".h5";
831 
832  // Write this directory to the user prefs as most recently used
833  userprefs.setValue("/dirs/data", fileDialog.directory().path());
834 
835  // Post to event queue
836  OpenFileEvent RTevent(filename, fifo);
837  RT::System::getInstance()->postEvent(&RTevent);
838 }
839 
840 // Insert channel to record into list
842 {
843  if (!blockList->count() || !channelList->count())
844  return;
845 
846  Channel *channel = new Channel();
847  channel->block = blockPtrList[blockList->currentIndex()];
848  switch (typeList->currentIndex())
849  {
850  case 0:
851  channel->type = Workspace::INPUT;
852  break;
853  case 1:
854  channel->type = Workspace::OUTPUT;
855  break;
856  case 2:
857  channel->type = Workspace::PARAMETER;
858  break;
859  case 3:
860  channel->type = Workspace::STATE;
861  break;
862  case 4:
863  channel->type = Workspace::EVENT;
864  break;
865  default:
866  ERROR_MSG("DataRecorder::Panel::insertChannel : invalid type selection\n");
867  typeList->setCurrentIndex(0);
868  channel->type = Workspace::INPUT;
869  }
870  channel->index = channelList->currentIndex();
871 
872  channel->name.sprintf("%s %ld : %s", channel->block->getName().c_str(),
873  channel->block->getID(), channel->block->getName(channel->type, channel->index).c_str());
874 
875  if(selectionBox->findItems(QString(channel->name), Qt::MatchExactly).isEmpty())
876  {
877  InsertChannelEvent RTevent(recording, channels, channels.end(), *channel);
878  if (!RT::System::getInstance()->postEvent(&RTevent))
879  selectionBox->addItem(channel->name);
880  }
881 
882  if(selectionBox->count())
883  {
884  lButton->setEnabled(true);
885  if(!fileNameEdit->text().isEmpty())
886  {
887  startRecordButton->setEnabled(true);
888  }
889  }
890  else
891  {
892  startRecordButton->setEnabled(false);
893  lButton->setEnabled(false);
894  }
895 }
896 
897 // Remove channel from recorder list
899 {
900  if(!selectionBox->count() || selectionBox->selectedItems().isEmpty())
901  return;
902 
903  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i != end; ++i)
904  if (i->name == selectionBox->selectedItems().first()->text())
905  {
906  RemoveChannelEvent RTevent(recording, channels, *i);
907  if (!RT::System::getInstance()->postEvent(&RTevent))
908  selectionBox->takeItem(selectionBox->row(selectionBox->selectedItems().first()));
909  break;
910  }
911 
912  if(selectionBox->count())
913  {
914  startRecordButton->setEnabled(true);
915  lButton->setEnabled(true);
916  }
917  else
918  {
919  startRecordButton->setEnabled(false);
920  lButton->setEnabled(false);
921  }
922 }
923 
924 // Register new data tag/stamp
926 {
927  std::string newTag(std::to_string(RT::OS::getTime()));
928  newTag += ",";
929  newTag += timeStampEdit->text().toStdString();
930  dataTags.push_back(newTag);
931  timeStampEdit->clear();
932  recordStatus->setText("Tagged");
933 }
934 
935 // Start recording slot
937 {
938  if(fileNameEdit->text().isEmpty())
939  {
940  QMessageBox::critical(
941  this, "Data file not specified.",
942  "Please specify a file to write data to.",
943  QMessageBox::Ok, QMessageBox::NoButton);
944  return;
945  }
946 
947  StartRecordingEvent RTevent(recording, fifo);
948  RT::System::getInstance()->postEvent(&RTevent);
949 }
950 
951 // Stop recording slot
953 {
954  StopRecordingEvent RTevent(recording, fifo);
955  RT::System::getInstance()->postEvent(&RTevent);
956 }
957 
958 // Update downsample rate
960 {
961  downsample_rate = r;
962 }
963 
964 // Custom event handler
966 {
967  if (e->type() == QFileExistsEvent)
968  {
969  mutex.lock();
970  CustomEvent * event = static_cast<CustomEvent *>(e);
971  FileExistsEventData *data = reinterpret_cast<FileExistsEventData *> (event->getData());
972  data->response = QMessageBox::question(this, "File exists",
973  "The file already exists. What would you like to do?",
974  "Append", "Overwrite", "Cancel", 0, 2);
975  recordStatus->setText("Not Recording");
976  data->done.wakeAll();
977  mutex.unlock();
978  }
979  else if (e->type() == QSetFileNameEditEvent)
980  {
981  mutex.lock();
982  CustomEvent * event = static_cast<CustomEvent *>(e);
983  SetFileNameEditEventData *data = reinterpret_cast<SetFileNameEditEventData *> (event->getData());
984  fileNameEdit->setText(data->filename);
985  recordStatus->setText("Ready.");
986  if(selectionBox->count())
987  {
988  startRecordButton->setEnabled(true);
989  }
990  data->done.wakeAll();
991  mutex.unlock();
992  }
993  else if (e->type() == QDisableGroupsEvent)
994  {
995  startRecordButton->setEnabled(false);
996  stopRecordButton->setEnabled(true);
997  closeButton->setEnabled(false);
998  channelGroup->setEnabled(false);
999  sampleGroup->setEnabled(false);
1000  recordStatus->setText("Recording...");
1001  }
1002  else if (e->type() == QEnableGroupsEvent)
1003  {
1004  startRecordButton->setEnabled(true);
1005  stopRecordButton->setEnabled(false);
1006  closeButton->setEnabled(true);
1007  channelGroup->setEnabled(true);
1008  sampleGroup->setEnabled(true);
1009  recordStatus->setText("Ready.");
1010  fileSize->setNum(int(QFile(fileNameEdit->text()).size())/1024.0/1024.0);
1011  trialLength->setNum(double(RT::System::getInstance()->getPeriod()*1e-9* fixedcount));
1012  count = 0;
1013  }
1014 }
1015 
1017 {
1018  for (int i = 0; i < s.loadInteger("Num Channels"); ++i)
1019  {
1020  Channel *channel;
1021  IO::Block *block;
1022  std::ostringstream str;
1023  str << i;
1024 
1025  block = dynamic_cast<IO::Block *> (Settings::Manager::getInstance()->getObject(s.loadInteger(str.str() + " ID")));
1026  if (!block)
1027  continue;
1028 
1029  channel = new Channel();
1030  channel->block = block;
1031  channel->type = s.loadInteger(str.str() + " type");
1032  channel->index = s.loadInteger(str.str() + " index");
1033  channel->name.sprintf("%s %ld : %s", channel->block->getName().c_str(),
1034  channel->block->getID(), channel->block->getName(channel->type, channel->index).c_str());
1035 
1036  channels.insert(channels.end(), *channel);
1037  selectionBox->addItem(channel->name);
1038  if(selectionBox->count())
1039  lButton->setEnabled(true);
1040  }
1041 }
1042 
1044 {
1045  if (s.loadInteger("Maximized"))
1046  showMaximized();
1047  else if (s.loadInteger("Minimized"))
1048  showMinimized();
1049 
1050  downsampleSpin->setValue(s.loadInteger("Downsample"));
1051  parentWidget()->move(s.loadInteger("X"), s.loadInteger("Y"));
1052 }
1053 
1055 {
1056  if (isMaximized())
1057  s.saveInteger("Maximized", 1);
1058  else if (isMinimized())
1059  s.saveInteger("Minimized", 1);
1060 
1061  QPoint pos = parentWidget()->pos();
1062  s.saveInteger("X", pos.x());
1063  s.saveInteger("Y", pos.y());
1064 
1065  s.saveInteger("Downsample", downsampleSpin->value());
1066  s.saveInteger("Num Channels", channels.size());
1067  size_t n = 0;
1068  for (RT::List<Channel>::const_iterator i = channels.begin(), end = channels.end(); i != end; ++i)
1069  {
1070  std::ostringstream str;
1071  str << n++;
1072 
1073  s.saveInteger(str.str() + " ID", i->block->getID());
1074  s.saveInteger(str.str() + " type", i->type);
1075  s.saveInteger(str.str() + " index", i->index);
1076  }
1077 }
1078 
1080 {
1081  Panel *that = reinterpret_cast<Panel *> (param);
1082  if (that)
1083  {
1084  that->processData();
1085  }
1086  return 0;
1087 }
1088 
1090 {
1091  enum
1092  {
1093  CLOSED, OPENED, RECORD,
1094  } state = CLOSED;
1095 
1096  tokenRetrieved = false;
1097  for (;;)
1098  {
1099  if(!tokenRetrieved)
1100  {
1101  // Returns true if data was available and retrieved
1102  if(fifo.read(&_token, sizeof(_token)))
1103  tokenRetrieved = true;
1104  else
1105  {
1106  // Sleep loop then restart if no token was retrieved
1107  nanosleep(&sleep, NULL);
1108  continue;
1109  }
1110  }
1111  if (_token.type == SYNC)
1112  {
1113  if (state == RECORD)
1114  {
1115  double data[_token.size / sizeof(double)];
1116  if(!fifo.read(data, _token.size))
1117  continue; // Restart loop if data is not available
1118  H5PTappend(file.cdata, 1, data);
1119  ++file.idx;
1120  }
1121  }
1122  else if (_token.type == ASYNC)
1123  {
1124  if (state == RECORD)
1125  {
1126  double data[_token.size / sizeof(double)];
1127  if(!fifo.read(data, _token.size))
1128  continue; // Restart loop if data is not available
1129  if (data)
1130  {
1131  hsize_t array_size[] = { _token.size / sizeof(double) };
1132  hid_t array_space = H5Screate_simple(1, array_size, array_size);
1133  hid_t array_type = H5Tarray_create(H5T_IEEE_F64LE, 1, array_size);
1134 
1135  QString data_name = QString::number(static_cast<unsigned long long> (_token.time));
1136  hid_t adata = H5Dcreate(file.adata, data_name.toLatin1().constData(),
1137  array_type, array_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1138  H5Dwrite(adata, array_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data);
1139 
1140  H5Dclose(adata);
1141  H5Tclose(array_type);
1142  H5Sclose(array_space);
1143  }
1144  }
1145  }
1146  else if (_token.type == OPEN)
1147  {
1148  if (state == RECORD)
1149  stopRecording(_token.time);
1150  if (state != CLOSED)
1151  closeFile();
1152  char filename_string[_token.size];
1153  if(!fifo.read(filename_string, _token.size))
1154  continue; // Restart loop if data is not available
1155  QString filename = filename_string;
1156  if (openFile(filename))
1157  state = CLOSED;
1158  else
1159  state = OPENED;
1160  }
1161  else if (_token.type == CLOSE)
1162  {
1163  if (state == RECORD)
1165  if (state != CLOSED)
1166  closeFile();
1167  state = CLOSED;
1168  }
1169  else if (_token.type == START)
1170  {
1171  if (state == OPENED)
1172  {
1173  count = 0;
1174  startRecording(_token.time);
1175  state = RECORD;
1176  QEvent *event = new QEvent(static_cast<QEvent::Type>QDisableGroupsEvent);
1177  QApplication::postEvent(this, event);
1178  }
1179  }
1180  else if (_token.type == STOP)
1181  {
1182  if (state == RECORD)
1183  {
1184  stopRecording(_token.time);
1185  state = OPENED;
1186  fixedcount = count;
1187  QEvent *event = new QEvent(static_cast<QEvent::Type>QEnableGroupsEvent);
1188  QApplication::postEvent(this, event);
1189  }
1190  }
1191  else if (_token.type == DONE)
1192  {
1193  if (state == RECORD)
1194  stopRecording(_token.time);
1195  if (state != CLOSED)
1196  closeFile(true);
1197  break;
1198  }
1199  else if (_token.type == PARAM)
1200  {
1201  param_change_t data;
1202  if(!fifo.read(&data, sizeof(data)))
1203  continue; // Restart loop if data is not available
1204 
1205  IO::Block *block = dynamic_cast<IO::Block *> (Settings::Manager::getInstance()->getObject(data.id));
1206 
1207  if (block && state == RECORD)
1208  {
1209  param_hdf_t param = { data.step, data.value, };
1210 
1211  hid_t param_type;
1212  param_type = H5Tcreate(H5T_COMPOUND, sizeof(param_hdf_t));
1213  H5Tinsert(param_type, "index", HOFFSET(param_hdf_t,index), H5T_STD_I64LE);
1214  H5Tinsert(param_type, "value", HOFFSET(param_hdf_t,value), H5T_IEEE_F64LE);
1215 
1216  QString parameter_name = QString::number(block->getID()) + " "
1217  + QString::fromStdString(block->getName()) + " : "
1218  + QString::fromStdString(block->getName(Workspace::PARAMETER, data.index));
1219 
1220  hid_t data = H5PTopen(file.pdata, parameter_name.toLatin1().constData());
1221  H5PTappend(data, 1, &param);
1222  H5PTclose(data);
1223  H5Tclose(param_type);
1224  }
1225  }
1226  tokenRetrieved = false;
1227  }
1228 }
1229 
1230 int DataRecorder::Panel::openFile(QString &filename)
1231 {
1232 #ifdef DEBUG
1233  if(!pthread_equal(pthread_self(),thread))
1234  {
1235  ERROR_MSG("DataRecorder::Panel::openFile : called by invalid thread\n");
1236  PRINT_BACKTRACE();
1237  }
1238 #endif
1239 
1240  if (QFile::exists(filename))
1241  {
1242  mutex.lock();
1243  CustomEvent *event = new CustomEvent(static_cast<QEvent::Type>QFileExistsEvent);
1244  FileExistsEventData data;
1245  event->setData(static_cast<void *>(&data));
1246  data.filename = filename;
1247  QApplication::postEvent(this, event);
1248  data.done.wait(&mutex);
1249  mutex.unlock();
1250 
1251  if (data.response == 0) // append
1252  {
1253  file.id = H5Fopen(filename.toLatin1().constData(), H5F_ACC_RDWR, H5P_DEFAULT);
1254  size_t trial_num;
1255  QString trial_name;
1256  H5Eset_auto(H5E_DEFAULT, NULL, NULL);
1257  for (trial_num = 1;; ++trial_num)
1258  {
1259  trial_name = "/Trial" + QString::number(trial_num);
1260  file.trial = H5Gopen(file.id, trial_name.toLatin1().constData(), H5P_DEFAULT);
1261  if (file.trial < 0)
1262  {
1263  H5Eclear(H5E_DEFAULT);
1264  break;
1265  }
1266  else
1267  {
1268  H5Gclose(file.trial);
1269  }
1270  }
1271  trialNum->setNum(int(trial_num)-1);
1272  }
1273  else if (data.response == 1) //overwrite
1274  {
1275  file.id = H5Fcreate(filename.toLatin1().constData(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
1276  trialNum->setText("0");
1277  }
1278  else
1279  {
1280  return -1;
1281  }
1282  }
1283  else
1284  {
1285  file.id = H5Fcreate(filename.toLatin1().constData(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
1286  trialNum->setText("0");
1287  }
1288  if (file.id < 0)
1289  {
1290  H5E_type_t error_type;
1291  size_t error_size;
1292  error_size = H5Eget_msg(file.id, &error_type, NULL, 0);
1293  char error_msg[error_size + 1];
1294  H5Eget_msg(file.id, &error_type, error_msg, error_size);
1295  error_msg[error_size] = 0;
1296  H5Eclear(file.id);
1297 
1298  ERROR_MSG("DataRecorder::Panel::processData : failed to open \"%s\" for writing with error : %s\n", filename.toStdString().c_str(),error_msg);
1299  return -1;
1300  }
1301 
1302  mutex.lock();
1303  CustomEvent *event = new CustomEvent(static_cast<QEvent::Type>QSetFileNameEditEvent);
1304  SetFileNameEditEventData data;
1305  data.filename = filename;
1306  event->setData(static_cast<void*>(&data));
1307  QApplication::postEvent(this, event);
1308  data.done.wait(&mutex);
1309  mutex.unlock();
1310 
1311  return 0;
1312 }
1313 
1315 {
1316 #ifdef DEBUG
1317  if(!pthread_equal(pthread_self(),thread))
1318  {
1319  ERROR_MSG("DataRecorder::Panel::closeFile : called by invalid thread\n");
1320  PRINT_BACKTRACE();
1321  }
1322 #endif
1323 
1324  if(!dataTags.empty())
1325  {
1326  // Write tags to data file
1327  hid_t tag_type, tag_space, data;
1328  herr_t status;
1329  hsize_t dims[1] = {1};
1330  tag_type = H5Tcreate(H5T_STRING, TAG_SIZE);
1331  tag_space = H5Screate_simple(1, dims, NULL);
1332 
1333  // Create group for tags
1334  file.tdata = H5Gcreate(file.id, "Tags", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1335 
1336  // Iterate over vector (buffer) and put into data file
1337  size_t i = 0;
1338  for(std::vector<std::string>::iterator it = dataTags.begin(); it != dataTags.end(); ++it)
1339  {
1340  data = H5Dcreate(file.tdata, std::string("Tag " + std::to_string(i++)).c_str(), tag_type, tag_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1341  status = H5Dwrite(data, tag_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, it->c_str());
1342  }
1343  dataTags.clear();
1344 
1345  // Close all open structs
1346  H5Dclose(data);
1347  H5Sclose(tag_space);
1348  H5Tclose(tag_type);
1349  H5Gclose(file.tdata);
1350  }
1351 
1352  // Close file
1353  H5Fclose(file.id);
1354 
1355  if (!shutdown)
1356  {
1357  mutex.lock();
1358  CustomEvent *event = new CustomEvent(static_cast<QEvent::Type>QSetFileNameEditEvent);
1359  SetFileNameEditEventData data;
1360  data.filename = "";
1361  event->setData(static_cast<void*>(&data));
1362  QApplication::postEvent(this, event);
1363  data.done.wait(&mutex);
1364  mutex.unlock();
1365  }
1366 }
1367 
1368 int DataRecorder::Panel::startRecording(long long timestamp)
1369 {
1370 #ifdef DEBUG
1371  if(!pthread_equal(pthread_self(),thread))
1372  {
1373  ERROR_MSG("DataRecorder::Panel::startRecording : called by invalid thread\n");
1374  PRINT_BACKTRACE();
1375  }
1376 #endif
1377 
1378  size_t trial_num;
1379  QString trial_name;
1380 
1381  H5Eset_auto(H5E_DEFAULT, NULL, NULL);
1382 
1383  for (trial_num = 1;; ++trial_num)
1384  {
1385  trial_name = "/Trial" + QString::number(trial_num);
1386  file.trial = H5Gopen(file.id, trial_name.toLatin1().constData(), H5P_DEFAULT);
1387 
1388  if (file.trial < 0)
1389  {
1390  H5Eclear(H5E_DEFAULT);
1391  break;
1392  }
1393  else
1394  H5Gclose(file.trial);
1395  }
1396 
1397  trialNum->setNum(int(trial_num));
1398  file.trial = H5Gcreate(file.id, trial_name.toLatin1().constData(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1399  file.pdata = H5Gcreate(file.trial, "Parameters", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1400  file.adata = H5Gcreate(file.trial, "Asynchronous Data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1401  file.sdata = H5Gcreate(file.trial, "Synchronous Data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1402  file.sysdata = H5Gcreate(file.trial, "System Settings", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1403 
1404  hid_t scalar_space = H5Screate(H5S_SCALAR);
1405  hid_t string_type = H5Tcopy(H5T_C_S1);
1406  size_t string_size = 1024;
1407  H5Tset_size(string_type, string_size);
1408  hid_t data;
1409 
1410  data = H5Dcreate(file.trial, "Version", string_type,
1411  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1412  std::string version_string = QString(VERSION).toStdString();
1413  char * version_c_string = new char[version_string.length()+1];
1414  std::strcpy(version_c_string, version_string.c_str());
1415  H5Dwrite(data, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, version_c_string);
1416  delete[] version_c_string;
1417  H5Dclose(data);
1418 
1419  long long period = RT::System::getInstance()->getPeriod();
1420  data = H5Dcreate(file.trial, "Period (ns)", H5T_STD_U64LE, scalar_space,
1421  H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1422  H5Dwrite(data, H5T_STD_U64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &period);
1423  H5Dclose(data);
1424 
1425  long long downsample = downsample_rate;
1426  data = H5Dcreate(file.trial, "Downsampling Rate", H5T_STD_U64LE,
1427  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1428  H5Dwrite(data, H5T_STD_U64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &downsample);
1429  H5Dclose(data);
1430 
1431  data = H5Dcreate(file.trial, "Timestamp Start (ns)", H5T_STD_U64LE,
1432  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1433  H5Dwrite(data, H5T_STD_U64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &timestamp);
1434  H5Dclose(data);
1435 
1436  data = H5Dcreate(file.trial, "Date", string_type,
1437  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1438  std::string date_string = QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
1439  char * date_c_string = new char[date_string.length()+1];
1440  std::strcpy(date_c_string, date_string.c_str());
1441  H5Dwrite(data, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, date_c_string);
1442  delete[] date_c_string;
1443  H5Dclose(data);
1444 
1445  hid_t param_type;
1446  param_type = H5Tcreate(H5T_COMPOUND, sizeof(param_hdf_t));
1447  H5Tinsert(param_type, "index", HOFFSET(param_hdf_t,index), H5T_STD_I64LE);
1448  H5Tinsert(param_type, "value", HOFFSET(param_hdf_t,value), H5T_IEEE_F64LE);
1449 
1450  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i != end; ++i)
1451  {
1452  IO::Block *block = i->block;
1453  for (size_t j = 0; j < block->getCount(Workspace::PARAMETER); ++j)
1454  {
1455  QString parameter_name = QString::number(block->getID()) + " "
1456  + QString::fromStdString(block->getName()) + " : " + QString::fromStdString(block->getName(Workspace::PARAMETER, j));
1457  data = H5PTcreate_fl(file.pdata, parameter_name.toLatin1().constData(), param_type, sizeof(param_hdf_t), -1);
1458  struct param_hdf_t value = { 0, block->getValue(Workspace::PARAMETER, j),};
1459  H5PTappend(data, 1, &value);
1460  H5PTclose(data);
1461  }
1462  for (size_t j = 0; j < block->getCount(Workspace::COMMENT); ++j)
1463  {
1464  QString comment_name = QString::number(block->getID()) + " "
1465  + QString::fromStdString(block->getName()) + " : " + QString::fromStdString(block->getName(Workspace::COMMENT, j));
1466  hsize_t dims = dynamic_cast<Workspace::Instance *> (block)->getValueString(Workspace::COMMENT, j).size() + 1;
1467  hid_t comment_space = H5Screate_simple(1, &dims, &dims);
1468  data = H5Dcreate(file.pdata, comment_name.toLatin1().constData(), H5T_C_S1, comment_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1469  H5Dwrite(data, H5T_C_S1, H5S_ALL, H5S_ALL, H5P_DEFAULT, dynamic_cast<Workspace::Instance *> (block)->getValueString(Workspace::COMMENT, j).c_str());
1470  H5Dclose(data);
1471  }
1472  }
1473 
1474  H5Tclose(param_type);
1475 
1476  size_t count = 0;
1477  for (RT::List<Channel>::iterator i = channels.begin(), end = channels.end(); i != end; ++i)
1478  {
1479  std::string rec_chan_name = std::to_string(++count) + " " + i->name.toStdString();
1480  hid_t data = H5Dcreate(file.sdata, rec_chan_name.c_str(), string_type, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1481  H5Dwrite(data, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, rec_chan_name.c_str());
1482  H5Dclose(data);
1483  }
1484 
1485  DAQ::Device *dev;
1486  {
1487  struct find_daq_t info = { 0, 0, };
1488  DAQ::Manager::getInstance()->foreachDevice(findDAQDevice, &info);
1489  dev = info.device;
1490  }
1491 
1492  // Save channel configurations
1493  if(dev)
1494  for(size_t i=0; i<dev->getChannelCount(DAQ::AI); ++i)
1495  if(dev->getChannelActive(DAQ::AI,static_cast<DAQ::index_t>(i)))
1496  {
1497  std::string chan_name = "Analog Channel " + std::to_string(i);
1498  file.chandata = H5Gcreate(file.sysdata, chan_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1499 
1500  hid_t data = H5Dcreate(file.chandata, "Range", string_type, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1501  std::string range_string = dev->getAnalogRangeString(DAQ::AI,static_cast<DAQ::index_t>(i),dev->getAnalogRange(DAQ::AI,static_cast<DAQ::index_t>(i)));
1502  char * range_c_string = new char[range_string.length()+1];
1503  std::strcpy(range_c_string, range_string.c_str());
1504  H5Dwrite(data, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, range_c_string);
1505  delete[] range_c_string;
1506 
1507  data = H5Dcreate(file.chandata, "Reference", string_type, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1508  std::string ref_string = dev->getAnalogReferenceString(DAQ::AI,static_cast<DAQ::index_t>(i),dev->getAnalogReference(DAQ::AI,static_cast<DAQ::index_t>(i)));
1509  char * ref_c_string = new char[ref_string.length()+1];
1510  std::strcpy(ref_c_string, ref_string.c_str());
1511  H5Dwrite(data, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, ref_c_string);
1512  delete[] ref_c_string;
1513 
1514  double scale = dev->getAnalogGain(DAQ::AI,static_cast<DAQ::index_t>(i));
1515  data = H5Dcreate(file.chandata, "Gain", H5T_IEEE_F64LE, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1516  H5Dwrite(data, H5T_IEEE_F64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &scale);
1517 
1518  double offset = dev->getAnalogZeroOffset(DAQ::AI,static_cast<DAQ::index_t>(i));
1519  data = H5Dcreate(file.chandata, "Offset", H5T_IEEE_F64LE, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1520  H5Dwrite(data, H5T_IEEE_F64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &offset);
1521 
1522  int downsample = dev->getAnalogDownsample(DAQ::AI,static_cast<DAQ::index_t>(i));
1523  data = H5Dcreate(file.chandata, "Downsample", H5T_STD_I16LE, scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1524  H5Dwrite(data, H5T_STD_I16LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &downsample);
1525  H5Dclose(data);
1526  }
1527 
1528  H5Tclose(string_type);
1529  H5Sclose(scalar_space);
1530 
1531  if (channels.size())
1532  {
1533  hsize_t array_size[] = { channels.size() };
1534  hid_t array_type = H5Tarray_create(H5T_IEEE_F64LE, 1, array_size);
1535  file.cdata = H5PTcreate_fl(file.sdata, "Channel Data", array_type, (hsize_t) 64, 1);
1536  H5Tclose(array_type);
1537  }
1538 
1539  file.idx = 0;
1540 
1541  return 0;
1542 }
1543 
1544 void DataRecorder::Panel::stopRecording(long long timestamp)
1545 {
1546 #ifdef DEBUG
1547  if(!pthread_equal(pthread_self(),thread))
1548  {
1549  ERROR_MSG("DataRecorder::Panel::stopRecording : called by invalid thread\n");
1550  PRINT_BACKTRACE();
1551  }
1552 #endif
1553 
1554  // Write stop time to data file
1555  hid_t scalar_space = H5Screate(H5S_SCALAR);
1556  hid_t data = H5Dcreate(file.trial, "Timestamp Stop (ns)", H5T_STD_U64LE,
1557  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1558  H5Dwrite(data, H5T_STD_U64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &timestamp);
1559  H5Dclose(data);
1560 
1561  // Write trial length to data file
1562  fixedcount = count;
1563  long long period = RT::System::getInstance()->getPeriod();
1564  long long datalength = period * fixedcount;
1565  data = H5Dcreate(file.trial, "Trial Length (ns)", H5T_STD_U64LE,
1566  scalar_space, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
1567  H5Dwrite(data, H5T_STD_U64LE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &datalength);
1568  H5Dclose(data);
1569 
1570  // Close all open structs
1571  H5Sclose(scalar_space);
1572  H5PTclose(file.cdata);
1573  H5Gclose(file.sdata);
1574  H5Gclose(file.pdata);
1575  H5Gclose(file.adata);
1576  H5Gclose(file.sysdata);
1577  H5Gclose(file.chandata);
1578  H5Gclose(file.trial);
1579 
1580  H5Fflush(file.id, H5F_SCOPE_LOCAL);
1581  void *file_handle;
1582  H5Fget_vfd_handle(file.id, H5P_DEFAULT, &file_handle);
1583  if (fsync(*static_cast<int *> (file_handle)))
1584  {
1585  DEBUG_MSG("DataRecorder::Panel::stopRecording : fsync failed, running sync\n");
1586  sync();
1587  }
1588 }
1589 
1591 {
1593 }
1594 
1596 {
1597  // get the HDF data recorder buffer size from user preference
1598  QSettings userprefs;
1599  userprefs.setPath(QSettings::NativeFormat, QSettings::SystemScope, "/usr/local/share/rtxi/");
1600  buffersize = (userprefs.value("/system/HDFbuffer", 10).toInt())*1048576;
1601  MainWindow::getInstance()->createSystemMenuItem("Data Recorder",this,SLOT(createDataRecorderPanel(void)));
1602 }
1603 
1605 {
1606  while (panelList.size())
1607  delete panelList.front();
1608  instance = 0;
1609 }
1610 
1612 {
1613  Panel *panel = new Panel(MainWindow::getInstance()->centralWidget(), buffersize);
1614  panelList.push_back(panel);
1615  return panel;
1616 }
1617 
1619 {
1620  panelList.remove(panel);
1621 }
1622 
1624 {
1625  size_t i = 0;
1626  for (std::list<Panel *>::iterator j = panelList.begin(), end = panelList.end(); j != end; ++j)
1627  (*j)->deferred(s.loadState(QString::number(i++).toStdString()));
1628 }
1629 
1631 {
1632  for (size_t i = 0; i < static_cast<size_t> (s.loadInteger("Num Panels")); ++i)
1633  {
1634  Panel *panel = new Panel(MainWindow::getInstance()->centralWidget(), buffersize);
1635  panelList.push_back(panel);
1636  panel->load(s.loadState(QString::number(i).toStdString()));
1637  }
1638 }
1639 
1641 {
1642  s.saveInteger("Num Panels", panelList.size());
1643  size_t n = 0;
1644  for (std::list<Panel *>::const_iterator i = panelList.begin(), end = panelList.end(); i != end; ++i)
1645  s.saveState(QString::number(n++).toStdString(), (*i)->save());
1646 }
1647 
1648 static Mutex mutex;
1650 
1652 {
1653  if (instance)
1654  return instance;
1655 
1656  /*************************************************************************
1657  * Seems like alot of hoops to jump through, but allocation isn't *
1658  * thread-safe. So effort must be taken to ensure mutual exclusion. *
1659  *************************************************************************/
1660 
1661  Mutex::Locker lock(&::mutex);
1662  if (!instance)
1663  instance = new Plugin();
1664 
1665  return instance;
1666 }
static MainWindow * getInstance(void)
QGroupBox * sampleGroup
long long getPeriod(void) const
Definition: rt.h:392
bool isLockFree() const
Definition: atomic_fifo.cpp:94
const char * OPEN_FILE_EVENT
Definition: event.cpp:40
virtual double getAnalogZeroOffset(type_t type, index_t index) const =0
virtual void doLoad(const Settings::Object::State &)
Definition: io.h:187
int openFile(QString &)
void buildChannelList(void)
QPushButton * rButton
void unlock(void)
Definition: mutex.cpp:74
QLineEdit * fileNameEdit
void * getParam(const char *) const
Definition: event.cpp:81
virtual size_t getAnalogDownsample(type_t type, index_t index) const =0
#define QSetFileNameEditEvent
void closeFile(bool=false)
void postEvent(const Object *event)
Definition: event.cpp:110
virtual index_t getAnalogRange(type_t type, index_t index) const =0
Settings::Object::ID id
Definition: data_recorder.h:58
void updateDownsampleRate(int)
QComboBox * channelList
static Connector * getInstance(void)
Definition: io.cpp:421
static Plugin * instance
Panel * createDataRecorderPanel(void)
bool isRealtime(void)
QPushButton * lButton
QGroupBox * channelGroup
virtual void doLoad(const Settings::Object::State &)
Lockfree SINGLE producer / SINGLE consumer FIFO.
Definition: atomic_fifo.h:33
#define VERSION
Definition: rtxi_config.h:78
Definition: mutex.h:28
double arg(const complex _z)
Definition: complex.h:133
QListWidget * selectionBox
unsigned long ID
Definition: settings.h:53
Object * getObject(Object::ID) const
Definition: settings.cpp:212
const char * IO_BLOCK_REMOVE_EVENT
Definition: event.cpp:32
const char * START_RECORDING_EVENT
Definition: event.cpp:41
Panel(QWidget *, size_t)
#define ERROR_MSG(fmt, args...)
Definition: debug.h:41
static Manager * getInstance(void)
Definition: event.cpp:149
QDebug operator<<(QDebug str, const QEvent *ev)
const char * STOP_RECORDING_EVENT
Definition: event.cpp:42
Plugin::Object * createRTXIPlugin(void *)
void load(const State &)
Definition: settings.cpp:195
QComboBox * typeList
#define TAG_SIZE
void receiveEventRT(const Event::Object *)
QPushButton * addTag
QMdiSubWindow * subWindow
#define QEnableGroupsEvent
void foreachDevice(void(*callback)(Device *, void *), void *param)
Definition: daq.cpp:43
long long getTime(void)
DAQ::Device * device
virtual int callback(void)=0
virtual void doSave(Settings::Object::State &) const
Definition: daq.h:40
virtual bool getChannelActive(type_t type, index_t index) const =0
QPushButton * startRecordButton
void stopRecording(long long)
virtual void doDeferred(const Settings::Object::State &)
QComboBox * blockList
void stopRecording(void)
void startRecording(void)
void createMdi(QMdiSubWindow *)
void foreachBlock(void(*callback)(Block *, void *), void *param)
Definition: io.cpp:257
QPushButton * closeButton
const char * WORKSPACE_PARAMETER_CHANGE_EVENT
Definition: event.cpp:35
int loadInteger(const std::string &name) const
Definition: settings.cpp:77
std::string getName(void) const
Definition: io.h:214
void setActive(bool)
Definition: rt.cpp:152
struct timespec sleep
QGroupBox * buttonGroup
const char * ASYNC_DATA_EVENT
Definition: event.cpp:43
virtual index_t getAnalogReference(type_t type, index_t index) const =0
void startRecordClicked(void)
unsigned long flags_t
Definition: io.h:40
void stopRecordClicked(void)
void removeDataRecorderPanel(Panel *)
static Manager * getInstance(void)
Definition: daq.cpp:129
QGroupBox * listGroup
const char * RT_POSTPERIOD_EVENT
Definition: event.cpp:26
const char * IO_BLOCK_INSERT_EVENT
Definition: event.cpp:31
void lock(void)
Definition: mutex.cpp:58
virtual std::string getAnalogReferenceString(type_t type, index_t index, index_t reference) const =0
Definition: rt.h:70
void openFile(const QString &)
virtual double getValue(flags_t type, size_t index) const
Definition: io.cpp:109
static void * bounce(void *)
void saveState(const std::string &name, const State &value)
Definition: settings.cpp:136
long long index
QLineEdit * timeStampEdit
void saveInteger(const std::string &name, int)
Definition: settings.cpp:112
Realtime Oriented Classes.
Definition: rt.h:34
ID getID(void) const
Definition: settings.h:131
virtual std::string getAnalogRangeString(type_t type, index_t index, index_t range) const =0
void customEvent(QEvent *)
std::vector< IO::Block * > blockPtrList
virtual size_t getCount(flags_t type) const
Definition: io.cpp:82
#define QFileExistsEvent
static Plugin * getInstance(void)
static System * getInstance(void)
Definition: rt.cpp:361
State loadState(const std::string &name) const
Definition: settings.cpp:124
static Manager * getInstance(void)
Definition: settings.cpp:534
void postEventRT(const Object *event)
Definition: event.cpp:118
void shutdown(void)
Definition: rt_os-posix.cpp:63
virtual void doSave(Settings::Object::State &) const
void receiveEvent(const Event::Object *)
QAction * createSystemMenuItem(const QString &label, const QObject *handler, const char *slot)
int startRecording(long long)
virtual size_t getChannelCount(type_t type) const =0
virtual double getAnalogGain(type_t type, index_t index) const =0
void postAsyncData(const double *, size_t)
QPushButton * stopRecordButton
#define DEBUG_MSG(fmt, args...)
Prints debug messages to standard error.
Definition: debug.h:53
QSpinBox * downsampleSpin
virtual void doDeferred(const Settings::Object::State &)
Classes associated with the loading/unloading of binaries at run-time.
Definition: plugin.h:35
int postEvent(Event *event, bool blocking=true)
Definition: rt.cpp:218
#define QDisableGroupsEvent
const char * getName(void) const
Definition: event.h:136
QGroupBox * stampGroup
QGroupBox * fileGroup