1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-01 13:47:01 -04:00

Remote TCP updates:

Add support for public list of SDRangel servers that can be displayed on Map.
Add FLAC and zlib IQ compression.
Add IQ squelch for compression.
Add remote device/antenna position and direction reporting.
Add text messaging.
This commit is contained in:
srcejon
2024-09-22 10:44:30 +01:00
parent 7d1beb9eff
commit 8bf1a2f803
41 changed files with 7181 additions and 800 deletions
@@ -40,16 +40,27 @@
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgConfigureRemoteTCPInput, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportTCPBuffer, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgSaveReplay, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgSendMessage, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportPosition, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportDirection, Message)
RemoteTCPInput::RemoteTCPInput(DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_settings(),
m_remoteInputTCPPHandler(nullptr),
m_deviceDescription("RemoteTCPInput")
m_deviceDescription("RemoteTCPInput"),
m_running(false),
m_latitude(std::numeric_limits<float>::quiet_NaN()),
m_longitude(std::numeric_limits<float>::quiet_NaN()),
m_altitude(std::numeric_limits<float>::quiet_NaN()),
m_isotropic(false),
m_azimuth(std::numeric_limits<float>::quiet_NaN()),
m_elevation(std::numeric_limits<float>::quiet_NaN())
{
m_sampleFifo.setLabel(m_deviceDescription);
m_sampleFifo.setSize(48000 * 8);
m_remoteInputTCPPHandler = new RemoteTCPInputTCPHandler(&m_sampleFifo, m_deviceAPI);
m_remoteInputTCPPHandler = new RemoteTCPInputTCPHandler(&m_sampleFifo, m_deviceAPI, &m_replayBuffer);
m_remoteInputTCPPHandler->moveToThread(&m_thread);
m_remoteInputTCPPHandler->setMessageQueueToInput(&m_inputMessageQueue);
@@ -66,6 +77,7 @@ RemoteTCPInput::RemoteTCPInput(DeviceAPI *deviceAPI) :
RemoteTCPInput::~RemoteTCPInput()
{
qDebug() << "RemoteTCPInput::~RemoteTCPInput";
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
@@ -84,25 +96,45 @@ void RemoteTCPInput::destroy()
void RemoteTCPInput::init()
{
qDebug() << "*************** RemoteTCPInput::init";
applySettings(m_settings, QList<QString>(), true);
}
bool RemoteTCPInput::start()
{
qDebug() << "RemoteTCPInput::start";
if (m_running) {
qDebug() << "RemoteTCPInput::stop - Already running";
return true;
}
m_remoteInputTCPPHandler->reset();
m_remoteInputTCPPHandler->start();
qDebug() << "************ RemoteTCPInput::start" << m_settings.m_dataAddress;
m_remoteInputTCPPHandler->getInputMessageQueue()->push(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler::create(m_settings, QList<QString>(), true));
m_thread.start();
m_running = true;
return true;
}
void RemoteTCPInput::stop()
{
qDebug() << "RemoteTCPInput::stop";
if (!m_running) {
// For wasm, important not to call m_remoteInputTCPPHandler->stop() twice
// as mutex can deadlock when this object is being deleted
qDebug() << "RemoteTCPInput::stop - Not running";
return;
}
m_remoteInputTCPPHandler->stop();
qDebug() << "RemoteTCPInput::stop1";
m_thread.quit();
qDebug() << "RemoteTCPInput::stop2";
#ifndef __EMSCRIPTEN__
qDebug() << "RemoteTCPInput::stop3";
m_thread.wait();
#endif
m_running = false;
qDebug() << "RemoteTCPInput::stopped";
}
QByteArray RemoteTCPInput::serialize() const
@@ -120,6 +152,8 @@ bool RemoteTCPInput::deserialize(const QByteArray& data)
success = false;
}
qDebug() << "************** RemoteTCPInput::deserialize" << m_settings.m_dataAddress;
MsgConfigureRemoteTCPInput* message = MsgConfigureRemoteTCPInput::create(m_settings, QList<QString>(), true);
m_inputMessageQueue.push(message);
@@ -196,6 +230,7 @@ bool RemoteTCPInput::handleMessage(const Message& message)
{
qDebug() << "RemoteTCPInput::handleMessage:" << message.getIdentifier();
MsgConfigureRemoteTCPInput& conf = (MsgConfigureRemoteTCPInput&) message;
qDebug() << "*********** RemoteTCPInput::handleMessage MsgConfigureRemoteTCPInput" << m_settings.m_dataAddress;
applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce());
return true;
}
@@ -210,6 +245,42 @@ bool RemoteTCPInput::handleMessage(const Message& message)
}
return true;
}
else if (MsgSaveReplay::match(message))
{
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
return true;
}
else if (MsgSendMessage::match(message))
{
MsgSendMessage& msg = (MsgSendMessage&) message;
m_remoteInputTCPPHandler->getInputMessageQueue()->push(MsgSendMessage::create(msg.getCallsign(), msg.getText(), msg.getBroadcast()));
return true;
}
else if (MsgReportPosition::match(message))
{
MsgReportPosition& report = (MsgReportPosition&) message;
m_latitude = report.getLatitude();
m_longitude = report.getLongitude();
m_altitude = report.getAltitude();
emit positionChanged(m_latitude, m_longitude, m_altitude);
return true;
}
else if (MsgReportDirection::match(message))
{
MsgReportDirection& report = (MsgReportDirection&) message;
m_isotropic = report.getIsotropic();
m_azimuth = report.getAzimuth();
m_elevation = report.getElevation();
emit directionChanged(m_isotropic, m_azimuth, m_elevation);
return true;
}
else
{
return false;
@@ -242,6 +313,12 @@ void RemoteTCPInput::applySettings(const RemoteTCPInputSettings& settings, const
forwardChange = true;
}
if ((settingsKeys.contains("channelSampleRate") || force)
&& (settings.m_devSampleRate != m_settings.m_devSampleRate))
{
m_replayBuffer.clear();
}
mutexLocker.unlock();
if (settings.m_useReverseAPI)
@@ -265,6 +342,18 @@ void RemoteTCPInput::applySettings(const RemoteTCPInputSettings& settings, const
m_settings.applySettings(settingsKeys, settings);
}
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
m_replayBuffer.setSize(m_settings.m_replayLength, m_settings.m_devSampleRate);
}
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * m_settings.m_devSampleRate)) * 2);
}
if (settingsKeys.contains("replayLoop") || force) {
m_replayBuffer.setLoop(m_settings.m_replayLoop);
}
m_remoteInputTCPPHandler->getInputMessageQueue()->push(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler::create(m_settings, settingsKeys, force));
}
@@ -459,6 +548,9 @@ int RemoteTCPInput::webapiReportGet(
void RemoteTCPInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
{
response.getRemoteTcpInputReport()->setSampleRate(m_settings.m_channelSampleRate);
response.getRemoteTcpInputReport()->setLatitude(m_latitude);
response.getRemoteTcpInputReport()->setLongitude(m_longitude);
response.getRemoteTcpInputReport()->setAltitude(m_altitude);
}
void RemoteTCPInput::webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const RemoteTCPInputSettings& settings, bool force)
@@ -31,13 +31,14 @@
#include <QNetworkRequest>
#include "dsp/devicesamplesource.h"
#include "dsp/replaybuffer.h"
#include "remotetcpinputsettings.h"
#include "remotetcpinputtcphandler.h"
class QNetworkAccessManager;
class QNetworkReply;
class DeviceAPI;
class RemoteTCPInputTCPHandler;
class RemoteTCPInput : public DeviceSampleSource {
Q_OBJECT
@@ -124,6 +125,100 @@ public:
{ }
};
class MsgSaveReplay : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getFilename() const { return m_filename; }
static MsgSaveReplay* create(const QString& filename) {
return new MsgSaveReplay(filename);
}
protected:
QString m_filename;
MsgSaveReplay(const QString& filename) :
Message(),
m_filename(filename)
{ }
};
class MsgSendMessage : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getCallsign() const { return m_callsign; }
const QString& getText() const { return m_text; }
bool getBroadcast() const { return m_broadcast; }
static MsgSendMessage* create(const QString& callsign, const QString& text, bool broadcast) {
return new MsgSendMessage(callsign, text, broadcast);
}
protected:
QString m_callsign;
QString m_text;
bool m_broadcast;
MsgSendMessage(const QString& callsign, const QString& text, bool broadcast) :
Message(),
m_callsign(callsign),
m_text(text),
m_broadcast(broadcast)
{ }
};
class MsgReportPosition : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getLatitude() const { return m_latitude; }
float getLongitude() const { return m_longitude; }
float getAltitude() const { return m_altitude; }
static MsgReportPosition* create(float latitude, float longitude, float altitude) {
return new MsgReportPosition(latitude, longitude, altitude);
}
private:
float m_latitude;
float m_longitude;
float m_altitude;
MsgReportPosition(float latitude, float longitude, float altitude) :
Message(),
m_latitude(latitude),
m_longitude(longitude),
m_altitude(altitude)
{ }
};
class MsgReportDirection : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getIsotropic() const { return m_isotropic; }
float getAzimuth() const { return m_azimuth; }
float getElevation() const { return m_elevation; }
static MsgReportDirection* create(bool isotropic, float azimuth, float elevation) {
return new MsgReportDirection(isotropic, azimuth, elevation);
}
private:
bool m_isotropic;
float m_azimuth;
float m_elevation;
MsgReportDirection(bool isotropic, float azimuth, float elevation) :
Message(),
m_isotropic(isotropic),
m_azimuth(azimuth),
m_elevation(elevation)
{ }
};
RemoteTCPInput(DeviceAPI *deviceAPI);
virtual ~RemoteTCPInput();
virtual void destroy();
@@ -177,6 +272,15 @@ public:
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response);
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_remoteInputTCPPHandler) {
m_remoteInputTCPPHandler->getMagSqLevels(avg, peak, nbSamples);
} else {
avg = 0.0; peak = 0.0; nbSamples = 1;
}
}
private:
DeviceAPI *m_deviceAPI;
QRecursiveMutex m_mutex;
@@ -185,7 +289,15 @@ private:
QString m_deviceDescription;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
ReplayBuffer<FixReal> m_replayBuffer;
QThread m_thread;
bool m_running;
float m_latitude; // Position of remote device (antenna)
float m_longitude;
float m_altitude;
bool m_isotropic; // Direction of remote anntenna
float m_azimuth;
float m_elevation;
void applySettings(const RemoteTCPInputSettings& settings, const QList<QString>& settingsKeys, bool force = false);
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
@@ -20,6 +20,7 @@
#include <QMessageBox>
#include <QDateTime>
#include <QString>
#include <QFileDialog>
#include "ui_remotetcpinputgui.h"
#include "gui/colormapper.h"
@@ -30,17 +31,19 @@
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
#include "util/db.h"
#include "remotetcpinputgui.h"
#include "remotetcpinputtcphandler.h"
#include "maincore.h"
RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
DeviceGUI(parent),
ui(new Ui::RemoteTCPInputGui),
m_settings(),
m_sampleSource(0),
m_lastEngineState(DeviceAPI::StNotStarted),
m_sampleSource(nullptr),
m_sampleRate(0),
m_centerFrequency(0),
m_tickCount(0),
m_doApplySettings(true),
m_forceSettings(true),
m_deviceGains(nullptr),
@@ -71,12 +74,15 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
displaySettings();
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(deviceUISet->m_deviceAPI, &DeviceAPI::stateChanged, this, &RemoteTCPInputGui::updateStatus);
updateStatus();
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
m_sampleSource = (RemoteTCPInput*) m_deviceUISet->m_deviceAPI->getSampleSource();
@@ -89,11 +95,12 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
makeUIConnections();
DialPopup::addPopupsToChildDials(this);
m_resizer.enableChildMouseTracking();
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
}
RemoteTCPInputGui::~RemoteTCPInputGui()
{
m_statusTimer.stop();
m_updateTimer.stop();
delete ui;
}
@@ -110,6 +117,7 @@ void RemoteTCPInputGui::destroy()
void RemoteTCPInputGui::resetToDefaults()
{
qDebug() << "*************** RemoteTCPInputGui::resetToDefaults";
m_settings.resetToDefaults();
displaySettings();
m_forceSettings = true;
@@ -150,6 +158,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
} else {
m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
}
qDebug() << "********* RemoteTCPInputGui::handleMessage MsgConfigureRemoteTCPInput" << m_settings.m_dataAddress;
blockApplySettings(true);
displaySettings();
@@ -224,24 +233,27 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
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";
bool spyServer = report.getProtocol() == "Spy Server";
if (spyServer) {
m_sdra = report.getProtocol() == "SDRA";
m_spyServer = report.getProtocol() == "Spy Server";
m_remoteControl = report.getRemoteControl();
m_iqOnly = report.getIQOnly();
if (m_spyServer) {
m_spyServerGains.m_gains[0].m_max = report.getMaxGain();
}
if ((sdra || spyServer) && (ui->sampleBits->count() < 4))
if ((m_sdra || m_spyServer) && (ui->sampleBits->count() < 4))
{
ui->sampleBits->addItem("16");
ui->sampleBits->addItem("24");
ui->sampleBits->addItem("32");
}
else if (!(sdra || spyServer) && (ui->sampleBits->count() != 1))
else if (!(m_sdra || m_spyServer) && (ui->sampleBits->count() != 1))
{
while (ui->sampleBits->count() > 1) {
ui->sampleBits->removeItem(ui->sampleBits->count() - 1);
}
}
if ((sdra || spyServer) && (ui->decim->count() != 7))
if ((m_sdra || m_spyServer) && (ui->decim->count() != 7))
{
ui->decim->addItem("2");
ui->decim->addItem("4");
@@ -250,26 +262,19 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
ui->decim->addItem("32");
ui->decim->addItem("64");
}
else if (!(sdra || spyServer) && (ui->decim->count() != 1))
else if (!(m_sdra || m_spyServer) && (ui->decim->count() != 1))
{
while (ui->decim->count() > 1) {
ui->decim->removeItem(ui->decim->count() - 1);
}
}
if (!sdra)
if (!m_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) {
if (m_sdra) {
ui->centerFrequency->setValueRange(9, 0, 999999999); // Should add transverter control to protocol in the future
} else {
ui->centerFrequency->setValueRange(7, 0, 9999999);
@@ -291,19 +296,7 @@ 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);
displayEnabled();
displayGains();
return true;
}
@@ -314,13 +307,33 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
if (report.getConnected())
{
m_connectionError = false;
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
//ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
}
else
{
m_connectionError = true;
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
//ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
}
updateStatus();
return true;
}
else if (RemoteTCPInput::MsgSendMessage::match(message))
{
const RemoteTCPInput::MsgSendMessage& msg = (const RemoteTCPInput::MsgSendMessage&) message;
ui->messages->addItem(QString("%1> %2").arg(msg.getCallsign()).arg(msg.getText()));
ui->messages->scrollToBottom();
return true;
}
else if (RemoteTCPInput::MsgReportPosition::match(message))
{
// Could display in future
return true;
}
else if (RemoteTCPInput::MsgReportDirection::match(message))
{
// Could display in future
return true;
}
else
@@ -329,6 +342,87 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
}
}
void RemoteTCPInputGui::displayEnabled()
{
int state = m_deviceUISet->m_deviceAPI->state();
bool remoteControl;
bool enableMessages;
bool enableSquelchEnable;
bool enableSquelch;
bool sdra;
if (state == DeviceAPI::StRunning)
{
sdra = m_sdra;
remoteControl = m_remoteControl;
enableMessages = !m_iqOnly;
enableSquelchEnable = !m_iqOnly;
enableSquelch = !m_iqOnly && m_settings.m_squelchEnabled;
}
else
{
sdra = m_settings.m_protocol == "SDRangel";
remoteControl = m_settings.m_overrideRemoteSettings;
enableMessages = false;
enableSquelchEnable = m_settings.m_overrideRemoteSettings;
enableSquelch = m_settings.m_overrideRemoteSettings && m_settings.m_squelchEnabled;
}
ui->deltaFrequencyLabel->setEnabled(sdra && remoteControl);
ui->deltaFrequency->setEnabled(sdra && remoteControl);
ui->deltaUnits->setEnabled(sdra && remoteControl);
ui->channelGainLabel->setEnabled(sdra && remoteControl);
ui->channelGain->setEnabled(sdra && remoteControl);
ui->channelGainText->setEnabled(sdra && remoteControl);
ui->decimation->setEnabled(sdra && remoteControl);
ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->devSampleRateLabel->setEnabled(!m_spyServer && remoteControl);
ui->devSampleRate->setEnabled(!m_spyServer && remoteControl);
ui->devSampleRateUnits->setEnabled(!m_spyServer && remoteControl);
ui->agc->setEnabled(!m_spyServer && remoteControl);
ui->rfBWLabel->setEnabled(!m_spyServer && remoteControl);
ui->rfBW->setEnabled(!m_spyServer && remoteControl);
ui->rfBWUnits->setEnabled(!m_spyServer && remoteControl);
ui->dcOffset->setEnabled(!m_spyServer && remoteControl);
ui->iqImbalance->setEnabled(!m_spyServer && remoteControl);
ui->ppm->setEnabled(!m_spyServer && remoteControl);
ui->ppmLabel->setEnabled(!m_spyServer && remoteControl);
ui->ppmText->setEnabled(!m_spyServer && remoteControl);
ui->centerFrequency->setEnabled(remoteControl);
ui->biasTee->setEnabled(remoteControl);
ui->directSampling->setEnabled(remoteControl);
ui->decimLabel->setEnabled(remoteControl);
ui->decim->setEnabled(remoteControl);
ui->gain1Label->setEnabled(remoteControl);
ui->gain1->setEnabled(remoteControl);
ui->gain1Text->setEnabled(remoteControl);
ui->gain2Label->setEnabled(remoteControl);
ui->gain2->setEnabled(remoteControl);
ui->gain2Text->setEnabled(remoteControl);
ui->gain3Label->setEnabled(remoteControl);
ui->gain3->setEnabled(remoteControl);
ui->gain3Text->setEnabled(remoteControl);
ui->sampleBitsLabel->setEnabled(remoteControl);
ui->sampleBits->setEnabled(remoteControl);
ui->sampleBitsUnits->setEnabled(remoteControl);
ui->squelchEnabled->setEnabled(enableSquelchEnable);
ui->squelch->setEnabled(enableSquelch);
ui->squelchText->setEnabled(enableSquelch);
ui->squelchUnits->setEnabled(enableSquelch);
ui->squelchGate->setEnabled(enableSquelch);
ui->sendMessage->setEnabled(enableMessages);
ui->txAddress->setEnabled(enableMessages);
ui->txMessage->setEnabled(enableMessages);
ui->messages->setEnabled(enableMessages);
}
void RemoteTCPInputGui::handleInputMessages()
{
Message* message;
@@ -386,12 +480,14 @@ void RemoteTCPInputGui::displaySettings()
ui->channelSampleRate->setValue(m_settings.m_channelSampleRate);
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->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->squelchEnabled->setChecked(m_settings.m_squelchEnabled);
ui->squelch->setValue(m_settings.m_squelch);
ui->squelchText->setText(QString::number(m_settings.m_squelch));
ui->squelchGate->setValue(m_settings.m_squelchGate);
ui->dataPort->setValue(m_settings.m_dataPort);
ui->dataAddress->blockSignals(true);
ui->dataAddress->clear();
for (const auto& address : m_settings.m_addressList) {
@@ -412,6 +508,11 @@ void RemoteTCPInputGui::displaySettings()
}
displayGains();
displayReplayLength();
displayReplayOffset();
displayReplayStep();
ui->replayLoop->setChecked(m_settings.m_replayLoop);
displayEnabled();
blockApplySettings(false);
}
@@ -517,6 +618,7 @@ const QHash<RemoteTCPProtocol::Device, const RemoteTCPInputGui::DeviceGains *> R
{
{RemoteTCPProtocol::RTLSDR_E4000, &m_rtlSDRe4kGains},
{RemoteTCPProtocol::RTLSDR_R820T, &m_rtlSDRR820Gains},
{RemoteTCPProtocol::RTLSDR_R828D, &m_rtlSDRR820Gains},
{RemoteTCPProtocol::AIRSPY, &m_airspyGains},
{RemoteTCPProtocol::AIRSPY_HF, &m_airspyHFGains},
{RemoteTCPProtocol::BLADE_RF1, &m_baldeRF1Gains},
@@ -609,7 +711,12 @@ void RemoteTCPInputGui::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
m_connectionError = false;
if (m_connectionError)
{
// Clear previous error
m_connectionError = false;
updateStatus();
}
RemoteTCPInput::MsgStartStop *message = RemoteTCPInput::MsgStartStop::create(checked);
m_sampleSource->getInputMessageQueue()->push(message);
}
@@ -789,6 +896,29 @@ void RemoteTCPInputGui::on_sampleBits_currentIndexChanged(int index)
sendSettings();
}
void RemoteTCPInputGui::on_squelchEnabled_toggled(bool checked)
{
m_settings.m_squelchEnabled = checked;
m_settingsKeys.append("squelchEnabled");
displayEnabled();
sendSettings();
}
void RemoteTCPInputGui::on_squelch_valueChanged(int value)
{
m_settings.m_squelch = value;
ui->squelchText->setText(QString::number(m_settings.m_squelch));
m_settingsKeys.append("squelch");
sendSettings();
}
void RemoteTCPInputGui::on_squelchGate_valueChanged(double value)
{
m_settings.m_squelchGate = value;
m_settingsKeys.append("squelchGate");
sendSettings();
}
void RemoteTCPInputGui::on_dataAddress_editingFinished()
{
m_settings.m_dataAddress = ui->dataAddress->currentText();
@@ -810,17 +940,9 @@ void RemoteTCPInputGui::on_dataAddress_currentIndexChanged(int index)
sendSettings();
}
void RemoteTCPInputGui::on_dataPort_editingFinished()
void RemoteTCPInputGui::on_dataPort_valueChanged(int value)
{
bool ok;
quint16 udpPort = ui->dataPort->text().toInt(&ok);
if ((!ok) || (udpPort < 1024)) {
udpPort = 9998;
}
m_settings.m_dataPort = udpPort;
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
m_settings.m_dataPort = value;
m_settingsKeys.append("dataPort");
sendSettings();
@@ -831,6 +953,7 @@ void RemoteTCPInputGui::on_overrideRemoteSettings_toggled(bool checked)
m_settings.m_overrideRemoteSettings = checked;
m_settingsKeys.append("overrideRemoteSettings");
sendSettings();
displayEnabled();
}
void RemoteTCPInputGui::on_preFill_valueChanged(int value)
@@ -867,10 +990,14 @@ void RemoteTCPInputGui::updateHardware()
void RemoteTCPInputGui::updateStatus()
{
int state = m_deviceUISet->m_deviceAPI->state();
if (!m_connectionError && (m_lastEngineState != state))
if (m_connectionError)
{
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
}
else
{
int state = m_deviceUISet->m_deviceAPI->state();
switch(state)
{
case DeviceAPI::StNotStarted:
@@ -889,9 +1016,28 @@ void RemoteTCPInputGui::updateStatus()
default:
break;
}
m_lastEngineState = state;
}
displayEnabled();
}
void RemoteTCPInputGui::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_sampleSource->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(tr("%1").arg(powDbAvg, 0, 'f', 1));
}
m_tickCount++;
}
void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
@@ -899,6 +1045,9 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
if (m_contextMenuType == ContextMenuDeviceSettings)
{
BasicDeviceSettingsDialog dialog(this);
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2 * sizeof(FixReal));
dialog.setReplayLength(m_settings.m_replayLength);
dialog.setReplayStep(m_settings.m_replayStep);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
@@ -908,6 +1057,11 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_replayLength = dialog.getReplayLength();
m_settings.m_replayStep = dialog.getReplayStep();
displayReplayLength();
displayReplayOffset();
displayReplayStep();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
@@ -919,6 +1073,110 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
resetContextMenuType();
}
void RemoteTCPInputGui::displayReplayLength()
{
bool replayEnabled = m_settings.m_replayLength > 0.0f;
if (!replayEnabled) {
ui->replayOffset->setMaximum(0);
} else {
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
}
ui->replayLabel->setEnabled(replayEnabled);
ui->replayOffset->setEnabled(replayEnabled);
ui->replayOffsetText->setEnabled(replayEnabled);
ui->replaySave->setEnabled(replayEnabled);
}
void RemoteTCPInputGui::displayReplayOffset()
{
bool replayEnabled = m_settings.m_replayLength > 0.0f;
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
}
void RemoteTCPInputGui::displayReplayStep()
{
QString step;
float intpart;
float frac = modf(m_settings.m_replayStep, &intpart);
if (frac == 0.0f) {
step = QString::number((int)intpart);
} else {
step = QString::number(m_settings.m_replayStep, 'f', 1);
}
ui->replayPlus->setText(QString("+%1s").arg(step));
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
ui->replayMinus->setText(QString("-%1s").arg(step));
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
}
void RemoteTCPInputGui::on_replayOffset_valueChanged(int value)
{
m_settings.m_replayOffset = value / 10.0f;
displayReplayOffset();
m_settingsKeys.append("replayOffset");
sendSettings();
}
void RemoteTCPInputGui::on_replayNow_clicked()
{
ui->replayOffset->setValue(0);
}
void RemoteTCPInputGui::on_replayPlus_clicked()
{
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
}
void RemoteTCPInputGui::on_replayMinus_clicked()
{
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
}
void RemoteTCPInputGui::on_replaySave_clicked()
{
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
RemoteTCPInput::MsgSaveReplay *message = RemoteTCPInput ::MsgSaveReplay::create(fileNames[0]);
m_sampleSource->getInputMessageQueue()->push(message);
}
}
}
void RemoteTCPInputGui::on_replayLoop_toggled(bool checked)
{
m_settings.m_replayLoop = checked;
m_settingsKeys.append("replayLoop");
sendSettings();
}
void RemoteTCPInputGui::on_sendMessage_clicked()
{
QString message = ui->txMessage->text().trimmed();
if (!message.isEmpty())
{
ui->messages->addItem(QString("< %1").arg(message));
ui->messages->scrollToBottom();
bool broadcast = ui->txAddress->currentText() == "All";
QString callsign = MainCore::instance()->getSettings().getStationName();
m_sampleSource->getInputMessageQueue()->push(RemoteTCPInput::MsgSendMessage::create(callsign, message, broadcast));
}
}
void RemoteTCPInputGui::on_txMessage_returnPressed()
{
on_sendMessage_clicked();
ui->txMessage->selectAll();
}
void RemoteTCPInputGui::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_startStop_toggled);
@@ -940,10 +1198,21 @@ void RemoteTCPInputGui::makeUIConnections()
QObject::connect(ui->channelSampleRate, &ValueDial::changed, this, &RemoteTCPInputGui::on_channelSampleRate_changed);
QObject::connect(ui->decimation, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_decimation_toggled);
QObject::connect(ui->sampleBits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_sampleBits_currentIndexChanged);
QObject::connect(ui->squelchEnabled, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_squelchEnabled_toggled);
QObject::connect(ui->squelch, &QDial::valueChanged, this, &RemoteTCPInputGui::on_squelch_valueChanged);
QObject::connect(ui->squelchGate, &PeriodDial::valueChanged, this, &RemoteTCPInputGui::on_squelchGate_valueChanged);
QObject::connect(ui->dataAddress->lineEdit(), &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataAddress_editingFinished);
QObject::connect(ui->dataAddress, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_dataAddress_currentIndexChanged);
QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataPort_editingFinished);
QObject::connect(ui->dataPort, QOverload<int>::of(&QSpinBox::valueChanged), this, &RemoteTCPInputGui::on_dataPort_valueChanged);
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<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_protocol_currentIndexChanged);
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &RemoteTCPInputGui::on_replayOffset_valueChanged);
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayNow_clicked);
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayPlus_clicked);
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayMinus_clicked);
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replaySave_clicked);
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_replayLoop_toggled);
QObject::connect(ui->sendMessage, &QToolButton::clicked, this, &RemoteTCPInputGui::on_sendMessage_clicked);
QObject::connect(ui->txMessage, &QLineEdit::returnPressed, this, &RemoteTCPInputGui::on_txMessage_returnPressed);
}
@@ -108,12 +108,11 @@ private:
QList<QString> m_settingsKeys;
RemoteTCPInput* m_sampleSource;
QTimer m_updateTimer;
QTimer m_statusTimer;
int m_lastEngineState;
MessageQueue m_inputMessageQueue;
int m_sampleRate;
quint64 m_centerFrequency;
uint32_t m_tickCount;
bool m_doApplySettings;
bool m_forceSettings;
@@ -125,6 +124,11 @@ private:
DeviceGains::GainRange m_spyServerGainRange;
DeviceGains m_spyServerGains;
bool m_sdra;
bool m_spyServer;
bool m_remoteControl;
bool m_iqOnly;
static const DeviceGains::GainRange m_rtlSDR34kGainRange;
static const DeviceGains m_rtlSDRe4kGains;
static const DeviceGains::GainRange m_rtlSDRR820GainRange;
@@ -175,9 +179,13 @@ private:
void blockApplySettings(bool block);
void displaySettings();
QString gainText(int stage);
void displayEnabled();
void displayGains();
void displayRemoteSettings();
void displayRemoteShift();
void displayReplayLength();
void displayReplayOffset();
void displayReplayStep();
void sendSettings();
void updateSampleRateAndFrequency();
void applyDecimation();
@@ -206,15 +214,27 @@ private slots:
void on_channelSampleRate_changed(quint64 value);
void on_decimation_toggled(bool checked);
void on_sampleBits_currentIndexChanged(int index);
void on_squelchEnabled_toggled(bool checked);
void on_squelch_valueChanged(int value);
void on_squelchGate_valueChanged(double value);
void on_dataAddress_editingFinished();
void on_dataAddress_currentIndexChanged(int index);
void on_dataPort_editingFinished();
void on_dataPort_valueChanged(int value);
void on_overrideRemoteSettings_toggled(bool checked);
void on_preFill_valueChanged(int value);
void on_protocol_currentIndexChanged(int index);
void on_replayOffset_valueChanged(int value);
void on_replayNow_clicked();
void on_replayPlus_clicked();
void on_replayMinus_clicked();
void on_replaySave_clicked();
void on_replayLoop_toggled(bool checked);
void on_sendMessage_clicked();
void on_txMessage_returnPressed();
void updateHardware();
void updateStatus();
void openDeviceSettingsDialog(const QPoint& p);
void tick();
};
#endif // INCLUDE_REMOTETCPINPUTGUI_H
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>360</width>
<height>360</height>
<height>586</height>
</rect>
</property>
<property name="sizePolicy">
@@ -19,13 +19,13 @@
<property name="minimumSize">
<size>
<width>360</width>
<height>360</height>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>491</width>
<height>360</height>
<width>533</width>
<height>610</height>
</size>
</property>
<property name="font">
@@ -863,6 +863,184 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="signalLevelLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="squelchLayout">
<item>
<widget class="ButtonSwitch" name="squelchEnabled">
<property name="toolTip">
<string>Check to enable IQ squelch</string>
</property>
<property name="text">
<string>SQ</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_12">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="squelch">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>IQ squelch power level in dB</string>
</property>
<property name="minimum">
<number>-150</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="squelchText">
<property name="minimumSize">
<size>
<width>32</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>-150</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="squelchUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_13">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="PeriodDial" name="squelchGate" native="true">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>IQ squelch gate time</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_14">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="channelPower">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
@@ -914,33 +1092,18 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
</widget>
</item>
<item>
<widget class="QLineEdit" name="dataPort">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<widget class="QSpinBox" name="dataPort">
<property name="toolTip">
<string>Remote data port (rtl_tcp defaults to 1234)</string>
</property>
<property name="inputMask">
<string>00000</string>
<property name="minimum">
<number>1024</number>
</property>
<property name="text">
<string>0</string>
<property name="maximum">
<number>65535</number>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<property name="value">
<number>1234</number>
</property>
</widget>
</item>
@@ -961,7 +1124,7 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
<widget class="QComboBox" name="protocol">
<property name="minimumSize">
<size>
<width>75</width>
<width>92</width>
<height>0</height>
</size>
</property>
@@ -1162,6 +1325,182 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="txMessagesLayout">
<item>
<widget class="QToolButton" name="sendMessage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Click to send message</string>
</property>
<property name="text">
<string>TX</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="txAddress">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Who to send message to</string>
</property>
<item>
<property name="text">
<string>Host</string>
</property>
</item>
<item>
<property name="text">
<string>All</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txMessage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Message to transmit</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="messagesLayout">
<item>
<widget class="QListWidget" name="messages">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Messages</string>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="replayLayout">
<item>
<widget class="QLabel" name="replayLabel">
<property name="minimumSize">
<size>
<width>65</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Time Delay</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="replayOffset">
<property name="toolTip">
<string>Replay time delay in seconds</string>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="replayOffsetText">
<property name="toolTip">
<string>Replay time delay in seconds</string>
</property>
<property name="text">
<string>0.0s</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="replayNow">
<property name="toolTip">
<string>Set time delay to 0 seconds</string>
</property>
<property name="text">
<string>Now</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="replayPlus">
<property name="toolTip">
<string>Add displayed number of seconds to time delay</string>
</property>
<property name="text">
<string>+5s</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="replayMinus">
<property name="toolTip">
<string>Remove displayed number of seconds from time delay</string>
</property>
<property name="text">
<string>-5s</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="replayLoop">
<property name="toolTip">
<string>Repeatedly replay data in replay buffer</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="replaySave">
<property name="toolTip">
<string>Save replay buffer to a file</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/save.png</normaloff>:/save.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
@@ -1214,6 +1553,18 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PeriodDial</class>
<extends>QWidget</extends>
<header>gui/perioddial.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>startStop</tabstop>
@@ -1232,7 +1583,8 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
<tabstop>channelGain</tabstop>
<tabstop>decimation</tabstop>
<tabstop>sampleBits</tabstop>
<tabstop>dataPort</tabstop>
<tabstop>dataAddress</tabstop>
<tabstop>protocol</tabstop>
<tabstop>overrideRemoteSettings</tabstop>
<tabstop>preFill</tabstop>
</tabstops>
@@ -53,6 +53,13 @@ void RemoteTCPInputSettings::resetToDefaults()
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_replayOffset = 0.0f;
m_replayLength = 20.0f;
m_replayStep = 5.0f;
m_replayLoop = false;
m_squelchEnabled = false;
m_squelch = -100.0f;
m_squelchGate = 0.001f;
}
QByteArray RemoteTCPInputSettings::serialize() const
@@ -83,11 +90,19 @@ QByteArray RemoteTCPInputSettings::serialize() const
s.writeU32(23, m_reverseAPIDeviceIndex);
s.writeList(24, m_addressList);
s.writeString(25, m_protocol);
s.writeFloat(26, m_replayOffset);
s.writeFloat(27, m_replayLength);
s.writeFloat(28, m_replayStep);
s.writeBool(29, m_replayLoop);
for (int i = 0; i < m_maxGains; i++) {
s.writeS32(30+i, m_gain[i]);
}
s.writeBool(40, m_squelchEnabled);
s.writeFloat(41, m_squelch);
s.writeFloat(42, m_squelchGate);
return s.final();
}
@@ -140,10 +155,19 @@ bool RemoteTCPInputSettings::deserialize(const QByteArray& data)
d.readList(24, &m_addressList);
d.readString(25, &m_protocol, "SDRangel");
d.readFloat(26, &m_replayOffset, 0.0f);
d.readFloat(27, &m_replayLength, 20.0f);
d.readFloat(28, &m_replayStep, 5.0f);
d.readBool(29, &m_replayLoop, false);
for (int i = 0; i < m_maxGains; i++) {
d.readS32(30+i, &m_gain[i], 0);
}
d.readBool(40, &m_squelchEnabled, false);
d.readFloat(41, &m_squelch, -100.0f);
d.readFloat(42, &m_squelchGate, 0.001f);
return true;
}
else
@@ -212,7 +236,7 @@ void RemoteTCPInputSettings::applySettings(const QStringList& settingsKeys, cons
if (settingsKeys.contains("preFill")) {
m_preFill = settings.m_preFill;
}
if (settingsKeys.contains("_useReverseAPI")) {
if (settingsKeys.contains("useReverseAPI")) {
m_useReverseAPI = settings.m_useReverseAPI;
}
if (settingsKeys.contains("reverseAPIAddress")) {
@@ -230,6 +254,27 @@ void RemoteTCPInputSettings::applySettings(const QStringList& settingsKeys, cons
if (settingsKeys.contains("protocol")) {
m_protocol = settings.m_protocol;
}
if (settingsKeys.contains("replayOffset")) {
m_replayOffset = settings.m_replayOffset;
}
if (settingsKeys.contains("replayLength")) {
m_replayLength = settings.m_replayLength;
}
if (settingsKeys.contains("replayStep")) {
m_replayStep = settings.m_replayStep;
}
if (settingsKeys.contains("replayLoop")) {
m_replayLoop = settings.m_replayLoop;
}
if (settingsKeys.contains("squelchEnabled")) {
m_squelchEnabled = settings.m_squelchEnabled;
}
if (settingsKeys.contains("squelch")) {
m_squelch = settings.m_squelch;
}
if (settingsKeys.contains("squelchGate")) {
m_squelchGate = settings.m_squelchGate;
}
for (int i = 0; i < m_maxGains; i++)
{
@@ -318,6 +363,27 @@ QString RemoteTCPInputSettings::getDebugString(const QStringList& settingsKeys,
if (settingsKeys.contains("protocol") || force) {
ostr << " m_protocol: " << m_protocol.toStdString();
}
if (settingsKeys.contains("replayOffset") || force) {
ostr << " m_replayOffset: " << m_replayOffset;
}
if (settingsKeys.contains("replayLength") || force) {
ostr << " m_replayLength: " << m_replayLength;
}
if (settingsKeys.contains("replayStep") || force) {
ostr << " m_replayStep: " << m_replayStep;
}
if (settingsKeys.contains("replayLoop") || force) {
ostr << " m_replayLoop: " << m_replayLoop;
}
if (settingsKeys.contains("squelchEnabled") || force) {
ostr << " m_squelchEnabled: " << m_squelchEnabled;
}
if (settingsKeys.contains("squelch") || force) {
ostr << " m_squelch: " << m_squelch;
}
if (settingsKeys.contains("squelchGate") || force) {
ostr << " m_squelchGate: " << m_squelchGate;
}
for (int i = 0; i < m_maxGains; i++)
{
@@ -54,6 +54,13 @@ struct RemoteTCPInputSettings
uint16_t m_reverseAPIDeviceIndex;
QStringList m_addressList; // List of dataAddresses that have been used in the past
QString m_protocol; // "SDRangel" or "Spy Server"
float m_replayOffset; //!< Replay offset in seconds
float m_replayLength; //!< Replay buffer size in seconds
float m_replayStep; //!< Replay forward/back step size in seconds
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
bool m_squelchEnabled;
float m_squelch;
float m_squelchGate;
RemoteTCPInputSettings();
void resetToDefaults();
File diff suppressed because it is too large Load Diff
@@ -26,7 +26,12 @@
#include <QRecursiveMutex>
#include <QDateTime>
#include <FLAC/stream_decoder.h>
#include <zlib.h>
#include "util/messagequeue.h"
#include "util/movingaverage.h"
#include "dsp/replaybuffer.h"
#include "remotetcpinputsettings.h"
#include "../../channelrx/remotetcpsink/remotetcpprotocol.h"
#include "spyserver.h"
@@ -35,6 +40,31 @@ class SampleSinkFifo;
class MessageQueue;
class DeviceAPI;
class FIFO {
public:
FIFO(qsizetype elements = 10);
qsizetype write(quint8 *data, qsizetype elements);
qsizetype read(quint8 *data, qsizetype elements);
qsizetype readPtr(quint8 **data, qsizetype elements);
void read(qsizetype elements);
void resize(qsizetype elements); // Sets capacity
void clear();
qsizetype fill() const { return m_fill; } // Number of elements in use
bool empty() const { return m_fill == 0; }
bool full() const { return m_fill == m_data.size(); }
private:
qsizetype m_readPtr;
qsizetype m_writePtr;
qsizetype m_fill;
QByteArray m_data;
};
class RemoteTCPInputTCPHandler : public QObject
{
Q_OBJECT
@@ -71,22 +101,28 @@ public:
public:
RemoteTCPProtocol::Device getDevice() const { return m_device; }
QString getProtocol() const { return m_protocol; }
bool getIQOnly() const { return m_iqOnly; }
bool getRemoteControl() const { return m_remoteControl; }
int getMaxGain() const { return m_maxGain; }
static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain = 0)
static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol, bool iqOnly, bool remoteControl, int maxGain = 0)
{
return new MsgReportRemoteDevice(device, protocol, maxGain);
return new MsgReportRemoteDevice(device, protocol, iqOnly, remoteControl, maxGain);
}
protected:
RemoteTCPProtocol::Device m_device;
QString m_protocol;
bool m_iqOnly;
bool m_remoteControl;
int m_maxGain;
MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain) :
MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol, bool iqOnly, bool remoteControl, int maxGain) :
Message(),
m_device(device),
m_protocol(protocol),
m_iqOnly(iqOnly),
m_remoteControl(remoteControl),
m_maxGain(maxGain)
{ }
};
@@ -111,7 +147,7 @@ public:
{ }
};
RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI);
RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI, ReplayBuffer<FixReal> *replayBuffer);
~RemoteTCPInputTCPHandler();
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToInput(MessageQueue *queue) { m_messageQueueToInput = queue; }
@@ -120,6 +156,29 @@ public:
void start();
void stop();
int getBufferGauge() const { return 0; }
void processCommands();
FLAC__StreamDecoderReadStatus flacRead(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes);
FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
void flacError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status);
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
public slots:
void dataReadyRead();
@@ -129,11 +188,22 @@ public slots:
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
DeviceAPI *m_deviceAPI;
bool m_running;
QTcpSocket *m_dataSocket;
char *m_tcpBuf;
SampleSinkFifo *m_sampleFifo;
ReplayBuffer<FixReal> *m_replayBuffer;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToInput;
MessageQueue *m_messageQueueToGUI;
@@ -148,6 +218,8 @@ private:
SpyServerProtocol::Header m_spyServerHeader;
enum {HEADER, DATA} m_state; //!< FSM for reading Spy Server packets
RemoteTCPProtocol::Command m_command;
quint32 m_commandLength;
int32_t *m_converterBuffer;
uint32_t m_converterBufferNbSamples;
@@ -155,13 +227,38 @@ private:
QRecursiveMutex m_mutex;
RemoteTCPInputSettings m_settings;
void applyTCPLink(const QString& address, quint16 port);
bool m_remoteControl;
bool m_iqOnly;
QByteArray m_compressedData;
// FLAC decompression
qint64 m_compressedFrames;
qint64 m_uncompressedFrames;
FIFO m_uncompressedData;
FLAC__StreamDecoder *m_decoder;
int m_remainingSamples;
// Zlib decompression
z_stream m_zStream;
QByteArray m_zOutBuf;
static const int m_zBufSize = 32768+128; //
bool m_blacklisted;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 16> m_movingAverage;
bool handleMessage(const Message& message);
void convert(int nbSamples);
void connectToHost(const QString& address, quint16 port);
void disconnectFromHost();
//void disconnectFromHost();
void cleanup();
void clearBuffer();
void sendCommand(RemoteTCPProtocol::Command cmd, quint32 value);
void sendCommandFloat(RemoteTCPProtocol::Command cmd, float value);
void setSampleRate(int sampleRate);
void setCenterFrequency(quint64 frequency);
void setTunerAGC(bool agc);
@@ -180,6 +277,10 @@ private:
void setChannelFreqOffset(int offset);
void setChannelGain(int gain);
void setSampleBitDepth(int sampleBits);
void setSquelchEnabled(bool enabled);
void setSquelch(float squelch);
void setSquelchGate(float squelchGate);
void sendMessage(const QString& callsign, const QString& text, bool broadcast);
void applySettings(const RemoteTCPInputSettings& settings, const QList<QString>& settingsKeys, bool force = false);
void processMetaData();
void spyServerConnect();
@@ -190,6 +291,11 @@ private:
void processSpyServerDevice(const SpyServerProtocol::Device* ssDevice);
void processSpyServerState(const SpyServerProtocol::State* ssState, bool initial);
void processSpyServerData(int requiredBytes, bool clear);
void processDecompressedData(int requiredSamples);
void processUncompressedData(const char *inBuf, int nbSamples);
void processDecompressedZlibData(const char *inBuf, int nbSamples);
void calcPower(const Sample *iq, int nbSamples);
void sendSettings(const RemoteTCPInputSettings& settings, const QStringList& settingsKeys);
private slots:
void started();