RTXI  3.0.0
The Real-Time eXperiment Interface Reference Manual
data_recorder.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 Georgia Institute of Technology, University of Utah,
3  Weill Cornell Medical College
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 
20 #include <QFileDialog>
21 #include <QMessageBox>
22 #include <QSettings>
23 #include <QTimer>
24 #include <cstddef>
25 #include <cstring>
26 #include <iostream>
27 #include <mutex>
28 #include <sstream>
29 #include <string>
30 
31 #include "data_recorder.hpp"
32 
33 #include <H5Ipublic.h>
34 #include <unistd.h>
35 
36 #include "debug.hpp"
37 #include "main_window.hpp"
38 
39 DataRecorder::Panel::Panel(QMainWindow* mwindow, Event::Manager* ev_manager)
40  : Widgets::Panel(
41  std::string(DataRecorder::MODULE_NAME), mwindow, ev_manager)
42  , buttonGroup(new QGroupBox)
43  , blockList(new QComboBox)
44  , channelList(new QComboBox)
45  , typeList(new QComboBox)
46  , selectionBox(new QListWidget)
47  , recordStatus(new QLabel)
48  , downsampleSpin(new QSpinBox(this))
49  , fileNameEdit(new QLineEdit)
50  , timeStampEdit(new QLineEdit)
51  , fileSizeLbl(new QLabel)
52  , fileSize(new QLabel)
53  , trialLengthLbl(new QLabel)
54  , trialLength(new QLabel)
55  , trialNumLbl(new QLabel)
56  , trialNum(new QLabel)
57  , recording_timer(new QTimer(this))
58 {
59  setWhatsThis(
60  "<p><b>Data Recorder:</b><br>The Data Recorder writes data to an HDF5 "
61  "file format "
62  "All available signals for saving to file are automatically detected. "
63  "Currently "
64  "loaded user modules are listed in the \"Block\" drop-down box. "
65  "Available DAQ cards "
66  "are listed here as /proc/analogy/devices. Use the \"Type\" and "
67  "\"Channel\" drop-down boxes "
68  "to select the signals that you want to save. Use the left and right "
69  "arrow buttons to "
70  "add these signals to the file. You may select a downsampling rate that "
71  "is applied "
72  "to the real-time period for execution (set in the System Control "
73  "Panel). The real-time "
74  "period and the data downsampling rate are both saved as metadata in the "
75  "HDF5 file "
76  "so that you can reconstruct your data correctly. The current recording "
77  "status of "
78  "the Data Recorder is shown at the bottom.</p>");
79  auto* layout = new QGridLayout;
80 
81  channelGroup = new QGroupBox(tr("Channel Selection"));
82  auto* channelLayout = new QVBoxLayout;
83 
84  channelLayout->addWidget(new QLabel(tr("Block:")));
85  channelLayout->addWidget(blockList);
86  QObject::connect(
87  blockList, SIGNAL(activated(int)), this, SLOT(buildChannelList()));
88 
89  channelLayout->addWidget(new QLabel(tr("Type:")));
90 
91  channelLayout->addWidget(typeList);
92  typeList->addItem("Output", QVariant::fromValue(IO::OUTPUT));
93  typeList->addItem("Input", QVariant::fromValue(IO::INPUT));
94  QObject::connect(
95  typeList, SIGNAL(activated(int)), this, SLOT(buildChannelList()));
96 
97  channelLayout->addWidget(new QLabel(tr("Channel:")));
98 
99  channelLayout->addWidget(channelList);
100 
101  // Attach layout to child widget
102  channelGroup->setLayout(channelLayout);
103 
104  // Create elements for arrow
105  addRecorderButton = new QPushButton("Add");
106  channelLayout->addWidget(addRecorderButton);
107  QObject::connect(
108  addRecorderButton, SIGNAL(released()), this, SLOT(insertChannel()));
109  addRecorderButton->setEnabled(false);
110  removeRecorderButton = new QPushButton("Remove");
111  channelLayout->addWidget(removeRecorderButton);
112  QObject::connect(
113  removeRecorderButton, SIGNAL(released()), this, SLOT(removeChannel()));
114  removeRecorderButton->setEnabled(false);
115 
116  // Timestamp
117  stampGroup = new QGroupBox(tr("Tag Data"));
118  auto* stampLayout = new QHBoxLayout;
119 
120  // Add timestamp elements
121 
122  stampLayout->addWidget(timeStampEdit);
123  addTag = new QPushButton(tr("Tag"));
124  stampLayout->addWidget(addTag);
125  QObject::connect(addTag, SIGNAL(released()), this, SLOT(addNewTag()));
126 
127  // Attach layout to child widget
128  stampGroup->setLayout(stampLayout);
129 
130  // Create child widget and layout
131  sampleGroup = new QGroupBox(tr("Trial Metadata"));
132  auto* sampleLayout = new QHBoxLayout;
133 
134  // create elements for sample box
135 
136  trialNumLbl->setText("Trial #:");
137  trialNumLbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
138  sampleLayout->addWidget(trialNumLbl);
139 
140  trialNum->setText("0");
141  trialNum->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
142  sampleLayout->addWidget(trialNum);
143 
144  trialLengthLbl->setText("Trial Length (s):");
145  sampleLayout->addWidget(trialLengthLbl);
146 
147  trialLength->setText("No data recorded");
148  trialLength->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
149  sampleLayout->addWidget(trialLength);
150 
151  fileSizeLbl->setText("File Size (MB):");
152  sampleLayout->addWidget(fileSizeLbl);
153 
154  fileSize->setText("No data recorded");
155  sampleLayout->addWidget(fileSize);
156  fileSize->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
157 
158  // Attach layout to child widget
159  sampleGroup->setLayout(sampleLayout);
160 
161  // Create child widget and layout for file control
162  fileGroup = new QGroupBox(tr("File Control"));
163  auto* fileLayout = new QHBoxLayout;
164 
165  // Create elements for file control
166  fileLayout->addWidget(new QLabel(tr("File Name:")));
167 
168  fileNameEdit->setReadOnly(true);
169  fileLayout->addWidget(fileNameEdit);
170  auto* fileChangeButton = new QPushButton("Choose File");
171  fileLayout->addWidget(fileChangeButton);
172  QObject::connect(
173  fileChangeButton, SIGNAL(released()), this, SLOT(changeDataFile()));
174 
175  fileLayout->addWidget(new QLabel(tr("Downsample \nRate:")));
176 
177  downsampleSpin->setMinimum(1);
178  downsampleSpin->setMaximum(500);
179  fileLayout->addWidget(downsampleSpin);
180  QObject::connect(downsampleSpin,
181  SIGNAL(valueChanged(int)),
182  this,
183  SLOT(updateDownsampleRate(int)));
184 
185  // Attach layout to child
186  fileGroup->setLayout(fileLayout);
187 
188  // Create child widget and layout
189  listGroup = new QGroupBox(tr("Currently Recording"));
190  auto* listLayout = new QGridLayout;
191 
192  // Create elements for box
193 
194  listLayout->addWidget(selectionBox, 1, 1, 4, 5);
195 
196  // Attach layout to child
197  listGroup->setLayout(listLayout);
198 
199  // Create child widget and layout for buttons
200 
201  auto* buttonLayout = new QHBoxLayout;
202 
203  // Create elements for box
204  startRecordButton = new QPushButton("Start Recording");
205  QObject::connect(
206  startRecordButton, SIGNAL(released()), this, SLOT(startRecordClicked()));
207  buttonLayout->addWidget(startRecordButton);
208  startRecordButton->setEnabled(false);
209  stopRecordButton = new QPushButton("Stop Recording");
210  QObject::connect(
211  stopRecordButton, SIGNAL(released()), this, SLOT(stopRecordClicked()));
212  buttonLayout->addWidget(stopRecordButton);
213  stopRecordButton->setEnabled(false);
214  closeButton = new QPushButton("Close");
215  QObject::connect(
216  closeButton, SIGNAL(released()), parentWidget(), SLOT(close()));
217  buttonLayout->addWidget(closeButton);
218 
219  buttonLayout->addWidget(recordStatus);
220  recordStatus->setText("Not ready.");
221  recordStatus->setFrameStyle(QFrame::Panel);
222  recordStatus->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
223 
224  // Attach layout to group
225  buttonGroup->setLayout(buttonLayout);
226 
227  // Attach child widgets to parent
228  layout->addWidget(channelGroup, 0, 0, 1, 2);
229  layout->addWidget(listGroup, 0, 2, 1, 4);
230  layout->addWidget(stampGroup, 2, 0, 2, 6);
231  layout->addWidget(fileGroup, 4, 0, 1, 6);
232  layout->addWidget(sampleGroup, 5, 0, 1, 6);
233  layout->addWidget(buttonGroup, 6, 0, 1, 6);
234 
235  setLayout(layout);
236  setWindowTitle(tr(std::string(DataRecorder::MODULE_NAME).c_str()));
237 
238  // Set layout to Mdi
239  this->getMdiWindow()->setFixedSize(this->minimumSizeHint());
240 
241  this->buildBlockList();
242  this->buildChannelList();
243 
244  this->recording_timer->setInterval(1000);
245  QObject::connect(
246  this->recording_timer, SIGNAL(timeout()), this, SLOT(processData()));
247  QObject::connect(
248  this, SIGNAL(updateBlockInfo()), this, SLOT(buildBlockList()));
249  QObject::connect(this->fileNameEdit,
250  SIGNAL(textChanged(const QString&)),
251  this,
252  SLOT(syncEnableRecordingButtons(const QString&)));
253  recording_timer->start();
254 }
255 
256 void DataRecorder::Panel::buildBlockList()
257 {
258  // Build initial block list
260  this->getRTXIEventManager()->postEvent(&event);
261  auto blockPtrList =
262  std::any_cast<std::vector<IO::Block*>>(event.getParam("blockList"));
263  auto prev_selected_block = blockList->currentData();
264  blockList->clear();
265  for (auto* blockptr : blockPtrList) {
266  if (blockptr->getName().find("Probe") != std::string::npos
267  || blockptr->getName().find("Recording") != std::string::npos)
268  {
269  continue;
270  }
271  blockList->addItem(QString(blockptr->getName().c_str()) + " "
272  + QString::number(blockptr->getID()),
273  QVariant::fromValue(blockptr));
274  }
275  blockList->setCurrentIndex(blockList->findData(prev_selected_block));
276 }
277 
278 void DataRecorder::Panel::buildChannelList()
279 {
280  channelList->clear();
281  if (blockList->count() == 0 || blockList->currentIndex() < 0) {
282  return;
283  }
284 
285  auto* block = blockList->currentData().value<IO::Block*>();
286 
287  auto type = this->typeList->currentData().value<IO::flags_t>();
288  for (size_t i = 0; i < block->getCount(type); ++i) {
289  channelList->addItem(QString(block->getChannelName(type, i).c_str()),
290  QVariant::fromValue(i));
291  }
292  addRecorderButton->setEnabled(channelList->count() != 0);
293 }
294 
295 void DataRecorder::Panel::changeDataFile()
296 {
297  QFileDialog fileDialog(this);
298  // Accept save mode will automatically confirm whether to "overwrite"
299  fileDialog.setAcceptMode(QFileDialog::AcceptSave);
300  fileDialog.setFileMode(QFileDialog::AnyFile);
301  fileDialog.setWindowTitle("Select Data File");
302 
303  QSettings userprefs;
304  QSettings::setPath(QSettings::NativeFormat,
305  QSettings::SystemScope,
306  "/usr/local/share/rtxi/");
307  fileDialog.setDirectory(
308  userprefs.value("/dirs/data", getenv("HOME")).toString()); // NOLINT
309 
310  QStringList filterList;
311  filterList.push_back("HDF5 files (*.h5)");
312  filterList.push_back("All files (*.*)");
313  fileDialog.setNameFilters(filterList);
314  fileDialog.selectNameFilter("HDF5 files (*.h5)");
315 
316  fileDialog.selectFile(
317  QString("rtxi_datafile_") + QDate::currentDate().toString("yyyyMMdd")
318  + QString("_") + QTime::currentTime().toString("hhmmss"));
319  QStringList files;
320  if (fileDialog.exec() != QDialog::Accepted) {
321  return;
322  }
323  files = fileDialog.selectedFiles();
324  QString filename;
325  if (files.isEmpty() || files[0] == nullptr || files[0] == "/") {
326  return;
327  }
328  filename = files[0];
329  if (!filename.toLower().endsWith(QString(".h5"))) {
330  filename += ".h5";
331  }
332 
333  // Write this directory to the user prefs as most recently used
334  userprefs.setValue("/dirs/data", fileDialog.directory().path());
335 
336  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
337  hplugin->change_file(filename.toStdString());
338  this->fileNameEdit->setText(QString(hplugin->getOpenFilename().c_str()));
339 }
340 
341 // Insert channel to record into list
342 void DataRecorder::Panel::insertChannel()
343 {
344  if ((blockList->count() == 0) || (channelList->count() == 0)) {
345  return;
346  }
347 
349  endpoint.block = blockList->currentData().value<IO::Block*>();
350  endpoint.direction = typeList->currentData().value<IO::flags_t>();
351  endpoint.port = channelList->currentData().value<size_t>();
352 
353  for (int row = 0; row < this->selectionBox->count(); row++) {
354  if (this->selectionBox->item(row)->data(Qt::UserRole).value<IO::endpoint>()
355  == endpoint)
356  {
357  this->selectionBox->setCurrentRow(row);
358  return;
359  }
360  }
361 
362  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
363  const int result = hplugin->create_component(endpoint);
364 
365  if (result != 0) {
366  ERROR_MSG(
367  "DataRecorder::Panel::insertChannel : Unable to create recording "
368  "component");
369  return;
370  }
371 
372  QListWidgetItem* temp_item = nullptr;
373  const std::string formatting = "{} {} direction: {} port: {}";
374  const std::string temp_name =
375  fmt::format(formatting,
377  endpoint.block->getID(),
378  endpoint.direction == IO::INPUT ? "INPUT" : "OUTPUT",
379  endpoint.port);
380  temp_item = new QListWidgetItem(QString(temp_name.c_str()));
381  temp_item->setData(Qt::UserRole, QVariant::fromValue(endpoint));
382  selectionBox->addItem(temp_item);
383 
384  removeRecorderButton->setEnabled(selectionBox->count() != 0);
385 }
386 
387 void DataRecorder::Panel::removeChannel()
388 {
389  if ((selectionBox->count() == 0) || selectionBox->selectedItems().isEmpty()) {
390  return;
391  }
392  QListWidgetItem* currentItem =
393  this->selectionBox->takeItem(this->selectionBox->currentRow());
394  auto endpoint = currentItem->data(Qt::UserRole).value<IO::endpoint>();
395  this->selectionBox->setCurrentRow(-1);
396  this->selectionBox->removeItemWidget(currentItem);
397  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
398  hplugin->destroy_component(endpoint);
399  // Taking an item out means we have to handle deletion ourselves
400  delete currentItem;
401 
402  removeRecorderButton->setEnabled(selectionBox->count() != 0);
403 }
404 
405 void DataRecorder::Panel::addNewTag()
406 {
407  // std::string newTag(std::to_string(RT::OS::getTime()));
408  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
409  const std::string tag = this->timeStampEdit->text().toStdString();
410  if (hplugin->apply_tag(tag) != 0) {
411  ERROR_MSG("DataRecorder::Panel::addNewTag : could not tag data with tag {}",
412  tag);
413  timeStampEdit->clear();
414  recordStatus->setText("Tagging Failed!");
415  return;
416  }
417  timeStampEdit->clear();
418  recordStatus->setText("Tagged");
419 }
420 
421 // Start recording slot
423 {
424  if (fileNameEdit->text().isEmpty()) {
425  QMessageBox::critical(this,
426  "Data file not specified.",
427  "Please specify a file to write data to.",
428  QMessageBox::Ok,
429  QMessageBox::NoButton);
430  return;
431  }
432 
433  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
434  hplugin->startRecording();
435  this->recordStatus->setText(hplugin->isRecording() ? "Recording..."
436  : "Not ready");
437  if (hplugin->isRecording()) {
438  this->starting_record_time = QTime::currentTime();
439  this->trialNum->setNum(hplugin->getTrialCount());
440  this->trialLength->setText("Recording...");
441  }
442 }
443 
444 // Stop recording slot
446 {
447  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
448  hplugin->stopRecording();
449  this->recordStatus->setText(!hplugin->isRecording() ? "Ready"
450  : "Recording...");
451  if (!hplugin->isRecording()) {
452  this->trialNum->setNum(hplugin->getTrialCount());
453  this->trialLength->setNum(
454  this->starting_record_time.secsTo(QTime::currentTime()));
455  this->fileSize->setNum(
456  static_cast<double>(QFile(fileNameEdit->text()).size())
457  / (1024.0 * 1024.0));
458  }
459 }
460 
461 // Update downsample rate
463 {
464  this->downsample_rate = rate;
465 }
466 
468 {
469  std::vector<QListWidgetItem*> all_recorders;
470  IO::endpoint temp_endpoint;
471  for (int row = 0; row < this->selectionBox->count(); row++) {
472  temp_endpoint =
473  this->selectionBox->item(row)->data(Qt::UserRole).value<IO::endpoint>();
474  if (temp_endpoint.block == block) {
475  all_recorders.push_back(this->selectionBox->item(row));
476  }
477  }
478  for (auto* item : all_recorders) {
479  this->selectionBox->removeItemWidget(item);
480  }
481 }
482 
483 void DataRecorder::Panel::processData()
484 {
485  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(this->getHostPlugin());
486  hplugin->process_data_worker();
487 }
488 
489 void DataRecorder::Panel::syncEnableRecordingButtons(const QString& /*unused*/)
490 {
491  auto* hplugin = dynamic_cast<DataRecorder::Plugin*>(getHostPlugin());
492  const bool ready = hplugin->isFileOpen();
493  startRecordButton->setEnabled(ready);
494  stopRecordButton->setEnabled(ready);
495  this->recordStatus->setText(ready ? "Ready" : "Not ready");
496 }
497 
499  : Widgets::Plugin(ev_manager, std::string(DataRecorder::MODULE_NAME))
500  , recording(false)
501 {
502 }
503 
505 {
506  this->stopRecording();
507  this->closeFile();
509  std::vector<Event::Object> unload_events;
510  for (auto& entry : this->m_recording_channels_list) {
511  unload_events.emplace_back(event_type);
512  unload_events.back().setParam(
513  "thread", std::any(static_cast<RT::Thread*>(entry.component.get())));
514  }
515  this->getEventManager()->postEvent(unload_events);
516 }
517 
519 {
520  IO::Block* block = nullptr;
521  std::vector<IO::endpoint> endpoints;
522  switch (event->getType()) {
525  dynamic_cast<DataRecorder::Panel*>(this->getPanel())
526  ->removeRecorders(block);
527  for (const auto& entry : this->m_recording_channels_list) {
528  if (entry.channel.endpoint.block == block) {
529  endpoints.push_back(entry.channel.endpoint);
530  }
531  }
532  for (const auto& endpoint : endpoints) {
533  this->destroy_component(endpoint);
534  }
535  dynamic_cast<DataRecorder::Panel*>(this->getPanel())->updateBlockInfo();
536  break;
539  dynamic_cast<DataRecorder::Panel*>(this->getPanel())->updateBlockInfo();
540  break;
541  default:
542  break;
543  }
544 }
545 
547 {
548  if (this->recording.load() || this->m_recording_channels_list.empty()) {
549  return;
550  }
551  const std::unique_lock<std::shared_mutex> lk(this->m_channels_list_mut);
552  this->append_new_trial();
554  std::vector<Event::Object> start_recording_event;
555  for (auto& rec_channel : this->m_recording_channels_list) {
556  start_recording_event.emplace_back(event_type);
557  start_recording_event.back().setParam(
558  "thread", static_cast<RT::Thread*>(rec_channel.component.get()));
559  }
560  this->getEventManager()->postEvent(start_recording_event);
561  this->recording.store(true);
562 }
563 
565 {
566  if (!this->recording.load()) {
567  return;
568  }
569  const std::unique_lock<std::shared_mutex> lk(this->m_channels_list_mut);
571  std::vector<Event::Object> stop_recording_event;
572  for (auto& rec_chan : this->m_recording_channels_list) {
573  stop_recording_event.emplace_back(event_type);
574  stop_recording_event.back().setParam(
575  "thread", static_cast<RT::Thread*>(rec_chan.component.get()));
576  }
577  this->getEventManager()->postEvent(stop_recording_event);
578  this->recording.store(false);
579 }
580 
581 void DataRecorder::Plugin::close_trial_group()
582 {
583  if (!open_file.load()) {
584  return;
585  }
586  for (auto& channel : this->m_recording_channels_list) {
587  if (channel.hdf5_data_handle != H5I_INVALID_HID) {
588  H5PTclose(channel.hdf5_data_handle);
589  channel.hdf5_data_handle = H5I_INVALID_HID;
590  }
591  }
592  if (this->hdf5_handles.sync_group_handle != H5I_INVALID_HID) {
593  H5Gclose(this->hdf5_handles.sync_group_handle);
594  this->hdf5_handles.sync_group_handle = H5I_INVALID_HID;
595  }
596  if (this->hdf5_handles.async_group_handle != H5I_INVALID_HID) {
597  H5Gclose(this->hdf5_handles.async_group_handle);
598  this->hdf5_handles.async_group_handle = H5I_INVALID_HID;
599  }
600  if (this->hdf5_handles.sys_data_group_handle != H5I_INVALID_HID) {
601  H5Gclose(this->hdf5_handles.sys_data_group_handle);
602  this->hdf5_handles.sys_data_group_handle = H5I_INVALID_HID;
603  }
604  if (this->hdf5_handles.trial_group_handle != H5I_INVALID_HID) {
605  H5Gclose(this->hdf5_handles.trial_group_handle);
606  this->hdf5_handles.trial_group_handle = H5I_INVALID_HID;
607  }
608 }
609 
610 void DataRecorder::Plugin::open_trial_group()
611 {
612  this->trial_count += 1;
613  hid_t compression_property = H5I_INVALID_HID;
614  std::string trial_name = "/Trial";
615  trial_name += std::to_string(this->trial_count);
616  this->hdf5_handles.trial_group_handle =
617  H5Gcreate(this->hdf5_handles.file_handle,
618  trial_name.c_str(),
619  H5P_DEFAULT,
620  H5P_DEFAULT,
621  H5P_DEFAULT);
622  this->hdf5_handles.sync_group_handle =
623  H5Gcreate(this->hdf5_handles.trial_group_handle,
624  (trial_name + "/Synchronous Data").c_str(),
625  H5P_DEFAULT,
626  H5P_DEFAULT,
627  H5P_DEFAULT);
628  this->hdf5_handles.async_group_handle =
629  H5Gcreate(this->hdf5_handles.trial_group_handle,
630  (trial_name + "/Asynchronous Data").c_str(),
631  H5P_DEFAULT,
632  H5P_DEFAULT,
633  H5P_DEFAULT);
634  this->hdf5_handles.sys_data_group_handle =
635  H5Gcreate(this->hdf5_handles.trial_group_handle,
636  (trial_name + "/System Settings").c_str(),
637  H5P_DEFAULT,
638  H5P_DEFAULT,
639  H5P_DEFAULT);
640  for (auto& channel : this->m_recording_channels_list) {
641  compression_property = H5Pcreate(H5P_DATASET_CREATE);
642  H5Pset_deflate(compression_property, 7);
643  channel.hdf5_data_handle =
644  H5PTcreate(this->hdf5_handles.sync_group_handle,
645  channel.channel.name.c_str(),
646  this->hdf5_handles.channel_datatype_handle,
647  this->m_data_chunk_size,
648  compression_property);
649  }
650 }
651 
652 void DataRecorder::Plugin::append_new_trial()
653 {
654  this->close_trial_group();
655  // We have to flush all of the data from the buffers that did not make it
656  std::array<data_token_t, 100> tempbuffer {};
657  for (auto& recorder : this->m_recording_channels_list) {
658  while (recorder.channel.data_source->read(
659  tempbuffer.data(),
660  sizeof(DataRecorder::data_token_t) * tempbuffer.size())
661  > 0)
662  {
663  }
664  }
665  this->open_trial_group();
666 }
667 
668 void DataRecorder::Plugin::openFile(const std::string& file_name)
669 {
670  if (this->open_file.load()) {
671  return;
672  }
673  const std::unique_lock<std::shared_mutex> lk(this->m_channels_list_mut);
674  this->hdf5_handles.file_handle =
675  H5Fcreate(file_name.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
676  if (this->hdf5_handles.file_handle == H5I_INVALID_HID) {
677  ERROR_MSG("DataRecorder::Plugin::openFile : Unable to open file {}",
678  file_name);
679  H5Fclose(this->hdf5_handles.file_handle);
680  this->hdf5_filename = "";
681  return;
682  }
683  this->hdf5_filename = file_name;
684  this->trial_count = 0;
685  this->hdf5_handles.channel_datatype_handle =
686  H5Tcreate(H5T_COMPOUND, sizeof(DataRecorder::data_token_t));
687  H5Tinsert(this->hdf5_handles.channel_datatype_handle,
688  "time",
689  HOFFSET(DataRecorder::data_token_t, time),
690  H5T_STD_I64LE);
691  H5Tinsert(this->hdf5_handles.channel_datatype_handle,
692  "value",
693  HOFFSET(DataRecorder::data_token_t, value),
694  H5T_IEEE_F64LE);
695  // this->open_trial_group();
696  this->open_file.store(true);
697 }
698 
700 {
701  if (!this->open_file.load()) {
702  return;
703  }
704  const std::unique_lock<std::shared_mutex> lk(this->m_channels_list_mut);
705  // Attempt to close all group and dataset handles in hdf5 before closing file
706  close_trial_group();
707  H5Tclose(this->hdf5_handles.channel_datatype_handle);
708  if (H5Fclose(this->hdf5_handles.file_handle) != 0) {
709  ERROR_MSG("DataRecorder::Plugin::closeFile : Unable to close file {}",
710  this->hdf5_filename);
711  }
712  this->open_file = false;
713 }
714 
715 void DataRecorder::Plugin::change_file(const std::string& file_name)
716 {
717  this->closeFile();
718  this->openFile(file_name);
719 }
720 
722 {
723  if (endpoint.block == nullptr) {
724  return -1;
725  }
726  auto iter = std::find_if(this->m_recording_channels_list.begin(),
727  this->m_recording_channels_list.end(),
728  [endpoint](const recorder_t& rec)
729  { return rec.channel.endpoint == endpoint; });
730  if (iter != this->m_recording_channels_list.end()) {
731  return 0;
732  }
734  chan.name = endpoint.block->getName();
735  chan.name += " ";
736  chan.name += std::to_string(endpoint.block->getID());
737  chan.name += " ";
738  chan.name += "Recording Component";
739  chan.endpoint = endpoint;
740  std::unique_ptr<DataRecorder::Component> component =
741  std::make_unique<DataRecorder::Component>(this, chan.name);
742  chan.data_source = component->get_fifo();
744  component->setActive(this->recording.load());
745  event.setParam("thread", static_cast<RT::Thread*>(component.get()));
746  this->getEventManager()->postEvent(&event);
747  RT::block_connection_t connection;
748  connection.src = endpoint.block;
749  connection.src_port_type = endpoint.direction;
750  connection.src_port = endpoint.port;
751  connection.dest = component.get();
752  connection.dest_port = 0; // Recording components only have one input
754  connect_event.setParam("connection", std::any(connection));
755  this->getEventManager()->postEvent(&connect_event);
756  hid_t data_handle = H5I_INVALID_HID;
757  const std::unique_lock<std::shared_mutex> lk(this->m_channels_list_mut);
758  if (this->hdf5_handles.file_handle != H5I_INVALID_HID
759  && this->hdf5_handles.sync_group_handle != H5I_INVALID_HID)
760  {
761  const hid_t compression_property = H5Pcreate(H5P_DATASET_CREATE);
762  H5Pset_deflate(compression_property, 7);
763  data_handle = H5PTcreate(this->hdf5_handles.sync_group_handle,
764  chan.name.c_str(),
765  this->hdf5_handles.channel_datatype_handle,
766  this->m_data_chunk_size,
767  compression_property);
768  }
769  this->m_recording_channels_list.emplace_back(
770  chan, std::move(component), data_handle);
771  return 0;
772 }
773 
775 {
776  DataRecorder::Component* component = this->getRecorderPtr(endpoint);
778  pause_event.setParam("thread", static_cast<RT::Thread*>(component));
779  this->getEventManager()->postEvent(&pause_event);
781  unplug_event.setParam("thread", static_cast<RT::Thread*>(component));
782  this->getEventManager()->postEvent(&unplug_event);
783  const std::unique_lock<std::shared_mutex> recorder_lock(
784  this->m_channels_list_mut);
785  auto iter = std::find_if(this->m_recording_channels_list.begin(),
786  this->m_recording_channels_list.end(),
787  [component](const recorder_t& rec_chan)
788  { return rec_chan.component.get() == component; });
789  if (iter == this->m_recording_channels_list.end()) {
790  return;
791  }
792  H5PTclose(iter->hdf5_data_handle);
793  this->m_recording_channels_list.erase(iter);
794 }
795 
797 {
798  const std::shared_lock<std::shared_mutex> lk(this->m_channels_list_mut);
799  auto iter = std::find_if(this->m_recording_channels_list.begin(),
800  this->m_recording_channels_list.end(),
801  [endpoint](const recorder_t& rec_chan)
802  { return rec_chan.channel.endpoint == endpoint; });
803  if (iter == this->m_recording_channels_list.end()) {
804  return "";
805  }
806  return iter->channel.name;
807 }
808 
811 {
812  const std::shared_lock<std::shared_mutex> lk(this->m_channels_list_mut);
813  auto iter = std::find_if(this->m_recording_channels_list.begin(),
814  this->m_recording_channels_list.end(),
815  [endpoint](const recorder_t& rec_chan)
816  { return rec_chan.channel.endpoint == endpoint; });
817  if (iter == this->m_recording_channels_list.end()) {
818  return nullptr;
819  }
820  return iter->component.get();
821 }
822 
823 std::vector<DataRecorder::record_channel>
825 {
826  const std::shared_lock<std::shared_mutex> lk(this->m_channels_list_mut);
827  std::vector<DataRecorder::record_channel> result(
828  this->m_recording_channels_list.size());
829  for (size_t i = 0; i < result.size(); i++) {
830  result[i] = this->m_recording_channels_list[i].channel;
831  }
832  return result;
833 }
834 
835 int DataRecorder::Plugin::apply_tag(const std::string& tag)
836 {
837  return 0;
838 }
839 
841 {
842  if (!this->open_file) {
843  return;
844  }
845  std::vector<DataRecorder::data_token_t> data_buffer(this->m_data_chunk_size);
846  const size_t packet_byte_size = sizeof(DataRecorder::data_token_t);
847  int64_t read_bytes = 0;
848  size_t packet_count = 0;
849  const std::shared_lock<std::shared_mutex> lk(this->m_channels_list_mut);
850  for (auto& channel : this->m_recording_channels_list) {
851  while (read_bytes = channel.channel.data_source->read(
852  data_buffer.data(), packet_byte_size * data_buffer.size()),
853  read_bytes > 0)
854  {
855  packet_count = static_cast<size_t>(read_bytes) / packet_byte_size;
856  DataRecorder::Plugin::save_data(
857  channel.hdf5_data_handle, data_buffer, packet_count);
858  }
859  }
860 }
861 
862 void DataRecorder::Plugin::save_data(
863  hid_t data_id,
864  const std::vector<DataRecorder::data_token_t>& data,
865  size_t packet_count)
866 {
867  const herr_t err =
868  H5PTappend(data_id, static_cast<hsize_t>(packet_count), data.data());
869  if (err < 0) {
870  ERROR_MSG("Unable to write data into hdf5 file!");
871  }
872 }
873 
875  const std::string& probe_name)
876  : Widgets::Component(hplugin,
877  probe_name,
880 {
881  if (RT::OS::getFifo(this->m_fifo, DataRecorder::DEFAULT_BUFFER_SIZE) != 0) {
882  ERROR_MSG("Unable to create xfifo for Oscilloscope Component {}",
883  probe_name);
884  }
885 }
886 
888 {
889  DataRecorder::data_token_t data_sample;
890  const double value = readinput(0);
891  switch (this->getState()) {
892  case RT::State::EXEC:
893  data_sample.time = RT::OS::getTime();
894  data_sample.value = value;
895  this->m_fifo->writeRT(&data_sample, sizeof(DataRecorder::data_token_t));
896  break;
897  case RT::State::PAUSE:
898  break;
899  case RT::State::UNPAUSE:
900  case RT::State::INIT:
901  this->setState(RT::State::EXEC);
902  break;
903  default:
904  break;
905  }
906 }
907 
909 {
910  return this->m_fifo.get();
911 }
912 
913 std::unique_ptr<Widgets::Plugin> DataRecorder::createRTXIPlugin(
914  Event::Manager* ev_manager)
915 {
916  return std::make_unique<DataRecorder::Plugin>(ev_manager);
917 }
918 
920  Event::Manager* ev_manager)
921 {
922  return static_cast<Widgets::Panel*>(
923  new DataRecorder::Panel(mwindow, ev_manager));
924 }
925 
926 std::unique_ptr<Widgets::Component> DataRecorder::createRTXIComponent(
927  Widgets::Plugin* /*host_plugin*/)
928 {
929  return std::unique_ptr<DataRecorder::Component>(nullptr);
930 }
931 
933 {
938  return fact;
939 }
RT::OS::Fifo * get_fifo()
Component(Widgets::Plugin *hplugin, const std::string &probe_name)
void updateDownsampleRate(size_t rate)
Panel(const Panel &)=delete
void removeRecorders(IO::Block *block)
void destroy_component(IO::endpoint endpoint)
int apply_tag(const std::string &tag)
void openFile(const std::string &file_name)
std::string getRecorderName(IO::endpoint endpoint)
void change_file(const std::string &file_name)
int create_component(IO::endpoint endpoint)
DataRecorder::Component * getRecorderPtr(IO::endpoint endpoint)
void receiveEvent(Event::Object *event) override
Plugin(const Plugin &)=delete
std::vector< record_channel > get_recording_channels()
void setParam(const std::string &param_name, const std::any &param_value)
Definition: event.cpp:191
Event::Type getType() const
Definition: event.cpp:228
Definition: io.hpp:79
size_t getID() const
Definition: io.hpp:202
std::string getName() const
Definition: io.hpp:108
QMdiSubWindow * getMdiWindow()
Definition: widgets.hpp:286
void ERROR_MSG(const std::string &errmsg, Args... args)
Definition: debug.hpp:36
constexpr std::string_view MODULE_NAME
Definition: connector.hpp:35
constexpr size_t DEFAULT_BUFFER_SIZE
constexpr std::string_view MODULE_NAME
Widgets::FactoryMethods getFactories()
std::unique_ptr< Widgets::Plugin > createRTXIPlugin(Event::Manager *ev_manager)
struct DataRecorder::data_token_t data_token_t
std::vector< Widgets::Variable::Info > get_default_vars()
std::unique_ptr< Widgets::Component > createRTXIComponent(Widgets::Plugin *host_plugin)
Widgets::Panel * createRTXIPanel(QMainWindow *mwindow, Event::Manager *ev_manager)
std::vector< IO::channel_t > get_default_channels()
Type
Definition: event.hpp:55
@ RT_THREAD_PAUSE_EVENT
Definition: event.hpp:62
@ RT_THREAD_UNPAUSE_EVENT
Definition: event.hpp:63
@ IO_LINK_INSERT_EVENT
Definition: event.hpp:71
@ RT_THREAD_INSERT_EVENT
Definition: event.hpp:60
@ RT_DEVICE_INSERT_EVENT
Definition: event.hpp:64
@ RT_THREAD_REMOVE_EVENT
Definition: event.hpp:61
@ IO_BLOCK_QUERY_EVENT
Definition: event.hpp:73
@ RT_DEVICE_REMOVE_EVENT
Definition: event.hpp:65
flags_t
Definition: io.hpp:52
@ INPUT
Definition: io.hpp:54
@ OUTPUT
Definition: io.hpp:53
struct IO::endpoint endpoint
int getFifo(std::unique_ptr< Fifo > &fifo, size_t fifo_size)
Definition: fifo.cpp:194
int64_t getTime()
@ EXEC
Definition: rt.hpp:55
@ UNPAUSE
Definition: rt.hpp:59
@ PAUSE
Definition: rt.hpp:58
@ INIT
Definition: rt.hpp:54
Definition: rt.hpp:35
IO::flags_t direction
Definition: io.hpp:260
size_t port
Definition: io.hpp:259
IO::Block * block
Definition: io.hpp:258
IO::Block * dest
Definition: rt.hpp:190
IO::Block * src
Definition: rt.hpp:187
IO::flags_t src_port_type
Definition: rt.hpp:188
std::unique_ptr< Widgets::Plugin >(* createPlugin)(Event::Manager *)
Function that returns a smart pointer to plugin object.
Definition: widgets.hpp:145
Widgets::Panel *(* createPanel)(QMainWindow *, Event::Manager *)
Definition: widgets.hpp:170
std::unique_ptr< Widgets::Component >(* createComponent)(Widgets::Plugin *)
Definition: widgets.hpp:156