SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
remoteoutputgui.cpp
Go to the documentation of this file.
1 // Copyright (C) 2016 Edouard Griffiths, F4EXB //
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 as 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 V3 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/>. //
17 
18 #include <QDebug>
19 #include <QTime>
20 #include <QDateTime>
21 #include <QString>
22 #include <QMessageBox>
23 #include <QNetworkAccessManager>
24 #include <QNetworkReply>
25 #include <QJsonParseError>
26 
27 #include <boost/algorithm/string.hpp>
28 #include <boost/lexical_cast.hpp>
29 
30 #include "ui_remoteoutputgui.h"
31 #include "plugin/pluginapi.h"
32 #include "gui/colormapper.h"
33 #include "gui/glspectrum.h"
34 #include "gui/crightclickenabler.h"
36 #include "dsp/dspengine.h"
37 #include "dsp/dspcommands.h"
38 
39 #include "mainwindow.h"
40 
41 #include "device/deviceapi.h"
42 #include "device/deviceuiset.h"
43 #include "remoteoutputgui.h"
44 
46 
47 #include "udpsinkfec.h"
48 
49 RemoteOutputSinkGui::RemoteOutputSinkGui(DeviceUISet *deviceUISet, QWidget* parent) :
50  QWidget(parent),
51  ui(new Ui::RemoteOutputGui),
52  m_deviceUISet(deviceUISet),
53  m_settings(),
54  m_deviceSampleSink(0),
55  m_deviceCenterFrequency(0),
56  m_samplesCount(0),
57  m_tickCount(0),
58  m_nbSinceLastFlowCheck(0),
59  m_lastEngineState(DeviceAPI::StNotStarted),
60  m_doApplySettings(true),
61  m_forceSettings(true)
62 {
64  m_countRecovered = 0;
68  m_resetCounts = true;
69 
70  m_paletteGreenText.setColor(QPalette::WindowText, Qt::green);
71  m_paletteRedText.setColor(QPalette::WindowText, Qt::red);
72  m_paletteWhiteText.setColor(QPalette::WindowText, Qt::white);
73 
74  ui->setupUi(this);
75 
76  ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
77  ui->centerFrequency->setValueRange(7, 0, pow(10,7));
78 
79  ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
80  ui->sampleRate->setValueRange(7, 32000U, 9000000U);
81 
82  ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
83 
84  connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
85  connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
86  connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
87  m_statusTimer.start(500);
88 
90 
91  connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
92 
93  m_networkManager = new QNetworkAccessManager();
94  connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
95 
97 
98  m_time.start();
101 
102  CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop);
103  connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
104 
105  displaySettings();
106  sendSettings();
107 }
108 
110 {
111  disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
112  delete m_networkManager;
113  delete ui;
114 }
115 
117 {
118  m_doApplySettings = !block;
119 }
120 
122 {
123  delete this;
124 }
125 
126 void RemoteOutputSinkGui::setName(const QString& name)
127 {
128  setObjectName(name);
129 }
130 
132 {
133  return objectName();
134 }
135 
137 {
138  blockApplySettings(true);
140  displaySettings();
141  blockApplySettings(false);
142  sendSettings();
143 }
144 
146 {
147  return m_settings.serialize();
148 }
149 
150 bool RemoteOutputSinkGui::deserialize(const QByteArray& data)
151 {
152  blockApplySettings(true);
153 
154  if(m_settings.deserialize(data))
155  {
156  displaySettings();
157  blockApplySettings(false);
158  m_forceSettings = true;
159  sendSettings();
160  return true;
161  }
162  else
163  {
164  blockApplySettings(false);
165  return false;
166  }
167 }
168 
170 {
172  {
174  m_settings = cfg.getSettings();
175  blockApplySettings(true);
176  displaySettings();
177  blockApplySettings(false);
178  return true;
179  }
180  else if (RemoteOutput::MsgStartStop::match(message))
181  {
183  blockApplySettings(true);
184  ui->startStop->setChecked(notif.getStartStop());
185  blockApplySettings(false);
186  return true;
187  }
188  else
189  {
190  return false;
191  }
192 }
193 
195 {
196  Message* message;
197 
198  while ((message = m_inputMessageQueue.pop()) != 0)
199  {
200  if (DSPSignalNotification::match(*message))
201  {
202  DSPSignalNotification* notif = (DSPSignalNotification*) message;
203  m_sampleRate = notif->getSampleRate();
204  qDebug("RemoteOutputSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency());
206 
207  delete message;
208  }
209  else
210  {
211  if (handleMessage(*message)) {
212  delete message;
213  }
214  }
215  }
216 }
217 
219 {
221  ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000));
222 }
223 
225 {
226  int samplesPerBlock = RemoteNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8);
227  double delay = ((127*samplesPerBlock*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks);
228  ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0)));
229 }
230 
232 {
233  blockApplySettings(true);
234  ui->centerFrequency->setValue(m_deviceCenterFrequency / 1000);
235  ui->sampleRate->setValue(m_settings.m_sampleRate);
236  ui->txDelay->setValue(m_settings.m_txDelay*100);
237  ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100));
238  ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks);
239 
240  QString s0 = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0);
241  QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0);
242  ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(s1));
243 
244  ui->deviceIndex->setText(tr("%1").arg(m_settings.m_deviceIndex));
245  ui->channelIndex->setText(tr("%1").arg(m_settings.m_channelIndex));
246  ui->apiAddress->setText(m_settings.m_apiAddress);
247  ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort));
248  ui->dataAddress->setText(m_settings.m_dataAddress);
249  ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
250  blockApplySettings(false);
251 }
252 
254 {
255  if(!m_updateTimer.isActive())
256  m_updateTimer.start(100);
257 }
258 
259 
261 {
262  qDebug() << "RemoteOutputSinkGui::updateHardware";
265  m_forceSettings = false;
266  m_updateTimer.stop();
267 }
268 
270 {
271  int state = m_deviceUISet->m_deviceAPI->state();
272 
273  if(m_lastEngineState != state)
274  {
275  switch(state)
276  {
278  ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
279  break;
280  case DeviceAPI::StIdle:
281  ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
282  break;
284  ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
285  break;
286  case DeviceAPI::StError:
287  ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
288  QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage());
289  break;
290  default:
291  break;
292  }
293 
294  m_lastEngineState = state;
295  }
296 }
297 
299 {
300  m_settings.m_sampleRate = value;
302  sendSettings();
303 }
304 
306 {
307  m_settings.m_txDelay = value / 100.0;
308  ui->txDelayText->setText(tr("%1").arg(value));
310  sendSettings();
311 }
312 
314 {
315  m_settings.m_nbFECBlocks = value;
316  int nbOriginalBlocks = 128;
317  int nbFECBlocks = value;
318  QString s = QString::number(nbOriginalBlocks + nbFECBlocks, 'f', 0);
319  QString s1 = QString::number(nbFECBlocks, 'f', 0);
320  ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1));
322  sendSettings();
323 }
324 
326 {
327  bool dataOk;
328  int deviceIndex = ui->deviceIndex->text().toInt(&dataOk);
329 
330  if ((!dataOk) || (deviceIndex < 0)) {
331  return;
332  } else {
333  m_settings.m_deviceIndex = deviceIndex;
334  }
335 
336  sendSettings();
337 }
338 
340 {
341  bool dataOk;
342  int channelIndex = ui->channelIndex->text().toInt(&dataOk);
343 
344  if ((!dataOk) || (channelIndex < 0)) {
345  return;
346  } else {
347  m_settings.m_channelIndex = channelIndex;
348  }
349 
350  sendSettings();
351 }
352 
354 {
355  m_settings.m_apiAddress = ui->apiAddress->text();
356  sendSettings();
357 
358  QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
359  m_networkRequest.setUrl(QUrl(infoURL));
361 }
362 
364 {
365  bool dataOk;
366  int apiPort = ui->apiPort->text().toInt(&dataOk);
367 
368  if((!dataOk) || (apiPort < 1024) || (apiPort > 65535)) {
369  return;
370  } else {
371  m_settings.m_apiPort = apiPort;
372  }
373 
374  sendSettings();
375 
376  QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
377  m_networkRequest.setUrl(QUrl(infoURL));
379 }
380 
382 {
383  m_settings.m_dataAddress = ui->dataAddress->text();
384  sendSettings();
385 }
386 
388 {
389  bool dataOk;
390  int dataPort = ui->dataPort->text().toInt(&dataOk);
391 
392  if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) {
393  return;
394  } else {
395  m_settings.m_dataPort = dataPort;
396  }
397 
398  sendSettings();
399 }
400 
402 {
403  (void) checked;
404  m_settings.m_apiAddress = ui->apiAddress->text();
405 
406  bool apiOk;
407  int apiPort = ui->apiPort->text().toInt(&apiOk);
408 
409  if((apiOk) && (apiPort >= 1024) && (apiPort < 65535))
410  {
411  m_settings.m_apiPort = apiPort;
412  }
413 
414  sendSettings();
415 
416  QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort);
417  m_networkRequest.setUrl(QUrl(infoURL));
419 }
420 
422 {
423  (void) checked;
424  m_settings.m_dataAddress = ui->dataAddress->text();
425 
426  bool dataOk;
427  int udpDataPort = ui->dataPort->text().toInt(&dataOk);
428 
429  if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535))
430  {
431  m_settings.m_dataPort = udpDataPort;
432  }
433 
434  sendSettings();
435 }
436 
438 {
439  if (m_doApplySettings)
440  {
443  }
444 }
445 
447 {
448  (void) checked;
450  m_countRecovered = 0;
451  m_time.start();
454 }
455 
457 {
458  QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0'));
459  ui->eventUnrecText->setText(nstr);
460  nstr = QString("%1").arg(m_countRecovered, 3, 10, QChar('0'));
461  ui->eventRecText->setText(nstr);
462 }
463 
464 void RemoteOutputSinkGui::displayEventStatus(int recoverableCount, int unrecoverableCount)
465 {
466 
467  if (unrecoverableCount == 0)
468  {
469  if (recoverableCount == 0) {
470  ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }");
471  } else {
472  ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
473  }
474  }
475  else
476  {
477  ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }");
478  }
479 }
480 
482 {
483  int elapsedTimeMillis = m_time.elapsed();
484  QTime recordLength(0, 0, 0, 0);
485  recordLength = recordLength.addSecs(elapsedTimeMillis/1000);
486  QString s_time = recordLength.toString("HH:mm:ss");
487  ui->eventCountsTimeText->setText(s_time);
488 }
489 
491 {
492  if (++m_tickCount == 20) // once per second
493  {
494  QString reportURL;
495 
496  reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report")
498  .arg(m_settings.m_apiPort)
501  m_networkRequest.setUrl(QUrl(reportURL));
503 
505 
506  m_tickCount = 0;
507  }
508 }
509 
511 {
512  if (reply->error())
513  {
514  ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
515  ui->statusText->setText(reply->errorString());
516  return;
517  }
518 
519  QString answer = reply->readAll();
520 
521  try
522  {
523  QByteArray jsonBytes(answer.toStdString().c_str());
524  QJsonParseError error;
525  QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error);
526 
527  if (error.error == QJsonParseError::NoError)
528  {
529  ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }");
530  ui->statusText->setText(QString("API OK"));
531  analyzeApiReply(doc.object());
532  }
533  else
534  {
535  ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
536  QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset);
537  ui->statusText->setText(QString("JSON error. See log"));
538  qInfo().noquote() << "RemoteOutputSinkGui::networkManagerFinished" << errorMsg;
539  }
540  }
541  catch (const std::exception& ex)
542  {
543  ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
544  QString errorMsg = QString("Error parsing request: ") + ex.what();
545  ui->statusText->setText("Error parsing request. See log for details");
546  qInfo().noquote() << "RemoteOutputSinkGui::networkManagerFinished" << errorMsg;
547  }
548 }
549 
550 void RemoteOutputSinkGui::analyzeApiReply(const QJsonObject& jsonObject)
551 {
552  QString infoLine;
553 
554  if (jsonObject.contains("RemoteSourceReport"))
555  {
556  QJsonObject report = jsonObject["RemoteSourceReport"].toObject();
557  m_deviceCenterFrequency = report["deviceCenterFreq"].toInt() * 1000;
559  ui->centerFrequency->setValue(m_deviceCenterFrequency/1000);
560  int remoteRate = report["deviceSampleRate"].toInt();
561  ui->remoteRateText->setText(tr("%1k").arg((float)(remoteRate) / 1000));
562  int queueSize = report["queueSize"].toInt();
563  queueSize = queueSize == 0 ? 10 : queueSize;
564  int queueLength = report["queueLength"].toInt();
565  QString queueLengthText = QString("%1/%2").arg(queueLength).arg(queueSize);
566  ui->queueLengthText->setText(queueLengthText);
567  int queueLengthPercent = (queueLength*100)/queueSize;
568  ui->queueLengthGauge->setValue(queueLengthPercent);
569  int unrecoverableCount = report["uncorrectableErrorsCount"].toInt();
570  int recoverableCount = report["correctableErrorsCount"].toInt();
571  uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt();
572 
573  if (!m_resetCounts)
574  {
575  int recoverableCountDelta = recoverableCount - m_lastCountRecovered;
576  int unrecoverableCountDelta = unrecoverableCount - m_lastCountUnrecoverable;
577  displayEventStatus(recoverableCountDelta, unrecoverableCountDelta);
578  m_countRecovered += recoverableCountDelta;
579  m_countUnrecoverable += unrecoverableCountDelta;
581  }
582 
583  uint32_t sampleCountDelta, sampleCount;
584  sampleCount = report["samplesCount"].toInt();
585 
586  if (sampleCount < m_lastSampleCount) {
587  sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1;
588  } else {
589  sampleCountDelta = sampleCount - m_lastSampleCount;
590  }
591 
592  if (sampleCountDelta == 0)
593  {
594  ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }");
595  }
596 
597  double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs);
598 
599  if (remoteStreamRate != 0) {
600  ui->remoteStreamRateText->setText(QString("%1").arg(remoteStreamRate, 0, 'f', 0));
601  }
602 
603  m_resetCounts = false;
604  m_lastCountRecovered = recoverableCount;
605  m_lastCountUnrecoverable = unrecoverableCount;
606  m_lastSampleCount = sampleCount;
607  m_lastTimestampUs = timestampUs;
608  }
609 
610  if (jsonObject.contains("version")) {
611  infoLine = "v" + jsonObject["version"].toString();
612  }
613 
614  if (jsonObject.contains("qtVersion")) {
615  infoLine += " Qt" + jsonObject["qtVersion"].toString();
616  }
617 
618  if (jsonObject.contains("architecture")) {
619  infoLine += " " + jsonObject["architecture"].toString();
620  }
621 
622  if (jsonObject.contains("os")) {
623  infoLine += " " + jsonObject["os"].toString();
624  }
625 
626  if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) {
627  infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt());
628  }
629 
630  if (infoLine.size() > 0) {
631  ui->infoText->setText(infoLine);
632  }
633 }
634 
636 {
637  BasicDeviceSettingsDialog dialog(this);
642 
643  dialog.move(p);
644  dialog.exec();
645 
650 
651  sendSettings();
652 }
Message * pop()
Pop message from queue.
const QString & getReverseAPIAddress() const
void analyzeApiReply(const QJsonObject &jsonObject)
void push(Message *message, bool emitSignal=true)
Push message onto queue.
void setSampleRate(qint32 sampleRate)
Definition: glspectrum.cpp:211
void setUseReverseAPI(bool useReverseAPI)
QString getName() const
void displayEventStatus(int recoverableCount, int unrecoverableCount)
const RemoteOutputSettings & getSettings() const
Definition: remoteoutput.h:47
void networkManagerFinished(QNetworkReply *reply)
const QTimer & getMasterTimer() const
This is the DSPEngine master timer.
Definition: deviceapi.h:173
static MsgConfigureRemoteOutput * create(const RemoteOutputSettings &settings, bool force=false)
Definition: remoteoutput.h:50
QString errorMessage()
Last error message from the device engine.
Definition: deviceapi.cpp:290
void on_eventCountsReset_clicked(bool checked)
DeviceSampleSink * getSampleSink()
Return pointer to the device sample sink (single Tx) or nullptr.
Definition: deviceapi.cpp:222
bool deserialize(const QByteArray &data)
RemoteOutputSettings m_settings
current settings
unsigned int uint32_t
Definition: rtptypes_win.h:46
Fixed< IntType, IntBits > arg(const std::complex< Fixed< IntType, IntBits > > &val)
Definition: fixed.h:2401
GLSpectrum * getSpectrum()
Direct spectrum getter.
Definition: deviceuiset.h:57
engine is before initialization
Definition: deviceapi.h:53
MessageQueue m_inputMessageQueue
qint64 getCenterFrequency() const
Definition: dspcommands.h:329
EngineState state() const
Return the state of the device engine corresponding to the stream type.
Definition: deviceapi.cpp:277
#define SDR_RX_SAMP_SZ
Definition: dsptypes.h:32
DeviceAPI * m_deviceAPI
Definition: deviceuiset.h:48
engine is idle
Definition: deviceapi.h:54
MessageQueue * getInputMessageQueue()
static MsgStartStop * create(bool startStop)
Definition: remoteoutput.h:92
QNetworkRequest m_networkRequest
void on_dataApplyButton_clicked(bool checked)
void on_startStop_toggled(bool checked)
virtual bool handleMessage(const Message &message)
DeviceSampleSink * m_deviceSampleSink
static bool match(const Message *message)
Definition: message.cpp:45
RemoteOutputSinkGui(DeviceUISet *deviceUISet, QWidget *parent=0)
QByteArray serialize() const
void openDeviceSettingsDialog(const QPoint &p)
void on_sampleRate_changed(quint64 value)
uint32_t m_lastCountUnrecoverable
void on_nbFECBlocks_valueChanged(int value)
virtual void destroy()
int getSampleRate() const
Definition: dspcommands.h:328
void setCenterFrequency(qint64 frequency)
Definition: glspectrum.cpp:175
QByteArray serialize() const
void setReverseAPIAddress(const QString &address)
void on_txDelay_valueChanged(int value)
void setReverseAPIDeviceIndex(uint16_t deviceIndex)
void on_apiApplyButton_clicked(bool checked)
quint64 m_deviceCenterFrequency
Center frequency in device.
engine is running
Definition: deviceapi.h:56
void setName(const QString &name)
DeviceUISet * m_deviceUISet
bool deserialize(const QByteArray &data)
void blockApplySettings(bool block)
Ui::RemoteOutputGui * ui
QNetworkAccessManager * m_networkManager
engine is in error
Definition: deviceapi.h:57
unsigned __int64 uint64_t
Definition: rtptypes_win.h:48