RTXI  3.0.0
The Real-Time eXperiment Interface Reference Manual
oscilloscope.cpp
Go to the documentation of this file.
1 /*
2  The Real-Time eXperiment Interface (RTXI)
3  Copyright (C) 2011 Georgia Institute of Technology, University of Utah,
4  Weill Cornell Medical College
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*
21  This class creates and controls the drawing parameters
22  A control panel is instantiated for all the active channels/modules
23  and user selection is enabled to change color, style, width and other
24  oscilloscope properties.
25  */
26 
27 #include <QButtonGroup>
28 #include <QRadioButton>
29 #include <QTimer>
30 #include <cmath>
31 #include <sstream>
32 
33 #include "oscilloscope.hpp"
34 
35 #include <qwt_plot_renderer.h>
36 
37 #include "debug.hpp"
38 #include "main_window.hpp"
39 #include "rt.hpp"
40 #include "scope.hpp"
41 
43 {
44  auto* module_panel = dynamic_cast<Oscilloscope::Panel*>(this->getPanel());
45  switch (event->getType()) {
48  module_panel->updateBlockInfo();
49  break;
51  module_panel->updateBlockChannels(
52  std::any_cast<RT::Thread*>(event->getParam("thread")));
53  module_panel->updateBlockInfo();
54  break;
56  module_panel->updateBlockChannels(
57  std::any_cast<RT::Device*>(event->getParam("device")));
58  module_panel->updateBlockInfo();
59  break;
60  default:
61  break;
62  }
63 }
64 
65 void Oscilloscope::Panel::updateChannelScale(IO::endpoint probe_info)
66 {
67  const auto scale = this->scalesList->currentData().value<double>();
68  this->scopeWindow->setChannelScale(probe_info, scale);
69 }
70 
71 void Oscilloscope::Panel::updateChannelOffset(IO::endpoint probe_info)
72 {
73  const double chanoffset = this->offsetsEdit->text().toDouble()
74  * offsetsList->currentData().value<double>();
75  this->scopeWindow->setChannelOffset(probe_info, chanoffset);
76 }
77 
78 void Oscilloscope::Panel::updateChannelPen(IO::endpoint endpoint)
79 {
80  QPen pen = QPen();
81  pen.setColor(this->colorsList->currentData().value<QColor>());
82  pen.setWidth(this->widthsList->currentData().value<int>());
83  pen.setStyle(this->stylesList->currentData().value<Qt::PenStyle>());
84  this->scopeWindow->setChannelPen(endpoint, pen);
85 }
86 
87 void Oscilloscope::Panel::updateChannelLabel(IO::endpoint probe_info)
88 {
89  const QString chanlabel = QString::number(probe_info.block->getID()) + " "
90  + QString(probe_info.block->getName().c_str()) + " "
91  + this->scalesList->currentText();
92 
93  this->scopeWindow->setChannelLabel(probe_info, chanlabel);
94 }
95 
96 void Oscilloscope::Panel::updateWindowTimeDiv()
97 {
98  auto divt = this->timesList->currentData().value<int64_t>();
99  this->scopeWindow->setDivT(divt);
100 }
101 
102 void Oscilloscope::Panel::enableChannel()
103 {
104  // make some initial checks
105  if (!this->activateButton->isChecked()) {
106  return;
107  }
108 
109  // create component before we create the channel proper
110  auto* oscilloscope_plugin =
111  dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
112  auto* chanblock = this->blocksListDropdown->currentData().value<IO::Block*>();
113  auto chanport = this->channelsList->currentData().value<size_t>();
114  auto chandirection = this->typesList->currentData().value<IO::flags_t>();
115 
116  // this will try to create the probing component first. return if something
117  // goes wrong
118  const IO::endpoint endpoint {chanblock, chanport, chandirection};
119  RT::OS::Fifo* probe_fifo = oscilloscope_plugin->createProbe(endpoint);
120  if (probe_fifo == nullptr) {
121  ERROR_MSG(
122  "Oscilloscope::Panel::enableChannel Unable to create probing channel "
123  "for block {}",
124  chanblock->getName());
125  return;
126  }
127 
128  this->scopeWindow->createChannel(endpoint, probe_fifo);
129  // we were able to create the probe, so we should populate metainfo about it
130  // in scope window
131  this->updateChannelOffset(endpoint);
132  this->updateChannelScale(endpoint);
133  this->updateChannelPen(endpoint);
134 }
135 
136 void Oscilloscope::Panel::disableChannel()
137 {
138  // make some initial checks
139  if (!this->activateButton->isChecked()) {
140  return;
141  }
142 
143  auto* oscilloscope_plugin =
144  dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
145  auto* chanblock = this->blocksListDropdown->currentData().value<IO::Block*>();
146  auto chanport = this->channelsList->currentData().value<size_t>();
147  auto chandirection = this->typesList->currentData().value<IO::flags_t>();
148 
149  const IO::endpoint probe {chanblock, chanport, chandirection};
150  // we should remove the scope channel before we attempt to remove block
151  this->scopeWindow->removeChannel(probe);
152  oscilloscope_plugin->deleteProbe(probe);
153 }
154 
155 void Oscilloscope::Panel::activateChannel(bool active)
156 {
157  const bool enable =
158  active && blocksListDropdown->count() > 0 && channelsList->count() > 0;
159  scalesList->setEnabled(enable);
160  offsetsEdit->setEnabled(enable);
161  offsetsList->setEnabled(enable);
162  colorsList->setEnabled(enable);
163  widthsList->setEnabled(enable);
164  stylesList->setEnabled(enable);
165  this->activateButton->setChecked(enable);
166 }
167 
168 void Oscilloscope::Panel::apply()
169 {
170  switch (tabWidget->currentIndex()) {
171  case 0:
172  applyChannelTab();
173  break;
174  case 1:
175  applyDisplayTab();
176  break;
177  default:
178  ERROR_MSG("Oscilloscope::Panel::showTab : invalid tab\n");
179  }
180 }
181 
182 void Oscilloscope::Panel::buildChannelList()
183 {
184  if (blocksListDropdown->count() <= 0) {
185  return;
186  }
187 
188  if (blocksListDropdown->currentIndex() < 0) {
189  blocksListDropdown->setCurrentIndex(0);
190  }
191 
192  auto* block = this->blocksListDropdown->currentData().value<IO::Block*>();
193  auto type = this->typesList->currentData().value<IO::flags_t>();
194  channelsList->clear();
195  for (size_t i = 0; i < block->getCount(type); ++i) {
196  channelsList->addItem(QString(block->getChannelName(type, i).c_str()),
197  QVariant::fromValue(i));
198  }
199  channelsList->setCurrentIndex(0);
200  showChannelTab();
201 }
202 
203 void Oscilloscope::Panel::showTab(int index)
204 {
205  switch (index) {
206  case 0:
207  showChannelTab();
208  break;
209  case 1:
210  showDisplayTab();
211  break;
212  default:
213  ERROR_MSG("Oscilloscope::Panel::showTab : invalid tab\n");
214  }
215 }
216 
218 {
219  auto* hplugin = dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
220  hplugin->setProbeActivity(endpoint, activity);
221 }
222 
223 void Oscilloscope::Panel::applyChannelTab()
224 {
225  if (this->blocksListDropdown->count() <= 0
226  || this->channelsList->count() <= 0) {
227  return;
228  }
229 
230  auto* block = this->blocksListDropdown->currentData().value<IO::Block*>();
231  auto port = this->channelsList->currentData().value<size_t>();
232  auto type = this->typesList->currentData().value<IO::flags_t>();
233  auto* host_plugin =
234  dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
235  const IO::endpoint probeInfo {block, port, type};
236  this->scopeWindow->setPause(/*value=*/true);
237  if (!activateButton->isChecked()) {
238  scopeWindow->removeChannel(probeInfo);
239  host_plugin->deleteProbe(probeInfo);
240  } else {
241  if (!this->scopeWindow->channelRegistered(probeInfo)) {
242  RT::OS::Fifo* fifo = host_plugin->createProbe(probeInfo);
243  if (fifo != nullptr) {
244  this->scopeWindow->createChannel(probeInfo, fifo);
245  }
246  }
247  this->updateChannelScale(probeInfo);
248  this->updateChannelOffset(probeInfo);
249  this->updateChannelPen(probeInfo);
250  this->updateChannelLabel(probeInfo);
251  }
252  scopeWindow->replot();
253  this->scopeWindow->setPause(/*value=*/false);
254  this->syncChannelProperties();
255  showChannelTab();
256 }
257 
258 void Oscilloscope::Panel::applyDisplayTab()
259 {
260  updateTrigger();
261  updateWindowTimeDiv();
262  scopeWindow->replot();
263  showDisplayTab();
264 }
265 
266 void Oscilloscope::Panel::buildBlockList()
267 {
269  this->getRTXIEventManager()->postEvent(&event);
270  auto blocklist =
271  std::any_cast<std::vector<IO::Block*>>(event.getParam("blockList"));
272  auto* previous_block =
273  this->blocksListDropdown->currentData().value<IO::Block*>();
274  blocksListDropdown->clear();
275  for (auto* block : blocklist) {
276  // Ignore blocks created from oscilloscope (probing blocks),
277  // and from recorder (recorder components)
278  if (block->getName().find("Probe") != std::string::npos
279  || block->getName().find("Recording") != std::string::npos)
280  {
281  continue;
282  }
283  this->blocksListDropdown->addItem(QString(block->getName().c_str()) + " "
284  + QString::number(block->getID()),
285  QVariant::fromValue(block));
286  }
287  blocksListDropdown->setCurrentIndex(
288  this->blocksListDropdown->findData(QVariant::fromValue(previous_block)));
289 }
290 
291 QWidget* Oscilloscope::Panel::createChannelTab(QWidget* parent)
292 {
293  setWhatsThis(
294  "<p><b>Oscilloscope: Channel Options</b><br>"
295  "Use the dropdown boxes to select the signal streams you want to plot "
296  "from "
297  "any loaded modules or your DAQ device. You may change the plotting "
298  "scale for "
299  "the signal, apply a DC offset, and change the color and style of the "
300  "line.</p>");
301 
302  auto* page = new QWidget(parent);
303 
304  // Create group and layout for buttons at bottom of scope
305  auto* bttnLayout = new QGridLayout(page);
306 
307  // Create Channel box
308  auto* row1Layout = new QHBoxLayout;
309  auto* channelLabel = new QLabel(tr("Channel:"), page);
310  row1Layout->addWidget(channelLabel);
311  blocksListDropdown = new QComboBox(page);
312  blocksListDropdown->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
313  blocksListDropdown->setSizeAdjustPolicy(QComboBox::AdjustToContents);
314  QObject::connect(blocksListDropdown,
315  SIGNAL(activated(int)),
316  this,
317  SLOT(buildChannelList()));
318  row1Layout->addWidget(blocksListDropdown);
319 
320  // Create Type box
321  typesList = new QComboBox(page);
322  typesList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
323  typesList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
324  typesList->addItem("Output", QVariant::fromValue(IO::OUTPUT));
325  typesList->addItem("Input", QVariant::fromValue(IO::INPUT));
326  row1Layout->addWidget(typesList);
327  QObject::connect(
328  typesList, SIGNAL(activated(int)), this, SLOT(buildChannelList()));
329 
330  // Create Channels box
331  channelsList = new QComboBox(page);
332  channelsList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
333  channelsList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
334  QObject::connect(
335  channelsList, SIGNAL(activated(int)), this, SLOT(showChannelTab()));
336  row1Layout->addWidget(channelsList);
337 
338  // Create elements for display box
339  row1Layout->addSpacerItem(
340  new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
341  auto* scaleLabel = new QLabel(tr("Scale:"), page);
342  row1Layout->addWidget(scaleLabel);
343  scalesList = new QComboBox(page);
344  row1Layout->addWidget(scalesList);
345  const QFont scalesListFont("DejaVu Sans Mono");
346  const QString postfix = "/div";
347  std::array<std::string, 6> unit_array = {"V", "mV", "µV", "nV", "pV", "fV"};
348  size_t unit_array_index = 0;
349  const std::array<double, 4> fixed_values = {10, 5, 2.5, 2};
350  double value_scale = 1.0;
351  scalesList->setFont(scalesListFont);
352  const std::string formatting = "{:.1f} {}/div";
353  double temp_value = 0.0;
354  while (unit_array_index < unit_array.size()) {
355  for (auto current_fixed_value : fixed_values) {
356  temp_value =
357  current_fixed_value * std::pow(1e3, unit_array_index) * value_scale;
358  if (temp_value < 1) {
359  unit_array_index++;
360  if (unit_array_index >= 6) {
361  break;
362  }
363  temp_value =
364  current_fixed_value * std::pow(1e3, unit_array_index) * value_scale;
365  }
366  scalesList->addItem(
367  QString(fmt::format(
368  formatting, temp_value, unit_array.at(unit_array_index))
369  .c_str()),
370  current_fixed_value * value_scale);
371  }
372  value_scale = value_scale / 10.0;
373  }
374  // Offset items
375  auto* offsetLabel = new QLabel(tr("Offset:"), page);
376  row1Layout->addWidget(offsetLabel);
377  offsetsEdit = new QLineEdit(page);
378  offsetsEdit->setMaximumWidth(offsetsEdit->minimumSizeHint().width() * 2);
379  offsetsEdit->setValidator(new QDoubleValidator(offsetsEdit));
380  row1Layout->addWidget(offsetsEdit); //, Qt::AlignRight);
381  offsetsList = new QComboBox(page);
382  row1Layout->addWidget(offsetsList); //, Qt::AlignRight);
383  offsetsList->addItem("V", 1.0);
384  offsetsList->addItem("mV", 1e-3);
385  offsetsList->addItem(QString::fromUtf8("µV"), 1e-6);
386  offsetsList->addItem("nV", 1e-9);
387  offsetsList->addItem("pV", 1e-12);
388 
389  // Create elements for graphic
390  auto* row2Layout = new QHBoxLayout; //(page);
391  row2Layout->setAlignment(Qt::AlignLeft);
392  auto* colorLabel = new QLabel(tr("Color:"), page);
393  row2Layout->addWidget(colorLabel);
394  colorsList = new QComboBox(page);
395  colorsList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
396  colorsList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
397  row2Layout->addWidget(colorsList);
398  QPixmap tmp(25, 25);
399  std::string color_name;
400  for (size_t i = 0; i < penColors.size(); i++) {
401  tmp.fill(penColors.at(i));
402  color_name = Oscilloscope::color2string.at(i);
403  colorsList->addItem(
404  tmp, QString(color_name.c_str()), Oscilloscope::penColors.at(i));
405  }
406 
407  auto* widthLabel = new QLabel(tr("Width:"), page);
408  row2Layout->addWidget(widthLabel);
409  widthsList = new QComboBox(page);
410  widthsList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
411  widthsList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
412  row2Layout->addWidget(widthsList);
413  tmp.fill(Qt::white);
414  QPainter painter(&tmp);
415  for (int i = 1; i < 6; i++) {
416  painter.setPen(
418  painter.drawLine(0, 12, 25, 12);
419  widthsList->addItem(tmp, QString::number(i) + QString(" Pixels"), i);
420  }
421 
422  // Create styles list
423  auto* styleLabel = new QLabel(tr("Style:"), page);
424  row2Layout->addWidget(styleLabel);
425  stylesList = new QComboBox(page);
426  stylesList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
427  stylesList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
428  row2Layout->addWidget(stylesList);
429  std::string temp_name;
430  for (size_t i = 0; i < Oscilloscope::penStyles.size(); i++) {
431  temp_name = Oscilloscope::penstyles2string.at(i);
432  tmp.fill(Qt::white);
433  painter.setPen(
435  3,
436  Oscilloscope::penStyles.at(i)));
437  painter.drawLine(0, 12, 25, 12);
438  stylesList->addItem(tmp,
439  QString(temp_name.c_str()),
440  QVariant::fromValue(Oscilloscope::penStyles.at(i)));
441  }
442 
443  // Activate button
444  row2Layout->addSpacerItem(
445  new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
446  activateButton = new QPushButton("Enable Channel", page);
447  row2Layout->addWidget(activateButton);
448  activateButton->setCheckable(true);
449  activateButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
450  QObject::connect(
451  activateButton, SIGNAL(toggled(bool)), this, SLOT(activateChannel(bool)));
452  activateChannel(/*active=*/false);
453 
454  bttnLayout->addLayout(row1Layout, 0, 0);
455  bttnLayout->addLayout(row2Layout, 1, 0);
456 
457  QObject::connect(blocksListDropdown,
458  SIGNAL(currentTextChanged(const QString&)),
459  this,
460  SLOT(syncChannelProperties()));
461  QObject::connect(typesList,
462  SIGNAL(currentTextChanged(const QString&)),
463  this,
464  SLOT(syncChannelProperties()));
465  QObject::connect(channelsList,
466  SIGNAL(currentTextChanged(const QString&)),
467  this,
468  SLOT(syncChannelProperties()));
469  return page;
470 }
471 
472 QWidget* Oscilloscope::Panel::createDisplayTab(QWidget* parent)
473 {
474  setWhatsThis(
475  "<p><b>Oscilloscope: Display Options</b><br>"
476  "Use the dropdown box to select the time scale for the Oscilloscope. "
477  "This "
478  "scaling is applied to all signals plotted in the same window. You may "
479  "also "
480  "set a trigger on any signal that is currently plotted in the window. A "
481  "yellow "
482  "line will appear at the trigger threshold.</p>");
483 
484  auto* page = new QWidget(parent);
485 
486  // Scope properties
487  auto* displayTabLayout = new QGridLayout(page);
488 
489  // Create elements for time settings
490  auto* row1Layout = new QHBoxLayout;
491  row1Layout->addWidget(new QLabel(tr("Time/Div:"), page));
492  timesList = new QComboBox(page);
493  row1Layout->addWidget(timesList);
494  const QFont timeListFont("DejaVu Sans Mono");
495  timesList->setFont(timeListFont);
496  timesList->addItem("5 s/div", QVariant::fromValue(5000000000));
497  timesList->addItem("2 s/div", QVariant::fromValue(2000000000));
498  timesList->addItem("1 s/div", QVariant::fromValue(1000000000));
499  timesList->addItem("500 ms/div", QVariant::fromValue(500000000));
500  timesList->addItem("200 ms/div", QVariant::fromValue(200000000));
501  timesList->addItem("100 ms/div", QVariant::fromValue(100000000));
502  timesList->addItem("50 ms/div", QVariant::fromValue(50000000));
503  timesList->addItem("20 ms/div", QVariant::fromValue(20000000));
504  timesList->addItem("10 ms/div", QVariant::fromValue(10000000));
505  timesList->addItem("5 ms/div", QVariant::fromValue(5000000));
506  timesList->addItem("2 ms/div", QVariant::fromValue(2000000));
507  timesList->addItem("1 ms/div", QVariant::fromValue(1000000));
508  timesList->addItem(QString::fromUtf8("500 µs/div"),
509  QVariant::fromValue(500000));
510  timesList->addItem(QString::fromUtf8("200 µs/div"),
511  QVariant::fromValue(200000));
512  timesList->addItem(QString::fromUtf8("100 µs/div"),
513  QVariant::fromValue(100000));
514  timesList->addItem(QString::fromUtf8("50 µs/div"),
515  QVariant::fromValue(50000));
516  timesList->addItem(QString::fromUtf8("20 µs/div"),
517  QVariant::fromValue(20000));
518  timesList->addItem(QString::fromUtf8("10 µs/div"),
519  QVariant::fromValue(10000));
520  timesList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
521  timesList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
522 
523  auto* refreshLabel = new QLabel(tr("Refresh:"), page);
524  row1Layout->addWidget(refreshLabel);
525  refreshDropdown = new QComboBox(page);
526  row1Layout->addWidget(refreshDropdown);
527  refreshDropdown->addItem("60 Hz",
528  QVariant::fromValue(Oscilloscope::FrameRates::HZ60));
529  refreshDropdown->addItem(
530  "120 Hz", QVariant::fromValue(Oscilloscope::FrameRates::HZ120));
531  refreshDropdown->addItem(
532  "240 Hz", QVariant::fromValue(Oscilloscope::FrameRates::HZ240));
533 
534  // Display box for Buffer bit. Push it to the right.
535  row1Layout->addSpacerItem(
536  new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
537  auto* bufferLabel = new QLabel(tr("Buffer Size (MB):"), page);
538  row1Layout->addWidget(bufferLabel);
539  sizesEdit = new QLineEdit(page);
540  sizesEdit->setMaximumWidth(sizesEdit->minimumSizeHint().width() * 3);
541  sizesEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
542  row1Layout->addWidget(sizesEdit);
543  const auto total_bytes = static_cast<double>(scopeWindow->getDataSize()
544  * sizeof(Oscilloscope::sample));
545  sizesEdit->setText(QString::number(total_bytes / 1e6));
546  sizesEdit->setEnabled(false);
547 
548  // Trigger box
549  auto* row2Layout = new QHBoxLayout;
550  row2Layout->addWidget(new QLabel(tr("Edge:"), page));
551  trigsGroup = new QButtonGroup(page);
552 
553  auto* off = new QRadioButton(tr("Off"), page);
554  trigsGroup->addButton(off, Oscilloscope::Trigger::NONE);
555  row2Layout->addWidget(off);
556  auto* plus = new QRadioButton(tr("+"), page);
557  trigsGroup->addButton(plus, Oscilloscope::Trigger::POS);
558  row2Layout->addWidget(plus);
559  auto* minus = new QRadioButton(tr("-"), page);
560  trigsGroup->addButton(minus, Oscilloscope::Trigger::NEG);
561  row2Layout->addWidget(minus);
562 
563  row2Layout->addWidget(new QLabel(tr("Channel:"), page));
564  trigsChanList = new QComboBox(page);
565  trigsChanList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
566  trigsChanList->setSizeAdjustPolicy(QComboBox::AdjustToContents);
567  row2Layout->addWidget(trigsChanList);
568 
569  row2Layout->addWidget(new QLabel(tr("Threshold:"), page));
570  trigsThreshEdit = new QLineEdit(page);
571  trigsThreshEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
572  trigsThreshEdit->setMaximumWidth(trigsThreshEdit->minimumSizeHint().width()
573  * 3);
574  row2Layout->addWidget(trigsThreshEdit);
575  trigsThreshEdit->setValidator(new QDoubleValidator(trigsThreshEdit));
576  trigsThreshList = new QComboBox(page);
577  trigsThreshList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
578  row2Layout->addWidget(trigsThreshList);
579  trigsThreshList->addItem("V", 1.0);
580  trigsThreshList->addItem("mV", 1e-3);
581  trigsThreshList->addItem(QString::fromUtf8("µV"), 1e-6);
582  trigsThreshList->addItem("nV", 1e-9);
583  trigsThreshList->addItem("pV", 1e-12);
584 
585  // TODO: determine the proper implementation of trigger windows
586  // row2Layout->addWidget(new QLabel(tr("Window:"), page));
587  // trigWindowEdit = new QLineEdit(page);
588  // trigWindowEdit->setText(QString::number(scopeWindow->getWindowTimewidth()));
589  // trigWindowEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
590  // trigWindowEdit->setMaximumWidth(trigWindowEdit->minimumSizeHint().width() *
591  // 3);
592 
593  // trigWindowEdit->setValidator(new QDoubleValidator(trigWindowEdit));
594  // row2Layout->addWidget(trigWindowEdit);
595  // trigWindowList = new QComboBox(page);
596  // trigWindowList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
597  // row2Layout->addWidget(trigWindowList);
598  // trigWindowList->addItem("s");
599  // trigWindowList->addItem("ms");
600  // trigWindowList->addItem(QString::fromUtf8("µs"));
601  // trigWindowList->setCurrentIndex(1);
602 
603  displayTabLayout->addLayout(row1Layout, 0, 0);
604  displayTabLayout->addLayout(row2Layout, 1, 0);
605 
606  return page;
607 }
608 
609 void Oscilloscope::Panel::syncBlockInfo()
610 {
611  this->buildBlockList();
612  this->buildChannelList();
613  this->showChannelTab();
614 }
615 
616 void Oscilloscope::Panel::showChannelTab()
617 {
618  auto type = static_cast<IO::flags_t>(this->typesList->currentData().toInt());
619  auto* block = this->blocksListDropdown->currentData().value<IO::Block*>();
620  auto port = this->channelsList->currentData().value<size_t>();
621  const IO::endpoint chan {block, port, type};
622  const double scale = this->scopeWindow->getChannelScale(chan);
623  double offset = this->scopeWindow->getChannelOffset(chan);
624  scalesList->setCurrentIndex(
625  static_cast<int>(round(4 * (log10(1 / scale) + 1))));
626  int offsetUnits = 0;
627  if (offset * std::pow(10, -3 * offsetsList->count() - 3) < 1) {
628  offset = 0;
629  offsetUnits = 0;
630  } else {
631  while (fabs(offset) < 1 && offsetUnits < offsetsList->count()) {
632  offset *= 1000;
633  offsetUnits++;
634  }
635  }
636  offsetsEdit->setText(QString::number(offset));
637  offsetsList->setCurrentIndex(offsetUnits);
638  this->activateButton->setChecked(this->scopeWindow->channelRegistered(chan));
639 }
640 
641 void Oscilloscope::Panel::showDisplayTab()
642 {
643  timesList->setCurrentIndex(
644  timesList->findData(QVariant::fromValue(this->scopeWindow->getDivT())));
645 
646  // Find current trigger value and update gui
647  IO::endpoint trigger_endpoint;
648  auto* oscilloscope_plugin =
649  dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
650  trigger_endpoint = this->scopeWindow->getTriggerEndpoint();
651  this->trigsGroup->button(static_cast<int>(trigger_endpoint.direction))
652  ->setChecked(true);
653 
654  trigsChanList->clear();
655  std::vector<IO::endpoint> endpoint_list;
656  if (oscilloscope_plugin != nullptr) {
657  endpoint_list = oscilloscope_plugin->getTrackedEndpoints();
658  }
659  std::string direction_str;
660  for (const auto& endpoint : endpoint_list) {
661  direction_str = endpoint.direction == IO::INPUT ? "INPUT" : "OUTPUT";
662  trigsChanList->addItem(QString(endpoint.block->getName().c_str()) + " "
663  + QString(direction_str.c_str()) + " "
664  + QString::number(endpoint.port),
665  QVariant::fromValue(endpoint));
666  }
667  trigsChanList->addItem("<None>");
668 
669  const int triglist_index =
670  trigsChanList->findData(QVariant::fromValue(trigger_endpoint));
671  trigsChanList->setCurrentIndex(triglist_index);
672 
673  int trigThreshUnits = 0;
674  double trigThresh = this->scopeWindow->getTriggerThreshold();
675  if (trigThresh * std::pow(10, -3 * this->trigsThreshList->count() - 1) < 1) {
676  trigThreshUnits = 0;
677  trigThresh = 0;
678  } else {
679  while (fabs(trigThresh) < 1
680  && trigThreshUnits < this->trigsThreshList->count()) {
681  trigThresh *= 1000;
682  ++trigThreshUnits;
683  }
684  }
685  trigsThreshList->setCurrentIndex(trigThreshUnits);
686  trigsThreshEdit->setText(QString::number(trigThresh));
687 
688  sizesEdit->setText(QString::number(scopeWindow->getDataSize()));
689 }
690 
691 Oscilloscope::Panel::Panel(QMainWindow* mw, Event::Manager* ev_manager)
692  : Widgets::Panel(std::string(Oscilloscope::MODULE_NAME), mw, ev_manager)
693  , tabWidget(new QTabWidget)
694  , scopeWindow(new Scope(this))
695  , layout(new QVBoxLayout)
696  , scopeGroup(new QWidget(this))
697  , setBttnGroup(new QGroupBox(this))
698 {
699  setWhatsThis(
700  "<p><b>Oscilloscope:</b><br>The Oscilloscope allows you to plot any "
701  "signal "
702  "in your workspace in real-time, including signals from your DAQ card "
703  "and those "
704  "generated by user modules. Multiple signals are overlaid in the window "
705  "and "
706  "different line colors and styles can be selected. When a signal is "
707  "added, a legend "
708  "automatically appears in the bottom of the window. Multiple "
709  "oscilloscopes can "
710  "be instantiated to give you multiple data windows. To select signals "
711  "for plotting, "
712  "use the right-click context \"Panel\" menu item. After selecting a "
713  "signal, you must "
714  "click the \"Enable\" button for it to appear in the window. To change "
715  "signal settings, "
716  "you must click the \"Apply\" button. The right-click context \"Pause\" "
717  "menu item "
718  "allows you to start and stop real-time plotting.</p>");
719 
720  // Create tab widget
721  tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
722  QObject::connect(
723  tabWidget, SIGNAL(currentChanged(int)), this, SLOT(showTab(int)));
724 
725  auto* scopeLayout = new QHBoxLayout(this);
726  scopeLayout->addWidget(scopeWindow);
727  scopeGroup->setLayout(scopeLayout);
728  auto* setBttnLayout = new QHBoxLayout(this);
729 
730  // Create buttons
731  pauseButton = new QPushButton("Pause");
732  pauseButton->setCheckable(true);
733  QObject::connect(pauseButton, SIGNAL(released()), this, SLOT(togglePause()));
734  setBttnLayout->addWidget(pauseButton);
735  applyButton = new QPushButton("Apply");
736  QObject::connect(applyButton, SIGNAL(released()), this, SLOT(apply()));
737  setBttnLayout->addWidget(applyButton);
738  settingsButton = new QPushButton("Screenshot");
739  QObject::connect(
740  settingsButton, SIGNAL(released()), this, SLOT(screenshot()));
741  setBttnLayout->addWidget(settingsButton);
742 
743  // Attach layout
744  setBttnGroup->setLayout(setBttnLayout);
745 
746  // Create tabs
747  tabWidget->setTabPosition(QTabWidget::North);
748  tabWidget->addTab(createChannelTab(this), "Channel");
749  tabWidget->addTab(createDisplayTab(this), "Display");
750 
751  // Setup main layout
752  layout->addWidget(scopeGroup);
753  layout->addWidget(tabWidget);
754  layout->addWidget(setBttnGroup);
755 
756  // Set
757  setLayout(layout);
758 
759  // Show stuff
760  adjustDataSize();
761  showDisplayTab();
762  getMdiWindow()->setMinimumSize(this->minimumSizeHint().width(), 450);
763  getMdiWindow()->resize(this->minimumSizeHint().width() + 50, 600);
764 
765  // Initialize vars
766  setWindowTitle(tr(std::string(Oscilloscope::MODULE_NAME).c_str()));
767 
768  auto* otimer = new QTimer(this);
769  otimer->setTimerType(Qt::PreciseTimer);
770  otimer->start(Oscilloscope::FrameRates::HZ60);
771 
772  qRegisterMetaType<IO::Block*>("IO::Block*");
773  QObject::connect(this,
775  this,
776  &Oscilloscope::Panel::removeBlockChannels);
777  QObject::connect(
778  this, SIGNAL(updateBlockInfo()), this, SLOT(syncBlockInfo()));
779 
780  this->updateBlockInfo();
781  this->buildChannelList();
782  scopeWindow->replot();
783 }
784 
786  const std::string& probe_name)
787  : Widgets::Component(hplugin,
788  probe_name,
791 {
792  if (RT::OS::getFifo(this->fifo, Oscilloscope::DEFAULT_BUFFER_SIZE) != 0) {
793  ERROR_MSG("Unable to create xfifo for Oscilloscope Component {}",
794  probe_name);
795  }
796 }
797 
798 // TODO: Handle trigger synchronization between oscilloscope components
800 {
802  switch (this->getState()) {
803  case RT::State::EXEC: {
805  sample.value = this->readinput(0);
806  this->fifo->writeRT(&sample, sizeof(Oscilloscope::sample));
807  break;
808  }
809  case RT::State::INIT:
810  case RT::State::UNPAUSE:
811  this->setState(RT::State::EXEC);
812  break;
813  case RT::State::PAUSE:
814  case RT::State::MODIFY:
815  case RT::State::EXIT:
816  case RT::State::PERIOD:
817  break;
818  }
819 }
820 
821 void Oscilloscope::Panel::screenshot()
822 {
823  QwtPlotRenderer renderer;
824  renderer.exportTo(scopeWindow, "screenshot.pdf");
825 }
826 
828 {
829  this->scopeWindow->setPause(this->pauseButton->isChecked());
830  auto* hplugin = dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
831  hplugin->setAllProbesActivity(this->pauseButton->isChecked());
832 }
833 
835 {
837  while (this->fifo->read(&sample, sizeof(Oscilloscope::sample)) > 0) {
838  }
839 }
840 
841 // TODO: fix rt buffer size adjustments for components
843 {
844  // Event::Object event(Event::Type::RT_GET_PERIOD_EVENT);
845  // this->getRTXIEventManager()->postEvent(&event);
846  // auto period = std::any_cast<int64_t>(event.getParam("period"));
847  // const double timedivs = scopeWindow->getDivT();
848  // const double xdivs =
849  // static_cast<double>(scopeWindow->getDivX()) /
850  // static_cast<double>(period);
851  // const size_t size = static_cast<size_t>(ceil(timedivs + xdivs)) + 1;
852  // scopeWindow->setDataSize(size);
853  // sizesEdit->setText(QString::number(scopeWindow->getDataSize()));
854 }
855 
857 
858 void Oscilloscope::Panel::removeBlockChannels(IO::Block* block)
859 {
860  this->scopeWindow->removeBlockChannels(block);
861  auto* hplugin = dynamic_cast<Oscilloscope::Plugin*>(this->getHostPlugin());
862  hplugin->deleteAllProbes(block);
863 }
864 
865 void Oscilloscope::Panel::syncChannelProperties()
866 {
867  IO::endpoint probe_info {};
868  probe_info.block = blocksListDropdown->currentData().value<IO::Block*>();
869  probe_info.direction = typesList->currentData().value<IO::flags_t>();
870  probe_info.port = channelsList->currentData().value<size_t>();
871 
872  // we don't bother updating if channel is not active
873  if (!scopeWindow->channelRegistered(probe_info)) {
874  return;
875  }
876 
877  const QColor color = scopeWindow->getChannelColor(probe_info);
878  colorsList->setCurrentIndex(colorsList->findData(color));
879  const int width = scopeWindow->getChannelWidth(probe_info);
880  widthsList->setCurrentIndex(widthsList->findData(width));
881  const Qt::PenStyle style = scopeWindow->getChannelStyle(probe_info);
882  stylesList->setCurrentIndex(stylesList->findData(QVariant::fromValue(style)));
883  double offset = scopeWindow->getChannelOffset(probe_info);
884  const double scale = scopeWindow->getChannelScale(probe_info);
885  scalesList->setCurrentIndex(
886  static_cast<int>(round(4 * (log10(1 / scale) + 1))));
887  int offsetUnits = 0;
888  if (offset * std::pow(10, -3 * offsetsList->count() - 3) < 1) {
889  offset = 0;
890  offsetUnits = 0;
891  } else {
892  while (fabs(offset) < 1 && offsetUnits < offsetsList->count()) {
893  offset *= 1000;
894  offsetUnits++;
895  }
896  }
897  offsetsEdit->setText(QString::number(offset));
898  offsetsList->setCurrentIndex(offsetUnits);
899 }
900 
902  : Widgets::Plugin(ev_manager, std::string(Oscilloscope::MODULE_NAME))
903 {
904 }
905 
907 {
908  std::vector<Event::Object> unloadEvents;
909  for (auto& registry_entry : this->m_component_registry) {
910  unloadEvents.emplace_back(Event::Type::RT_THREAD_REMOVE_EVENT);
911  unloadEvents.back().setParam(
912  "thread",
913  std::any(static_cast<RT::Thread*>(registry_entry.component.get())));
914  }
915  this->getEventManager()->postEvent(unloadEvents);
916 }
917 
918 // TODO:make this thread safe
920 {
921  auto probe_loc = std::find_if(this->m_component_registry.begin(),
922  this->m_component_registry.end(),
923  [&](const registry_entry_t& entry)
924  { return entry.endpoint == probe_info; });
925  if (probe_loc != this->m_component_registry.end()) {
926  return probe_loc->component->getFifoPtr();
927  }
928  const std::string comp_name =
929  fmt::format("{} Probe for Block {} Channel {} {} with Id {} ",
930  std::string(Oscilloscope::MODULE_NAME),
931  probe_info.block->getName(),
932  probe_info.direction == IO::OUTPUT ? "Output " : "Input ",
933  probe_info.port,
934  probe_info.block->getID());
935  this->m_component_registry.push_back(
936  {probe_info, std::make_unique<Oscilloscope::Component>(this, comp_name)});
937  Oscilloscope::Component* measuring_component =
938  this->m_component_registry.back().component.get();
939  measuring_component->setActive(/*act=*/true);
940  RT::block_connection_t connection;
941  connection.src = probe_info.block;
942  connection.src_port_type = probe_info.direction;
943  connection.src_port = probe_info.port;
944  connection.dest = measuring_component;
945  connection.dest_port = 0;
946  std::vector<Event::Object> events;
947  events.emplace_back(Event::Type::RT_THREAD_INSERT_EVENT);
948  events.back().setParam(
949  "thread", std::any(static_cast<RT::Thread*>(measuring_component)));
950  events.emplace_back(Event::Type::IO_LINK_INSERT_EVENT);
951  events.back().setParam("connection", std::any(connection));
952  this->getEventManager()->postEvent(events);
953  return measuring_component->getFifoPtr();
954  // TODO: complete proper handling of errors if not able to register probe
955  // thread
956 }
957 
959 {
960  auto probe_loc = std::find_if(this->m_component_registry.begin(),
961  this->m_component_registry.end(),
962  [&](const registry_entry_t& entry)
963  { return entry.endpoint == probe_info; });
964  if (probe_loc == this->m_component_registry.end()) {
965  return;
966  }
967  Oscilloscope::Component* measuring_component = probe_loc->component.get();
969  event.setParam("thread",
970  std::any(static_cast<RT::Thread*>(measuring_component)));
971  this->getEventManager()->postEvent(&event);
972  this->m_component_registry.erase(probe_loc);
973 }
974 
976 {
977  std::vector<IO::endpoint> all_endpoints;
978  for (auto& entry : this->m_component_registry) {
979  if (entry.endpoint.block == block) {
980  all_endpoints.push_back(entry.endpoint);
981  }
982  }
983  for (auto endpoint : all_endpoints) {
984  this->deleteProbe(endpoint);
985  }
986 }
987 
989  bool activity)
990 {
991  auto probe_loc = std::find_if(this->m_component_registry.begin(),
992  this->m_component_registry.end(),
993  [&](const registry_entry_t& entry)
994  { return entry.endpoint == endpoint; });
995  if (probe_loc == this->m_component_registry.end()) {
996  return;
997  }
998  const Event::Type event_type = activity
1001  Event::Object activity_event(event_type);
1002  activity_event.setParam("thread",
1003  static_cast<RT::Thread*>(probe_loc->component.get()));
1004  this->getEventManager()->postEvent(&activity_event);
1005 }
1006 
1008 {
1009  std::vector<IO::endpoint> result;
1010  result.reserve(this->m_component_registry.size());
1011  for (const auto& entry : this->m_component_registry) {
1012  result.push_back(entry.endpoint);
1013  }
1014  return result;
1015 }
1016 
1018 {
1019  std::vector<Event::Object> events;
1020  events.reserve(this->m_component_registry.size());
1021  const Event::Type event_type = activity
1024  for (const auto& entry : this->m_component_registry) {
1025  events.emplace_back(event_type);
1026  events.back().setParam("thread",
1027  static_cast<RT::Thread*>(entry.component.get()));
1028  }
1029  this->getEventManager()->postEvent(events);
1030 }
1031 
1034 {
1035  auto iter = std::find_if(this->m_component_registry.begin(),
1036  this->m_component_registry.end(),
1037  [&](const registry_entry_t& entry)
1038  { return entry.endpoint == endpoint; });
1039  if (iter == this->m_component_registry.end()) {
1040  return nullptr;
1041  }
1042  return iter->component.get();
1043 }
1044 
1045 std::unique_ptr<Widgets::Plugin> Oscilloscope::createRTXIPlugin(
1046  Event::Manager* ev_manager)
1047 {
1048  return std::make_unique<Oscilloscope::Plugin>(ev_manager);
1049 }
1050 
1052  Event::Manager* ev_manager)
1053 {
1054  return static_cast<Widgets::Panel*>(
1055  new Oscilloscope::Panel(main_window, ev_manager));
1056 }
1057 
1058 std::unique_ptr<Widgets::Component> Oscilloscope::createRTXIComponent(
1059  Widgets::Plugin* /*host_plugin*/)
1060 {
1061  return std::unique_ptr<Oscilloscope::Component>(nullptr);
1062 }
1063 
1065 {
1070  return fact;
1071 }
void setParam(const std::string &param_name, const std::any &param_value)
Definition: event.cpp:191
Event::Type getType() const
Definition: event.cpp:228
std::any getParam(const std::string &param_name) const
Definition: event.cpp:170
Definition: io.hpp:79
size_t getID() const
Definition: io.hpp:202
void setActive(bool act)
Definition: io.hpp:188
std::string getName() const
Definition: io.hpp:108
Component(Widgets::Plugin *hplugin, const std::string &probe_name)
void execute() override
RT::OS::Fifo * getFifoPtr()
void updateBlockChannels(IO::Block *block)
Panel(QMainWindow *mw, Event::Manager *ev_manager)
void setActivity(IO::endpoint endpoint, bool activity)
Plugin(Event::Manager *ev_manager)
void receiveEvent(Event::Object *event) override
void setProbeActivity(IO::endpoint endpoint, bool activity)
void deleteProbe(IO::endpoint probe_info)
Oscilloscope::Component * getProbeComponentPtr(IO::endpoint endpoint)
RT::OS::Fifo * createProbe(IO::endpoint probe_info)
std::vector< IO::endpoint > getTrackedEndpoints()
void setAllProbesActivity(bool activity)
void deleteAllProbes(IO::Block *block)
virtual int64_t writeRT(void *buf, size_t data_size)=0
virtual int64_t read(void *buf, size_t data_size)=0
QMdiSubWindow * getMdiWindow()
Definition: widgets.hpp:286
Widgets::Panel * getPanel()
Definition: widgets.cpp:594
void ERROR_MSG(const std::string &errmsg, Args... args)
Definition: debug.hpp:36
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
constexpr size_t HZ240
Definition: scope.hpp:81
constexpr size_t HZ120
Definition: scope.hpp:80
constexpr size_t HZ60
Definition: scope.hpp:79
constexpr std::array< std::string_view, 7 > color2string
Definition: scope.hpp:129
constexpr std::string_view MODULE_NAME
struct Oscilloscope::sample sample
constexpr std::array< Qt::PenStyle, 5 > penStyles
Definition: scope.hpp:132
std::unique_ptr< Widgets::Plugin > createRTXIPlugin(Event::Manager *ev_manager)
Widgets::FactoryMethods getFactories()
constexpr std::array< std::string_view, 5 > penstyles2string
Definition: scope.hpp:138
const std::array< QColor, 7 > penColors
Definition: scope.hpp:121
constexpr size_t DEFAULT_BUFFER_SIZE
Definition: scope.hpp:84
std::unique_ptr< Widgets::Component > createRTXIComponent(Widgets::Plugin *host_plugin)
std::vector< Widgets::Variable::Info > get_default_vars()
Widgets::Panel * createRTXIPanel(QMainWindow *main_window, Event::Manager *ev_manager)
std::vector< IO::channel_t > get_default_channels()
int getFifo(std::unique_ptr< Fifo > &fifo, size_t fifo_size)
Definition: fifo.cpp:194
int64_t getTime()
@ EXIT
Definition: rt.hpp:60
@ EXEC
Definition: rt.hpp:55
@ UNPAUSE
Definition: rt.hpp:59
@ PAUSE
Definition: rt.hpp:58
@ INIT
Definition: rt.hpp:54
@ MODIFY
Definition: rt.hpp:56
@ PERIOD
Definition: rt.hpp:57
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