diff --git a/app/main.cpp b/app/main.cpp
index f8211904a..cb6a2f310 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -36,6 +36,7 @@
#include "loggerwithfile.h"
#include "mainwindow.h"
+#include "remotetcpsinkstarter.h"
#include "dsp/dsptypes.h"
static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *logger)
@@ -74,7 +75,6 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
if (settings.contains(uiScaleFactor))
{
QString scaleFactor = settings.value(uiScaleFactor).toString();
- qDebug() << "Setting QT_SCALE_FACTOR to" << scaleFactor;
qputenv("QT_SCALE_FACTOR", scaleFactor.toLatin1());
}
@@ -178,7 +178,27 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
applicationPid);
#endif
+ if (parser.getListDevices())
+ {
+ // Disable log on console, so we can more easily see device list
+ logger->setConsoleMinMessageLevel(QtFatalMsg);
+ // Don't pass logger to MainWindow, otherwise it can reenable log output
+ logger = nullptr;
+ }
+
MainWindow w(logger, parser);
+
+ if (parser.getListDevices())
+ {
+ // List available physical devices and exit
+ RemoteTCPSinkStarter::listAvailableDevices();
+ exit (EXIT_SUCCESS);
+ }
+
+ if (parser.getRemoteTCPSink()) {
+ RemoteTCPSinkStarter::start(parser);
+ }
+
w.show();
return a.exec();
diff --git a/appsrv/main.cpp b/appsrv/main.cpp
index d94df1c3f..46ce93378 100644
--- a/appsrv/main.cpp
+++ b/appsrv/main.cpp
@@ -1,8 +1,6 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// //
-// Swagger server adapter interface //
-// //
// 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 //
// the Free Software Foundation as version 3 of the License, or //
@@ -26,6 +24,7 @@
#include "loggerwithfile.h"
#include "mainparser.h"
#include "mainserver.h"
+#include "remotetcpsinkstarter.h"
#include "dsp/dsptypes.h"
void handler(int sig) {
@@ -92,20 +91,39 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
QCoreApplication::applicationPid());
#endif
- MainServer m(logger, parser, &a);
+ if (parser.getListDevices())
+ {
+ // Disable log on console, so we can more easily see device list
+ logger->setConsoleMinMessageLevel(QtFatalMsg);
+ // Don't pass logger to MainWindow, otherwise it can reenable log output
+ logger = nullptr;
+ }
- // This will cause the application to exit when the main core is finished
- QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit()));
+ MainServer m(logger, parser, &a);
- return a.exec();
- }
+ // This will cause the application to exit when the main core is finished
+ QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit()));
- int main(int argc, char* argv[])
- {
- qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp);
- logger->installMsgHandler();
- int res = runQtApplication(argc, argv, logger);
- delete logger;
- qWarning("SDRangel quit.");
- return res;
- }
+ if (parser.getListDevices())
+ {
+ // List available physical devices and exit
+ RemoteTCPSinkStarter::listAvailableDevices();
+ exit (EXIT_SUCCESS);
+ }
+
+ if (parser.getRemoteTCPSink()) {
+ RemoteTCPSinkStarter::start(parser);
+ }
+
+ return a.exec();
+}
+
+int main(int argc, char* argv[])
+{
+ qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp);
+ logger->installMsgHandler();
+ int res = runQtApplication(argc, argv, logger);
+ delete logger;
+ qWarning("SDRangel quit.");
+ return res;
+}
diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt
index 5b640f7bb..63f0ed392 100644
--- a/sdrbase/CMakeLists.txt
+++ b/sdrbase/CMakeLists.txt
@@ -285,6 +285,7 @@ set(sdrbase_SOURCES
mainparser.cpp
maincore.cpp
+ remotetcpsinkstarter.cpp
resources/webapi.qrc
)
@@ -519,6 +520,7 @@ set(sdrbase_HEADERS
mainparser.h
maincore.h
+ remotetcpsinkstarter.h
)
include_directories(
diff --git a/sdrbase/mainparser.cpp b/sdrbase/mainparser.cpp
index 6ab22f2ce..dfb0116bd 100644
--- a/sdrbase/mainparser.cpp
+++ b/sdrbase/mainparser.cpp
@@ -36,7 +36,13 @@ MainParser::MainParser() :
"file",
""),
m_scratchOption("scratch", "Start from scratch (no current config)."),
- m_soapyOption("soapy", "Activate Soapy SDR support.")
+ m_soapyOption("soapy", "Activate Soapy SDR support."),
+ m_remoteTCPSinkOption("remote-tcp", "Start Remote TCP Sink"),
+ m_remoteTCPSinkAddressOption("remote-tcp-address", "Remote TCP Sink interface IP address (Default 127.0.0.1).", "address", "127.0.0.1"),
+ m_remoteTCPSinkPortOption("remote-tcp-port", "Remote TCP Sink port (Default 1234).", "port", "1234"),
+ m_remoteTCPSinkHWTypeOption("remote-tcp-hwtype", "Remote TCP Sink device hardware type (Optional. E.g. RTLSDR/SDRplayV3/AirspyHF).", "hwtype"),
+ m_remoteTCPSinkSerialOption("remote-tcp-serial", "Remote TCP Sink device serial (Optional).", "serial"),
+ m_listDevicesOption("list-devices", "List available physical devices.")
{
m_serverAddress = ""; // Bind to any address
@@ -44,6 +50,12 @@ MainParser::MainParser() :
m_scratch = false;
m_soapy = false;
m_fftwfWindowFileName = "";
+ m_remoteTCPSink = false;
+ m_remoteTCPSinkAddress = "127.0.0.1";
+ m_remoteTCPSinkPort = 1234;
+ m_remoteTCPSinkHWType = "";
+ m_remoteTCPSinkSerial = "";
+ m_listDevices = false;
m_parser.setApplicationDescription("Software Defined Radio application");
m_parser.addHelpOption();
@@ -54,6 +66,12 @@ MainParser::MainParser() :
m_parser.addOption(m_fftwfWisdomOption);
m_parser.addOption(m_scratchOption);
m_parser.addOption(m_soapyOption);
+ m_parser.addOption(m_remoteTCPSinkOption);
+ m_parser.addOption(m_remoteTCPSinkAddressOption);
+ m_parser.addOption(m_remoteTCPSinkPortOption);
+ m_parser.addOption(m_remoteTCPSinkHWTypeOption);
+ m_parser.addOption(m_remoteTCPSinkSerialOption);
+ m_parser.addOption(m_listDevicesOption);
}
MainParser::~MainParser()
@@ -65,19 +83,18 @@ void MainParser::parse(const QCoreApplication& app)
int pos;
bool ok;
+ QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
+ QRegularExpression ipRegex ("^" + ipRange
+ + "\\." + ipRange
+ + "\\." + ipRange
+ + "\\." + ipRange + "$");
+ QRegularExpressionValidator ipValidator(ipRegex);
// server address
QString serverAddress = m_parser.value(m_serverAddressOption);
if (!serverAddress.isEmpty())
{
- QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
- QRegularExpression ipRegex ("^" + ipRange
- + "\\." + ipRange
- + "\\." + ipRange
- + "\\." + ipRange + "$");
- QRegularExpressionValidator ipValidator(ipRegex);
-
if (ipValidator.validate(serverAddress, pos) == QValidator::Acceptable) {
m_serverAddress = serverAddress;
} else {
@@ -104,4 +121,37 @@ void MainParser::parse(const QCoreApplication& app)
// Soapy SDR support
m_soapy = m_parser.isSet(m_soapyOption);
+
+ // Remote TCP Sink options
+
+ m_remoteTCPSink = m_parser.isSet(m_remoteTCPSinkOption);
+
+ QString remoteTCPSinkAddress = m_parser.value(m_remoteTCPSinkAddressOption);
+ if (!remoteTCPSinkAddress.isEmpty())
+ {
+ if (ipValidator.validate(remoteTCPSinkAddress, pos) == QValidator::Acceptable) {
+ m_remoteTCPSinkAddress = remoteTCPSinkAddress;
+ } else {
+ qWarning() << "MainParser::parse: remote TCP Sink address invalid. Defaulting to " << m_remoteTCPSinkAddress;
+ }
+ }
+
+ QString remoteTCPSinkPortStr = m_parser.value(m_remoteTCPSinkPortOption);
+ int remoteTCPSinkPort = remoteTCPSinkPortStr.toInt(&ok);
+
+ if (ok && (remoteTCPSinkPort > 1023) && (remoteTCPSinkPort < 65536)) {
+ m_remoteTCPSinkPort = remoteTCPSinkPort;
+ } else {
+ qWarning() << "MainParser::parse: remote TCP Sink port invalid. Defaulting to " << m_serverPort;
+ }
+
+ m_remoteTCPSinkHWType = m_parser.value(m_remoteTCPSinkHWTypeOption);
+ m_remoteTCPSinkSerial = m_parser.value(m_remoteTCPSinkSerialOption);
+ m_listDevices = m_parser.isSet(m_listDevicesOption);
+
+ if (m_remoteTCPSink && m_remoteTCPSinkHWType.isEmpty() && m_remoteTCPSinkSerial.isEmpty())
+ {
+ qCritical() << "You must specify a device with either -remote-tcp-hwtype or -remote-tcp-serial";
+ exit (EXIT_FAILURE);
+ }
}
diff --git a/sdrbase/mainparser.h b/sdrbase/mainparser.h
index f472d2733..fb3705434 100644
--- a/sdrbase/mainparser.h
+++ b/sdrbase/mainparser.h
@@ -37,6 +37,12 @@ public:
bool getScratch() const { return m_scratch; }
bool getSoapy() const { return m_soapy; }
const QString& getFFTWFWisdomFileName() const { return m_fftwfWindowFileName; }
+ bool getRemoteTCPSink() const { return m_remoteTCPSink; }
+ const QString& getRemoteTCPSinkAddressOption() const { return m_remoteTCPSinkAddress; }
+ const int getRemoteTCPSinkPortOption() const { return m_remoteTCPSinkPort; }
+ const QString& getRemoteTCPSinkHWType() const { return m_remoteTCPSinkHWType; }
+ const QString& getRemoteTCPSinkSerial() const { return m_remoteTCPSinkSerial; }
+ const bool getListDevices() const { return m_listDevices; }
private:
QString m_serverAddress;
@@ -44,6 +50,12 @@ private:
QString m_fftwfWindowFileName;
bool m_scratch;
bool m_soapy;
+ bool m_remoteTCPSink;
+ QString m_remoteTCPSinkAddress;
+ int m_remoteTCPSinkPort;
+ QString m_remoteTCPSinkHWType;
+ QString m_remoteTCPSinkSerial;
+ bool m_listDevices;
QCommandLineParser m_parser;
QCommandLineOption m_serverAddressOption;
@@ -51,6 +63,12 @@ private:
QCommandLineOption m_fftwfWisdomOption;
QCommandLineOption m_scratchOption;
QCommandLineOption m_soapyOption;
+ QCommandLineOption m_remoteTCPSinkOption;
+ QCommandLineOption m_remoteTCPSinkAddressOption;
+ QCommandLineOption m_remoteTCPSinkPortOption;
+ QCommandLineOption m_remoteTCPSinkHWTypeOption;
+ QCommandLineOption m_remoteTCPSinkSerialOption;
+ QCommandLineOption m_listDevicesOption;
};
diff --git a/sdrbase/remotetcpsinkstarter.cpp b/sdrbase/remotetcpsinkstarter.cpp
new file mode 100644
index 000000000..0dd99e1b8
--- /dev/null
+++ b/sdrbase/remotetcpsinkstarter.cpp
@@ -0,0 +1,200 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2023 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 //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include
+
+#include "remotetcpsinkstarter.h"
+
+#include "maincore.h"
+#include "device/deviceset.h"
+#include "device/deviceapi.h"
+#include "device/deviceenumerator.h"
+#include "dsp/devicesamplesource.h"
+#include "channel/channelapi.h"
+#include "SWGChannelSettings.h"
+#include "SWGRemoteTCPSinkSettings.h"
+#include "SWGDeviceState.h"
+
+// Lists available physical devices to stdout
+void RemoteTCPSinkStarter::listAvailableDevices()
+{
+ int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
+
+ printf("Available devices:\n");
+ for (int i = 0; i < nbSamplingDevices; i++)
+ {
+ const PluginInterface::SamplingDevice *samplingDevice;
+
+ samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
+ if (samplingDevice->type == PluginInterface::SamplingDevice::PhysicalDevice)
+ {
+ printf(" HWType: %s", qPrintable(samplingDevice->hardwareId));
+ if (!samplingDevice->serial.isEmpty()) {
+ printf(" Serial: %s", qPrintable(samplingDevice->serial));
+ }
+ printf("\n");
+ }
+ }
+}
+
+// Instantiate specified sampling source device and create a RemoteTCPSink channel
+// on the specified address and port and start the device
+static void startRemoteTCPSink(const QString& address, int port, const QString& hwType, const QString& serial)
+{
+ MainCore *mainCore = MainCore::instance();
+
+ // Delete any existing device sets, in case requested device is already in use
+ int initialDeviceSets = mainCore->getDeviceSets().size();
+ for (int i = 0; i < initialDeviceSets; i++)
+ {
+ MainCore::MsgRemoveLastDeviceSet *msg = MainCore::MsgRemoveLastDeviceSet::create();
+ mainCore->getMainMessageQueue()->push(msg);
+ }
+
+ // Wait until they've been deleted
+ if (initialDeviceSets > 0)
+ {
+ do
+ {
+ QTime dieTime = QTime::currentTime().addMSecs(100);
+ while (QTime::currentTime() < dieTime) {
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
+ }
+ }
+ while (mainCore->getDeviceSets().size() > 0);
+ }
+
+ // Create DeviceSet
+ int deviceSetIndex = mainCore->getDeviceSets().size();
+ MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
+ mainCore->getMainMessageQueue()->push(msg);
+
+ // Switch to requested device type
+ int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
+ bool found = false;
+ for (int i = 0; i < nbSamplingDevices; i++)
+ {
+ const PluginInterface::SamplingDevice *samplingDevice;
+
+ samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
+
+ if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
+ continue;
+ }
+ if (!serial.isEmpty() && (serial != samplingDevice->serial)) {
+ continue;
+ }
+
+ int direction = 0;
+ MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
+ mainCore->getMainMessageQueue()->push(msg);
+ found = true;
+ break;
+ }
+ if (!found)
+ {
+ qCritical() << "startRemoteTCPSink: Failed to find device";
+ return;
+ }
+
+ // Add RemoteTCPSink channel
+ PluginAPI::ChannelRegistrations *channelRegistrations = mainCore->getPluginManager()->getRxChannelRegistrations();
+ int nbRegistrations = channelRegistrations->size();
+ int index = 0;
+ for (; index < nbRegistrations; index++)
+ {
+ if (channelRegistrations->at(index).m_channelId == "RemoteTCPSink") {
+ break;
+ }
+ }
+
+ if (index < nbRegistrations)
+ {
+ MainCore::MsgAddChannel *msg = MainCore::MsgAddChannel::create(deviceSetIndex, index, 0);
+ mainCore->getMainMessageQueue()->push(msg);
+ }
+ else
+ {
+ qCritical() << "startRemoteTCPSink: RemoteTCPSink is not available";
+ return;
+ }
+ int channelIndex = 0;
+
+ // Wait until device & channel are created - is there a better way?
+ DeviceSet *deviceSet = nullptr;
+ ChannelAPI *channelAPI = nullptr;
+ do
+ {
+ QTime dieTime = QTime::currentTime().addMSecs(100);
+ while (QTime::currentTime() < dieTime) {
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
+ }
+
+ if (mainCore->getDeviceSets().size() > deviceSetIndex)
+ {
+ deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
+ if (deviceSet) {
+ channelAPI = deviceSet->m_deviceAPI->getChanelSinkAPIAt(channelIndex);
+ }
+ }
+ }
+ while (channelAPI == nullptr);
+
+ // Set TCP settings
+ QStringList channelSettingsKeys = {"dataAddress", "dataPort"};
+ SWGSDRangel::SWGChannelSettings response;
+ response.init();
+ SWGSDRangel::SWGRemoteTCPSinkSettings *sinkSettings = response.getRemoteTcpSinkSettings();
+ sinkSettings->setDataAddress(new QString(address));
+ sinkSettings->setDataPort(port);
+ QString errorMessage;
+ channelAPI->webapiSettingsPutPatch(false, channelSettingsKeys, response, errorMessage);
+
+ // Wait some time for settings to be applied
+ QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
+
+ // Start the device (use WebAPI so GUI is updated)
+ DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
+ QStringList deviceActionsKeys;
+ SWGSDRangel::SWGDeviceState state;
+ state.init();
+ int res = source->webapiRun(true, state, errorMessage);
+ if (res != 200) {
+ qCritical() << "startRemoteTCPSink: Failed to start device: " << res;
+ } else {
+ qInfo().nospace().noquote() << "Remote TCP Sink started on " << address << ":" << port;
+ }
+}
+
+// Start Remote TCP Sink on specified device, with specified address and port
+void RemoteTCPSinkStarter::start(const MainParser& parser)
+{
+ QString remoteTCPSinkAddress = parser.getRemoteTCPSinkAddressOption();
+ int remoteTCPSinkPort = parser.getRemoteTCPSinkPortOption();
+ QString remoteTCPSinkHWType = parser.getRemoteTCPSinkHWType();
+ QString remoteTCPSinkSerial = parser.getRemoteTCPSinkSerial();
+
+ QTimer::singleShot(250, [=] {
+ startRemoteTCPSink(
+ remoteTCPSinkAddress,
+ remoteTCPSinkPort,
+ remoteTCPSinkHWType,
+ remoteTCPSinkSerial);
+ });
+}
diff --git a/sdrbase/remotetcpsinkstarter.h b/sdrbase/remotetcpsinkstarter.h
new file mode 100644
index 000000000..8b723b6ea
--- /dev/null
+++ b/sdrbase/remotetcpsinkstarter.h
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2023 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 //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef REMOTETCPSINKSTARTER_H
+#define REMOTETCPSINKSTARTER_H
+
+#include "mainparser.h"
+#include "export.h"
+
+class SDRBASE_API RemoteTCPSinkStarter {
+
+public:
+ static void listAvailableDevices();
+ static void start(const MainParser& parser);
+
+};
+
+#endif /* REMOTETCPSINKSTARTER_H */