diff --git a/plugins/samplesource/remotetcpinput/CMakeLists.txt b/plugins/samplesource/remotetcpinput/CMakeLists.txt index 39bd1d6f6..9603cb95a 100644 --- a/plugins/samplesource/remotetcpinput/CMakeLists.txt +++ b/plugins/samplesource/remotetcpinput/CMakeLists.txt @@ -14,6 +14,7 @@ set(remotetcpinput_HEADERS remotetcpinputsettings.h remotetcpinputwebapiadapter.h remotetcpinputplugin.h + spyserver.h ) include_directories( diff --git a/plugins/samplesource/remotetcpinput/readme.md b/plugins/samplesource/remotetcpinput/readme.md index c8c013428..9158dfd40 100644 --- a/plugins/samplesource/remotetcpinput/readme.md +++ b/plugins/samplesource/remotetcpinput/readme.md @@ -2,7 +2,8 @@

Introduction

-This input sample source plugin gets its I/Q samples over the network via a TCP/IP connection from a server such as rtl_tcp, rsp_tcp or SDRangel's [Remote TCP Channel Sink](../../channelrx/remotetcpsink/readme.md) plugin. +This input sample source plugin gets its I/Q samples over the network via a TCP/IP connection from a server such as +rtl_tcp, rsp_tcp, Spy Server or SDRangel's [Remote TCP Channel Sink](../../channelrx/remotetcpsink/readme.md) plugin.

Interface

@@ -92,7 +93,7 @@ When unchecked, the channel sample rate can be set to any value. Specifies number of bits per I/Q sample transmitted via TCP/IP. -When the protocol is RTL0, only 8-bits are supported. SDRA protocol supports 8, 16, 24 and 32-bit samples. +When the protocol is RTL0, only 8-bits are supported. SDRA and Spy Server protocol supports 8, 16, 24 and 32-bit samples.

19: Server IP address

@@ -102,7 +103,11 @@ IP address or hostname of the server that is running SDRangel's Remote TCP Sink TCP port on the server to connect to. -

21: Connection settings

+

21: Protocol

+ +Selects protocol to use. Set to SDRangel for rtl_tcp, rsp_tcp or SDRangel's own protocol. Alternative, Spy Server can be selected to connect to Spy Servers. + +

23: Connection settings

Determines which settings are used when connecting. @@ -110,31 +115,32 @@ When checked, settings in the RemoteTCPInput GUI are written to the remote devic When unchecked, if the remote server is using the SDRA protocol, the RemoteTCPInput GUI will be updated with the current settings from the remote device. If the remote server is using the RTL0 protocol, the GUI will not be updated, which may mean the two are inconsistent. -

22: Pre-fill

+

24: Pre-fill

Determines how many seconds of I/Q samples are buffered locally from the remote device, before being processed in SDRangel. More buffering can handle more network congestion and other network problems, without gaps in the output, but increases the latency in changes to remote device settings. -

23: Input buffer gauge

+

25: Input buffer gauge

Shows how much data is in the input buffer. Typically this will be just under the pre-fill setting. If it becomes empty, the plugin will pause outputting of data until the buffer is refilled to the pre-fill level. If the buffer repeatedly runs empty, this suggests you do not have enough network bandwidth for the current combination of channel sample rate and sample bit depth. Reducing these to lower values may be required for uninterrupted data. -

24: Output buffer gauge

+

26: Output buffer gauge

Shows how much data is in the output buffer. This should typically be empty. If not empty, this suggests your CPU can't keep up with the amount of data being received. -

25: Device status

+

27: Device status

Shows the type of remote device that has been connected to. -

26: Protocol status

+

28: Protocol status

-Shows the protocol being used by the remote server. This will be RTL0 or SDRA. +Shows the protocol being used by the remote server. This will be RTL0, SDRA or Spy Server. rtl_tcp and rsp_tcp always use the RTL0 protocol. SDRangel's Remote TCP Sink plugin can use RTL0 or SDRA. RTL0 is limited to sending 8-bit data, doesn't support decimation and does not send the current device settings on connection. +Spy Server supports decimation and gain, but no other settings. diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp index 2880c362a..7c705d0c7 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp +++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2024 Jon Beniston, M7RCE // // Copyright (C) 2022 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // @@ -49,7 +49,9 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent) m_forceSettings(true), m_deviceGains(nullptr), m_remoteDevice(RemoteTCPProtocol::RTLSDR_R820T), - m_connectionError(false) + m_connectionError(false), + m_spyServerGainRange("Gain", 0, 41, 1, ""), + m_spyServerGains({m_spyServerGainRange}, false, false) { m_deviceUISet = deviceUISet; setAttribute(Qt::WA_DeleteOnClose, true); @@ -223,23 +225,27 @@ bool RemoteTCPInputGui::handleMessage(const Message& message) device = devices.value(m_remoteDevice); } ui->device->setText(QString("Device: %1").arg(device)); - ui->protocol->setText(QString("Protocol: %1").arg(report.getProtocol())); + ui->detectedProtocol->setText(QString("Protocol: %1").arg(report.getProtocol())); // Update GUI so we only show widgets available for the protocol in use bool sdra = report.getProtocol() == "SDRA"; - if (sdra && (ui->sampleBits->count() != 4)) + bool spyServer = report.getProtocol() == "Spy Server"; + if (spyServer) { + m_spyServerGains.m_gains[0].m_max = report.getMaxGain(); + } + if ((sdra || spyServer) && (ui->sampleBits->count() < 4)) { ui->sampleBits->addItem("16"); ui->sampleBits->addItem("24"); ui->sampleBits->addItem("32"); } - else if (!sdra && (ui->sampleBits->count() != 1)) + else if (!(sdra || spyServer) && (ui->sampleBits->count() != 1)) { while (ui->sampleBits->count() > 1) { ui->sampleBits->removeItem(ui->sampleBits->count() - 1); } } - if (sdra && (ui->decim->count() != 7)) + if ((sdra || spyServer) && (ui->decim->count() != 7)) { ui->decim->addItem("2"); ui->decim->addItem("4"); @@ -248,19 +254,24 @@ bool RemoteTCPInputGui::handleMessage(const Message& message) ui->decim->addItem("32"); ui->decim->addItem("64"); } - else if (!sdra && (ui->decim->count() != 1)) + else if (!(sdra || spyServer) && (ui->decim->count() != 1)) { while (ui->decim->count() > 1) { ui->decim->removeItem(ui->decim->count() - 1); } } - if (!sdra) { + if (!sdra) + { ui->deltaFrequency->setValue(0); ui->channelGain->setValue(0); ui->decimation->setChecked(true); } + ui->deltaFrequencyLabel->setEnabled(sdra); ui->deltaFrequency->setEnabled(sdra); + ui->deltaUnits->setEnabled(sdra); + ui->channelGainLabel->setEnabled(sdra); ui->channelGain->setEnabled(sdra); + ui->channelGainText->setEnabled(sdra); ui->decimation->setEnabled(sdra); if (sdra) { ui->centerFrequency->setValueRange(9, 0, 999999999); // Should add transverter control to protocol in the future @@ -271,7 +282,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message) // Set sample rate range if (m_sampleRateRanges.contains(m_remoteDevice)) { - const SampleRateRange *range = m_sampleRateRanges.value(m_remoteDevice); + const SampleRateRange *range = m_sampleRateRanges.value(m_remoteDevice); ui->devSampleRate->setValueRange(8, range->m_min, range->m_max); } else if (m_sampleRateLists.contains(m_remoteDevice)) @@ -284,6 +295,18 @@ bool RemoteTCPInputGui::handleMessage(const Message& message) { ui->devSampleRate->setValueRange(8, 0, 99999999); } + ui->devSampleRateLabel->setEnabled(!spyServer); + ui->devSampleRate->setEnabled(!spyServer); + ui->devSampleRateUnits->setEnabled(!spyServer); + ui->agc->setEnabled(!spyServer); + ui->rfBWLabel->setEnabled(!spyServer); + ui->rfBW->setEnabled(!spyServer); + ui->rfBWUnits->setEnabled(!spyServer); + ui->dcOffset->setEnabled(!spyServer); + ui->iqImbalance->setEnabled(!spyServer); + ui->ppm->setEnabled(!spyServer); + ui->ppmLabel->setEnabled(!spyServer); + ui->ppmText->setEnabled(!spyServer); displayGains(); return true; @@ -368,7 +391,9 @@ void RemoteTCPInputGui::displaySettings() ui->deviceRateText->setText(tr("%1k").arg(m_settings.m_channelSampleRate / 1000.0)); ui->decimation->setChecked(!m_settings.m_channelDecimation); ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation); - ui->sampleBits->setCurrentIndex(m_settings.m_sampleBits/8-1); + ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation); + ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation); + ui->sampleBits->setCurrentText(QString::number(m_settings.m_sampleBits)); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); ui->dataAddress->blockSignals(true); @@ -385,6 +410,10 @@ void RemoteTCPInputGui::displaySettings() ui->preFill->setValue((int)(m_settings.m_preFill * 10.0)); ui->preFillText->setText(QString("%1s").arg(m_settings.m_preFill, 0, 'f', 2)); + int idx = ui->protocol->findText(m_settings.m_protocol); + if (idx > 0) { + ui->protocol->setCurrentIndex(idx); + } displayGains(); blockApplySettings(false); @@ -526,7 +555,11 @@ void RemoteTCPInputGui::displayGains() QLabel *gainTexts[3] = {ui->gain1Text, ui->gain2Text, ui->gain3Text}; QWidget *gainLine[2] = {ui->gainLine1, ui->gainLine2}; - m_deviceGains = m_gains.value(m_remoteDevice); + if (m_settings.m_protocol == "Spy Server") { + m_deviceGains = &m_spyServerGains; + } else { + m_deviceGains = m_gains.value(m_remoteDevice); + } if (m_deviceGains) { ui->agc->setVisible(m_deviceGains->m_agc); @@ -746,12 +779,16 @@ void RemoteTCPInputGui::on_decimation_toggled(bool checked) ui->channelSampleRate->setValue(m_settings.m_channelSampleRate); } ui->channelSampleRate->setEnabled(!checked); + ui->channelSampleRateLabel->setEnabled(!checked); + ui->channelSampleRateUnit->setEnabled(!checked); sendSettings(); } void RemoteTCPInputGui::on_sampleBits_currentIndexChanged(int index) { - m_settings.m_sampleBits = 8 * (index + 1); + (void) index; + + m_settings.m_sampleBits = ui->sampleBits->currentText().toInt(); m_settingsKeys.append("sampleBits"); sendSettings(); } @@ -808,6 +845,14 @@ void RemoteTCPInputGui::on_preFill_valueChanged(int value) sendSettings(); } +void RemoteTCPInputGui::on_protocol_currentIndexChanged(int index) +{ + m_settings.m_protocol = ui->protocol->currentText(); + m_settingsKeys.append("protocol"); + sendSettings(); + displayGains(); +} + void RemoteTCPInputGui::updateHardware() { if (m_doApplySettings) @@ -902,4 +947,5 @@ void RemoteTCPInputGui::makeUIConnections() QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataPort_editingFinished); QObject::connect(ui->overrideRemoteSettings, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_overrideRemoteSettings_toggled); QObject::connect(ui->preFill, &QDial::valueChanged, this, &RemoteTCPInputGui::on_preFill_valueChanged); + QObject::connect(ui->protocol, QOverload::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_protocol_currentIndexChanged); } diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.h b/plugins/samplesource/remotetcpinput/remotetcpinputgui.h index 3de89c95d..9afcec66a 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.h +++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2024 Jon Beniston, M7RCE // // Copyright (C) 2022 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // @@ -122,6 +122,9 @@ private: RemoteTCPProtocol::Device m_remoteDevice; // Remote device reported when connecting bool m_connectionError; + DeviceGains::GainRange m_spyServerGainRange; + DeviceGains m_spyServerGains; + static const DeviceGains::GainRange m_rtlSDR34kGainRange; static const DeviceGains m_rtlSDRe4kGains; static const DeviceGains::GainRange m_rtlSDRR820GainRange; @@ -208,6 +211,7 @@ private slots: void on_dataPort_editingFinished(); void on_overrideRemoteSettings_toggled(bool checked); void on_preFill_valueChanged(int value); + void on_protocol_currentIndexChanged(int index); void updateHardware(); void updateStatus(); void openDeviceSettingsDialog(const QPoint& p); diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui b/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui index 6d8d881fd..ec526650d 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui +++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui @@ -742,7 +742,7 @@ Use to ensure full dynamic range of 8-bit data is used. - + Ch SR @@ -777,7 +777,7 @@ Use to ensure full dynamic range of 8-bit data is used. - + S/s @@ -957,6 +957,29 @@ Use to ensure full dynamic range of 8-bit data is used. + + + + + 75 + 0 + + + + Protocol to use. SDRangel (inc. rtl_tcp) or Spy Server + + + + SDRangel + + + + + Spy Server + + + + @@ -1163,7 +1186,7 @@ This should typically be empty. If full, your CPU cannot keep up and data will b - + @@ -1174,6 +1197,11 @@ This should typically be empty. If full, your CPU cannot keep up and data will b + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
ValueDialZ QWidget @@ -1186,11 +1214,6 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
gui/valuedial.h
1
- - ButtonSwitch - QToolButton -
gui/buttonswitch.h
-
startStop diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp index 2b693ac8e..ad5291794 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp +++ b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2024 Jon Beniston, M7RCE // // Copyright (C) 2022 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // @@ -48,6 +48,7 @@ void RemoteTCPInputSettings::resetToDefaults() m_dataPort = 1234; m_overrideRemoteSettings = true; m_preFill = 1.0f; + m_protocol = "SDRangel"; m_useReverseAPI = false; m_reverseAPIAddress = "127.0.0.1"; m_reverseAPIPort = 8888; @@ -81,6 +82,7 @@ QByteArray RemoteTCPInputSettings::serialize() const s.writeU32(22, m_reverseAPIPort); s.writeU32(23, m_reverseAPIDeviceIndex); s.writeList(24, m_addressList); + s.writeString(25, m_protocol); for (int i = 0; i < m_maxGains; i++) { s.writeS32(30+i, m_gain[i]); @@ -136,6 +138,7 @@ bool RemoteTCPInputSettings::deserialize(const QByteArray& data) m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval; d.readList(24, &m_addressList); + d.readString(25, &m_protocol, "SDRangel"); for (int i = 0; i < m_maxGains; i++) { d.readS32(30+i, &m_gain[i], 0); @@ -224,6 +227,9 @@ void RemoteTCPInputSettings::applySettings(const QStringList& settingsKeys, cons if (settingsKeys.contains("addressList")) { m_addressList = settings.m_addressList; } + if (settingsKeys.contains("protocol")) { + m_protocol = settings.m_protocol; + } for (int i = 0; i < m_maxGains; i++) { @@ -309,6 +315,9 @@ QString RemoteTCPInputSettings::getDebugString(const QStringList& settingsKeys, if (settingsKeys.contains("addressList") || force) { ostr << " m_addressList: " << m_addressList.join(",").toStdString(); } + if (settingsKeys.contains("protocol") || force) { + ostr << " m_protocol: " << m_protocol.toStdString(); + } for (int i = 0; i < m_maxGains; i++) { diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h index 1694d9b31..a95dc9773 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h +++ b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h @@ -2,7 +2,7 @@ // Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // // written by Christian Daniel // // Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB // -// Copyright (C) 2020, 2022-2023 Jon Beniston, M7RCE // +// Copyright (C) 2020, 2022-2024 Jon Beniston, M7RCE // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -53,6 +53,7 @@ struct RemoteTCPInputSettings uint16_t m_reverseAPIPort; uint16_t m_reverseAPIDeviceIndex; QStringList m_addressList; // List of dataAddresses that have been used in the past + QString m_protocol; // "SDRangel" or "Spy Server" RemoteTCPInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp index c44118d2d..e9770d46a 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp +++ b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2022-2023 Jon Beniston, M7RCE // +// Copyright (C) 2022-2024 Jon Beniston, M7RCE // // Copyright (C) 2022 Edouard Griffiths, F4EXB // // Copyright (C) 2022 Jiří Pinkava // // // @@ -167,9 +167,19 @@ void RemoteTCPInputTCPHandler::clearBuffer() { if (m_dataSocket) { - m_dataSocket->flush(); - m_dataSocket->readAll(); - m_fillBuffer = true; + if (m_spyServer) + { + // Can't just flush buffer, otherwise we'll lose header sync + // Read and throw away any available data + processSpyServerData(m_dataSocket->bytesAvailable(), true); + m_fillBuffer = true; + } + else + { + m_dataSocket->flush(); + m_dataSocket->readAll(); + m_fillBuffer = true; + } } } @@ -389,6 +399,60 @@ void RemoteTCPInputTCPHandler::setSampleBitDepth(int sampleBits) } } +void RemoteTCPInputTCPHandler::spyServerConnect() +{ + QMutexLocker mutexLocker(&m_mutex); + + quint8 request[8+4+9]; + SpyServerProtocol::encodeUInt32(&request[0], 0); + SpyServerProtocol::encodeUInt32(&request[4], 4+9); + SpyServerProtocol::encodeUInt32(&request[8], SpyServerProtocol::ProtocolID); + memcpy(&request[8+5], "SDRangel", 9); + if (m_dataSocket) { + m_dataSocket->write((char*)request, sizeof(request)); + } +} + +void RemoteTCPInputTCPHandler::spyServerSet(int setting, int value) +{ + QMutexLocker mutexLocker(&m_mutex); + + quint8 request[8+8]; + SpyServerProtocol::encodeUInt32(&request[0], 2); + SpyServerProtocol::encodeUInt32(&request[4], 8); + SpyServerProtocol::encodeUInt32(&request[8], setting); + SpyServerProtocol::encodeUInt32(&request[12], value); + if (m_dataSocket) { + m_dataSocket->write((char*)request, sizeof(request)); + } +} + +void RemoteTCPInputTCPHandler::spyServerSetIQFormat(int sampleBits) +{ + quint32 format; + + if (sampleBits == 8) { + format = 1; + } else if (sampleBits == 16) { + format = 2; + } else if (sampleBits == 24) { + format = 3; + } else if (sampleBits == 32) { + format = 4; // This is float + } else { + qDebug() << "RemoteTCPInputTCPHandler::spyServerSetIQFormat: Unsupported value" << sampleBits; + format = 1; + } + spyServerSet(SpyServerProtocol::setIQFormat, format); +} + +void RemoteTCPInputTCPHandler::spyServerSetStreamIQ() +{ + spyServerSetIQFormat(m_settings.m_sampleBits); + spyServerSet(SpyServerProtocol::setStreamingMode, 1); // Stream IQ only + spyServerSet(SpyServerProtocol::setStreamingEnabled, 1); // Enable streaming +} + void RemoteTCPInputTCPHandler::applySettings(const RemoteTCPInputSettings& settings, const QList& settingsKeys, bool force) { qDebug() << "RemoteTCPInputTCPHandler::applySettings: " @@ -396,86 +460,124 @@ void RemoteTCPInputTCPHandler::applySettings(const RemoteTCPInputSettings& setti << settings.getDebugString(settingsKeys, force); QMutexLocker mutexLocker(&m_mutex); - if (settingsKeys.contains("centerFrequency") || force) { - setCenterFrequency(settings.m_centerFrequency); - } - if (settingsKeys.contains("loPpmCorrection") || force) { - setFreqCorrection(settings.m_loPpmCorrection); - } - if (settingsKeys.contains("dcBlock") || force) { - if (m_sdra) { - setDCOffsetRemoval(settings.m_dcBlock); - } - } - if (settingsKeys.contains("iqCorrection") || force) { - if (m_sdra) { - setIQCorrection(settings.m_iqCorrection); - } - } - if (settingsKeys.contains("biasTee") || force) { - setBiasTee(settings.m_biasTee); - } - if (settingsKeys.contains("directSampling") || force) { - setDirectSampling(settings.m_directSampling); - } - if (settingsKeys.contains("log2Decim") || force) { - if (m_sdra) { - setDecimation(settings.m_log2Decim); - } - } - if (settingsKeys.contains("devSampleRate") || force) { - setSampleRate(settings.m_devSampleRate); - } - if (settingsKeys.contains("agc") || force) { - setAGC(settings.m_agc); - } - if (force) { - setTunerAGC(1); // The SDRangel RTLSDR driver always has tuner gain as manual - } - if (settingsKeys.contains("gain[0]") || force) { - setTunerGain(settings.m_gain[0]); - } - for (int i = 1; i < 3; i++) + if (m_spyServer) { - if (settingsKeys.contains(QString("gain[%1]").arg(i)) || force) { - setIFGain(i, settings.m_gain[i]); + if (settingsKeys.contains("centerFrequency") || force) { + spyServerSet(SpyServerProtocol::setCenterFrequency, settings.m_centerFrequency); } - } - if (settingsKeys.contains("rfBW") || force) { - setBandwidth(settings.m_rfBW); - } - if (settingsKeys.contains("inputFrequencyOffset") || force) { - if (m_sdra) { - setChannelFreqOffset(settings.m_inputFrequencyOffset); - } - } - if (settingsKeys.contains("channelGain") || force) { - if (m_sdra) { - setChannelGain(settings.m_channelGain); - } - } - if ((settings.m_channelSampleRate != m_settings.m_channelSampleRate) || force) - { - // Resize FIFO to give us 1 second - if ((settingsKeys.contains("channelSampleRate") || force) && (settings.m_channelSampleRate > (qint32)m_sampleFifo->size())) + if ((settings.m_channelSampleRate != m_settings.m_channelSampleRate) || force) { - qDebug() << "RemoteTCPInputTCPHandler::applySettings: Resizing sample FIFO from " << m_sampleFifo->size() << "to" << settings.m_channelSampleRate; - m_sampleFifo->setSize(settings.m_channelSampleRate); - delete[] m_tcpBuf; - m_tcpBuf = new char[m_sampleFifo->size()*2*4]; - m_fillBuffer = true; // So we reprime FIFO + // Resize FIFO to give us 1 second + if ((settingsKeys.contains("channelSampleRate") || force) && (settings.m_channelSampleRate > (qint32)m_sampleFifo->size())) + { + qDebug() << "RemoteTCPInputTCPHandler::applySettings: Resizing sample FIFO from " << m_sampleFifo->size() << "to" << settings.m_channelSampleRate; + m_sampleFifo->setSize(settings.m_channelSampleRate); + delete[] m_tcpBuf; + m_tcpBuf = new char[m_sampleFifo->size()*2*4]; + m_fillBuffer = true; // So we reprime FIFO + } + // Protocol only seems to allow changing decimation + //spyServerSet(SpyServerProtocol::???, settings.m_channelSampleRate); + clearBuffer(); } - if (m_sdra) { - setChannelSampleRate(settings.m_channelSampleRate); + if (settingsKeys.contains("sampleBits") || force) + { + spyServerSetIQFormat(settings.m_sampleBits); + clearBuffer(); + } + if (settingsKeys.contains("log2Decim") || force) + { + spyServerSet(SpyServerProtocol::setIQDecimation, settings.m_log2Decim); + clearBuffer(); + } + if (settingsKeys.contains("gain[0]") || force) + { + spyServerSet(SpyServerProtocol::setGain, settings.m_gain[0] / 10); // Convert 10ths dB to index } - clearBuffer(); } - if (settingsKeys.contains("sampleBits") || force) + else { - if (m_sdra) { - setSampleBitDepth(settings.m_sampleBits); + if (settingsKeys.contains("centerFrequency") || force) { + setCenterFrequency(settings.m_centerFrequency); + } + if (settingsKeys.contains("loPpmCorrection") || force) { + setFreqCorrection(settings.m_loPpmCorrection); + } + if (settingsKeys.contains("dcBlock") || force) { + if (m_sdra) { + setDCOffsetRemoval(settings.m_dcBlock); + } + } + if (settingsKeys.contains("iqCorrection") || force) { + if (m_sdra) { + setIQCorrection(settings.m_iqCorrection); + } + } + if (settingsKeys.contains("biasTee") || force) { + setBiasTee(settings.m_biasTee); + } + if (settingsKeys.contains("directSampling") || force) { + setDirectSampling(settings.m_directSampling); + } + if (settingsKeys.contains("log2Decim") || force) { + if (m_sdra) { + setDecimation(settings.m_log2Decim); + } + } + if (settingsKeys.contains("devSampleRate") || force) { + setSampleRate(settings.m_devSampleRate); + } + if (settingsKeys.contains("agc") || force) { + setAGC(settings.m_agc); + } + if (force) { + setTunerAGC(1); // The SDRangel RTLSDR driver always has tuner gain as manual + } + if (settingsKeys.contains("gain[0]") || force) { + setTunerGain(settings.m_gain[0]); + } + for (int i = 1; i < 3; i++) + { + if (settingsKeys.contains(QString("gain[%1]").arg(i)) || force) { + setIFGain(i, settings.m_gain[i]); + } + } + if (settingsKeys.contains("rfBW") || force) { + setBandwidth(settings.m_rfBW); + } + if (settingsKeys.contains("inputFrequencyOffset") || force) { + if (m_sdra) { + setChannelFreqOffset(settings.m_inputFrequencyOffset); + } + } + if (settingsKeys.contains("channelGain") || force) { + if (m_sdra) { + setChannelGain(settings.m_channelGain); + } + } + if ((settings.m_channelSampleRate != m_settings.m_channelSampleRate) || force) + { + // Resize FIFO to give us 1 second + if ((settingsKeys.contains("channelSampleRate") || force) && (settings.m_channelSampleRate > (qint32)m_sampleFifo->size())) + { + qDebug() << "RemoteTCPInputTCPHandler::applySettings: Resizing sample FIFO from " << m_sampleFifo->size() << "to" << settings.m_channelSampleRate; + m_sampleFifo->setSize(settings.m_channelSampleRate); + delete[] m_tcpBuf; + m_tcpBuf = new char[m_sampleFifo->size()*2*4]; + m_fillBuffer = true; // So we reprime FIFO + } + if (m_sdra) { + setChannelSampleRate(settings.m_channelSampleRate); + } + clearBuffer(); + } + if (settingsKeys.contains("sampleBits") || force) + { + if (m_sdra) { + setSampleBitDepth(settings.m_sampleBits); + } + clearBuffer(); } - clearBuffer(); } // Don't use force, as disconnect can cause rtl_tcp to quit @@ -501,6 +603,10 @@ void RemoteTCPInputTCPHandler::connected() MsgReportConnection *msg = MsgReportConnection::create(true); m_messageQueueToGUI->push(msg); } + m_spyServer = m_settings.m_protocol == "Spy Server"; + m_state = HEADER; + m_sdra = false; + spyServerConnect(); } void RemoteTCPInputTCPHandler::reconnect() @@ -542,116 +648,366 @@ void RemoteTCPInputTCPHandler::dataReadyRead() { QMutexLocker mutexLocker(&m_mutex); - if (!m_readMetaData) + if (!m_readMetaData && !m_spyServer) { - quint8 metaData[RemoteTCPProtocol::m_sdraMetaDataSize]; - if (m_dataSocket->bytesAvailable() >= (qint64)sizeof(metaData)) + processMetaData(); + } + else if (!m_readMetaData && m_spyServer) + { + processSpyServerMetaData(); + } +} + +void RemoteTCPInputTCPHandler::processMetaData() +{ + quint8 metaData[RemoteTCPProtocol::m_sdraMetaDataSize]; + if (m_dataSocket->bytesAvailable() >= (qint64)sizeof(metaData)) + { + qint64 bytesRead = m_dataSocket->read((char *)&metaData[0], 4); + if (bytesRead == 4) { - qint64 bytesRead = m_dataSocket->read((char *)&metaData[0], 4); - if (bytesRead == 4) + // Read first 4 bytes which indicate which protocol is in use + // RTL0 or SDRA + char protochars[5]; + memcpy(protochars, metaData, 4); + protochars[4] = '\0'; + QString protocol(protochars); + + if (protocol == "RTL0") { - // Read first 4 bytes which indicate which protocol is in use - // RTL0 or SDRA - char protochars[5]; - memcpy(protochars, metaData, 4); - protochars[4] = '\0'; - QString protocol(protochars); + m_sdra = false; + m_spyServer = false; + bytesRead = m_dataSocket->read((char *)&metaData[4], RemoteTCPProtocol::m_rtl0MetaDataSize-4); - if (protocol == "RTL0") + m_device = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]); + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol)); + } + if (m_settings.m_sampleBits != 8) { - m_sdra = false; - bytesRead = m_dataSocket->read((char *)&metaData[4], RemoteTCPProtocol::m_rtl0MetaDataSize-4); - - RemoteTCPProtocol::Device tuner = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]); - if (m_messageQueueToGUI) { - m_messageQueueToGUI->push(MsgReportRemoteDevice::create(tuner, protocol)); + RemoteTCPInputSettings& settings = m_settings; + settings.m_sampleBits = 8; + QList settingsKeys{"sampleBits"}; + if (m_messageQueueToInput) { + m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); } - if (m_settings.m_sampleBits != 8) - { - RemoteTCPInputSettings& settings = m_settings; - settings.m_sampleBits = 8; - QList settingsKeys{"sampleBits"}; - if (m_messageQueueToInput) { - m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); - } - if (m_messageQueueToGUI) { - m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); - } + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); } } - else if (protocol == "SDRA") - { - m_sdra = true; - bytesRead = m_dataSocket->read((char *)&metaData[4], RemoteTCPProtocol::m_sdraMetaDataSize-4); + } + else if (protocol == "SDRA") + { + m_sdra = true; + m_spyServer = false; + bytesRead = m_dataSocket->read((char *)&metaData[4], RemoteTCPProtocol::m_sdraMetaDataSize-4); - RemoteTCPProtocol::Device device = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]); - if (m_messageQueueToGUI) { - m_messageQueueToGUI->push(MsgReportRemoteDevice::create(device, protocol)); - } - if (!m_settings.m_overrideRemoteSettings) + m_device = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]); + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol)); + } + if (!m_settings.m_overrideRemoteSettings) + { + // Update local settings to match remote + RemoteTCPInputSettings& settings = m_settings; + QList settingsKeys; + settings.m_centerFrequency = RemoteTCPProtocol::extractUInt64(&metaData[8]); + settingsKeys.append("centerFrequency"); + settings.m_loPpmCorrection = RemoteTCPProtocol::extractUInt32(&metaData[16]); + settingsKeys.append("loPpmCorrection"); + quint32 flags = RemoteTCPProtocol::extractUInt32(&metaData[20]); + settings.m_biasTee = flags & 1; + settingsKeys.append("biasTee"); + settings.m_directSampling = (flags >> 1) & 1; + settingsKeys.append("directSampling"); + settings.m_agc = (flags >> 2) & 1; + settingsKeys.append("agc"); + settings.m_dcBlock = (flags >> 3) & 1; + settingsKeys.append("dcBlock"); + settings.m_iqCorrection = (flags >> 4) & 1; + settingsKeys.append("iqCorrection"); + settings.m_devSampleRate = RemoteTCPProtocol::extractUInt32(&metaData[24]); + settingsKeys.append("devSampleRate"); + settings.m_log2Decim = RemoteTCPProtocol::extractUInt32(&metaData[28]); + settingsKeys.append("log2Decim"); + settings.m_gain[0] = RemoteTCPProtocol::extractInt16(&metaData[32]); + settings.m_gain[1] = RemoteTCPProtocol::extractInt16(&metaData[34]); + settings.m_gain[2] = RemoteTCPProtocol::extractInt16(&metaData[36]); + settingsKeys.append("gain[0]"); + settingsKeys.append("gain[1]"); + settingsKeys.append("gain[2]"); + settings.m_rfBW = RemoteTCPProtocol::extractUInt32(&metaData[40]); + settingsKeys.append("rfBW"); + settings.m_inputFrequencyOffset = RemoteTCPProtocol::extractUInt32(&metaData[44]); + settingsKeys.append("inputFrequencyOffset"); + settings.m_channelGain = RemoteTCPProtocol::extractUInt32(&metaData[48]); + settingsKeys.append("channelGain"); + settings.m_channelSampleRate = RemoteTCPProtocol::extractUInt32(&metaData[52]); + settingsKeys.append("channelSampleRate"); + settings.m_sampleBits = RemoteTCPProtocol::extractUInt32(&metaData[56]); + settingsKeys.append("sampleBits"); + if (settings.m_channelSampleRate != (settings.m_devSampleRate >> settings.m_log2Decim)) { - // Update local settings to match remote - RemoteTCPInputSettings& settings = m_settings; - QList settingsKeys; - settings.m_centerFrequency = RemoteTCPProtocol::extractUInt64(&metaData[8]); - settingsKeys.append("centerFrequency"); - settings.m_loPpmCorrection = RemoteTCPProtocol::extractUInt32(&metaData[16]); - settingsKeys.append("loPpmCorrection"); - quint32 flags = RemoteTCPProtocol::extractUInt32(&metaData[20]); - settings.m_biasTee = flags & 1; - settingsKeys.append("biasTee"); - settings.m_directSampling = (flags >> 1) & 1; - settingsKeys.append("directSampling"); - settings.m_agc = (flags >> 2) & 1; - settingsKeys.append("agc"); - settings.m_dcBlock = (flags >> 3) & 1; - settingsKeys.append("dcBlock"); - settings.m_iqCorrection = (flags >> 4) & 1; - settingsKeys.append("iqCorrection"); - settings.m_devSampleRate = RemoteTCPProtocol::extractUInt32(&metaData[24]); - settingsKeys.append("devSampleRate"); - settings.m_log2Decim = RemoteTCPProtocol::extractUInt32(&metaData[28]); - settingsKeys.append("log2Decim"); - settings.m_gain[0] = RemoteTCPProtocol::extractInt16(&metaData[32]); - settings.m_gain[1] = RemoteTCPProtocol::extractInt16(&metaData[34]); - settings.m_gain[2] = RemoteTCPProtocol::extractInt16(&metaData[36]); - settingsKeys.append("gain[0]"); - settingsKeys.append("gain[1]"); - settingsKeys.append("gain[2]"); - settings.m_rfBW = RemoteTCPProtocol::extractUInt32(&metaData[40]); - settingsKeys.append("rfBW"); - settings.m_inputFrequencyOffset = RemoteTCPProtocol::extractUInt32(&metaData[44]); - settingsKeys.append("inputFrequencyOffset"); - settings.m_channelGain = RemoteTCPProtocol::extractUInt32(&metaData[48]); - settingsKeys.append("channelGain"); - settings.m_channelSampleRate = RemoteTCPProtocol::extractUInt32(&metaData[52]); - settingsKeys.append("channelSampleRate"); - settings.m_sampleBits = RemoteTCPProtocol::extractUInt32(&metaData[56]); - settingsKeys.append("sampleBits"); - if (settings.m_channelSampleRate != (settings.m_devSampleRate >> settings.m_log2Decim)) - { - settings.m_channelDecimation = true; - settingsKeys.append("channelDecimation"); - } - if (m_messageQueueToInput) { - m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); - } - if (m_messageQueueToGUI) { - m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); - } + settings.m_channelDecimation = true; + settingsKeys.append("channelDecimation"); + } + if (m_messageQueueToInput) { + m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } + } + } + else + { + qDebug() << "RemoteTCPInputTCPHandler::dataReadyRead: Unknown protocol: " << protocol; + } + if (m_settings.m_overrideRemoteSettings) + { + // Force settings to be sent to remote device (this needs to be after m_sdra is determined above) + applySettings(m_settings, QList(), true); + } + } + m_readMetaData = true; + } +} + +void RemoteTCPInputTCPHandler::processSpyServerMetaData() +{ + bool done = false; + + while (!done) + { + if (m_state == HEADER) + { + if (m_dataSocket->bytesAvailable() >= (qint64)sizeof(SpyServerProtocol::Header)) + { + qint64 bytesRead = m_dataSocket->read((char *)&m_spyServerHeader, sizeof(SpyServerProtocol::Header)); + if (bytesRead == sizeof(SpyServerProtocol::Header)) { + m_state = DATA; + } else { + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerMetaData: Failed to read:" << bytesRead << "/" << sizeof(SpyServerProtocol::Header); + } + } + else + { + done = true; + } + } + else if (m_state == DATA) + { + if (m_dataSocket->bytesAvailable() >= m_spyServerHeader.m_size) + { + qint64 bytesRead = m_dataSocket->read(&m_tcpBuf[0], m_spyServerHeader.m_size); + if (bytesRead == m_spyServerHeader.m_size) + { + if (m_spyServerHeader.m_message == SpyServerProtocol::DeviceMessage) + { + processSpyServerDevice((SpyServerProtocol::Device *) &m_tcpBuf[0]); + m_state = HEADER; + } + else if (m_spyServerHeader.m_message == SpyServerProtocol::StateMessage) + { + // This call can result in applySettings() calling clearBuffer() then processSpyServerData() + processSpyServerState((SpyServerProtocol::State *) &m_tcpBuf[0], true); + spyServerSetStreamIQ(); + + m_state = HEADER; + m_readMetaData = true; + done = true; + } + else + { + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerMetaData: Unexpected message type" << m_spyServerHeader.m_message; + m_state = HEADER; } } else { - qDebug() << "RemoteTCPInputTCPHandler::dataReadyRead: Unknown protocol: " << protocol; - } - if (m_settings.m_overrideRemoteSettings) - { - // Force settings to be sent to remote device (this needs to be after m_sdra is determined above) - applySettings(m_settings, QList(), true); + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerMetaData: Failed to read:" << bytesRead << "/" << m_spyServerHeader.m_size; } } - m_readMetaData = true; + else + { + done = true; + } + } + } +} + +void RemoteTCPInputTCPHandler::processSpyServerDevice(const SpyServerProtocol::Device* ssDevice) +{ + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerDevice:" + << "device:" << ssDevice->m_device + << "serial:" << ssDevice->m_serial + << "sampleRate:" << ssDevice->m_sampleRate + << "decimationStages:" << ssDevice->m_decimationStages + << "maxGainIndex:" << ssDevice->m_maxGainIndex + << "minFrequency:" << ssDevice->m_minFrequency + << "maxFrequency:" << ssDevice->m_maxFrequency + << "sampleBits:" << ssDevice->m_sampleBits + << "minDecimation:" << ssDevice->m_minDecimation; + + switch (ssDevice->m_device) + { + case 1: + m_device = RemoteTCPProtocol::AIRSPY; + break; + case 2: + m_device = RemoteTCPProtocol::AIRSPY_HF; + break; + case 3: + m_device = ssDevice->m_maxGainIndex == 14 + ? RemoteTCPProtocol::RTLSDR_E4000 + : RemoteTCPProtocol::RTLSDR_R820T; + break; + default: + m_device = RemoteTCPProtocol::UNKNOWN; + break; + } + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, "Spy Server", ssDevice->m_maxGainIndex)); + } + + RemoteTCPInputSettings& settings = m_settings; + QList settingsKeys{}; + // We can't change sample rate, so always have to update local setting to match + m_settings.m_devSampleRate = settings.m_devSampleRate = ssDevice->m_sampleRate; + settingsKeys.append("devSampleRate"); + // Make sure decimation setting is at least the minimum + if (!m_settings.m_overrideRemoteSettings || (settings.m_log2Decim < ssDevice->m_minDecimation)) + { + m_settings.m_log2Decim = settings.m_log2Decim = ssDevice->m_minDecimation; + settingsKeys.append("log2Decim"); + } + if (m_messageQueueToInput) { + m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } +} + +void RemoteTCPInputTCPHandler::processSpyServerState(const SpyServerProtocol::State* ssState, bool initial) +{ + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerState: " + << "initial:" << initial + << "controllable:" << ssState->m_controllable + << "gain:" << ssState->m_gain + << "deviceCenterFrequency:" << ssState->m_deviceCenterFrequency + << "iqCenterFrequency:" << ssState->m_iqCenterFrequency; + + if (initial && ssState->m_controllable && m_settings.m_overrideRemoteSettings) + { + // Force client settings to be sent to server + applySettings(m_settings, QList(), true); + } + else + { + // Update client settings with that from server + RemoteTCPInputSettings& settings = m_settings; + QList settingsKeys; + + if (m_settings.m_centerFrequency != ssState->m_iqCenterFrequency) + { + settings.m_centerFrequency = ssState->m_iqCenterFrequency; + settingsKeys.append("centerFrequency"); + } + if (m_settings.m_gain[0] != ssState->m_gain) + { + settings.m_gain[0] = ssState->m_gain; + settingsKeys.append("gain[0]"); + } + if (settingsKeys.size() > 0) + { + if (m_messageQueueToInput) { + m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } + if (m_messageQueueToGUI) { + m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys)); + } + } + } +} + +void RemoteTCPInputTCPHandler::processSpyServerData(int requiredBytes, bool clear) +{ + if (!m_readMetaData) { + return; + } + + bool done = false; + + while (!done) + { + if (m_state == HEADER) + { + if (m_dataSocket->bytesAvailable() >= sizeof(SpyServerProtocol::Header)) + { + qint64 bytesRead = m_dataSocket->read((char *) &m_spyServerHeader, sizeof(SpyServerProtocol::Header)); + if (bytesRead == sizeof(SpyServerProtocol::Header)) { + m_state = DATA; + } else { + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerData: Failed to read:" << bytesRead << "/" << sizeof(SpyServerProtocol::Header); + } + } + else + { + done = true; + } + } + else if (m_state == DATA) + { + int bytes; + + if ((m_spyServerHeader.m_message >= SpyServerProtocol::IQ8MMessage) && (m_spyServerHeader.m_message <= SpyServerProtocol::IQ32Message)) { + bytes = std::min(requiredBytes, (int) m_spyServerHeader.m_size); + } else { + bytes = m_spyServerHeader.m_size; + } + + if (m_dataSocket->bytesAvailable() >= bytes) + { + qint64 bytesRead = m_dataSocket->read(&m_tcpBuf[0], bytes); + if (bytesRead == bytes) + { + if ((m_spyServerHeader.m_message >= SpyServerProtocol::IQ8MMessage) && (m_spyServerHeader.m_message <= SpyServerProtocol::IQ32Message)) + { + if (!clear) + { + const int bytesPerIQPair = 2 * m_settings.m_sampleBits / 8; + convert(bytesRead / bytesPerIQPair); + } + m_spyServerHeader.m_size -= bytesRead; + requiredBytes -= bytesRead; + if (m_spyServerHeader.m_size == 0) { + m_state = HEADER; + } + if (requiredBytes <= 0) { + done = true; + } + } + else if (m_spyServerHeader.m_message == SpyServerProtocol::StateMessage) + { + processSpyServerState((SpyServerProtocol::State *) &m_tcpBuf[0], false); + m_state = HEADER; + } + else + { + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerData: Skipping unsupported message"; + m_state = HEADER; + } + } + else + { + qDebug() << "RemoteTCPInputTCPHandler::processSpyServerData: Failed to read:" << bytesRead << "/" << bytes; + } + } + else + { + done = true; + } } } } @@ -663,12 +1019,12 @@ void RemoteTCPInputTCPHandler::processData() if (m_dataSocket && (m_dataSocket->state() == QAbstractSocket::ConnectedState)) { int sampleRate = m_settings.m_channelSampleRate; - int bytesPerSample = m_settings.m_sampleBits / 8; - int bytesPerSecond = sampleRate * 2 * bytesPerSample; + int bytesPerIQPair = 2 * m_settings.m_sampleBits / 8; + int bytesPerSecond = sampleRate * bytesPerIQPair; if (m_dataSocket->bytesAvailable() < (0.1f * m_settings.m_preFill * bytesPerSecond)) { - qDebug() << "RemoteTCPInputTCPHandler::processData: Buffering!"; + qDebug() << "RemoteTCPInputTCPHandler::processData: Buffering - bytesAvailable:" << m_dataSocket->bytesAvailable(); m_fillBuffer = true; } @@ -690,7 +1046,7 @@ void RemoteTCPInputTCPHandler::processData() { if (m_dataSocket->bytesAvailable() >= m_settings.m_preFill * bytesPerSecond) { - qDebug() << "Buffer primed bytesAvailable:" << m_dataSocket->bytesAvailable(); + qDebug() << "RemoteTCPInputTCPHandler::processData: Buffer primed - bytesAvailable:" << m_dataSocket->bytesAvailable(); m_fillBuffer = false; m_prevDateTime = QDateTime::currentDateTime(); factor = 1.0f / 4.0f; // If this is too high, samples can just be dropped downstream @@ -708,10 +1064,20 @@ void RemoteTCPInputTCPHandler::processData() if (!m_fillBuffer) { - if (m_dataSocket->bytesAvailable() >= requiredSamples*2*bytesPerSample) + if (!m_spyServer) { - m_dataSocket->read(&m_tcpBuf[0], requiredSamples*2*bytesPerSample); - convert(requiredSamples); + // rtl_tcp/SDRA stream is just IQ samples + if (m_dataSocket->bytesAvailable() >= requiredSamples*bytesPerIQPair) + { + m_dataSocket->read(&m_tcpBuf[0], requiredSamples*bytesPerIQPair); + convert(requiredSamples); + } + } + else + { + // SpyServer stream is packetized, into a header and body, with multiple packet types + int requiredBytes = requiredSamples*bytesPerIQPair; + processSpyServerData(requiredBytes, false); } } } diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h index 6f6cf18b6..a829c9370 100644 --- a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h +++ b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2022 Jon Beniston, M7RCE // +// Copyright (C) 2022-2024 Jon Beniston, M7RCE // // Copyright (C) 2022 Edouard Griffiths, F4EXB // // Copyright (C) 2022 Jiří Pinkava // // // @@ -29,6 +29,7 @@ #include "util/messagequeue.h" #include "remotetcpinputsettings.h" #include "../../channelrx/remotetcpsink/remotetcpprotocol.h" +#include "spyserver.h" class SampleSinkFifo; class MessageQueue; @@ -70,20 +71,23 @@ public: public: RemoteTCPProtocol::Device getDevice() const { return m_device; } QString getProtocol() const { return m_protocol; } + int getMaxGain() const { return m_maxGain; } - static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol) + static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain = 0) { - return new MsgReportRemoteDevice(device, protocol); + return new MsgReportRemoteDevice(device, protocol, maxGain); } protected: RemoteTCPProtocol::Device m_device; QString m_protocol; + int m_maxGain; - MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol) : + MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain) : Message(), m_device(device), - m_protocol(protocol) + m_protocol(protocol), + m_maxGain(maxGain) { } }; @@ -139,6 +143,11 @@ private: QTimer m_reconnectTimer; QDateTime m_prevDateTime; bool m_sdra; + bool m_spyServer; + RemoteTCPProtocol::Device m_device; + SpyServerProtocol::Header m_spyServerHeader; + enum {HEADER, DATA} m_state; //!< FSM for reading Spy Server packets + int32_t *m_converterBuffer; uint32_t m_converterBufferNbSamples; @@ -172,6 +181,15 @@ private: void setChannelGain(int gain); void setSampleBitDepth(int sampleBits); void applySettings(const RemoteTCPInputSettings& settings, const QList& settingsKeys, bool force = false); + void processMetaData(); + void spyServerConnect(); + void spyServerSet(int setting, int value); + void spyServerSetIQFormat(int sampleBits); + void spyServerSetStreamIQ(); + void processSpyServerMetaData(); + void processSpyServerDevice(const SpyServerProtocol::Device* ssDevice); + void processSpyServerState(const SpyServerProtocol::State* ssState, bool initial); + void processSpyServerData(int requiredBytes, bool clear); private slots: void started(); diff --git a/plugins/samplesource/remotetcpinput/spyserver.h b/plugins/samplesource/remotetcpinput/spyserver.h new file mode 100644 index 000000000..fc7cda019 --- /dev/null +++ b/plugins/samplesource/remotetcpinput/spyserver.h @@ -0,0 +1,85 @@ +#ifndef SPY_SERVER_H +#define SPY_SERVER_H + +#include + +class SpyServerProtocol { + +public: + + static constexpr int ProtocolID = (2<<24) | 1700; + + enum Command { + setStreamingMode = 0, + setStreamingEnabled = 1, + setGain = 2, + setIQFormat = 100, + setCenterFrequency = 101, + setIQDecimation = 102, + }; + + enum Message { + DeviceMessage = 0, + StateMessage = 1, + IQ8MMessage = 100, + IQ16Message = 101, + IQ24Message = 102, + IQ32Message = 103 + }; + + struct Header { + quint32 m_id; + quint32 m_message; + quint32 m_unused1; + quint32 m_unused2; + quint32 m_size; + }; + + struct Device { + quint32 m_device; + quint32 m_serial; + quint32 m_sampleRate; + quint32 m_unused1; + quint32 m_decimationStages; // 8 for Airspy HF, 11 for Airspy, 9 for E4000/R828D/R820 + quint32 m_unused2; + quint32 m_maxGainIndex; // 8 for Airspy HF, 21 for Airspy, 14 for E4000, 29 for R828D/R820 + quint32 m_minFrequency; + quint32 m_maxFrequency; + quint32 m_sampleBits; + quint32 m_minDecimation; // Set when maximum_bandwidth is set in spyserver.config + quint32 m_unused3; + }; + + struct State { + quint32 m_controllable; + quint32 m_gain; + quint32 m_deviceCenterFrequency; + quint32 m_iqCenterFrequency; + quint32 m_unused1; + quint32 m_unused2; + quint32 m_unused3; + quint32 m_unused4; + quint32 m_unused5; + }; + + static void encodeUInt32(quint8 *p, quint32 data) + { + p[3] = (data >> 24) & 0xff; + p[2] = (data >> 16) & 0xff; + p[1] = (data >> 8) & 0xff; + p[0] = data & 0xff; + } + + static quint32 extractUInt32(quint8 *p) + { + quint32 data; + data = (p[0] & 0xff) + | ((p[1] & 0xff) << 8) + | ((p[2] & 0xff) << 16) + | ((p[3] & 0xff) << 24); + return data; + } + +}; + +#endif /* SPY_SERVER_H */ diff --git a/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml b/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml index df1bf0f8f..09cc073e6 100644 --- a/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml +++ b/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml @@ -43,6 +43,9 @@ RemoteTCPInputSettings: type: integer preFill: type: integer + protocol: + description: (SDRangel or Spy Server) + type: string useReverseAPI: description: Synchronize with reverse API (1 for yes, 0 for no) type: integer