diff --git a/.appveyor.yml b/.appveyor.yml
index bce5c11b26..e7893e0893 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -84,7 +84,7 @@ for:
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \
qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtgraphicaleffects \
libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \
- libqt5charts5-dev libqt5texttospeech5-dev libqt5gamepad5-dev libqt5svg5-dev libfaad-dev zlib1g-dev \
+ libqt5charts5-dev libqt5texttospeech5-dev libqt5gamepad5-dev libqt5svg5-dev libfaad-dev libflac-dev zlib1g-dev \
libusb-1.0-0-dev libhidapi-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \
libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \
libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \
diff --git a/cmake/Modules/FindFLAC.cmake b/cmake/Modules/FindFLAC.cmake
new file mode 100644
index 0000000000..f6af1b52a0
--- /dev/null
+++ b/cmake/Modules/FindFLAC.cmake
@@ -0,0 +1,31 @@
+IF(NOT FLAC_FOUND)
+ INCLUDE(FindPkgConfig)
+ PKG_CHECK_MODULES(PC_FLAC flac)
+
+ FIND_PATH(
+ FLAC_INCLUDE_DIR
+ NAMES FLAC/stream_encoder.h
+ HINTS ${PC_FLAC_INCLUDE_DIRS}
+ PATHS /usr/local/include
+ /usr/include
+ )
+
+ FIND_LIBRARY(
+ FLAC_LIBRARY
+ NAMES FLAC
+ libFLAC
+ HINTS ${FLAC_DIR}/lib
+ ${PC_FLAC_LIBRARY_DIRS}
+ PATHS /usr/local/lib
+ /usr/lib
+ /usr/lib64
+ )
+
+ message(STATUS "FLAC LIBRARY " ${FLAC_LIBRARY})
+ message(STATUS "FLAC INCLUDE DIR " ${FLAC_INCLUDE_DIR})
+
+ INCLUDE(FindPackageHandleStandardArgs)
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLAC DEFAULT_MSG FLAC_LIBRARY FLAC_INCLUDE_DIR)
+ MARK_AS_ADVANCED(FLAC_LIBRARY FLAC_INCLUDE_DIR)
+
+ENDIF(NOT FLAC_FOUND)
diff --git a/debian/control b/debian/control
index 710d236a62..2498d2c482 100644
--- a/debian/control
+++ b/debian/control
@@ -45,6 +45,7 @@ Build-Depends: debhelper (>= 9),
flex,
ffmpeg,
libfaad-dev,
+ libflac-dev,
libavcodec-dev,
libavformat-dev,
libopus-dev,
diff --git a/doc/img/RemoteTCPInput_plugin.png b/doc/img/RemoteTCPInput_plugin.png
index e9b3200973..5a41c2269c 100644
Binary files a/doc/img/RemoteTCPInput_plugin.png and b/doc/img/RemoteTCPInput_plugin.png differ
diff --git a/doc/img/RemoteTCPSink.png b/doc/img/RemoteTCPSink.png
index 2deb2f1577..b22c358246 100644
Binary files a/doc/img/RemoteTCPSink.png and b/doc/img/RemoteTCPSink.png differ
diff --git a/doc/img/RemoteTCPSink_settings.png b/doc/img/RemoteTCPSink_settings.png
new file mode 100644
index 0000000000..491987f0b2
Binary files /dev/null and b/doc/img/RemoteTCPSink_settings.png differ
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 618a858999..137e77f265 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -851,6 +851,38 @@ if(ENABLE_FEATURE_SATELLITETRACKER OR ENABLE_CHANNELRX_DEMODAPT)
endif ()
endif ()
+if(ENABLE_CHANNELRX_REMOTETCPSINK)
+ if (WIN32)
+ set(FLAC_LIBRARIES "${SDRANGEL_BINARY_LIB_DIR}/FLAC.lib" CACHE INTERNAL "")
+ elseif (LINUX)
+ set(FLAC_LIBRARIES "${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac-build/src/libFLAC/libFLAC${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE INTERNAL "")
+ elseif (APPLE)
+ set(FLAC_LIBRARIES "${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac-build/src/libFLAC/libFLAC${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE INTERNAL "")
+ elseif (EMSCRIPTEN)
+ set(FLAC_LIBRARIES "${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac-build/src/libFLAC/libFLAC.a" CACHE INTERNAL "")
+ endif()
+ ExternalProject_Add(flac
+ GIT_REPOSITORY https://github.com/xiph/flac.git
+ PREFIX "${EXTERNAL_BUILD_LIBRARIES}/flac"
+ CMAKE_ARGS ${COMMON_CMAKE_ARGS} -DINSTALL_MANPAGES=OFF -D=BUILD_SHARED_LIBS=ON -DWITH_FORTIFY_SOURCE=OFF -DWITH_STACK_PROTECTOR=PFF -DBUILD_PROGRAMS=OFF -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -DWITH_OGG=OFF -DBUILD_DOCS=OFF
+ BUILD_BYPRODUCTS "${FLAC_LIBRARIES}"
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+ )
+ ExternalProject_Get_Property(flac source_dir binary_dir)
+ set_global(FLAC_DEPENDS flac)
+ set_global_cache(FLAC_FOUND ON)
+ set(FLAC_EXTERNAL ON CACHE INTERNAL "")
+ set(FLAC_INCLUDE_DIR "${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac/include" CACHE INTERNAL "")
+ if (WIN32)
+ install(FILES "${SDRANGEL_BINARY_BIN_DIR}/FLAC${CMAKE_SHARED_LIBRARY_SUFFIX}" DESTINATION "${INSTALL_LIB_DIR}")
+ elseif (APPLE)
+ install(DIRECTORY "${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac-build/src/libFLAC" DESTINATION "${INSTALL_LIB_DIR}"
+ FILES_MATCHING PATTERN "libFLAC*${CMAKE_SHARED_LIBRARY_SUFFIX}")
+ set(MACOS_EXTERNAL_LIBS_FIXUP "${MACOS_EXTERNAL_LIBS_FIXUP};${EXTERNAL_BUILD_LIBRARIES}/flac/src/flac-build/src/libFLAC")
+ endif ()
+endif ()
+
# For Morse Decoder feature
if(ENABLE_FEATURE_MORSEDECODER)
if (WIN32)
diff --git a/plugins/channelrx/remotetcpsink/CMakeLists.txt b/plugins/channelrx/remotetcpsink/CMakeLists.txt
index 2b42dc98e9..a5750a7990 100644
--- a/plugins/channelrx/remotetcpsink/CMakeLists.txt
+++ b/plugins/channelrx/remotetcpsink/CMakeLists.txt
@@ -21,6 +21,8 @@ set(remotetcpsink_HEADERS
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
+ ${FLAC_INCLUDE_DIR}
+ ${ZLIB_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
@@ -28,10 +30,13 @@ if(NOT SERVER_MODE)
${remotetcpsink_SOURCES}
remotetcpsinkgui.cpp
remotetcpsinkgui.ui
+ remotetcpsinksettingsdialog.cpp
+ remotetcpsinksettingsdialog.ui
)
set(remotetcpsink_HEADERS
${remotetcpsink_HEADERS}
remotetcpsinkgui.h
+ remotetcpsinksettingsdialog.h
)
set(TARGET_NAME ${PLUGINS_PREFIX}remotetcpsink)
set(TARGET_LIB "Qt::Widgets")
@@ -44,16 +49,25 @@ else()
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
-add_library(${TARGET_NAME} SHARED
- ${remotetcpsink_SOURCES}
-)
+if(NOT Qt6_FOUND)
+ add_library(${TARGET_NAME} ${remotetcpsink_SOURCES})
+else()
+ qt_add_plugin(${TARGET_NAME} CLASS_NAME RemoteTCPSinkPlugin ${remotetcpsink_SOURCES})
+endif()
+
+if(NOT BUILD_SHARED_LIBS)
+ set_property(GLOBAL APPEND PROPERTY STATIC_PLUGINS_PROPERTY ${TARGET_NAME})
+endif()
-target_link_libraries(${TARGET_NAME}
+target_link_libraries(${TARGET_NAME} PRIVATE
Qt::Core
+ Qt::WebSockets
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
+ ${FLAC_LIBRARIES}
+ ${ZLIB_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
diff --git a/plugins/channelrx/remotetcpsink/readme.md b/plugins/channelrx/remotetcpsink/readme.md
index aefe35deb4..6057b59372 100644
--- a/plugins/channelrx/remotetcpsink/readme.md
+++ b/plugins/channelrx/remotetcpsink/readme.md
@@ -1,11 +1,26 @@
-
Remote TCP sink channel plugin
+Remote TCP Sink Channel Plugin
Introduction
-This plugin sends I/Q samples from the baseband via TCP/IP across a network to a client application.
-The client application could be SDRangel using the [Remote TCP Input](../../samplesource/remotetcpinput/readme.md) plugin or a rtl_tcp compatible application.
+The Remote TCP Sink Channel plugin sends I/Q samples from the baseband via TCP/IP or a Secure WebSocket across a network to a client application.
+The client application could be SDRangel using the [Remote TCP Input](../../samplesource/remotetcpinput/readme.md) plugin or an rtl_tcp compatible application.
This means that applications using rtl_tcp protocol can connect to the wide variety of SDRs supported by SDRangel.
+While the plugin supports rtl_tcp's RTL0 protocol for compatibility with older applications, the newer SDRA protocol supports the following additional features:
+
+- Different bit depths (8, 16, 24 or 32),
+- Additional settings, such as decimation, frequency offset and channel gain,
+- Device settings can be sent to the client for display,
+- IQ compression, using FLAC or zlib, to reduce network bandwidth,
+- IQ squelch, to reduce network bandwidth when no signal is being received,
+- Real-time forwarding of device/antenna position and direction to client,
+- Text messaging between clients and server,
+- Use of either TCP or WSS (WebSocket Secure Protocol).
+
+The Remote TCP Sink can support multiple clients connected simultaneously, with a user-defined maximum client limit. Clients can also have a time limit applied.
+
+Connection details can optionally be sent to a public database at https://sdrangel.org to allow operation as a WebSDR. Public servers are viewable on the [Map Feature](../../feature/map/readme.md).
+
Interface
![Remote TCP sink channel plugin GUI](../../../doc/img/RemoteTCPSink.png)
@@ -20,25 +35,178 @@ This is used to select the desired part of the signal when the channel sample ra
Sets a gain figure in dB that is applied to I/Q samples before transmission via TCP/IP.
This option may be useful for amplifying very small signals from SDRs with high-dynamic range (E.g. 24-bits), when the network sample bit-depth is 8-bits.
-3: Sample rate
+3: Channel power
+
+Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
+
+4: Level meter in dB
+
+ - top bar (green): average value
+ - bottom bar (blue green): instantaneous peak value
+ - tip vertical bar (bright green): peak hold value
+
+5: IQ Squelch
+
+Check to enable IQ squelch. When IQ squelch is enabled, if the channel power falls below the specified power level (6),
+the plugin will squelch (suppress) all the signal and noise in the channel,
+so that it can be transmitted at a very high compression ratio, reducing network bandwidth.
+
+This option is particularly suitable for packetised data, where the client doesn't need to receive the noise between packets.
+
+6: IQ Squelch power level
+
+Sets the power level in dB, below which, IQ data will be squelched.
+
+7: IQ Squelch gate time
+
+Sets the IQ squelch gate time. The units can be us (microseconds), ms (milliseconds) or s (seconds).
+
+8: IQ Squelch indicator
+
+When IQ squelch is enabled, the icon will have a green background when a signal above the power level (6) is being transmitted and a grey background when the signal is squelched.
+
+9: Sample rate
Specifies the channel and network sample rate in samples per second. If this is different from the baseband sample rate, the baseband signal will be decimated to the specified rate.
-4: Sample bit depth
+10: Sample bit depth
Specifies number of bits per I/Q sample transmitted via TCP/IP.
-5: IP address
+11: IP address
IP address of the local network interface on which the server will listen for TCP/IP connections from network clients. Use 0.0.0.0 for any interface.
-6: Port
+12: Port
TCP port on which the server will listen for connections.
-7: Protocol
+13: Protocol
-Specifies the protocol used for sending IQ samples and metadata to clients via TCP/IP.
+Specifies the protocol used for sending IQ samples and metadata to clients:
- RTL0: Compatible with rtl_tcp - limited to 8-bit IQ data.
-- SDRA: Enhanced version of protocol that allows device settings to be sent to clients and for higher bit depths to be used (8, 16, 24 and 32).
+- SDRangel: Enhanced version of protocol via TCP Socket.
+- SDRangel wss: SDRangel protocol via a WebSocket Secure instead of a TCP Socket. You should use this if you wish to allow connections from the WebAssembly version of SDRangel.
+
+14: Display Settings
+
+Click to open the Settings Dialog.
+
+![Remote TCP sink Settings Dialog](../../../doc/img/RemoteTCPSink_settings.png)
+
+Max Clients
+
+Specify the maximum number of clients than can connect simultaneously.
+If additional clients attempt to connect, they will be held in a queue.
+
+Time Limit
+
+Specify a time limit in minutes for each client connection. Use 0 for no limit.
+After the time limit expires, a client will be disconnected.
+They will be allowed to reconnect if the maximum number of clients is not reached.
+
+Max Channel Sample Rate
+
+Specify the maximum channel sample rate that can be set. This allows a limit to be set on network bandwidth.
+
+IQ only
+
+When checked, only uncompressed IQ samples will be transmitted. This is for compatibilty with client software expecting the RTL0 protcol.
+Checking this option will disable support for compression, messaging, device location and direction.
+
+Compressor
+
+Specify the compressor to use. This can be FLAC or zlib.
+
+Compression Level
+
+Specifies the compression effort level. Higher settings can improve compression, but require more CPU time.
+
+Block size
+
+Specify the block size the compressor uses. Larger block sizes improve compression, but add latency.
+Generally it should be fine to use the largest setting, unless the sample rate is very low.
+
+SSL Certificate
+
+Specify an SSL certificate .pem file. This is required to use SDRangel wss protocol.
+This file can be generated in the same way as for a web server.
+
+SSL Key
+
+Specify an SSL key .pem file. This is required to use SDRangel wss protocol.
+This file can be generated in the same way as for a web server.
+
+List Server
+
+Check to list the server in a public directory on https://sdrangel.com.
+This will allow other users to find and connect to the server via the [Map Feature](../../feature/map/readme.md).
+
+Address
+
+Public IP address or hostname and port number to access the server.
+The port number specified here may differ from (12) if your router's
+port forwarding maps the port numbers.
+
+Frequency Range
+
+Specify minimum and maximum frequencies that users can expect to receive on.
+This will typically depend on the SDR and antenna.
+For information only and will be displayed on the [Map](../../feature/map/readme.md).
+
+Antenna
+
+Optionally enter details of the antenna.
+For information only and will be displayed on the [Map](../../feature/map/readme.md).
+
+Location
+
+Optionaly enter the location (Town and Country) of the antenna.
+For information only and will be displayed on the [Map](../../feature/map/readme.md).
+The position the SDRangel icon will be plotted on the Map will be
+taken from the device itself, which for most devices, will default
+to the position in Preferences > My Position.
+
+Isotropic
+
+Check to indicate the antenna is isotropic (non-directional).
+When unchecked, the direction the antenna points in can be specified below.
+
+Rotator
+
+Specify a Rotator feature that is controlling the direction of the antenna.
+Set to None to manually set the direction the antenna points.
+
+Direction
+
+Specify the direction the antenna is pointing, as Azimuth in degrees and Elevation in degrees.
+
+IP Blacklist
+
+Specify a list of IP addresses that will be prevented from connecting to the server.
+
+15: Remote Control
+
+When checked, remote clients will be able to change device settings. When unchecked, client requests to change settings will be ignored.
+
+16: TX
+
+When pressed, the text message (18) will be transmitted to the clients specified by (17).
+
+17: TX Address
+
+Specifies the TCP/IP address and port of the client that the message should be transmitted to, or ALL, if it should be transmitted to all clients.
+
+18: TX Message
+
+Specifies a text message to transmit to clients, when the TX button (16) is pressed.
+
+19: RX Messages
+
+Displays text messages received from clients.
+
+20: Connection Log
+
+Displays the IP addresses and TCP port numbers of clients that have connected, along with when they connected and disconnected
+and how long they were connected for.
diff --git a/plugins/channelrx/remotetcpsink/remotetcpprotocol.h b/plugins/channelrx/remotetcpsink/remotetcpprotocol.h
index 4803a21905..fb9e7b02d4 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpprotocol.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpprotocol.h
@@ -105,13 +105,23 @@ class RemoteTCPProtocol
setChannelFreqOffset = 0xc4,
setChannelGain = 0xc5,
setSampleBitDepth = 0xc6, // Bit depth for samples sent over network
+ setIQSquelchEnabled = 0xc7,
+ setIQSquelch = 0xc8,
+ setIQSquelchGate = 0xc9,
//setAntenna?
//setLOOffset?
+ sendMessage = 0xd0,
+ sendBlacklistedMessage = 0xd1,
+ dataIQ = 0xf0, // Uncompressed IQ data
+ dataIQFLAC = 0xf1, // IQ data compressed with FLAC
+ dataIQzlib = 0xf2, // IQ data compressed with zlib
+ dataPosition = 0xf3, // Lat, Long, Alt of anntenna
+ dataDirection = 0xf4 // Az/El of antenna
};
static const int m_rtl0MetaDataSize = 12;
static const int m_rsp0MetaDataSize = 45;
- static const int m_sdraMetaDataSize = 64;
+ static const int m_sdraMetaDataSize = 128;
static void encodeInt16(quint8 *p, qint16 data)
{
@@ -132,7 +142,7 @@ class RemoteTCPProtocol
encodeUInt32(p, (quint32)data);
}
- static qint16 extractInt16(quint8 *p)
+ static qint16 extractInt16(const quint8 *p)
{
qint16 data;
data = (p[1] & 0xff)
@@ -140,7 +150,7 @@ class RemoteTCPProtocol
return data;
}
- static quint32 extractUInt32(quint8 *p)
+ static quint32 extractUInt32(const quint8 *p)
{
quint32 data;
data = (p[3] & 0xff)
@@ -150,7 +160,7 @@ class RemoteTCPProtocol
return data;
}
- static qint32 extractInt32(quint8 *p)
+ static qint32 extractInt32(const quint8 *p)
{
return (qint32)extractUInt32(p);
}
@@ -167,7 +177,7 @@ class RemoteTCPProtocol
p[7] = data & 0xff;
}
- static quint64 extractUInt64(quint8 *p)
+ static quint64 extractUInt64(const quint8 *p)
{
quint64 data;
data = (p[7] & 0xff)
@@ -181,6 +191,27 @@ class RemoteTCPProtocol
return data;
}
+ static void encodeFloat(quint8 *p, float data)
+ {
+ quint32 t;
+
+ memcpy(&t, &data, 4);
+
+ encodeUInt32(p, t);
+ }
+
+ static float extractFloat(const quint8 *p)
+ {
+ quint32 t;
+ float f;
+
+ t = extractUInt32(p);
+
+ memcpy(&f, &t, 4);
+
+ return f;
+ }
+
};
#endif /* PLUGINS_CHANNELRX_REMOTETCPSINK_REMOTETCPPROTOCOL_H_ */
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsink.cpp b/plugins/channelrx/remotetcpsink/remotetcpsink.cpp
index 999ee8765f..7d0ff2a43c 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsink.cpp
+++ b/plugins/channelrx/remotetcpsink/remotetcpsink.cpp
@@ -24,6 +24,8 @@
#include
#include
#include
+#include
+#include
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
@@ -33,13 +35,17 @@
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "settings/serializable.h"
+#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "remotetcpsinkbaseband.h"
MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgConfigureRemoteTCPSink, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgReportConnection, Message)
+MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgReportDisconnect, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgReportBW, Message)
+MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgSendMessage, Message)
+MESSAGE_CLASS_DEFINITION(RemoteTCPSink::MsgError, Message)
const char* const RemoteTCPSink::m_channelIdURI = "sdrangel.channel.remotetcpsink";
const char* const RemoteTCPSink::m_channelId = "RemoteTCPSink";
@@ -47,7 +53,9 @@ const char* const RemoteTCPSink::m_channelId = "RemoteTCPSink";
RemoteTCPSink::RemoteTCPSink(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
- m_basebandSampleRate(0)
+ m_basebandSampleRate(0),
+ m_clients(0),
+ m_removeRequest(nullptr)
{
setObjectName(m_channelId);
@@ -77,7 +85,21 @@ RemoteTCPSink::RemoteTCPSink(DeviceAPI *deviceAPI) :
RemoteTCPSink::~RemoteTCPSink()
{
- qDebug("RemoteTCPSinkBaseband::~RemoteTCPSink");
+ qDebug("RemoteTCPSink::~RemoteTCPSink");
+
+ // Wait until remove listing request is finished
+ if (m_removeRequest && !m_removeRequest->isFinished())
+ {
+ qDebug() << "RemoteTCPSink::~RemoteTCPSink: Waiting for remove listing request to finish";
+ QEventLoop loop;
+ connect(m_removeRequest, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+ }
+
+ if (m_basebandSink->isRunning()) {
+ stop();
+ }
+
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
@@ -88,10 +110,6 @@ RemoteTCPSink::~RemoteTCPSink()
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
- if (m_basebandSink->isRunning()) {
- stop();
- }
-
m_basebandSink->deleteLater();
}
@@ -130,6 +148,8 @@ void RemoteTCPSink::start()
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
+
+ updatePublicListing();
}
void RemoteTCPSink::stop()
@@ -138,21 +158,24 @@ void RemoteTCPSink::stop()
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
+ if (m_settings.m_public) {
+ removePublicListing(m_settings.m_publicAddress, m_settings.m_publicPort);
+ }
}
bool RemoteTCPSink::handleMessage(const Message& cmd)
{
if (MsgConfigureRemoteTCPSink::match(cmd))
{
- MsgConfigureRemoteTCPSink& cfg = (MsgConfigureRemoteTCPSink&) cmd;
+ const MsgConfigureRemoteTCPSink& cfg = (const MsgConfigureRemoteTCPSink&) cmd;
qDebug() << "RemoteTCPSink::handleMessage: MsgConfigureRemoteTCPSink";
- applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce(), cfg.getRemoteChange());
+ applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce(), cfg.getRestartRequired());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
- DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
+ const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
qDebug() << "RemoteTCPSink::handleMessage: DSPSignalNotification: m_basebandSampleRate:" << m_basebandSampleRate;
@@ -166,6 +189,28 @@ bool RemoteTCPSink::handleMessage(const Message& cmd)
return true;
}
+ else if (MsgSendMessage::match(cmd))
+ {
+ const MsgSendMessage& msg = (const MsgSendMessage&) cmd;
+
+ // Forward to the sink
+ m_basebandSink->getInputMessageQueue()->push(MsgSendMessage::create(msg.getAddress(), msg.getPort(), msg.getCallsign(), msg.getText(), msg.getBroadcast()));
+ return true;
+ }
+ else if (MsgReportConnection::match(cmd))
+ {
+ const MsgReportConnection& msg = (const MsgReportConnection&) cmd;
+ m_clients = msg.getClients();
+ updatePublicListing();
+ return true;
+ }
+ else if (MsgReportDisconnect::match(cmd))
+ {
+ const MsgReportDisconnect& msg = (const MsgReportDisconnect&) cmd;
+ m_clients = msg.getClients();
+ updatePublicListing();
+ return true;
+ }
else
{
return false;
@@ -208,7 +253,7 @@ void RemoteTCPSink::setCenterFrequency(qint64 frequency)
}
}
-void RemoteTCPSink::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool remoteChange)
+void RemoteTCPSink::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool restartRequired)
{
qDebug() << "RemoteTCPSink::applySettings:"
<< " settingsKeys: " << settingsKeys
@@ -221,7 +266,7 @@ void RemoteTCPSink::applySettings(const RemoteTCPSinkSettings& settings, const Q
<< " m_protocol: " << settings.m_protocol
<< " m_streamIndex: " << settings.m_streamIndex
<< " force: " << force
- << " remoteChange: " << remoteChange;
+ << " restartRequired: " << restartRequired;
if (settingsKeys.contains("streamIndex"))
{
@@ -236,7 +281,7 @@ void RemoteTCPSink::applySettings(const RemoteTCPSinkSettings& settings, const Q
}
}
- MsgConfigureRemoteTCPSink *msg = MsgConfigureRemoteTCPSink::create(settings, settingsKeys, force, remoteChange);
+ MsgConfigureRemoteTCPSink *msg = MsgConfigureRemoteTCPSink::create(settings, settingsKeys, force, restartRequired);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
@@ -256,11 +301,31 @@ void RemoteTCPSink::applySettings(const RemoteTCPSinkSettings& settings, const Q
sendChannelSettings(pipes, settingsKeys, settings, force);
}
+ // Do we need to remove old listing
+ bool removeListing = false;
+ if (m_settings.m_public)
+ {
+ if ((settingsKeys.contains("public") || force) && !settings.m_public) {
+ removeListing = true;
+ }
+ if ((settingsKeys.contains("publicAddress") || force) && (settings.m_publicAddress != m_settings.m_publicAddress)) {
+ removeListing = true;
+ }
+ if ((settingsKeys.contains("publicPort") || force) && (settings.m_publicPort != m_settings.m_publicPort)) {
+ removeListing = true;
+ }
+ }
+ if (removeListing) {
+ removePublicListing(m_settings.m_publicAddress, m_settings.m_publicPort);
+ }
+
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
+
+ updatePublicListing();
}
int RemoteTCPSink::webapiSettingsGet(
@@ -571,6 +636,10 @@ void RemoteTCPSink::networkManagerFinished(QNetworkReply *reply)
qDebug("RemoteTCPSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
+ if (reply == m_removeRequest) {
+ m_removeRequest = nullptr;
+ }
+
reply->deleteLater();
}
@@ -586,3 +655,96 @@ void RemoteTCPSink::handleIndexInDeviceSetChanged(int index)
.arg(index);
m_basebandSink->setFifoLabel(fifoLabel);
}
+
+void RemoteTCPSink::removePublicListing(const QString& address, quint16 port)
+{
+ QUrl url = QUrl("https://sdrangel.org/websdr/removedb.php");
+
+ QNetworkRequest request;
+ request.setUrl(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ QJsonObject json;
+ json.insert("address", address);
+ json.insert("port", port);
+
+ QJsonDocument doc(json);
+ QByteArray data = doc.toJson();
+
+ m_removeRequest = m_networkManager->post(request, data);
+}
+
+void RemoteTCPSink::updatePublicListing()
+{
+ if (!m_settings.m_public || !m_thread.isRunning()) {
+ return;
+ }
+
+ QUrl url = QUrl("https://sdrangel.org/websdr/updatedb.php");
+
+ QNetworkRequest request;
+ request.setUrl(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ // Get device position
+ float latitude, longitude, altitude;
+
+ if (!ChannelWebAPIUtils::getDevicePosition(getDeviceSetIndex(), latitude, longitude, altitude))
+ {
+ latitude = MainCore::instance()->getSettings().getLatitude();
+ longitude = MainCore::instance()->getSettings().getLongitude();
+ altitude = MainCore::instance()->getSettings().getAltitude();
+ }
+ // FIXME: Optionally slightly obfuscate position
+
+ // Get antenna direction
+ double azimuth = m_settings.m_azimuth;
+ double elevation = m_settings.m_elevation;
+ if (!m_settings.m_isotropic && !m_settings.m_rotator.isEmpty() && (m_settings.m_rotator != "None"))
+ {
+ unsigned int rotatorFeatureSetIndex;
+ unsigned int rotatorFeatureIndex;
+
+ if (MainCore::getFeatureIndexFromId(m_settings.m_rotator, rotatorFeatureSetIndex, rotatorFeatureIndex))
+ {
+ ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentAzimuth", azimuth);
+ ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentElevation", elevation);
+ }
+ }
+
+ QString device = MainCore::instance()->getDevice(getDeviceSetIndex())->getHardwareId();
+
+ QString protocol;
+ if (m_settings.m_protocol == RemoteTCPSinkSettings::SDRA_WSS) {
+ protocol = "SDRangel wss";
+ } else {
+ protocol = "SDRangel";
+ }
+
+ QJsonObject json;
+ json.insert("address", m_settings.m_publicAddress);
+ json.insert("port", m_settings.m_publicPort);
+ json.insert("protocol", protocol);
+ json.insert("minFrequency", m_settings.m_minFrequency);
+ json.insert("maxFrequency", m_settings.m_maxFrequency);
+ json.insert("maxSampleRate", m_settings.m_maxSampleRate);
+ json.insert("device", device);
+ json.insert("antenna", m_settings.m_antenna);
+ json.insert("remoteControl", (int) m_settings.m_remoteControl);
+ json.insert("stationName", MainCore::instance()->getSettings().getStationName());
+ json.insert("location", m_settings.m_location);
+ json.insert("latitude", latitude);
+ json.insert("longitude", longitude);
+ json.insert("altitude", altitude);
+ json.insert("isotropic", (int) m_settings.m_isotropic);
+ json.insert("azimuth", azimuth);
+ json.insert("elevation", elevation);
+ json.insert("clients", m_clients);
+ json.insert("maxClients", m_settings.m_maxClients);
+ json.insert("timeLimit", m_settings.m_timeLimit);
+
+ QJsonDocument doc(json);
+ QByteArray data = doc.toJson();
+
+ m_networkManager->post(request, data);
+}
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsink.h b/plugins/channelrx/remotetcpsink/remotetcpsink.h
index 34ce5a6d0d..bc030e08c8 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsink.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpsink.h
@@ -42,25 +42,25 @@ class RemoteTCPSink : public BasebandSampleSink, public ChannelAPI {
const RemoteTCPSinkSettings& getSettings() const { return m_settings; }
const QList& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
- bool getRemoteChange() const { return m_remoteChange; }
+ bool getRestartRequired() const { return m_restartRequired; }
- static MsgConfigureRemoteTCPSink* create(const RemoteTCPSinkSettings& settings, const QList& settingsKeys, bool force, bool remoteChange = false)
+ static MsgConfigureRemoteTCPSink* create(const RemoteTCPSinkSettings& settings, const QList& settingsKeys, bool force, bool restartRequired = false)
{
- return new MsgConfigureRemoteTCPSink(settings, settingsKeys, force, remoteChange);
+ return new MsgConfigureRemoteTCPSink(settings, settingsKeys, force, restartRequired);
}
private:
RemoteTCPSinkSettings m_settings;
QList m_settingsKeys;
bool m_force;
- bool m_remoteChange; // This change of settings was requested by a remote client, so no need to restart server
+ bool m_restartRequired;
- MsgConfigureRemoteTCPSink(const RemoteTCPSinkSettings& settings, const QList& settingsKeys, bool force, bool remoteChange) :
+ MsgConfigureRemoteTCPSink(const RemoteTCPSinkSettings& settings, const QList& settingsKeys, bool force, bool restartRequired) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force),
- m_remoteChange(remoteChange)
+ m_restartRequired(restartRequired)
{ }
};
@@ -69,39 +69,138 @@ class RemoteTCPSink : public BasebandSampleSink, public ChannelAPI {
public:
int getClients() const { return m_clients; }
+ const QHostAddress& getAddress() const { return m_address; }
+ int getPort() const { return m_port; }
- static MsgReportConnection* create(int clients)
+
+ static MsgReportConnection* create(int clients, const QHostAddress& address, quint16 port)
+ {
+ return new MsgReportConnection(clients, address, port);
+ }
+
+ private:
+ int m_clients;
+ QHostAddress m_address;
+ quint16 m_port;
+
+ MsgReportConnection(int clients, const QHostAddress& address, quint16 port) :
+ Message(),
+ m_clients(clients),
+ m_address(address),
+ m_port(port)
+ { }
+ };
+
+ class MsgReportDisconnect : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getClients() const { return m_clients; }
+ const QHostAddress& getAddress() const { return m_address; }
+ quint16 getPort() const { return m_port; }
+
+ static MsgReportDisconnect* create(int clients, const QHostAddress& address, quint16 port)
{
- return new MsgReportConnection(clients);
+ return new MsgReportDisconnect(clients, address, port);
}
private:
int m_clients;
+ QHostAddress m_address;
+ quint16 m_port;
- MsgReportConnection(int clients) :
+ MsgReportDisconnect(int clients, const QHostAddress& address, quint16 port) :
Message(),
- m_clients(clients)
+ m_clients(clients),
+ m_address(address),
+ m_port(port)
{ }
};
- // Message to report actual transmit bandwidth in bits per second
+ // Message to report actual transmit bandwidth in bits per second and compression ratio
class MsgReportBW : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getBW() const { return m_bw; }
+ float getNetworkBW() const { return m_networkBW; }
+ qint64 getBytesUncompressed() const { return m_bytesUncompressed; }
+ qint64 getBytesCompressed() const { return m_bytesCompressed; }
+ qint64 getBytesTransmitted() const { return m_bytesTransmitted; }
- static MsgReportBW* create(float bw)
+ static MsgReportBW* create(float bw, float networkBW, qint64 bytesUncompressed, qint64 bytesCompressed, qint64 bytesTransmitted)
{
- return new MsgReportBW(bw);
+ return new MsgReportBW(bw, networkBW, bytesUncompressed, bytesCompressed, bytesTransmitted);
}
private:
float m_bw;
+ float m_networkBW;
+ qint64 m_bytesUncompressed;
+ qint64 m_bytesCompressed;
+ qint64 m_bytesTransmitted;
+
+ MsgReportBW(float bw, float networkBW, qint64 bytesUncompressed, qint64 bytesCompressed, qint64 bytesTransmitted) :
+ Message(),
+ m_bw(bw),
+ m_networkBW(networkBW),
+ m_bytesUncompressed(bytesUncompressed),
+ m_bytesCompressed(bytesCompressed),
+ m_bytesTransmitted(bytesTransmitted)
+ { }
+ };
+
+ // Send a text message to a client (or received message from client)
+ class MsgSendMessage : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ QHostAddress getAddress() const { return m_address; }
+ quint16 getPort() const { return m_port; }
+ const QString& getCallsign() const { return m_callsign; }
+ const QString& getText() const { return m_text; }
+ bool getBroadcast() const { return m_broadcast; }
- MsgReportBW(float bw) :
+ static MsgSendMessage* create(QHostAddress address, quint16 port, const QString& callsign, const QString& text, bool broadcast)
+ {
+ return new MsgSendMessage(address, port, callsign, text, broadcast);
+ }
+
+ private:
+ QHostAddress m_address;
+ quint16 m_port;
+ QString m_callsign;
+ QString m_text;
+ bool m_broadcast;
+
+ MsgSendMessage(QHostAddress address, quint16 port, const QString& callsign, const QString& text, bool broadcast) :
+ Message(),
+ m_address(address),
+ m_port(port),
+ m_callsign(callsign),
+ m_text(text),
+ m_broadcast(broadcast)
+ { }
+ };
+
+ class MsgError : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+
+ const QString& getError() const { return m_error; }
+
+ static MsgError *create(const QString& error)
+ {
+ return new MsgError(error);
+ }
+
+ private:
+ QString m_error;
+
+ explicit MsgError(const QString& error) :
Message(),
- m_bw(bw)
+ m_error(error)
{ }
};
@@ -164,6 +263,16 @@ class RemoteTCPSink : public BasebandSampleSink, public ChannelAPI {
uint32_t getNumberOfDeviceStreams() const;
int getBasebandSampleRate() const { return m_basebandSampleRate; }
+ bool getSquelchOpen() const { return m_basebandSink && m_basebandSink->getSquelchOpen(); }
+
+ void getMagSqLevels(double& avg, double& peak, int& nbSamples)
+ {
+ if (m_basebandSink) {
+ m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
+ } else {
+ avg = 0.0; peak = 0.0; nbSamples = 1;
+ }
+ }
void setMessageQueueToGUI(MessageQueue* queue) final {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
@@ -183,8 +292,11 @@ class RemoteTCPSink : public BasebandSampleSink, public ChannelAPI {
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
+ int m_clients; // Number of clients currently connected
+ QNetworkReply *m_removeRequest;
+
virtual bool handleMessage(const Message& cmd);
- void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool remoteChange = false);
+ void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool restartRequired = false);
void webapiReverseSendSettings(const QStringList& channelSettingsKeys, const RemoteTCPSinkSettings& settings, bool force);
void sendChannelSettings(
const QList& pipes,
@@ -198,6 +310,8 @@ class RemoteTCPSink : public BasebandSampleSink, public ChannelAPI {
const RemoteTCPSinkSettings& settings,
bool force
);
+ void removePublicListing(const QString& address, quint16 port);
+ void updatePublicListing();
private slots:
void networkManagerFinished(QNetworkReply *reply);
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.cpp b/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.cpp
index c23ce09857..6fc3d26a40 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.cpp
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.cpp
@@ -125,29 +125,37 @@ bool RemoteTCPSinkBaseband::handleMessage(const Message& cmd)
if (RemoteTCPSink::MsgConfigureRemoteTCPSink::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
- RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (RemoteTCPSink::MsgConfigureRemoteTCPSink&) cmd;
+ const RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (const RemoteTCPSink::MsgConfigureRemoteTCPSink&) cmd;
qDebug() << "RemoteTCPSinkBaseband::handleMessage: MsgConfigureRemoteTCPSink";
- applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce(), cfg.getRemoteChange());
+ applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce(), cfg.getRestartRequired());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
- DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
+ const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
qDebug() << "RemoteTCPSinkBaseband::handleMessage: DSPSignalNotification: basebandSampleRate:" << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
+ else if (RemoteTCPSink::MsgSendMessage::match(cmd))
+ {
+ const RemoteTCPSink::MsgSendMessage& msg = (const RemoteTCPSink::MsgSendMessage&) cmd;
+
+ m_sink.sendMessage(msg.getAddress(), msg.getPort(), msg.getCallsign(), msg.getText(), msg.getBroadcast());
+
+ return true;
+ }
else
{
return false;
}
}
-void RemoteTCPSinkBaseband::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool remoteChange)
+void RemoteTCPSinkBaseband::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool restartRequired)
{
qDebug() << "RemoteTCPSinkBaseband::applySettings:"
<< "m_channelSampleRate:" << settings.m_channelSampleRate
@@ -160,7 +168,7 @@ void RemoteTCPSinkBaseband::applySettings(const RemoteTCPSinkSettings& settings,
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
- m_sink.applySettings(settings, settingsKeys, force, remoteChange);
+ m_sink.applySettings(settings, settingsKeys, force, restartRequired);
if (force) {
m_settings = settings;
} else {
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.h b/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.h
index 88ffe7337f..a3710b6c4e 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinkbaseband.h
@@ -47,6 +47,8 @@ class RemoteTCPSinkBaseband : public QObject
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;
+ void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
+ bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); }
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setBasebandSampleRate(int sampleRate);
@@ -64,7 +66,7 @@ class RemoteTCPSinkBaseband : public QObject
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
- void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool remoteChange = false);
+ void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool restartRequired = false);
private slots:
void handleInputMessages();
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.cpp b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.cpp
index 3f3ca8ae6e..863fe575f0 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.cpp
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.cpp
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
-// Copyright (C) 2022-2023 Jon Beniston, M7RCE //
+// Copyright (C) 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 //
@@ -15,19 +15,26 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#include
#include
#include
+#include
+#include
#include "device/deviceuiset.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
+#include "gui/perioddial.h"
#include "dsp/dspcommands.h"
+#include "util/db.h"
+#include "maincore.h"
#include "remotetcpsinkgui.h"
#include "remotetcpsink.h"
#include "ui_remotetcpsinkgui.h"
+#include "remotetcpsinksettingsdialog.h"
+
+const QString RemoteTCPSinkGUI::m_dateTimeFormat = "yyyy.MM.dd hh:mm:ss";
RemoteTCPSinkGUI* RemoteTCPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx)
{
@@ -102,11 +109,74 @@ QString RemoteTCPSinkGUI::displayScaledF(float value, char type, int precision,
}
}
+void RemoteTCPSinkGUI::resizeTable()
+{
+ QDateTime dateTime = QDateTime::currentDateTime();
+ QString dateTimeString = dateTime.toString(m_dateTimeFormat);
+ int row = ui->connections->rowCount();
+ ui->connections->setRowCount(row + 1);
+ ui->connections->setItem(row, CONNECTIONS_COL_ADDRESS, new QTableWidgetItem("255.255.255.255"));
+ ui->connections->setItem(row, CONNECTIONS_COL_PORT, new QTableWidgetItem("65535"));
+ ui->connections->setItem(row, CONNECTIONS_COL_CONNECTED, new QTableWidgetItem(dateTimeString));
+ ui->connections->setItem(row, CONNECTIONS_COL_DISCONNECTED, new QTableWidgetItem(dateTimeString));
+ ui->connections->setItem(row, CONNECTIONS_COL_TIME, new QTableWidgetItem("1000 d"));
+ ui->connections->resizeColumnsToContents();
+ ui->connections->removeRow(row);
+}
+
+void RemoteTCPSinkGUI::addConnection(const QHostAddress& address, int port)
+{
+ QDateTime dateTime = QDateTime::currentDateTime();
+
+ int row = ui->connections->rowCount();
+ ui->connections->setRowCount(row + 1);
+
+ ui->connections->setItem(row, CONNECTIONS_COL_ADDRESS, new QTableWidgetItem(address.toString()));
+ ui->connections->setItem(row, CONNECTIONS_COL_PORT, new QTableWidgetItem(QString::number(port)));
+ ui->connections->setItem(row, CONNECTIONS_COL_CONNECTED, new QTableWidgetItem(dateTime.toString(m_dateTimeFormat)));
+ ui->connections->setItem(row, CONNECTIONS_COL_DISCONNECTED, new QTableWidgetItem(""));
+ ui->connections->setItem(row, CONNECTIONS_COL_TIME, new QTableWidgetItem(""));
+}
+
+void RemoteTCPSinkGUI::removeConnection(const QHostAddress& address, int port)
+{
+ QString addressString = address.toString();
+ QString portString = QString::number(port);
+
+ for (int row = 0; row < ui->connections->rowCount(); row++)
+ {
+ if ((ui->connections->item(row, CONNECTIONS_COL_ADDRESS)->text() == addressString)
+ && (ui->connections->item(row, CONNECTIONS_COL_PORT)->text() == portString)
+ && (ui->connections->item(row, CONNECTIONS_COL_DISCONNECTED)->text().isEmpty()))
+ {
+ QDateTime connected = QDateTime::fromString(ui->connections->item(row, CONNECTIONS_COL_CONNECTED)->text(), m_dateTimeFormat);
+ QDateTime disconnected = QDateTime::currentDateTime();
+ QString dateTimeString = disconnected.toString(m_dateTimeFormat);
+ QString time;
+ int secs = connected.secsTo(disconnected);
+ if (secs < 60) {
+ time = QString("%1 s").arg(secs);
+ } else if (secs < 60 * 60) {
+ time = QString("%1 m").arg(secs / 60);
+ } else if (secs < 60 * 60 * 24) {
+ time = QString("%1 h").arg(secs / 60 / 60);
+ } else {
+ time = QString("%1 d").arg(secs / 60 / 60 / 24);
+ }
+
+ ui->connections->item(row, CONNECTIONS_COL_DISCONNECTED)->setText(dateTimeString);
+ ui->connections->item(row, CONNECTIONS_COL_TIME)->setText(time);
+ break;
+ }
+ }
+}
+
bool RemoteTCPSinkGUI::handleMessage(const Message& message)
{
if (RemoteTCPSink::MsgConfigureRemoteTCPSink::match(message))
{
- const RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (RemoteTCPSink::MsgConfigureRemoteTCPSink&) message;
+ const RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (const RemoteTCPSink::MsgConfigureRemoteTCPSink&) message;
+
if ((cfg.getSettings().m_channelSampleRate != m_settings.m_channelSampleRate)
|| (cfg.getSettings().m_sampleBits != m_settings.m_sampleBits)) {
m_bwAvg.reset();
@@ -125,20 +195,58 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
}
else if (RemoteTCPSink::MsgReportConnection::match(message))
{
- const RemoteTCPSink::MsgReportConnection& report = (RemoteTCPSink::MsgReportConnection&) message;
- ui->clients->setText(QString("%1").arg(report.getClients()));
+ const RemoteTCPSink::MsgReportConnection& report = (const RemoteTCPSink::MsgReportConnection&) message;
+
+ ui->clients->setText(QString("%1/%2").arg(report.getClients()).arg(m_settings.m_maxClients));
+ QString ip = QString("%1:%2").arg(report.getAddress().toString()).arg(report.getPort());
+ if (ui->txAddress->findText(ip) == -1) {
+ ui->txAddress->addItem(ip);
+ }
+ addConnection(report.getAddress(), report.getPort());
+
+ return true;
+ }
+ else if (RemoteTCPSink::MsgReportDisconnect::match(message))
+ {
+ const RemoteTCPSink::MsgReportDisconnect& report = (const RemoteTCPSink::MsgReportDisconnect&) message;
+
+ ui->clients->setText(QString("%1/%2").arg(report.getClients()).arg(m_settings.m_maxClients));
+ QString ip = QString("%1:%2").arg(report.getAddress().toString()).arg(report.getPort());
+ int idx = ui->txAddress->findText(ip);
+ if (idx != -1) {
+ ui->txAddress->removeItem(idx);
+ }
+ removeConnection(report.getAddress(), report.getPort());
+
return true;
}
else if (RemoteTCPSink::MsgReportBW::match(message))
{
- const RemoteTCPSink::MsgReportBW& report = (RemoteTCPSink::MsgReportBW&) message;
+ const RemoteTCPSink::MsgReportBW& report = (const RemoteTCPSink::MsgReportBW&) message;
+
m_bwAvg(report.getBW());
- ui->bw->setText(QString("%1bps").arg(displayScaledF(m_bwAvg.instantAverage(), 'f', 3, true)));
+ m_networkBWAvg(report.getNetworkBW());
+
+ QString text = QString("%1bps").arg(displayScaledF(m_bwAvg.instantAverage(), 'f', 1, true));
+
+ if (!m_settings.m_iqOnly && (report.getBytesUncompressed() > 0))
+ {
+ float compressionSaving = 1.0f - (report.getBytesCompressed() / (float) report.getBytesUncompressed());
+ m_compressionAvg(compressionSaving);
+
+ QString compressionText = QString(" %1%").arg((int) std::round(m_compressionAvg.instantAverage() * 100.0f));
+ text.append(compressionText);
+ }
+
+ QString networkBWText = QString(" %1bps").arg(displayScaledF(m_networkBWAvg.instantAverage(), 'f', 1, true));
+ text.append(networkBWText);
+
+ ui->bw->setText(text);
return true;
}
else if (DSPSignalNotification::match(message))
{
- DSPSignalNotification& cfg = (DSPSignalNotification&) message;
+ const DSPSignalNotification& cfg = (const DSPSignalNotification&) message;
if (cfg.getSampleRate() != m_basebandSampleRate) {
m_bwAvg.reset();
}
@@ -150,6 +258,32 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
return true;
}
+ else if (RemoteTCPSink::MsgSendMessage::match(message))
+ {
+ const RemoteTCPSink::MsgSendMessage& msg = (const RemoteTCPSink::MsgSendMessage&) message;
+ QString address = QString("%1:%2").arg(msg.getAddress().toString()).arg(msg.getPort());
+ QString callsign = msg.getCallsign();
+ QString text = msg.getText();
+ bool broadcast = msg.getBroadcast();
+
+ // Display received message in GUI
+ ui->messages->addItem(QString("%1/%2> %3").arg(address).arg(callsign).arg(text));
+ ui->messages->scrollToBottom();
+
+ // Forward to other clients
+ if (broadcast) {
+ m_remoteSink->getInputMessageQueue()->push(RemoteTCPSink::MsgSendMessage::create(msg.getAddress(), msg.getPort(), callsign, text, broadcast));
+ }
+
+ return true;
+ }
+ else if (RemoteTCPSink::MsgError::match(message))
+ {
+ const RemoteTCPSink::MsgError& msg = (const RemoteTCPSink::MsgError&) message;
+ QString error = msg.getError();
+ QMessageBox::warning(this, "RemoteTCPSink", error, QMessageBox::Ok);
+ return true;
+ }
else
{
return false;
@@ -157,12 +291,14 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
}
RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) :
- ChannelGUI(parent),
- ui(new Ui::RemoteTCPSinkGUI),
- m_pluginAPI(pluginAPI),
- m_deviceUISet(deviceUISet),
- m_basebandSampleRate(0),
- m_deviceCenterFrequency(0)
+ ChannelGUI(parent),
+ ui(new Ui::RemoteTCPSinkGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceUISet(deviceUISet),
+ m_basebandSampleRate(0),
+ m_deviceCenterFrequency(0),
+ m_tickCount(0),
+ m_squelchOpen(false)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/remotetcpsink/readme.md";
@@ -177,6 +313,8 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
m_remoteSink->setMessageQueueToGUI(getInputMessageQueue());
m_basebandSampleRate = m_remoteSink->getBasebandSampleRate();
+ connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
+
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(m_settings.m_rgbColor);
m_channelMarker.setCenterFrequency(0);
@@ -189,12 +327,17 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
m_deviceUISet->addChannelMarker(&m_channelMarker);
+ ui->txAddress->clear();
+ ui->txAddress->addItem("All");
+
ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->channelSampleRate->setValueRange(8, 0, 99999999);
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
+ ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
+#ifndef __EMSCRIPTEN__
// Add all IP addresses
for (const QHostAddress& address: QNetworkInterface::allAddresses())
{
@@ -202,11 +345,14 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
ui->dataAddress->addItem(address.toString());
}
}
+#endif
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
+ resizeTable();
+
displaySettings();
makeUIConnections();
applyAllSettings();
@@ -269,12 +415,39 @@ void RemoteTCPSinkGUI::displaySettings()
ui->dataAddress->addItem(m_settings.m_dataAddress);
}
ui->dataAddress->setCurrentText(m_settings.m_dataAddress);
- ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
+ ui->dataPort->setValue(m_settings.m_dataPort);
ui->protocol->setCurrentIndex((int)m_settings.m_protocol);
+ ui->remoteControl->setChecked(m_settings.m_remoteControl);
+ ui->squelchEnabled->setChecked(m_settings.m_squelchEnabled);
+ displayIQOnly();
+ displaySquelch();
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
}
+void RemoteTCPSinkGUI::displayIQOnly()
+{
+ ui->messagesLayout->setEnabled(!m_settings.m_iqOnly);
+ ui->sendMessage->setEnabled(!m_settings.m_iqOnly);
+ ui->txAddress->setEnabled(!m_settings.m_iqOnly);
+ ui->txMessage->setEnabled(!m_settings.m_iqOnly);
+ ui->messagesContainer->setVisible(!m_settings.m_iqOnly);
+}
+
+void RemoteTCPSinkGUI::displaySquelch()
+{
+ ui->squelch->setValue(m_settings.m_squelch);
+ ui->squelchText->setText(QString::number(m_settings.m_squelch));
+ ui->squelch->setEnabled(m_settings.m_squelchEnabled);
+ ui->squelchText->setEnabled(m_settings.m_squelchEnabled);
+ ui->squelchUnits->setEnabled(m_settings.m_squelchEnabled);
+
+ ui->squelchGate->setValue(m_settings.m_squelchGate);
+ ui->squelchGate->setEnabled(m_settings.m_squelchEnabled);
+
+ ui->audioMute->setEnabled(m_settings.m_squelchEnabled);
+}
+
void RemoteTCPSinkGUI::displayRateAndShift()
{
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
@@ -430,27 +603,114 @@ void RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged(int index)
applySetting("dataAddress");
}
-void RemoteTCPSinkGUI::on_dataPort_editingFinished()
+void RemoteTCPSinkGUI::on_dataPort_valueChanged(int value)
+{
+ m_settings.m_dataPort = value;
+ applySetting("dataPort");
+}
+
+void RemoteTCPSinkGUI::on_protocol_currentIndexChanged(int index)
+{
+ m_settings.m_protocol = (RemoteTCPSinkSettings::Protocol)index;
+ applySetting("protocol");
+}
+
+void RemoteTCPSinkGUI::on_remoteControl_toggled(bool checked)
+{
+ m_settings.m_remoteControl = checked;
+ applySetting("remoteControl");
+}
+
+void RemoteTCPSinkGUI::on_squelchEnabled_toggled(bool checked)
+{
+ m_settings.m_squelchEnabled = checked;
+ applySetting("squelchEnabled");
+ displaySquelch();
+}
+
+void RemoteTCPSinkGUI::on_squelch_valueChanged(int value)
{
- bool dataOk;
- int dataPort = ui->dataPort->text().toInt(&dataOk);
+ m_settings.m_squelch = value;
+ ui->squelchText->setText(QString::number(m_settings.m_squelch));
+ applySetting("squelch");
+}
- if((!dataOk) || (dataPort < 1024) || (dataPort > 65535))
+void RemoteTCPSinkGUI::on_squelchGate_valueChanged(double value)
+{
+ m_settings.m_squelchGate = value;
+ applySetting("squelchGate");
+}
+
+void RemoteTCPSinkGUI::on_displaySettings_clicked()
+{
+ RemoteTCPSinkSettingsDialog dialog(&m_settings);
+
+ new DialogPositioner(&dialog, true);
+ if (dialog.exec() == QDialog::Accepted)
{
- return;
+ applySettings(dialog.getSettingsKeys());
+ displayIQOnly();
}
- else
+}
+
+void RemoteTCPSinkGUI::on_sendMessage_clicked()
+{
+ QString message = ui->txMessage->text().trimmed();
+ if (!message.isEmpty())
{
- m_settings.m_dataPort = dataPort;
+ ui->messages->addItem(QString("< %1").arg(message));
+ ui->messages->scrollToBottom();
+ bool broadcast = ui->txAddress->currentText() == "All";
+ QHostAddress address;
+ quint16 port = 0;
+ if (!broadcast)
+ {
+ QStringList parts = ui->txAddress->currentText().split(':');
+ address = QHostAddress(parts[0]);
+ port = parts[1].toInt();
+ }
+ QString callsign = MainCore::instance()->getSettings().getStationName();
+ m_remoteSink->getInputMessageQueue()->push(RemoteTCPSink::MsgSendMessage::create(address, port, callsign, message, broadcast));
}
+}
- applySetting("dataPort");
+void RemoteTCPSinkGUI::on_txMessage_returnPressed()
+{
+ on_sendMessage_clicked();
+ ui->txMessage->selectAll();
}
-void RemoteTCPSinkGUI::on_protocol_currentIndexChanged(int index)
+void RemoteTCPSinkGUI::tick()
{
- m_settings.m_protocol = (RemoteTCPSinkSettings::Protocol)index;
- applySetting("protocol");
+ double magsqAvg, magsqPeak;
+ int nbMagsqSamples;
+ m_remoteSink->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));
+ }
+
+ bool squelchOpen = m_remoteSink->getSquelchOpen() || !m_settings.m_squelchEnabled;
+
+ if (squelchOpen != m_squelchOpen)
+ {
+ /*if (squelchOpen) {
+ ui->audioMute->setStyleSheet("QToolButton { background-color : green; }");
+ } else {
+ ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
+ }*/
+ ui->audioMute->setChecked(!squelchOpen);
+ m_squelchOpen = squelchOpen;
+ }
+
+ m_tickCount++;
}
void RemoteTCPSinkGUI::makeUIConnections()
@@ -461,8 +721,15 @@ void RemoteTCPSinkGUI::makeUIConnections()
QObject::connect(ui->sampleBits, QOverload::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_sampleBits_currentIndexChanged);
QObject::connect(ui->dataAddress->lineEdit(), &QLineEdit::editingFinished, this, &RemoteTCPSinkGUI::on_dataAddress_editingFinished);
QObject::connect(ui->dataAddress, QOverload::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged);
- QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPSinkGUI::on_dataPort_editingFinished);
+ QObject::connect(ui->dataPort, QOverload::of(&QSpinBox::valueChanged), this, &RemoteTCPSinkGUI::on_dataPort_valueChanged);
QObject::connect(ui->protocol, QOverload::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_protocol_currentIndexChanged);
+ QObject::connect(ui->remoteControl, &ButtonSwitch::toggled, this, &RemoteTCPSinkGUI::on_remoteControl_toggled);
+ QObject::connect(ui->squelchEnabled, &ButtonSwitch::toggled, this, &RemoteTCPSinkGUI::on_squelchEnabled_toggled);
+ QObject::connect(ui->squelch, &QDial::valueChanged, this, &RemoteTCPSinkGUI::on_squelch_valueChanged);
+ QObject::connect(ui->squelchGate, &PeriodDial::valueChanged, this, &RemoteTCPSinkGUI::on_squelchGate_valueChanged);
+ QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &RemoteTCPSinkGUI::on_displaySettings_clicked);
+ QObject::connect(ui->sendMessage, &QToolButton::clicked, this, &RemoteTCPSinkGUI::on_sendMessage_clicked);
+ QObject::connect(ui->txMessage, &QLineEdit::returnPressed, this, &RemoteTCPSinkGUI::on_txMessage_returnPressed);
}
void RemoteTCPSinkGUI::updateAbsoluteCenterFrequency()
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.h b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.h
index 1dc58bd614..c44933b265 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.h
@@ -25,6 +25,7 @@
#include
#include
+#include
#include "dsp/channelmarker.h"
#include "channel/channelgui.h"
@@ -82,9 +83,23 @@ public slots:
bool m_doApplySettings;
RemoteTCPSink* m_remoteSink;
+ uint32_t m_tickCount;
+ bool m_squelchOpen;
MessageQueue m_inputMessageQueue;
MovingAverageUtil m_bwAvg;
+ MovingAverageUtil m_compressionAvg;
+ MovingAverageUtil m_networkBWAvg;
+
+ enum ConnectionsCol {
+ CONNECTIONS_COL_ADDRESS,
+ CONNECTIONS_COL_PORT,
+ CONNECTIONS_COL_CONNECTED,
+ CONNECTIONS_COL_DISCONNECTED,
+ CONNECTIONS_COL_TIME
+ };
+
+ static const QString m_dateTimeFormat;
explicit RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~RemoteTCPSinkGUI();
@@ -94,11 +109,16 @@ public slots:
void applySettings(const QStringList& settingsKeys, bool force = false);
void applyAllSettings();
void displaySettings();
+ void displayIQOnly();
+ void displaySquelch();
void displayRateAndShift();
bool handleMessage(const Message& message);
void makeUIConnections();
QString displayScaledF(float value, char type, int precision, bool showMult);
void updateAbsoluteCenterFrequency();
+ void resizeTable();
+ void addConnection(const QHostAddress& address, int port);
+ void removeConnection(const QHostAddress& address, int port);
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
@@ -111,10 +131,18 @@ private slots:
void on_sampleBits_currentIndexChanged(int index);
void on_dataAddress_editingFinished();
void on_dataAddress_currentIndexChanged(int index);
- void on_dataPort_editingFinished();
+ void on_dataPort_valueChanged(int value);
void on_protocol_currentIndexChanged(int index);
+ void on_remoteControl_toggled(bool checked);
+ void on_squelchEnabled_toggled(bool checked);
+ void on_squelch_valueChanged(int value);
+ void on_squelchGate_valueChanged(double value);
+ void on_displaySettings_clicked();
+ void on_sendMessage_clicked();
+ void on_txMessage_returnPressed();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
+ void tick();
};
#endif /* PLUGINS_CHANNELRX_REMOTETCPSINK_REMOTETCPSINKGUI_H_ */
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.ui b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.ui
index 28963c6be4..afbeba69d6 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinkgui.ui
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinkgui.ui
@@ -7,11 +7,11 @@
0
0
360
- 147
+ 646
-
+
0
0
@@ -24,7 +24,7 @@
- 560
+ 1000
16777215
@@ -41,8 +41,8 @@
0
0
- 340
- 141
+ 361
+ 191
@@ -108,7 +108,7 @@
PointingHandCursor
- Qt::StrongFocus
+ Qt::FocusPolicy::StrongFocus
Channel frequency shift from center in Hz
@@ -125,7 +125,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -174,8 +174,217 @@
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+
+ 30
+ 0
+
+
+
+ Channel power
+
+
+ Qt::LayoutDirection::RightToLeft
+
+
+ 0.0
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+ dB
+
+
+
+
+
+ -
+
+
-
+
+
+ dB
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 24
+
+
+
+
+ Liberation Mono
+ 8
+
+
+
+ Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold
+
+
+
+
+
+ -
+
+
-
+
+
+ Check to enable IQ squelch
+
+
+ SQ
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ IQ squelch power level in dB
+
+
+ -150
+
+
+ 0
+
+
+ 1
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ -150
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+ dB
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+
+ 40
+ 0
+
+
+
+ IQ squelch gate time
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+ false
+
+
+ Indicates when IQ squelch is open
+
+
+ ...
+
+
+
+ :/sound_on.png
+ :/sound_off.png:/sound_on.png
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
-
@@ -226,7 +435,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -288,7 +497,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -325,28 +534,22 @@
-
-
-
-
- 50
- 16777215
-
-
+
TCP port for server to listen for connections on
-
- 00000
+
+ 1024
-
- 0
+
+ 65535
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -365,9 +568,18 @@
-
+
+
+ 80
+ 0
+
+
Protocol to transmit data with
+
+ 0
+
-
RTL0
@@ -375,19 +587,48 @@
-
- SDRA
+ SDRangel
+ -
+
+ SDRangel wss
+
+
+
+
+ -
+
+
+ Display settings dialog
+
+
+
+
+
+
+ :/listing.png:/listing.png
+
-
+
-
+
+
+ Allow remote control
+
+
+ RC
+
+
+
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -407,17 +648,17 @@
-
- Number of clients connected
+ Number of clients connected / maximum clients
- 0
+ 0/0
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -431,10 +672,14 @@
-
- Transmit bandwidth for a single TCP connection averaged over the last 10 seconds in bits per second
+ - Channel IQ bandwidth in bits per second
+- Compression saving in percent
+- Total outgoing network bandwidth in bits per second
+
+Values are averaged over the last 10 seconds
- 0.000Mbps
+ 0.0Mbps 0% 0.0Mbps
@@ -442,8 +687,163 @@
+
+
+
+ 0
+ 200
+ 351
+ 191
+
+
+
+
+ 0
+ 0
+
+
+
+ Messages
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
-
+
+
+ TX
+
+
+
+ -
+
+
+
+ 140
+ 0
+
+
+
-
+
+ 127.127.127.127:1234
+
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
+
+ 0
+ 410
+ 351
+ 191
+
+
+
+
+ 0
+ 0
+
+
+
+ Connection Log
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
-
+
+
+
+ Address
+
+
+ IP address of client
+
+
+
+
+ Port
+
+
+ TCP port number of client
+
+
+
+
+ Connected
+
+
+ Date and time client connected
+
+
+
+
+ Disconnected
+
+
+ Date and time client disconnected
+
+
+
+
+ Time
+
+
+ Time client was connected for
+
+
+
+
+
+
+
+
+
+ ButtonSwitch
+ QToolButton
+
+
RollupContents
QWidget
@@ -456,12 +856,24 @@
1
+
+ LevelMeterSignalDB
+ QWidget
+
+ 1
+
ValueDial
QWidget
1
+
+ PeriodDial
+ QWidget
+
+ 1
+
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksettings.cpp b/plugins/channelrx/remotetcpsink/remotetcpsinksettings.cpp
index 19aeb6d26e..3eabba5c4a 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinksettings.cpp
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksettings.cpp
@@ -38,6 +38,31 @@ void RemoteTCPSinkSettings::resetToDefaults()
m_dataAddress = "0.0.0.0";
m_dataPort = 1234;
m_protocol = SDRA;
+ m_iqOnly = false;
+ m_compression = FLAC;
+ m_compressionLevel = 5;
+ m_blockSize = 16384;
+ m_squelchEnabled = false;
+ m_squelch = -100.0f;
+ m_squelchGate = 0.001f;
+ m_remoteControl = true;
+ m_maxClients = 4;
+ m_timeLimit = 0;
+ m_maxSampleRate = 10000000;
+ m_certificate = "";
+ m_key = "";
+ m_public = false;
+ m_publicAddress = "";
+ m_publicPort = 1234;
+ m_minFrequency = 0;
+ m_maxFrequency = 2000000000;
+ m_antenna = "";
+ m_location = "";
+ m_ipBlacklist = QStringList();
+ m_isotropic = true;
+ m_azimuth = 0.0f;
+ m_elevation = 0.0f;
+ m_rotator = "None";
m_rgbColor = QColor(140, 4, 4).rgb();
m_title = "Remote TCP sink";
m_channelMarker = nullptr;
@@ -62,6 +87,33 @@ QByteArray RemoteTCPSinkSettings::serialize() const
s.writeString(5, m_dataAddress);
s.writeU32(6, m_dataPort);
s.writeS32(7, (int)m_protocol);
+ s.writeBool(42, m_iqOnly);
+ s.writeS32(29, m_compression);
+ s.writeS32(38, m_compressionLevel);
+ s.writeS32(39, m_blockSize);
+ s.writeBool(40, m_squelchEnabled);
+ s.writeFloat(41, m_squelch);
+ s.writeFloat(43, m_squelchGate);
+ s.writeBool(23, m_remoteControl);
+ s.writeS32(24, m_maxClients);
+ s.writeS32(25, m_timeLimit);
+ s.writeS32(28, m_maxSampleRate);
+ s.writeString(26, m_certificate);
+ s.writeString(27, m_key);
+
+ s.writeBool(30, m_public);
+ s.writeString(31, m_publicAddress);
+ s.writeS32(32, m_publicPort);
+ s.writeS64(33, m_minFrequency);
+ s.writeS64(34, m_maxFrequency);
+ s.writeString(35, m_antenna);
+ s.writeString(37, m_location);
+ s.writeList(36, m_ipBlacklist);
+ s.writeBool(44, m_isotropic);
+ s.writeFloat(45, m_azimuth);
+ s.writeFloat(46, m_elevation);
+ s.writeString(47, m_rotator);
+
s.writeU32(8, m_rgbColor);
s.writeString(9, m_title);
s.writeBool(10, m_useReverseAPI);
@@ -115,6 +167,33 @@ bool RemoteTCPSinkSettings::deserialize(const QByteArray& data)
m_dataPort = 1234;
}
d.readS32(7, (int *)&m_protocol, (int)SDRA);
+ d.readBool(42, &m_iqOnly, false);
+ d.readS32(29, (int *)&m_compression, (int)FLAC);
+ d.readS32(38, &m_compressionLevel, 5);
+ d.readS32(39, &m_blockSize, 16384);
+ d.readBool(40, &m_squelchEnabled, false);
+ d.readFloat(41, &m_squelch, -100.0f);
+ d.readFloat(43, &m_squelchGate, 0.001f);
+ d.readBool(23, &m_remoteControl, true);
+ d.readS32(24, &m_maxClients, 4);
+ d.readS32(25, &m_timeLimit, 0);
+ d.readS32(28, &m_maxSampleRate, 10000000);
+
+ d.readString(26, &m_certificate, "");
+ d.readString(27, &m_key, "");
+
+ d.readBool(30, &m_public, false);
+ d.readString(31, &m_publicAddress, "");
+ d.readS32(32, &m_publicPort, 1234);
+ d.readS64(33, &m_minFrequency, 0);
+ d.readS64(34, &m_maxFrequency, 2000000000);
+ d.readString(35, &m_antenna, "");
+ d.readString(37, &m_location, "");
+ d.readList(36, &m_ipBlacklist);
+ d.readBool(44, &m_isotropic, true);
+ d.readFloat(45, &m_azimuth, 0.0f);
+ d.readFloat(46, &m_elevation, 0.0f);
+ d.readString(47, &m_rotator, "None");
d.readU32(8, &m_rgbColor, QColor(0, 255, 255).rgb());
d.readString(9, &m_title, "Remote TCP sink");
@@ -182,6 +261,78 @@ void RemoteTCPSinkSettings::applySettings(const QStringList& settingsKeys, const
if (settingsKeys.contains("protocol")) {
m_protocol = settings.m_protocol;
}
+ if (settingsKeys.contains("iqOnly")) {
+ m_iqOnly = settings.m_iqOnly;
+ }
+ if (settingsKeys.contains("compression")) {
+ m_compression = settings.m_compression;
+ }
+ if (settingsKeys.contains("compressionLevel")) {
+ m_compressionLevel = settings.m_compressionLevel;
+ }
+ if (settingsKeys.contains("blockSize")) {
+ m_blockSize = settings.m_blockSize;
+ }
+ 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;
+ }
+ if (settingsKeys.contains("remoteControl")) {
+ m_remoteControl = settings.m_remoteControl;
+ }
+ if (settingsKeys.contains("maxClients")) {
+ m_maxClients = settings.m_maxClients;
+ }
+ if (settingsKeys.contains("timeLimit")) {
+ m_timeLimit = settings.m_timeLimit;
+ }
+ if (settingsKeys.contains("maxSampleRate")) {
+ m_maxSampleRate = settings.m_maxSampleRate;
+ }
+ if (settingsKeys.contains("certificate")) {
+ m_certificate = settings.m_certificate;
+ }
+ if (settingsKeys.contains("key")) {
+ m_key = settings.m_key;
+ }
+ if (settingsKeys.contains("public")) {
+ m_public = settings.m_public;
+ }
+ if (settingsKeys.contains("publicAddress")) {
+ m_publicAddress = settings.m_publicAddress;
+ }
+ if (settingsKeys.contains("publicPort")) {
+ m_publicPort = settings.m_publicPort;
+ }
+ if (settingsKeys.contains("minFrequency")) {
+ m_minFrequency = settings.m_minFrequency;
+ }
+ if (settingsKeys.contains("maxFrequency")) {
+ m_maxFrequency = settings.m_maxFrequency;
+ }
+ if (settingsKeys.contains("antenna")) {
+ m_antenna = settings.m_antenna;
+ }
+ if (settingsKeys.contains("ipBlacklist")) {
+ m_ipBlacklist = settings.m_ipBlacklist;
+ }
+ if (settingsKeys.contains("isotrophic")) {
+ m_isotropic = settings.m_isotropic;
+ }
+ if (settingsKeys.contains("azimuth")) {
+ m_azimuth = settings.m_azimuth;
+ }
+ if (settingsKeys.contains("elevation")) {
+ m_elevation = settings.m_elevation;
+ }
+ if (settingsKeys.contains("rotator")) {
+ m_rotator = settings.m_rotator;
+ }
if (settingsKeys.contains("rgbColor")) {
m_rgbColor = settings.m_rgbColor;
}
@@ -239,6 +390,78 @@ QString RemoteTCPSinkSettings::getDebugString(const QStringList& settingsKeys, b
if (settingsKeys.contains("protocol") || force) {
ostr << " m_protocol: " << m_protocol;
}
+ if (settingsKeys.contains("iqOnly") || force) {
+ ostr << " m_iqOnly: " << m_iqOnly;
+ }
+ if (settingsKeys.contains("compression") || force) {
+ ostr << " m_compression: " << m_compression;
+ }
+ if (settingsKeys.contains("compressionLevel") || force) {
+ ostr << " m_compressionLevel: " << m_compressionLevel;
+ }
+ if (settingsKeys.contains("blockSize") || force) {
+ ostr << " m_blockSize: " << m_blockSize;
+ }
+ 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;
+ }
+ if (settingsKeys.contains("remoteControl") || force) {
+ ostr << " m_remoteControl: " << m_remoteControl;
+ }
+ if (settingsKeys.contains("maxClients") || force) {
+ ostr << " m_maxClients: " << m_maxClients;
+ }
+ if (settingsKeys.contains("timeLimit") || force) {
+ ostr << " m_timeLimit: " << m_timeLimit;
+ }
+ if (settingsKeys.contains("maxSampleRate") || force) {
+ ostr << " m_maxSampleRate: " << m_maxSampleRate;
+ }
+ if (settingsKeys.contains("certificate") || force) {
+ ostr << " m_certificate: " << m_certificate.toStdString();
+ }
+ if (settingsKeys.contains("key") || force) {
+ ostr << " m_key: " << m_key.toStdString();
+ }
+ if (settingsKeys.contains("public") || force) {
+ ostr << " m_public: " << m_public;
+ }
+ if (settingsKeys.contains("publicAddress") || force) {
+ ostr << " m_publicAddress: " << m_publicAddress.toStdString();
+ }
+ if (settingsKeys.contains("publicPort") || force) {
+ ostr << " m_publicPort: " << m_publicPort;
+ }
+ if (settingsKeys.contains("minFrequency") || force) {
+ ostr << " m_minFrequency: " << m_minFrequency;
+ }
+ if (settingsKeys.contains("maxFrequency") || force) {
+ ostr << " m_maxFrequency: " << m_maxFrequency;
+ }
+ if (settingsKeys.contains("antenna") || force) {
+ ostr << " m_antenna: " << m_antenna.toStdString();
+ }
+ if (settingsKeys.contains("ipBlacklist") || force) {
+ ostr << " m_ipBlacklist: " << m_ipBlacklist.join(" ").toStdString();
+ }
+ if (settingsKeys.contains("isotrophic") || force) {
+ ostr << " m_isotropic: " << m_isotropic;
+ }
+ if (settingsKeys.contains("azimuth") || force) {
+ ostr << " m_azimuth: " << m_azimuth;
+ }
+ if (settingsKeys.contains("elevation") || force) {
+ ostr << " m_elevation: " << m_elevation;
+ }
+ if (settingsKeys.contains("rotator") || force) {
+ ostr << " m_rotator: " << m_rotator.toStdString();
+ }
if (settingsKeys.contains("rgbColor") || force) {
ostr << " m_rgbColor: " << m_rgbColor;
}
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksettings.h b/plugins/channelrx/remotetcpsink/remotetcpsinksettings.h
index 7c54ea47ed..74900631e7 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinksettings.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksettings.h
@@ -24,6 +24,7 @@
#include
#include
+#include
class Serializable;
@@ -31,7 +32,13 @@ struct RemoteTCPSinkSettings
{
enum Protocol {
RTL0, // Compatible with rtl_tcp
- SDRA // SDRangel remote TCP protocol which extends rtl_tcp
+ SDRA, // SDRangel remote TCP protocol which extends rtl_tcp
+ SDRA_WSS // SDRA using WebSocket Secure
+ };
+
+ enum Compressor {
+ FLAC,
+ ZLIB
};
qint32 m_channelSampleRate;
@@ -41,6 +48,34 @@ struct RemoteTCPSinkSettings
QString m_dataAddress;
uint16_t m_dataPort;
enum Protocol m_protocol;
+ bool m_iqOnly; // Send uncompressed IQ only (No position or messages)
+ Compressor m_compression; // How IQ stream is compressed
+ int m_compressionLevel;
+ int m_blockSize;
+ bool m_squelchEnabled;
+ float m_squelch;
+ float m_squelchGate; // In seconds
+ bool m_remoteControl; // Whether remote control is enabled
+ int m_maxClients;
+ int m_timeLimit; // Time limit per connection in minutes, if server busy. 0 = no limit.
+ int m_maxSampleRate;
+
+ QString m_certificate; // SSL certificate
+ QString m_key; // SSL key
+
+ bool m_public; // Whether to list publically
+ QString m_publicAddress; // IP address / host for public listing
+ int m_publicPort; // What port number for public listing
+ qint64 m_minFrequency; // Minimum frequency for public listing
+ qint64 m_maxFrequency; // Maximum frequency for public listing
+ QString m_antenna; // Anntenna description for public listing
+ QString m_location; // Anntenna location for public listing
+ QStringList m_ipBlacklist; // List of IP addresses to refuse connections from
+ bool m_isotropic; // Antenna is isotropic
+ float m_azimuth; // Antenna azimuth angle
+ float m_elevation; // Antenna elevation angle
+ QString m_rotator; // Id of Rotator Controller feature to get az/el from
+
quint32 m_rgbColor;
QString m_title;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.cpp b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.cpp
new file mode 100644
index 0000000000..599bec544f
--- /dev/null
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.cpp
@@ -0,0 +1,372 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "remotetcpsinksettingsdialog.h"
+#include "ui_remotetcpsinksettingsdialog.h"
+
+RemoteTCPSinkSettingsDialog::RemoteTCPSinkSettingsDialog(RemoteTCPSinkSettings *settings, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::RemoteTCPSinkSettingsDialog),
+ m_settings(settings),
+ m_availableRotatorHandler({"sdrangel.feature.gs232controller"})
+{
+ ui->setupUi(this);
+
+ ui->maxClients->setValue(m_settings->m_maxClients);
+ ui->timeLimit->setValue(m_settings->m_timeLimit);
+ ui->maxSampleRate->setValue(m_settings->m_maxSampleRate);
+ ui->iqOnly->setChecked(m_settings->m_iqOnly);
+
+ ui->compressor->setCurrentIndex((int) m_settings->m_compression);
+ ui->compressionLevel->setValue(m_settings->m_compressionLevel);
+ ui->blockSize->setCurrentIndex(ui->blockSize->findText(QString::number(m_settings->m_blockSize)));
+
+ ui->certificate->setText(m_settings->m_certificate);
+ ui->key->setText(m_settings->m_key);
+
+ ui->publicListing->setChecked(m_settings->m_public);
+ ui->publicAddress->setText(m_settings->m_publicAddress);
+ ui->publicPort->setValue(m_settings->m_publicPort);
+ ui->minFrequency->setValue(m_settings->m_minFrequency / 1000000);
+ ui->maxFrequency->setValue(m_settings->m_maxFrequency / 1000000);
+ ui->antenna->setText(m_settings->m_antenna);
+ ui->location->setText(m_settings->m_location);
+ ui->isotropic->setChecked(m_settings->m_isotropic);
+ ui->azimuth->setValue(m_settings->m_azimuth);
+ ui->elevation->setValue(m_settings->m_elevation);
+ ui->rotator->setCurrentText(m_settings->m_rotator);
+
+ for (const auto& ip : m_settings->m_ipBlacklist) {
+ ui->ipBlacklist->addItem(ip);
+ }
+
+ QObject::connect(
+ &m_availableRotatorHandler,
+ &AvailableChannelOrFeatureHandler::channelsOrFeaturesChanged,
+ this,
+ &RemoteTCPSinkSettingsDialog::rotatorsChanged
+ );
+ m_availableRotatorHandler.scanAvailableChannelsAndFeatures();
+}
+
+RemoteTCPSinkSettingsDialog::~RemoteTCPSinkSettingsDialog()
+{
+ delete ui;
+}
+
+void RemoteTCPSinkSettingsDialog::accept()
+{
+ if (!isValid()) {
+ return;
+ }
+
+ QDialog::accept();
+
+ if (ui->maxClients->value() != m_settings->m_maxClients)
+ {
+ m_settings->m_maxClients = ui->maxClients->value();
+ m_settingsKeys.append("maxClients");
+ }
+ if (ui->timeLimit->value() != m_settings->m_timeLimit)
+ {
+ m_settings->m_timeLimit = ui->timeLimit->value();
+ m_settingsKeys.append("timeLimit");
+ }
+ if (ui->maxSampleRate->value() != m_settings->m_maxSampleRate)
+ {
+ m_settings->m_maxSampleRate = ui->maxSampleRate->value();
+ m_settingsKeys.append("maxSampleRate");
+ }
+ if (ui->iqOnly->isChecked() != m_settings->m_iqOnly)
+ {
+ m_settings->m_iqOnly = ui->iqOnly->isChecked();
+ m_settingsKeys.append("iqOnly");
+ }
+ RemoteTCPSinkSettings::Compressor compressor = (RemoteTCPSinkSettings::Compressor) ui->compressor->currentIndex();
+ if (compressor != m_settings->m_compression)
+ {
+ m_settings->m_compression = compressor;
+ m_settingsKeys.append("compression");
+ }
+ if (ui->compressionLevel->value() != m_settings->m_compressionLevel)
+ {
+ m_settings->m_compressionLevel = ui->compressionLevel->value();
+ m_settingsKeys.append("compressionLevel");
+ }
+ int blockSize = ui->blockSize->currentText().toInt();
+ if (blockSize != m_settings->m_blockSize)
+ {
+ m_settings->m_blockSize = blockSize;
+ m_settingsKeys.append("blockSize");
+ }
+ if (ui->certificate->text() != m_settings->m_certificate)
+ {
+ m_settings->m_certificate = ui->certificate->text();
+ m_settingsKeys.append("certificate");
+ }
+ if (ui->key->text() != m_settings->m_key)
+ {
+ m_settings->m_key = ui->key->text();
+ m_settingsKeys.append("key");
+ }
+ if (ui->publicListing->isChecked() != m_settings->m_public)
+ {
+ m_settings->m_public = ui->publicListing->isChecked();
+ m_settingsKeys.append("public");
+ }
+ if (ui->publicAddress->text() != m_settings->m_publicAddress)
+ {
+ m_settings->m_publicAddress = ui->publicAddress->text();
+ m_settingsKeys.append("publicAddress");
+ }
+ if (ui->publicPort->value() != m_settings->m_publicPort)
+ {
+ m_settings->m_publicPort = ui->publicPort->value();
+ m_settingsKeys.append("publicPort");
+ }
+ qint64 minFrequency = ui->minFrequency->value() * 1000000;
+ if (minFrequency != m_settings->m_minFrequency)
+ {
+ m_settings->m_minFrequency = minFrequency;
+ m_settingsKeys.append("minFrequency");
+ }
+ qint64 maxFrequency = ui->maxFrequency->value() * 1000000;
+ if (maxFrequency != m_settings->m_maxFrequency)
+ {
+ m_settings->m_maxFrequency = maxFrequency;
+ m_settingsKeys.append("maxFrequency");
+ }
+ if (ui->antenna->text() != m_settings->m_antenna)
+ {
+ m_settings->m_antenna = ui->antenna->text();
+ m_settingsKeys.append("antenna");
+ }
+ if (ui->location->text() != m_settings->m_location)
+ {
+ m_settings->m_location = ui->location->text();
+ m_settingsKeys.append("location");
+ }
+ if (ui->isotropic->isChecked() != m_settings->m_isotropic)
+ {
+ m_settings->m_isotropic = ui->isotropic->isChecked();
+ m_settingsKeys.append("isotropic");
+ }
+ if (ui->azimuth->value() != m_settings->m_azimuth)
+ {
+ m_settings->m_azimuth = ui->azimuth->value();
+ m_settingsKeys.append("azimuth");
+ }
+ if (ui->elevation->value() != m_settings->m_elevation)
+ {
+ m_settings->m_elevation = ui->elevation->value();
+ m_settingsKeys.append("elevation");
+ }
+ if (ui->rotator->currentText() != m_settings->m_rotator)
+ {
+ m_settings->m_rotator = ui->rotator->currentText();
+ m_settingsKeys.append("rotator");
+ }
+ QStringList ipBlacklist;
+ for (int i = 0; i < ui->ipBlacklist->count(); i++)
+ {
+ QString ip = ui->ipBlacklist->item(i)->text().trimmed();
+ if (!ip.isEmpty()) {
+ ipBlacklist.append(ip);
+ }
+ }
+ if (ipBlacklist != m_settings->m_ipBlacklist)
+ {
+ m_settings->m_ipBlacklist = ipBlacklist;
+ m_settingsKeys.append("ipBlacklist");
+ }
+}
+
+void RemoteTCPSinkSettingsDialog::on_browseCertificate_clicked()
+{
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Select SSL Certificate"),
+ "",
+ tr("SSL certificate (*.cert *.pem)"));
+ if (!fileName.isEmpty()) {
+ ui->certificate->setText(fileName);
+ }
+}
+
+void RemoteTCPSinkSettingsDialog::on_browseKey_clicked()
+{
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Select SSL Key"),
+ "",
+ tr("SSL key (*.key *.pem)"));
+ if (!fileName.isEmpty()) {
+ ui->key->setText(fileName);
+ }
+}
+
+void RemoteTCPSinkSettingsDialog::on_addIP_clicked()
+{
+ QListWidgetItem *item = new QListWidgetItem("1.1.1.1");
+ item->setFlags(Qt::ItemIsEditable | item->flags());
+ ui->ipBlacklist->addItem(item);
+ item->setSelected(true);
+}
+
+void RemoteTCPSinkSettingsDialog::on_removeIP_clicked()
+{
+ qDeleteAll(ui->ipBlacklist->selectedItems());
+}
+
+void RemoteTCPSinkSettingsDialog::on_publicListing_toggled()
+{
+ displayValid();
+ displayEnabled();
+}
+
+void RemoteTCPSinkSettingsDialog::on_publicAddress_textChanged()
+{
+ displayValid();
+}
+
+void RemoteTCPSinkSettingsDialog::on_compressor_currentIndexChanged(int index)
+{
+ if (index == 0)
+ {
+ // FLAC settings
+ ui->compressionLevel->setMaximum(8);
+ ui->blockSize->clear();
+ ui->blockSize->addItem("4096");
+ ui->blockSize->addItem("16384");
+ ui->blockSize->setCurrentIndex(1);
+ }
+ else if (index == 1)
+ {
+ // zlib settings
+ ui->compressionLevel->setMaximum(9);
+ ui->blockSize->clear();
+ ui->blockSize->addItem("4096");
+ ui->blockSize->addItem("8192");
+ ui->blockSize->addItem("16384");
+ ui->blockSize->addItem("32768");
+ ui->blockSize->setCurrentIndex(3);
+ }
+}
+
+void RemoteTCPSinkSettingsDialog::on_iqOnly_toggled(bool checked)
+{
+ ui->compressionSettings->setEnabled(!checked);
+}
+
+void RemoteTCPSinkSettingsDialog::on_isotropic_toggled(bool checked)
+{
+ (void) checked;
+
+ displayEnabled();
+}
+
+void RemoteTCPSinkSettingsDialog::on_rotator_currentIndexChanged(int index)
+{
+ (void) index;
+
+ displayEnabled();
+}
+
+bool RemoteTCPSinkSettingsDialog::isValid()
+{
+ bool valid = true;
+
+ if (ui->publicListing->isChecked() && ui->publicAddress->text().isEmpty()) {
+ valid = false;
+ }
+
+ return valid;
+}
+
+void RemoteTCPSinkSettingsDialog::displayValid()
+{
+ bool valid = isValid();
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
+}
+
+void RemoteTCPSinkSettingsDialog::displayEnabled()
+{
+ bool enabled = ui->publicListing->isChecked();
+ bool none = ui->rotator->currentText() == "None";
+ bool isotropic = ui->isotropic->isChecked();
+
+ ui->publicAddressLabel->setEnabled(enabled);
+ ui->publicAddress->setEnabled(enabled);
+ ui->publicPort->setEnabled(enabled);
+ ui->frequencyLabel->setEnabled(enabled);
+ ui->minFrequency->setEnabled(enabled);
+ ui->maxFrequency->setEnabled(enabled);
+ ui->frequencyUnits->setEnabled(enabled);
+ ui->antennaLabel->setEnabled(enabled);
+ ui->antenna->setEnabled(enabled);
+ ui->locationLabel->setEnabled(enabled);
+ ui->location->setEnabled(enabled);
+ ui->isotropicLabel->setEnabled(enabled);
+ ui->isotropic->setEnabled(enabled);
+ ui->rotatorLabel->setEnabled(enabled && !isotropic);
+ ui->rotator->setEnabled(enabled && !isotropic);
+ ui->directionLabel->setEnabled(enabled && !isotropic && none);
+ ui->azimuthLabel->setEnabled(enabled && !isotropic && none);
+ ui->azimuth->setEnabled(enabled && !isotropic && none);
+ ui->elevationLabel->setEnabled(enabled && !isotropic && none);
+ ui->elevation->setEnabled(enabled && !isotropic && none);
+}
+
+void RemoteTCPSinkSettingsDialog::rotatorsChanged(const QStringList& renameFrom, const QStringList& renameTo)
+{
+ AvailableChannelOrFeatureList rotators = m_availableRotatorHandler.getAvailableChannelOrFeatureList();
+ updateRotatorList(rotators, renameFrom, renameTo);
+}
+
+void RemoteTCPSinkSettingsDialog::updateRotatorList(const AvailableChannelOrFeatureList& rotators, const QStringList& renameFrom, const QStringList& renameTo)
+{
+ // Update rotator settting if it has been renamed
+ if (renameFrom.contains(m_settings->m_rotator)) {
+ m_settings->m_rotator = renameTo[renameFrom.indexOf(m_settings->m_rotator)];
+ }
+
+ // Update list of rotators
+ ui->rotator->blockSignals(true);
+ ui->rotator->clear();
+ ui->rotator->addItem("None");
+
+ for (const auto& rotator : rotators) {
+ ui->rotator->addItem(rotator.getLongId());
+ }
+
+ // Rotator feature can be created after this plugin, so select it
+ // if the chosen rotator appears
+ int rotatorIndex = ui->rotator->findText(m_settings->m_rotator);
+
+ if (rotatorIndex >= 0)
+ {
+ ui->rotator->setCurrentIndex(rotatorIndex);
+ }
+ else
+ {
+ ui->rotator->setCurrentIndex(0); // return to None
+ }
+
+ ui->rotator->blockSignals(false);
+
+ displayEnabled();
+}
\ No newline at end of file
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.h b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.h
new file mode 100644
index 0000000000..8942351c40
--- /dev/null
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.h
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 INCLUDE_REMOTETCPSINKSETTINGSDIALOG_H
+#define INCLUDE_REMOTETCPSINKSETTINGSDIALOG_H
+
+#include
+
+#include "availablechannelorfeaturehandler.h"
+
+#include "remotetcpsinksettings.h"
+
+namespace Ui {
+ class RemoteTCPSinkSettingsDialog;
+}
+
+class RemoteTCPSinkSettingsDialog : public QDialog {
+ Q_OBJECT
+public:
+ explicit RemoteTCPSinkSettingsDialog(RemoteTCPSinkSettings *settings, QWidget* parent = nullptr);
+ ~RemoteTCPSinkSettingsDialog();
+
+ const QStringList& getSettingsKeys() const { return m_settingsKeys; };
+
+private slots:
+ void accept() override;
+ void on_browseCertificate_clicked();
+ void on_browseKey_clicked();
+ void on_addIP_clicked();
+ void on_removeIP_clicked();
+ void on_publicListing_toggled();
+ void on_publicAddress_textChanged();
+ void on_compressor_currentIndexChanged(int index);
+ void on_iqOnly_toggled(bool checked);
+ void on_isotropic_toggled(bool checked);
+ void on_rotator_currentIndexChanged(int index);
+ void rotatorsChanged(const QStringList& renameFrom, const QStringList& renameTo);
+
+private:
+ Ui::RemoteTCPSinkSettingsDialog *ui;
+ RemoteTCPSinkSettings *m_settings;
+ QStringList m_settingsKeys;
+ AvailableChannelOrFeatureHandler m_availableRotatorHandler;
+
+ bool isValid();
+ void displayValid();
+ void displayEnabled();
+ void updateRotatorList(const AvailableChannelOrFeatureList& rotators, const QStringList& renameFrom, const QStringList& renameTo);
+};
+
+#endif // INCLUDE_REMOTETCPSINKSETTINGSDIALOG_H
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.ui b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.ui
new file mode 100644
index 0000000000..269659df7d
--- /dev/null
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.ui
@@ -0,0 +1,675 @@
+
+
+ RemoteTCPSinkSettingsDialog
+
+
+
+ 0
+ 0
+ 409
+ 947
+
+
+
+
+ 9
+
+
+
+ Settings
+
+
+ -
+
+
+ Server Settings
+
+
+
-
+
+
+ Maximum channel sample rate
+
+
+ 2700
+
+
+ 100000000
+
+
+ 10000000
+
+
+
+ -
+
+
+ Time Limit
+
+
+
+ -
+
+
+ Max Clients
+
+
+
+ -
+
+
+ Connection time limit in minutes if max clients reached. 0 for no limit.
+
+
+ 100000
+
+
+
+ -
+
+
+ Max Ch. Sample Rate
+
+
+
+ -
+
+
+ S/s
+
+
+
+ -
+
+
+ Maximum number of simultaneous clients
+
+
+ 0
+
+
+ 10000
+
+
+
+ -
+
+
+ mins
+
+
+
+ -
+
+
+ IQ only
+
+
+
+ -
+
+
+ Transmit uncompressed IQ only. Disables compression, position and messaging support.
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Compression
+
+
+
-
+
+
+ Compressor
+
+
+
+ -
+
+
-
+
+ FLAC
+
+
+ -
+
+ zlib
+
+
+
+
+ -
+
+
+ Compression Level
+
+
+
+ -
+
+
+ 0 - Least compression. 8 - Most compression Higher compression requires more CPU.
+
+
+ 8
+
+
+
+ -
+
+
+ Block size
+
+
+
+ -
+
+
-
+
+ 4096
+
+
+ -
+
+ 16384
+
+
+
+
+
+
+
+ -
+
+
+ SSL Settings
+
+
+
-
+
+
+ SSL certificate
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/load.png:/load.png
+
+
+
+ -
+
+
+ Key
+
+
+
+ -
+
+
+ SSL certificate key
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Certificate
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/load.png:/load.png
+
+
+
+
+
+
+ -
+
+
+ Public Directory
+
+
+
-
+
+
+ false
+
+
+ Antenna
+
+
+
+ -
+
+
+ false
+
+
+ Minimum recommend frequency in MHz
+
+
+ 20000
+
+
+
+ -
+
+
+ false
+
+
+ Publically accessible port number
+
+
+ 1024
+
+
+ 65535
+
+
+ 1234
+
+
+
+ -
+
+
+ false
+
+
+ Direction
+
+
+
+ -
+
+
+ false
+
+
+ Town and country where antenna is located
+
+
+ 255
+
+
+
+ -
+
+
+ List Server
+
+
+
+ -
+
+
+ false
+
+
+ Isotropic
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Az
+
+
+
+ -
+
+
+ false
+
+
+ Antenna azimuth in degrees
+
+
+ 3
+
+
+ 360.000000000000000
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ false
+
+
+ El
+
+
+
+ -
+
+
+ false
+
+
+ Antenna elevation in degrees
+
+
+ 3
+
+
+ -90.000000000000000
+
+
+ 90.000000000000000
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Maximum recommend frequency in MHz
+
+
+ 20000
+
+
+
+ -
+
+
+ false
+
+
+ Address
+
+
+
+ -
+
+
+ false
+
+
+ Location
+
+
+
+ -
+
+
+ false
+
+
+ MHz
+
+
+
+ -
+
+
+ false
+
+
+ Publically accessible IP address or hostname
+
+
+
+ -
+
+
+ false
+
+
+ Check to indicate an antenna that is isotropic (non-directional)
+
+
+
+
+
+
+ -
+
+
+ Whether to list the server as publically accessible
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Frequency Range
+
+
+
+ -
+
+
+ false
+
+
+ Antenna description
+
+
+ 255
+
+
+
+ -
+
+
+ false
+
+
+ Rotator
+
+
+
+ -
+
+
+ false
+
+
+ Rotator feature to get antenna direction from
+
+
-
+
+ None
+
+
+
+
+
+
+
+ -
+
+
+ IP Blacklist
+
+
+
-
+
+
+ List of IP addresses from which connections should not be allowed
+
+
+ QAbstractItemView::MultiSelection
+
+
+
+ -
+
+
-
+
+
+ +
+
+
+
+ -
+
+
+ -
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ maxClients
+ timeLimit
+ maxSampleRate
+ iqOnly
+ compressor
+ compressionLevel
+ blockSize
+ certificate
+ browseCertificate
+ key
+ browseKey
+ publicListing
+ publicAddress
+ publicPort
+ minFrequency
+ maxFrequency
+ antenna
+ location
+ isotropic
+ rotator
+ azimuth
+ elevation
+ ipBlacklist
+ addIP
+ removeIP
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ RemoteTCPSinkSettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ RemoteTCPSinkSettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksink.cpp b/plugins/channelrx/remotetcpsink/remotetcpsinksink.cpp
index b00072440c..d5977e32bf 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinksink.cpp
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksink.cpp
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
-// Copyright (C) 2022-2023 Jon Beniston, M7RCE //
+// Copyright (C) 2022-2024 Jon Beniston, M7RCE //
// Copyright (C) 2022 Jiřà Pinkava //
// //
// This program is free software; you can redistribute it and/or modify //
@@ -18,6 +18,9 @@
#include
#include
+#include
+#include
+#include
#include "channel/channelwebapiutils.h"
#include "device/deviceapi.h"
@@ -26,22 +29,69 @@
#include "remotetcpsinksink.h"
#include "remotetcpsink.h"
+static FLAC__StreamEncoderWriteStatus flacWriteCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t currentFrame, void *clientData)
+{
+ RemoteTCPSinkSink *sink = (RemoteTCPSinkSink*) clientData;
+
+ return sink->flacWrite(encoder, buffer, bytes, samples, currentFrame);
+}
+
RemoteTCPSinkSink::RemoteTCPSinkSink() :
- m_running(false),
- m_messageQueueToGUI(nullptr),
- m_messageQueueToChannel(nullptr),
- m_channelFrequencyOffset(0),
- m_channelSampleRate(48000),
- m_linearGain(1.0f),
- m_server(nullptr)
+ m_running(false),
+ m_messageQueueToGUI(nullptr),
+ m_messageQueueToChannel(nullptr),
+ m_channelFrequencyOffset(0),
+ m_channelSampleRate(48000),
+ m_linearGain(1.0f),
+ m_server(nullptr),
+ m_webSocketServer(nullptr),
+ m_encoder(nullptr),
+ m_zStreamInitialised(false),
+ m_zInBuf(m_zBufSize, '\0'),
+ m_zOutBuf(m_zBufSize, '\0'),
+ m_zInBufCount(0),
+ m_bytesUncompressed(0),
+ m_bytesCompressed(0),
+ m_bytesTransmitted(0),
+ m_squelchLevel(-150.0f),
+ m_squelchCount(0),
+ m_squelchOpen(false),
+ m_squelchDelayLine(48000/2),
+ m_magsq(0.0f),
+ m_magsqSum(0.0f),
+ m_magsqPeak(0.0f),
+ m_magsqCount(0),
+ m_centerFrequency(0.0),
+ m_ppmCorrection(0),
+ m_biasTeeEnabled(false),
+ m_directSampling(false),
+ m_agc(false),
+ m_dcOffsetRemoval(false),
+ m_iqCorrection(false),
+ m_devSampleRate(0),
+ m_log2Decim(0),
+ m_rfBW(0),
+ m_gain(),
+ m_timer(this),
+ m_azimuth(std::numeric_limits::quiet_NaN()),
+ m_elevation(std::numeric_limits::quiet_NaN())
{
qDebug("RemoteTCPSinkSink::RemoteTCPSinkSink");
applySettings(m_settings, QStringList(), true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
+
+ // Get updated when position changes
+ connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &RemoteTCPSinkSink::preferenceChanged);
+
+ m_timer.setSingleShot(false);
+ m_timer.setInterval(500);
+ connect(&m_timer, &QTimer::timeout, this, &RemoteTCPSinkSink::checkDeviceSettings);
}
RemoteTCPSinkSink::~RemoteTCPSinkSink()
{
+ qDebug("RemoteTCPSinkSink::~RemoteTCPSinkSink");
+ disconnect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &RemoteTCPSinkSink::preferenceChanged);
stop();
}
@@ -71,6 +121,7 @@ void RemoteTCPSinkSink::started()
QMutexLocker mutexLocker(&m_mutex);
startServer();
disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
+ m_timer.start();
}
void RemoteTCPSinkSink::finished()
@@ -78,6 +129,7 @@ void RemoteTCPSinkSink::finished()
QMutexLocker mutexLocker(&m_mutex);
stopServer();
disconnect(thread(), SIGNAL(finished()), this, SLOT(finished()));
+ m_timer.stop();
m_running = false;
}
@@ -119,19 +171,27 @@ void RemoteTCPSinkSink::feed(const SampleVector::const_iterator& begin, const Sa
}
}
+ for (const auto client : m_clients) {
+ client->flush();
+ }
+ QDateTime currentDateTime = QDateTime::currentDateTime();
if (m_bwDateTime.isValid())
{
- QDateTime currentDateTime = QDateTime::currentDateTime();
qint64 msecs = m_bwDateTime.msecsTo(currentDateTime) ;
- if (msecs > 1000)
+ if (msecs >= 1000)
{
- float bw = (8*m_bwBytes)/(msecs/1000.0f);
+ float secs = msecs / 1000.0f;
+ float bw = (8 * m_bwBytes) / secs;
+ float networkBW = (8 * m_bytesTransmitted) / secs;
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgReportBW::create(bw));
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgReportBW::create(bw, networkBW, m_bytesUncompressed, m_bytesCompressed, m_bytesTransmitted));
}
m_bwDateTime = currentDateTime;
m_bwBytes = bytes;
+ m_bytesUncompressed = 0;
+ m_bytesCompressed = 0;
+ m_bytesTransmitted = 0;
}
else
{
@@ -140,73 +200,226 @@ void RemoteTCPSinkSink::feed(const SampleVector::const_iterator& begin, const Sa
}
else
{
- m_bwDateTime = QDateTime::currentDateTime();
+ m_bwDateTime = currentDateTime;
m_bwBytes = bytes;
}
}
}
+static qint32 clamp8(qint32 x)
+{
+ x = std::max(x, -128);
+ x = std::min(x, 127);
+ return x;
+}
+
+static qint32 clamp16(qint32 x)
+{
+ x = std::max(x, -32768);
+ x = std::min(x, 32767);
+ return x;
+}
+
+static qint32 clamp24(qint32 x)
+{
+ x = std::max(x, -8388608);
+ x = std::min(x, 8388607);
+ return x;
+}
+
void RemoteTCPSinkSink::processOneSample(Complex &ci)
{
- if (m_settings.m_sampleBits == 8)
+ // Apply gain
+ ci = ci * m_linearGain;
+
+ // Calculate channel power
+ Real re = ci.real();
+ Real im = ci.imag();
+ Real magsq = (re*re + im*im) / (SDR_RX_SCALED*SDR_RX_SCALED);
+ m_movingAverage(magsq);
+ m_magsq = m_movingAverage.asDouble();
+ m_magsqSum += magsq;
+ m_magsqPeak = std::max(magsq, m_magsqPeak);
+ m_magsqCount++;
+
+ // Squelch
+ if (m_settings.m_squelchEnabled)
{
- // Transmit data as per rtl_tcp - Interleaved unsigned 8-bit IQ
- quint8 iqBuf[2];
- iqBuf[0] = ((int)(ci.real() / SDR_RX_SCALEF * 256.0f * m_linearGain)) + 128;
- iqBuf[1] = ((int)(ci.imag() / SDR_RX_SCALEF * 256.0f * m_linearGain)) + 128;
- for (auto client : m_clients) {
- client->write((const char *)iqBuf, sizeof(iqBuf));
+ // Convert gate time from seconds to samples
+ int squelchGate = m_settings.m_squelchGate * m_channelSampleRate;
+
+ m_squelchDelayLine.write(ci);
+
+ if (m_magsq < m_squelchLevel)
+ {
+ if (m_squelchCount > 0) {
+ m_squelchCount--;
+ }
}
- }
- else if (m_settings.m_sampleBits == 16)
- {
- // Interleaved little-endian signed 16-bit IQ
- quint8 iqBuf[2*2];
- qint32 i, q;
- i = ((qint32)(ci.real() / SDR_RX_SCALEF * 65536.0f * m_linearGain));
- q = ((qint32)(ci.imag() / SDR_RX_SCALEF * 65536.0f * m_linearGain));
- iqBuf[1] = (i >> 8) & 0xff;
- iqBuf[0] = i & 0xff;
- iqBuf[3] = (q >> 8) & 0xff;
- iqBuf[2] = q & 0xff;
- for (auto client : m_clients) {
- client->write((const char *)iqBuf, sizeof(iqBuf));
+ else
+ {
+ m_squelchCount = squelchGate;
+ }
+ m_squelchOpen = m_squelchCount > 0;
+
+ if (m_squelchOpen) {
+ ci = m_squelchDelayLine.readBack(squelchGate);
+ } else {
+ ci = 0.0;
}
}
- else if (m_settings.m_sampleBits == 24)
+
+ if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC))
{
- // Interleaved little-endian signed 24-bit IQ
- quint8 iqBuf[3*2];
- qint32 i, q;
- i = ((qint32)(ci.real() * m_linearGain));
- q = ((qint32)(ci.imag() * m_linearGain));
- iqBuf[2] = (i >> 16) & 0xff;
- iqBuf[1] = (i >> 8) & 0xff;
- iqBuf[0] = i & 0xff;
- iqBuf[5] = (q >> 16) & 0xff;
- iqBuf[4] = (q >> 8) & 0xff;
- iqBuf[3] = q & 0xff;
- for (auto client : m_clients) {
- client->write((const char *)iqBuf, sizeof(iqBuf));
+ // Compress using FLAC
+ FLAC__int32 iqBuf[2];
+
+ if (m_settings.m_sampleBits == 8)
+ {
+ iqBuf[0] = (qint32) (ci.real() / 65536.0f);
+ iqBuf[1] = (qint32) (ci.imag() / 65536.0f);
+ iqBuf[0] = clamp8(iqBuf[0]);
+ iqBuf[1] = clamp8(iqBuf[1]);
+ }
+ else if (m_settings.m_sampleBits == 16)
+ {
+ iqBuf[0] = (qint32) (ci.real() / 256.0f);
+ iqBuf[1] = (qint32) (ci.imag() / 256.0f);
+ iqBuf[0] = clamp16(iqBuf[0]);
+ iqBuf[1] = clamp16(iqBuf[1]);
+ }
+ else if (m_settings.m_sampleBits == 24)
+ {
+ iqBuf[0] = (qint32) ci.real();
+ iqBuf[1] = (qint32) ci.imag();
+ iqBuf[0] = clamp24(iqBuf[0]);
+ iqBuf[1] = clamp24(iqBuf[1]);
+ }
+ else
+ {
+ iqBuf[0] = (qint32) ci.real();
+ iqBuf[1] = (qint32) ci.imag();
+ }
+ int bytes = 2 * m_settings.m_sampleBits / 8;
+ m_bytesUncompressed += bytes;
+
+ if (m_encoder && !FLAC__stream_encoder_process_interleaved(m_encoder, iqBuf, 1)) { // Number of samples in one channel
+ qDebug() << "RemoteTCPSinkSink::processOneSample: FLAC failed to encode:" << FLAC__stream_encoder_get_state(m_encoder);
}
}
else
{
- // Interleaved little-endian signed 32-bit IQ
quint8 iqBuf[4*2];
- qint32 i, q;
- i = ((qint32)(ci.real() * m_linearGain));
- q = ((qint32)(ci.imag() * m_linearGain));
- iqBuf[3] = (i >> 24) & 0xff;
- iqBuf[2] = (i >> 16) & 0xff;
- iqBuf[1] = (i >> 8) & 0xff;
- iqBuf[0] = i & 0xff;
- iqBuf[7] = (q >> 24) & 0xff;
- iqBuf[6] = (q >> 16) & 0xff;
- iqBuf[5] = (q >> 8) & 0xff;
- iqBuf[4] = q & 0xff;
- for (auto client : m_clients) {
- client->write((const char *)iqBuf, sizeof(iqBuf));
+
+ if (m_settings.m_sampleBits == 8)
+ {
+ // Transmit data as per rtl_tcp - Interleaved unsigned 8-bit IQ
+ iqBuf[0] = clamp8((qint32) (ci.real() / 65536.0f)) + 128;
+ iqBuf[1] = clamp8((qint32) (ci.imag() / 65536.0f)) + 128;
+ }
+ else if (m_settings.m_sampleBits == 16)
+ {
+ // Interleaved little-endian signed 16-bit IQ
+ qint32 i, q;
+ i = clamp16((qint32) (ci.real() / 256.0f));
+ q = clamp16((qint32) (ci.imag() / 256.0f));
+ iqBuf[1] = (i >> 8) & 0xff;
+ iqBuf[0] = i & 0xff;
+ iqBuf[3] = (q >> 8) & 0xff;
+ iqBuf[2] = q & 0xff;
+ }
+ else if (m_settings.m_sampleBits == 24)
+ {
+ // Interleaved little-endian signed 24-bit IQ
+ qint32 i, q;
+ i = clamp24((qint32) ci.real());
+ q = clamp24((qint32) ci.imag());
+ iqBuf[2] = (i >> 16) & 0xff;
+ iqBuf[1] = (i >> 8) & 0xff;
+ iqBuf[0] = i & 0xff;
+ iqBuf[5] = (q >> 16) & 0xff;
+ iqBuf[4] = (q >> 8) & 0xff;
+ iqBuf[3] = q & 0xff;
+ }
+ else
+ {
+ // Interleaved little-endian signed 32-bit IQ
+ qint32 i, q;
+ i = (qint32) ci.real();
+ q = (qint32) ci.imag();
+ iqBuf[3] = (i >> 24) & 0xff;
+ iqBuf[2] = (i >> 16) & 0xff;
+ iqBuf[1] = (i >> 8) & 0xff;
+ iqBuf[0] = i & 0xff;
+ iqBuf[7] = (q >> 24) & 0xff;
+ iqBuf[6] = (q >> 16) & 0xff;
+ iqBuf[5] = (q >> 8) & 0xff;
+ iqBuf[4] = q & 0xff;
+ }
+
+ int bytes = 2 * m_settings.m_sampleBits / 8;
+ m_bytesUncompressed += bytes;
+
+ if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::ZLIB))
+ {
+ if (m_zStreamInitialised)
+ {
+ // Store in block buffer
+ memcpy(&m_zInBuf.data()[m_zInBufCount], iqBuf, bytes);
+ m_zInBufCount += bytes;
+
+ if (m_zInBufCount >= m_settings.m_blockSize)
+ {
+ // Compress using zlib
+ m_zStream.next_in = (Bytef *) m_zInBuf.data();
+ m_zStream.avail_in = m_zInBufCount;
+ m_zStream.next_out = (Bytef *) m_zOutBuf.data();
+ m_zStream.avail_out = m_zOutBuf.size();
+ int ret = deflate(&m_zStream, Z_FINISH);
+
+ if (ret == Z_STREAM_END) {
+ deflateReset(&m_zStream);
+ } else if (ret != Z_OK) {
+ qDebug() << "RemoteTCPSinkSink::processOneSample: Failed to deflate" << ret;
+ }
+ if (m_zStream.avail_in != 0) {
+ qDebug() << "RemoteTCPSinkSink::processOneSample: Data still in input buffer";
+ }
+ int compressedBytes = m_zOutBuf.size() - m_zStream.avail_out;
+
+ //qDebug() << "zlib ret" << ret << "m_settings.m_blockSize" << m_settings.m_blockSize << "m_zInBufCount" << m_zInBufCount << "compressedBytes" << compressedBytes << "avail_in" << m_zStream.avail_in << "avail_out" << m_zStream.avail_out << " % " << round(100.0 * compressedBytes / (float) m_zInBufCount );
+
+ m_zInBufCount = 0;
+
+ // Send to clients
+
+ int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
+ char header[1+4];
+
+ header[0] = (char) RemoteTCPProtocol::dataIQzlib;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], compressedBytes);
+
+ for (int i = 0; i < clients; i++)
+ {
+ m_clients[i]->write(header, sizeof(header));
+ m_bytesTransmitted += sizeof(header);
+ m_clients[i]->write((const char *)m_zOutBuf.data(), compressedBytes);
+ m_bytesTransmitted += compressedBytes;
+ }
+ m_bytesCompressed += sizeof(header) + compressedBytes;
+ }
+ }
+ }
+ else
+ {
+ // Send uncompressed
+ int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
+
+ for (int i = 0; i < clients; i++)
+ {
+ m_clients[i]->write((const char *)iqBuf, bytes);
+ m_bytesTransmitted += bytes;
+ }
}
}
}
@@ -232,10 +445,15 @@ void RemoteTCPSinkSink::applyChannelSettings(int channelSampleRate, int channelF
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
+
+ m_squelchDelayLine.resize(m_settings.m_squelchGate * m_channelSampleRate + 1);
}
-void RemoteTCPSinkSink::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool remoteChange)
+void RemoteTCPSinkSink::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool restartRequired)
{
+ bool initFLAC = false;
+ bool initZLib = false;
+
QMutexLocker mutexLocker(&m_mutex);
qDebug() << "RemoteTCPSinkSink::applySettings:" << settings.getDebugString(settingsKeys, force) << " force: " << force;
@@ -252,14 +470,100 @@ void RemoteTCPSinkSink::applySettings(const RemoteTCPSinkSettings& settings, con
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
+ // Update time limit for connected clients
+ if (settingsKeys.contains("timeLimit") && (m_settings.m_timeLimit != settings.m_timeLimit))
+ {
+ if (settings.m_timeLimit > 0)
+ {
+ // Set new timelimit
+ for (int i = 0; i < m_timers.size(); i++) {
+ m_timers[i]->setInterval(settings.m_timeLimit * 60 * 1000);
+ }
+ // Start timers if they weren't previously started
+ if (m_settings.m_timeLimit == 0)
+ {
+ for (int i = 0; i < std::min((int) m_timers.size(), m_settings.m_maxClients); i++) {
+ m_timers[i]->start();
+ }
+ }
+ }
+ else
+ {
+ // Stop any existing timers
+ for (int i = 0; i < m_timers.size(); i++) {
+ m_timers[i]->stop();
+ }
+ }
+ }
+
+ if ((settingsKeys.contains("compressionLevel") && (settings.m_compressionLevel != m_settings.m_compressionLevel))
+ || (settingsKeys.contains("compression") && (settings.m_compression != m_settings.m_compression))
+ || (settingsKeys.contains("sampleBits") && (settings.m_sampleBits != m_settings.m_sampleBits))
+ || (settingsKeys.contains("blockSize") && (settings.m_blockSize != m_settings.m_blockSize))
+ || (settingsKeys.contains("channelSampleRate") && (settings.m_channelSampleRate != m_settings.m_channelSampleRate))
+ || force)
+ {
+ initFLAC = true;
+ }
+
+ if ((settingsKeys.contains("compressionLevel") && (settings.m_compressionLevel != m_settings.m_compressionLevel))
+ || (settingsKeys.contains("compression") && (settings.m_compression != m_settings.m_compression))
+ || force)
+ {
+ initZLib = true;
+ }
+
+ if (settingsKeys.contains("squelch") || force)
+ {
+ m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
+ m_movingAverage.reset();
+ m_squelchCount = 0;
+ }
+
+ if (settingsKeys.contains("squelchGate") || force) {
+ m_squelchDelayLine.resize(settings.m_squelchGate * m_channelSampleRate + 1);
+ }
+
// Do clients need to reconnect to get these updated settings?
+ // settingsKeys will be empty if force is set
bool restart = (settingsKeys.contains("dataAddress") && (m_settings.m_dataAddress != settings.m_dataAddress))
|| (settingsKeys.contains("dataPort") && (m_settings.m_dataPort != settings.m_dataPort))
+ || (settingsKeys.contains("certificate") && (m_settings.m_certificate != settings.m_certificate))
+ || (settingsKeys.contains("key") && (m_settings.m_key!= settings.m_key))
|| (settingsKeys.contains("sampleBits") && (m_settings.m_sampleBits != settings.m_sampleBits))
|| (settingsKeys.contains("protocol") && (m_settings.m_protocol != settings.m_protocol))
- || ( !remoteChange
- && (settingsKeys.contains("channelSampleRate") && (m_settings.m_channelSampleRate != settings.m_channelSampleRate))
- );
+ || (settingsKeys.contains("compression") && (m_settings.m_compression != settings.m_compression))
+ || (settingsKeys.contains("remoteControl") && (m_settings.m_remoteControl != settings.m_remoteControl))
+ || initFLAC
+ || restartRequired
+ ;
+
+ if (!restart && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0) && !m_settings.m_iqOnly)
+ {
+ // Forward settings to clients if they've changed
+ if ((settingsKeys.contains("channelSampleRate") || force) && (settings.m_channelSampleRate != m_settings.m_channelSampleRate)) {
+ sendCommand(RemoteTCPProtocol::setChannelSampleRate, settings.m_channelSampleRate);
+ }
+ if ((settingsKeys.contains("inputFrequencyOffset") || force) && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) {
+ sendCommand(RemoteTCPProtocol::setChannelFreqOffset, settings.m_inputFrequencyOffset);
+ }
+ if ((settingsKeys.contains("gain") || force) && (settings.m_gain != m_settings.m_gain)) {
+ sendCommand(RemoteTCPProtocol::setChannelGain, settings.m_gain);
+ }
+ if ((settingsKeys.contains("sampleBits") || force) && (settings.m_sampleBits != m_settings.m_sampleBits)) {
+ sendCommand(RemoteTCPProtocol::setSampleBitDepth, settings.m_sampleBits);
+ }
+ if ((settingsKeys.contains("squelchEnabled") || force) && (settings.m_squelchEnabled != m_settings.m_squelchEnabled)) {
+ sendCommand(RemoteTCPProtocol::setIQSquelchEnabled, (quint32) settings.m_squelchEnabled);
+ }
+ if ((settingsKeys.contains("squelch") || force) && (settings.m_squelch != m_settings.m_squelch)) {
+ sendCommandFloat(RemoteTCPProtocol::setIQSquelch, settings.m_squelch);
+ }
+ if ((settingsKeys.contains("squelchGate") || force) && (settings.m_squelchGate != m_settings.m_squelchGate)) {
+ sendCommandFloat(RemoteTCPProtocol::setIQSquelchGate, settings.m_squelchGate);
+ }
+ // m_remoteControl rather than restart?
+ }
if (force) {
m_settings = settings;
@@ -267,51 +571,228 @@ void RemoteTCPSinkSink::applySettings(const RemoteTCPSinkSettings& settings, con
m_settings.applySettings(settingsKeys, settings);
}
- if (m_running && restart) {
+ if (m_running && (restart || force)) {
startServer();
}
+
+ if (initFLAC && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC))
+ {
+ if (m_encoder)
+ {
+ // Delete existing decoder
+ FLAC__stream_encoder_finish(m_encoder);
+ FLAC__stream_encoder_delete(m_encoder);
+ m_encoder = nullptr;
+ m_flacHeader.clear();
+ }
+
+ // Create FLAC encoder
+ FLAC__StreamEncoderInitStatus init_status;
+ m_encoder = FLAC__stream_encoder_new();
+ if (m_encoder)
+ {
+ const int maxSampleRate = 176400; // Spec says max is 655350, but doesn't seem to work
+ FLAC__bool ok = true;
+
+ ok &= FLAC__stream_encoder_set_verify(m_encoder, false);
+ ok &= FLAC__stream_encoder_set_compression_level(m_encoder, m_settings.m_compressionLevel);
+ ok &= FLAC__stream_encoder_set_channels(m_encoder, 2);
+ ok &= FLAC__stream_encoder_set_bits_per_sample(m_encoder, m_settings.m_sampleBits);
+ // We'll get FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE if we use the real sample rate
+ if (m_settings.m_channelSampleRate < maxSampleRate) {
+ ok &= FLAC__stream_encoder_set_sample_rate(m_encoder, m_settings.m_channelSampleRate);
+ } else {
+ ok &= FLAC__stream_encoder_set_sample_rate(m_encoder, maxSampleRate);
+ }
+ ok &= FLAC__stream_encoder_set_total_samples_estimate(m_encoder, 0);
+ //ok &= FLAC__stream_encoder_set_do_mid_side_stereo(m_encoder, false);
+ // FLAC__MAX_BLOCK_SIZE is 65536
+ // However, FLAC__format_blocksize_is_subset says anything over 16384 is not streamable
+ // Also, if sampleRate <= 48000, then max block size is 4608
+ if (FLAC__format_blocksize_is_subset(m_settings.m_blockSize, m_settings.m_channelSampleRate)) {
+ ok &= FLAC__stream_encoder_set_blocksize(m_encoder, m_settings.m_blockSize);
+ } else {
+ ok &= FLAC__stream_encoder_set_blocksize(m_encoder, 4096);
+ }
+ if (ok)
+ {
+ init_status = FLAC__stream_encoder_init_stream(m_encoder, flacWriteCallback, nullptr, nullptr, nullptr, this);
+ if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
+ {
+ qDebug() << "RemoteTCPSinkSink::applySettings: Error initializing FLAC encoder:" << FLAC__StreamEncoderInitStatusString[init_status];
+ FLAC__stream_encoder_delete(m_encoder);
+ m_encoder = nullptr;
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPSinkSink::applySettings: Failed to configure FLAC encoder";
+ FLAC__stream_encoder_delete(m_encoder);
+ m_encoder = nullptr;
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPSinkSink::applySettings: Failed to allocate FLAC encoder";
+ }
+
+ m_bytesUncompressed = 0;
+ m_bytesCompressed = 0;
+ }
+
+ if (initZLib && (m_settings.m_compression == RemoteTCPSinkSettings::ZLIB))
+ {
+ // Intialise zlib compression
+ m_zStream.zalloc = nullptr;
+ m_zStream.zfree = nullptr;
+ m_zStream.opaque = nullptr;
+ m_zStream.data_type = Z_BINARY;
+ int windowBits = log2(m_settings.m_blockSize);
+
+ if (Z_OK == deflateInit2(&m_zStream, m_settings.m_compressionLevel, Z_DEFLATED, windowBits, 9, Z_DEFAULT_STRATEGY))
+ {
+ m_zStreamInitialised = true;
+ }
+ else
+ {
+ qDebug() << "RemoteTCPSinkSink::applySettings: deflateInit failed";
+ m_zStreamInitialised = false;
+ }
+
+ m_bytesUncompressed = 0;
+ m_bytesCompressed = 0;
+ }
+
}
void RemoteTCPSinkSink::startServer()
{
stopServer();
- m_server = new QTcpServer(this);
- if (!m_server->listen(QHostAddress(m_settings.m_dataAddress), m_settings.m_dataPort))
+ if (m_settings.m_protocol == RemoteTCPSinkSettings::SDRA_WSS)
{
- qCritical() << "RemoteTCPSink failed to listen on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
- // FIXME: Report to GUI?
+#ifndef QT_NO_OPENSSL
+ QSslConfiguration sslConfiguration;
+ qDebug() << "RemoteTCPSinkSink::startServer: SSL config: " << m_settings.m_certificate << m_settings.m_key;
+ if (m_settings.m_certificate.isEmpty())
+ {
+ QString msg = "RemoteTCPSink requires an SSL certificate in order to use wss protocol";
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ return;
+ }
+ if (m_settings.m_certificate.isEmpty())
+ {
+ QString msg = "RemoteTCPSink requires an SSL key in order to use wss protocol";
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ return;
+ }
+ QFile certFile(m_settings.m_certificate);
+ if (!certFile.open(QIODevice::ReadOnly))
+ {
+ QString msg = QString("RemoteTCPSink failed to open certificate %1: %2").arg(m_settings.m_certificate).arg(certFile.errorString());
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ return;
+ }
+ QFile keyFile(m_settings.m_key);
+ if (!keyFile.open(QIODevice::ReadOnly))
+ {
+ QString msg = QString("RemoteTCPSink failed to open key %1: %2").arg(m_settings.m_key).arg(keyFile.errorString());
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ return;
+ }
+ QSslCertificate certificate(&certFile, QSsl::Pem);
+ QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
+ certFile.close();
+ keyFile.close();
+ sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
+ sslConfiguration.setLocalCertificate(certificate);
+ sslConfiguration.setPrivateKey(sslKey);
+
+ m_webSocketServer = new QWebSocketServer(QStringLiteral("Remote TCP Sink"),
+ QWebSocketServer::SecureMode,
+ this);
+ m_webSocketServer->setSslConfiguration(sslConfiguration);
+
+ QHostAddress address(m_settings.m_dataAddress);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
+ m_webSocketServer->setSupportedSubprotocols({"binary"}); // Chrome wont connect without this - "Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received"
+#endif
+ if (!m_webSocketServer->listen(address, m_settings.m_dataPort))
+ {
+ QString msg = QString("RemoteTCPSink failed to listen on %1 port %2: %3").arg(m_settings.m_dataAddress).arg(m_settings.m_dataPort).arg(m_webSocketServer->errorString());
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ }
+ else
+ {
+ qInfo() << "RemoteTCPSink listening on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
+ connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &RemoteTCPSinkSink::acceptWebConnection);
+ connect(m_webSocketServer, &QWebSocketServer::sslErrors, this, &RemoteTCPSinkSink::onSslErrors);
+ }
+#else
+ QString msg = "RemoteTCPSink unable to use wss protocol as SSL is not supported";
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+#endif
}
else
{
- qInfo() << "RemoteTCPSink listening on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
- connect(m_server, &QTcpServer::newConnection, this, &RemoteTCPSinkSink::acceptConnection);
+ m_server = new QTcpServer(this);
+ if (!m_server->listen(QHostAddress(m_settings.m_dataAddress), m_settings.m_dataPort))
+ {
+ QString msg = QString("RemoteTCPSink failed to listen on %1 port %2: %3").arg(m_settings.m_dataAddress).arg(m_settings.m_dataPort).arg(m_webSocketServer->errorString());
+ qWarning() << msg;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
+ }
+ }
+ else
+ {
+ qInfo() << "RemoteTCPSink listening on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
+ connect(m_server, &QTcpServer::newConnection, this, &RemoteTCPSinkSink::acceptTCPConnection);
+ }
}
}
void RemoteTCPSinkSink::stopServer()
{
- for (auto client : m_clients)
- {
- qDebug() << "RemoteTCPSinkSink::stopServer: Closing connection to client";
- client->close();
- delete client;
- }
- if (m_clients.size() > 0)
- {
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgReportConnection::create(0));
- }
- m_clients.clear();
+ // Close connections to any existing clients
+ while (m_clients.size() > 0) {
+ m_clients[0]->close(); // This results in disconnected() being called, where we delete and remove from m_clients
}
+ // Close server sockets
if (m_server)
{
- qDebug() << "RemoteTCPSinkSink::stopServer: Closing old server";
+ qDebug() << "RemoteTCPSinkSink::stopServer: Closing old socket server";
m_server->close();
- delete m_server;
+ m_server->deleteLater();
m_server = nullptr;
}
+ if (m_webSocketServer)
+ {
+ qDebug() << "RemoteTCPSinkSink::stopServer: Closing old web socket server";
+ m_webSocketServer->close();
+ m_webSocketServer->deleteLater();
+ m_webSocketServer = nullptr;
+ }
}
RemoteTCPProtocol::Device RemoteTCPSinkSink::getDevice()
@@ -398,90 +879,220 @@ RemoteTCPProtocol::Device RemoteTCPSinkSink::getDevice()
return RemoteTCPProtocol::UNKNOWN;
}
-void RemoteTCPSinkSink::acceptConnection()
+void RemoteTCPSinkSink::acceptWebConnection()
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ QWebSocket *client = m_webSocketServer->nextPendingConnection();
+
+ connect(client, &QWebSocket::binaryMessageReceived, this, &RemoteTCPSinkSink::processCommand);
+ connect(client, &QWebSocket::disconnected, this, &RemoteTCPSinkSink::disconnected);
+ qDebug() << "RemoteTCPSinkSink::acceptWebConnection: client connected";
+
+ // https://bugreports.qt.io/browse/QTBUG-125874
+ QTimer::singleShot(200, this, [this, client] () {
+ QMutexLocker mutexLocker(&m_mutex);
+ m_clients.append(new WebSocket(client));
+ acceptConnection(m_clients.last());
+ });
+}
+
+void RemoteTCPSinkSink::acceptTCPConnection()
{
QMutexLocker mutexLocker(&m_mutex);
QTcpSocket *client = m_server->nextPendingConnection();
- if (!client) {
- qDebug() << "RemoteTCPSinkSink::acceptConnection: client is nullptr";
- return;
- }
- m_clients.append(client);
-
connect(client, &QIODevice::readyRead, this, &RemoteTCPSinkSink::processCommand);
- connect(client, SIGNAL(disconnected()), this, SLOT(disconnected()));
+ connect(client, &QTcpSocket::disconnected, this, &RemoteTCPSinkSink::disconnected);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(client, QOverload::of(&QAbstractSocket::error), this, &RemoteTCPSinkSink::errorOccurred);
#else
connect(client, &QAbstractSocket::errorOccurred, this, &RemoteTCPSinkSink::errorOccurred);
#endif
- qDebug() << "RemoteTCPSinkSink::acceptConnection: client connected";
+ qDebug() << "RemoteTCPSinkSink::acceptTCPConnection: client connected";
+
+ QTimer::singleShot(200, this, [this, client] () {
+ QMutexLocker mutexLocker(&m_mutex);
+ m_clients.append(new TCPSocket(client));
+ acceptConnection(m_clients.last());
+ });
+}
+
+FLAC__StreamEncoderWriteStatus RemoteTCPSinkSink::flacWrite(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t currentFrame)
+{
+ (void) encoder;
+
+ char header[1+4];
+
+ // Save FLAC header for clients that connect later
+ if ((currentFrame == 0) && (samples == 0))
+ {
+ m_flacHeader.append((const char *) buffer, bytes);
+
+ // Write complete header to all clients
+ if (m_flacHeader.size() == m_flacHeaderSize)
+ {
+ header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], m_flacHeader.size());
+
+ for (auto client : m_clients)
+ {
+ client->write(header, sizeof(header));
+ client->write(m_flacHeader.constData(), m_flacHeader.size());
+ m_bytesTransmitted += sizeof(header) + m_flacHeader.size();
+ client->flush();
+ }
+ }
+ }
+ else
+ {
+ // Send compressed IQ data to max number of clients
+ header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], bytes);
+ int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
+ for (int i = 0; i < clients; i++)
+ {
+ Socket *client = m_clients[i];
+ client->write(header, sizeof(header));
+ client->write((const char *) buffer, bytes);
+ m_bytesTransmitted += sizeof(header) + bytes;
+ client->flush();
+ }
+ }
+ m_bytesCompressed += sizeof(header) + bytes;
+
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+void RemoteTCPSinkSink::acceptConnection(Socket *client)
+{
if (m_settings.m_protocol == RemoteTCPSinkSettings::RTL0)
{
quint8 metaData[RemoteTCPProtocol::m_rtl0MetaDataSize] = {'R', 'T', 'L', '0'};
RemoteTCPProtocol::encodeUInt32(&metaData[4], getDevice()); // Tuner ID
RemoteTCPProtocol::encodeUInt32(&metaData[8], 1); // Gain stages
client->write((const char *)metaData, sizeof(metaData));
+ m_bytesTransmitted += sizeof(metaData);
+ client->flush();
}
else
{
quint8 metaData[RemoteTCPProtocol::m_sdraMetaDataSize] = {'S', 'D', 'R', 'A'};
RemoteTCPProtocol::encodeUInt32(&metaData[4], getDevice());
- // Send device/channel settings, so they can be displayed in the remote GUI
- double centerFrequency = 0.0;
- qint32 ppmCorrection = 0;
- quint32 flags = 0;
- int biasTeeEnabled = false;
- int directSampling = false;
- int agc = false;
- int dcOffsetRemoval = false;
- int iqCorrection = false;
- qint32 devSampleRate = 0;
- qint32 log2Decim = 0;
- qint32 gain[4] = {0, 0, 0, 0};
- qint32 rfBW = 0;
-
- ChannelWebAPIUtils::getCenterFrequency(m_deviceIndex, centerFrequency);
- ChannelWebAPIUtils::getLOPpmCorrection(m_deviceIndex, ppmCorrection);
- ChannelWebAPIUtils::getDevSampleRate(m_deviceIndex, devSampleRate);
- ChannelWebAPIUtils::getSoftDecim(m_deviceIndex, log2Decim);
+ // Send device/channel settings, so they can be displayed in the remote GUI
+ ChannelWebAPIUtils::getCenterFrequency(m_deviceIndex, m_centerFrequency);
+ ChannelWebAPIUtils::getLOPpmCorrection(m_deviceIndex, m_ppmCorrection);
+ ChannelWebAPIUtils::getDevSampleRate(m_deviceIndex, m_devSampleRate);
+ ChannelWebAPIUtils::getSoftDecim(m_deviceIndex, m_log2Decim);
for (int i = 0; i < 4; i++) {
- ChannelWebAPIUtils::getGain(m_deviceIndex, i, gain[i]);
- }
- ChannelWebAPIUtils::getRFBandwidth(m_deviceIndex, rfBW);
- ChannelWebAPIUtils::getBiasTee(m_deviceIndex, biasTeeEnabled);
- ChannelWebAPIUtils::getDeviceSetting(m_deviceIndex, "noModMode", directSampling);
- ChannelWebAPIUtils::getAGC(m_deviceIndex, agc);
- ChannelWebAPIUtils::getDCOffsetRemoval(m_deviceIndex, dcOffsetRemoval);
- ChannelWebAPIUtils::getIQCorrection(m_deviceIndex, iqCorrection);
- flags = (iqCorrection << 4)
- | (dcOffsetRemoval << 3)
- | (agc << 2)
- | (directSampling << 1)
- | biasTeeEnabled;
-
- RemoteTCPProtocol::encodeUInt64(&metaData[8], (quint64)centerFrequency);
- RemoteTCPProtocol::encodeUInt32(&metaData[16], ppmCorrection);
+ ChannelWebAPIUtils::getGain(m_deviceIndex, i, m_gain[i]);
+ }
+ ChannelWebAPIUtils::getRFBandwidth(m_deviceIndex, m_rfBW);
+ ChannelWebAPIUtils::getBiasTee(m_deviceIndex, m_biasTeeEnabled);
+ ChannelWebAPIUtils::getDeviceSetting(m_deviceIndex, "noModMode", m_directSampling);
+ ChannelWebAPIUtils::getAGC(m_deviceIndex, m_agc);
+ ChannelWebAPIUtils::getDCOffsetRemoval(m_deviceIndex, m_dcOffsetRemoval);
+ ChannelWebAPIUtils::getIQCorrection(m_deviceIndex, m_iqCorrection);
+
+ quint32 flags = ((!m_settings.m_iqOnly) << 7)
+ | (m_settings.m_remoteControl << 6)
+ | (m_settings.m_squelchEnabled << 5)
+ | (m_iqCorrection << 4)
+ | (m_dcOffsetRemoval << 3)
+ | (m_agc << 2)
+ | (m_directSampling << 1)
+ | m_biasTeeEnabled;
+
+ RemoteTCPProtocol::encodeUInt64(&metaData[8], (quint64)m_centerFrequency);
+ RemoteTCPProtocol::encodeUInt32(&metaData[16], m_ppmCorrection);
RemoteTCPProtocol::encodeUInt32(&metaData[20], flags);
- RemoteTCPProtocol::encodeUInt32(&metaData[24], devSampleRate);
- RemoteTCPProtocol::encodeUInt32(&metaData[28], log2Decim);
- RemoteTCPProtocol::encodeInt16(&metaData[32], gain[0]);
- RemoteTCPProtocol::encodeInt16(&metaData[34], gain[1]);
- RemoteTCPProtocol::encodeInt16(&metaData[36], gain[2]);
- RemoteTCPProtocol::encodeInt16(&metaData[38], gain[3]);
- RemoteTCPProtocol::encodeUInt32(&metaData[40], rfBW);
+ RemoteTCPProtocol::encodeUInt32(&metaData[24], m_devSampleRate);
+ RemoteTCPProtocol::encodeUInt32(&metaData[28], m_log2Decim);
+ RemoteTCPProtocol::encodeInt16(&metaData[32], m_gain[0]);
+ RemoteTCPProtocol::encodeInt16(&metaData[34], m_gain[1]);
+ RemoteTCPProtocol::encodeInt16(&metaData[36], m_gain[2]);
+ RemoteTCPProtocol::encodeInt16(&metaData[38], m_gain[3]);
+ RemoteTCPProtocol::encodeUInt32(&metaData[40], m_rfBW);
RemoteTCPProtocol::encodeInt32(&metaData[44], m_settings.m_inputFrequencyOffset);
RemoteTCPProtocol::encodeUInt32(&metaData[48], m_settings.m_gain);
RemoteTCPProtocol::encodeUInt32(&metaData[52], m_settings.m_channelSampleRate);
RemoteTCPProtocol::encodeUInt32(&metaData[56], m_settings.m_sampleBits);
+ RemoteTCPProtocol::encodeUInt32(&metaData[60], 1); // Protocol revision. 0=64 byte meta data, 1=128 byte meta data
+ RemoteTCPProtocol::encodeFloat(&metaData[64], m_settings.m_squelch);
+ RemoteTCPProtocol::encodeFloat(&metaData[68], m_settings.m_squelchGate);
// Send API port? Not accessible via MainCore
client->write((const char *)metaData, sizeof(metaData));
+ m_bytesTransmitted += sizeof(metaData);
+ client->flush();
+
+ // Inform client if they are in a queue
+ if (!m_settings.m_iqOnly && (m_clients.size() > m_settings.m_maxClients)) {
+ sendQueuePosition(client, m_clients.size() - m_settings.m_maxClients);
+ }
+
+ // Send existing FLAC header to new client
+ if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC) && (m_flacHeader.size() == m_flacHeaderSize))
+ {
+ char header[1+4];
+
+ header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], m_flacHeader.size());
+ client->write(header, sizeof(header));
+ client->write(m_flacHeader.constData(), m_flacHeader.size());
+ m_bytesTransmitted += sizeof(header) + m_flacHeader.size();
+ client->flush();
+ }
+
+ // Send position / direction of antenna
+ sendPosition();
+ if (m_settings.m_isotropic) {
+ sendDirection(true, std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN());
+ } else if (m_settings.m_rotator == "None") {
+ sendDirection(false, m_settings.m_azimuth, m_settings.m_elevation);
+ } else {
+ sendRotatorDirection(true);
+ }
+ }
+
+ // Create timer to disconnect client after timelimit reached
+ QTimer *timer = new QTimer();
+ timer->setSingleShot(true);
+ timer->callOnTimeout(this, [this, client] () {
+ qDebug() << "Disconnecting" << client->peerAddress() << "as time limit reached";
+ if (m_settings.m_compression) {
+ sendTimeLimit(client);
+ }
+ client->close();
+ });
+ if (m_settings.m_timeLimit > 0)
+ {
+ timer->setInterval(m_settings.m_timeLimit * 60 * 1000);
+ // Only start timer if we will receive data immediately
+ if (m_clients.size() <= m_settings.m_maxClients) {
+ timer->start();
+ }
}
+ m_timers.append(timer);
+
+ // Close connection if blacklisted
+ for (const auto& ip : m_settings.m_ipBlacklist)
+ {
+ QHostAddress address(ip);
+ if (address == client->peerAddress())
+ {
+ qDebug() << "Disconnecting" << client->peerAddress() << "as blacklisted";
+ if (m_settings.m_compression) {
+ sendBlacklisted(client);
+ }
+ client->close();
+ break;
+ }
+ }
+
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size(), client->peerAddress(), client->peerPort()));
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size()));
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size(), client->peerAddress(), client->peerPort()));
}
}
@@ -489,14 +1100,53 @@ void RemoteTCPSinkSink::disconnected()
{
QMutexLocker mutexLocker(&m_mutex);
qDebug() << "RemoteTCPSinkSink::disconnected";
- QTcpSocket *client = (QTcpSocket*)sender();
- client->deleteLater();
- m_clients.removeAll(client);
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size()));
+ QObject *object = sender();
+ QMutableListIterator itr(m_clients);
+
+ // Remove from list of clients
+ int idx = 0;
+ while (itr.hasNext())
+ {
+ Socket *socket = itr.next();
+ if (socket->socket() == object)
+ {
+ itr.remove();
+ delete m_timers.takeAt(idx);
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgReportDisconnect::create(m_clients.size(), socket->peerAddress(), socket->peerPort()));
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgReportDisconnect::create(m_clients.size(), socket->peerAddress(), socket->peerPort()));
+ }
+ socket->deleteLater();
+ break;
+ }
+ else
+ {
+ idx++;
+ }
+ }
+
+ // Start timer for next waiting client
+ if ((idx < m_settings.m_maxClients) && (m_settings.m_timeLimit > 0))
+ {
+ int newActiveIdx = m_settings.m_maxClients - 1;
+ if (newActiveIdx < m_clients.size()) {
+ m_timers[newActiveIdx]->start();
+ }
+ }
+
+ // Update other clients waiting with current queue position
+ for (int i = m_settings.m_maxClients; i < m_clients.size(); i++) {
+ sendQueuePosition(m_clients[i], i - m_settings.m_maxClients + 1);
}
}
+#ifndef QT_NO_OPENSSL
+void RemoteTCPSinkSink::onSslErrors(const QList &errors)
+{
+ qWarning() << "RemoteTCPSinkSink::onSslErrors: " << errors;
+}
+#endif
+
void RemoteTCPSinkSink::errorOccurred(QAbstractSocket::SocketError socketError)
{
qDebug() << "RemoteTCPSinkSink::errorOccurred: " << socketError;
@@ -507,180 +1157,606 @@ void RemoteTCPSinkSink::errorOccurred(QAbstractSocket::SocketError socketError)
}*/
}
+Socket *RemoteTCPSinkSink::getSocket(QObject *object) const
+{
+ for (const auto client : m_clients)
+ {
+ if (client->socket() == object) {
+ return client;
+ }
+ }
+
+ return nullptr;
+}
+
void RemoteTCPSinkSink::processCommand()
{
QMutexLocker mutexLocker(&m_mutex);
- QTcpSocket *client = (QTcpSocket*)sender();
+ Socket *client = getSocket(sender());
RemoteTCPSinkSettings settings = m_settings;
-
quint8 cmd[5];
+
while (client && (client->bytesAvailable() >= (qint64)sizeof(cmd)))
{
int len = client->read((char *)cmd, sizeof(cmd));
+
if (len == sizeof(cmd))
{
- switch (cmd[0])
+ if (cmd[0] == RemoteTCPProtocol::sendMessage)
{
- case RemoteTCPProtocol::setCenterFrequency:
+ quint32 msgLen = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ try
+ {
+ char *buf = new char[msgLen];
+ len = client->read((char *)buf, msgLen);
+ if (len == (int) msgLen)
+ {
+ bool broadcast = (bool) buf[0];
+ int i;
+ for (i = 1; i < (int) msgLen; i++)
+ {
+ if (buf[i] == '\0') {
+ break;
+ }
+ }
+ QString callsign = QString::fromUtf8(&buf[1]);
+ QString text = QString::fromUtf8(&buf[i+1]);
+
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgSendMessage::create(client->peerAddress(), client->peerPort(), callsign, text, broadcast));
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPSinkSink::processCommand: sendMessage: Failed to read" << msgLen;
+ }
+ delete[] buf;
+ }
+ catch(std::bad_alloc&)
+ {
+ qDebug() << "RemoteTCPSinkSink::processCommand: sendMessage - Failed to allocate" << msgLen;
+ }
+ }
+ else if (!m_settings.m_remoteControl)
{
- quint64 centerFrequency = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set center frequency " << centerFrequency;
- ChannelWebAPIUtils::setCenterFrequency(m_deviceIndex, (double)centerFrequency);
- break;
+ qDebug() << "RemoteTCPSinkSink::processCommand: Ignoring command from client as remote control disabled";
}
- case RemoteTCPProtocol::setSampleRate:
+ else
{
- int sampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set sample rate " << sampleRate;
- ChannelWebAPIUtils::setDevSampleRate(m_deviceIndex, sampleRate);
- if (m_settings.m_protocol == RemoteTCPSinkSettings::RTL0)
+ switch (cmd[0])
+ {
+ case RemoteTCPProtocol::setCenterFrequency:
+ {
+ quint64 centerFrequency = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set center frequency " << centerFrequency;
+ ChannelWebAPIUtils::setCenterFrequency(m_deviceIndex, (double)centerFrequency);
+ break;
+ }
+ case RemoteTCPProtocol::setSampleRate:
+ {
+ int sampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set sample rate " << sampleRate;
+ ChannelWebAPIUtils::setDevSampleRate(m_deviceIndex, sampleRate);
+ if (m_settings.m_protocol == RemoteTCPSinkSettings::RTL0)
+ {
+ // Match channel sample rate with device sample rate for RTL0 protocol
+ ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, 0);
+ settings.m_channelSampleRate = sampleRate;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false));
+ }
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setTunerGainMode:
+ // SDRangel's rtlsdr sample source always has this fixed as 1, so nothing to do
+ break;
+ case RemoteTCPProtocol::setTunerGain: // gain is gain in 10th of a dB
+ {
+ int gain = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set gain " << gain;
+ ChannelWebAPIUtils::setGain(m_deviceIndex, 0, gain);
+ break;
+ }
+ case RemoteTCPProtocol::setFrequencyCorrection:
+ {
+ int ppm = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set LO ppm correction " << ppm;
+ ChannelWebAPIUtils::setLOPpmCorrection(m_deviceIndex, ppm);
+ break;
+ }
+ case RemoteTCPProtocol::setTunerIFGain:
+ {
+ int v = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ int gain = (int)(qint16)(v & 0xffff);
+ int stage = (v >> 16) & 0xffff;
+ ChannelWebAPIUtils::setGain(m_deviceIndex, stage, gain);
+ break;
+ }
+ case RemoteTCPProtocol::setAGCMode:
+ {
+ int agc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set AGC " << agc;
+ ChannelWebAPIUtils::setAGC(m_deviceIndex, agc);
+ break;
+ }
+ case RemoteTCPProtocol::setDirectSampling:
+ {
+ int ds = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set direct sampling " << ds;
+ ChannelWebAPIUtils::patchDeviceSetting(m_deviceIndex, "noModMode", ds); // RTLSDR only
+ break;
+ }
+ case RemoteTCPProtocol::setBiasTee:
+ {
+ int biasTee = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set bias tee " << biasTee;
+ ChannelWebAPIUtils::setBiasTee(m_deviceIndex, biasTee);
+ break;
+ }
+ case RemoteTCPProtocol::setTunerBandwidth:
+ {
+ int rfBW = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set tuner bandwidth " << rfBW;
+ ChannelWebAPIUtils::setRFBandwidth(m_deviceIndex, rfBW);
+ break;
+ }
+ case RemoteTCPProtocol::setDecimation:
+ {
+ int dec = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set decimation " << dec;
+ ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, dec);
+ break;
+ }
+ case RemoteTCPProtocol::setDCOffsetRemoval:
+ {
+ int dc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set DC offset removal " << dc;
+ ChannelWebAPIUtils::setDCOffsetRemoval(m_deviceIndex, dc);
+ break;
+ }
+ case RemoteTCPProtocol::setIQCorrection:
{
- // Match channel sample rate with device sample rate for RTL0 protocol
- ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, 0);
- settings.m_channelSampleRate = sampleRate;
+ int iq = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set IQ correction " << iq;
+ ChannelWebAPIUtils::setIQCorrection(m_deviceIndex, iq);
+ break;
+ }
+ case RemoteTCPProtocol::setChannelSampleRate:
+ {
+ int channelSampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set channel sample rate " << channelSampleRate;
+ bool restartRequired;
+ if (channelSampleRate <= m_settings.m_maxSampleRate)
+ {
+ settings.m_channelSampleRate = channelSampleRate;
+ restartRequired = false;
+ }
+ else
+ {
+ settings.m_channelSampleRate = m_settings.m_maxSampleRate;
+ restartRequired = true; // Need to restart so client gets max sample rate
+ }
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, restartRequired));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, restartRequired));
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setChannelFreqOffset:
+ {
+ int offset = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set channel input frequency offset " << offset;
+ settings.m_inputFrequencyOffset = offset;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false));
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setChannelGain:
+ {
+ int gain = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set channel gain " << gain;
+ settings.m_gain = gain;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false));
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setSampleBitDepth:
+ {
+ int bits = RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set sample bit depth " << bits;
+ settings.m_sampleBits = bits;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false));
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelchEnabled:
+ {
+ bool enabled = (bool) RemoteTCPProtocol::extractUInt32(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch enabled " << enabled;
+ settings.m_squelchEnabled = enabled;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchEnabled"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchEnabled"}, false));
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelch:
+ {
+ float squelch = RemoteTCPProtocol::extractFloat(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch " << squelch;
+ settings.m_squelch = squelch;
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, true));
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelch"}, false));
}
if (m_messageQueueToChannel) {
- m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, true));
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelch"}, false));
}
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelchGate:
+ {
+ float squelchGate = RemoteTCPProtocol::extractFloat(&cmd[1]);
+ qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch gate " << squelchGate;
+ settings.m_squelchGate = squelchGate;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchGate"}, false));
+ }
+ if (m_messageQueueToChannel) {
+ m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchGate"}, false));
+ }
+ break;
+ }
+ default:
+ qDebug() << "RemoteTCPSinkSink::processCommand: unknown command " << cmd[0];
+ break;
}
- break;
}
- case RemoteTCPProtocol::setTunerGainMode:
- // SDRangel's rtlsdr sample source always has this fixed as 1, so nothing to do
- break;
- case RemoteTCPProtocol::setTunerGain: // gain is gain in 10th of a dB
- {
- int gain = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set gain " << gain;
- ChannelWebAPIUtils::setGain(m_deviceIndex, 0, gain);
- break;
+ }
+ else
+ {
+ qDebug() << "RemoteTCPSinkSink::processCommand: read only " << len << " bytes - Expecting " << sizeof(cmd);
+ }
+ }
+}
+
+void RemoteTCPSinkSink::sendCommand(RemoteTCPProtocol::Command cmdId, quint32 value)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ quint8 cmd[5];
+
+ cmd[0] = (quint8) cmdId;
+ RemoteTCPProtocol::encodeUInt32(&cmd[1], value);
+
+ for (const auto client : m_clients)
+ {
+ qint64 len = client->write((char *) cmd, sizeof(cmd));
+ if (len != sizeof(cmd)) {
+ qDebug() << "RemoteTCPSinkSink::sendCommand: Failed to write all of message:" << len;
+ }
+ m_bytesTransmitted += sizeof(cmd);
+ client->flush();
+ }
+}
+
+void RemoteTCPSinkSink::sendCommandFloat(RemoteTCPProtocol::Command cmdId, float value)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ quint8 cmd[5];
+
+ cmd[0] = (quint8) cmdId;
+ RemoteTCPProtocol::encodeFloat(&cmd[1], value);
+
+ for (const auto client : m_clients)
+ {
+ qint64 len = client->write((char *) cmd, sizeof(cmd));
+ if (len != sizeof(cmd)) {
+ qDebug() << "RemoteTCPSinkSink::sendCommand: Failed to write all of message:" << len;
+ }
+ m_bytesTransmitted += sizeof(cmd);
+ client->flush();
+ }
+}
+
+void RemoteTCPSinkSink::sendMessage(QHostAddress address, quint16 port, const QString& callsign, const QString& text, bool broadcast)
+{
+ qint64 len;
+ char cmd[1+4+1];
+ QByteArray callsignBytes = callsign.toUtf8();
+ QByteArray textBytes = text.toUtf8();
+ QByteArray bytes;
+
+ bytes.append(callsignBytes);
+ bytes.append('\0');
+ bytes.append(textBytes);
+ bytes.append('\0');
+
+ cmd[0] = (char) RemoteTCPProtocol::sendMessage;
+ RemoteTCPProtocol::encodeUInt32((quint8*) &cmd[1], bytes.size() + 1);
+ cmd[5] = (char) broadcast;
+
+ for (const auto client : m_clients)
+ {
+ bool addressMatch = (address == client->peerAddress()) && (port == client->peerPort());
+ if ((broadcast && !addressMatch) || (!broadcast && addressMatch))
+ {
+ len = client->write(cmd, sizeof(cmd));
+ if (len != sizeof(cmd)) {
+ qDebug() << "RemoteTCPSinkSink::sendMessage: Failed to write all of message header:" << len;
}
- case RemoteTCPProtocol::setFrequencyCorrection:
- {
- int ppm = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set LO ppm correction " << ppm;
- ChannelWebAPIUtils::setLOPpmCorrection(m_deviceIndex, ppm);
- break;
+ len = client->write(bytes.data(), bytes.size());
+ if (len != bytes.size()) {
+ qDebug() << "RemoteTCPSinkSink::sendMessage: Failed to write all of message:" << len;
}
- case RemoteTCPProtocol::setTunerIFGain:
+ m_bytesTransmitted += sizeof(cmd) + bytes.size();
+ client->flush();
+ qDebug() << "RemoteTCPSinkSink::sendMessage:" << client->peerAddress() << client->peerPort() << text;
+ }
+ }
+}
+
+void RemoteTCPSinkSink::sendQueuePosition(Socket *client, int position)
+{
+ QString callsign = MainCore::instance()->getSettings().getStationName();
+
+ sendMessage(client->peerAddress(), client->peerPort(), callsign, QString("Server busy. You are number %1 in the queue.").arg(position), false);
+}
+
+void RemoteTCPSinkSink::sendBlacklisted(Socket *client)
+{
+ char cmd[1+4];
+
+ cmd[0] = (char) RemoteTCPProtocol::sendBlacklistedMessage;
+ RemoteTCPProtocol::encodeUInt32((quint8*) &cmd[1], 0);
+
+ qint64 len = client->write(cmd, sizeof(cmd));
+ if (len != sizeof(cmd)) {
+ qDebug() << "RemoteTCPSinkSink::sendBlacklisted: Failed to write all of message:" << len;
+ }
+ m_bytesTransmitted += sizeof(cmd);
+ client->flush();
+}
+
+void RemoteTCPSinkSink::sendTimeLimit(Socket *client)
+{
+ QString callsign = MainCore::instance()->getSettings().getStationName();
+
+ sendMessage(client->peerAddress(), client->peerPort(), callsign, "Time limit reached.", false);
+}
+
+void RemoteTCPSinkSink::sendPosition(float latitude, float longitude, float altitude)
+{
+ char msg[1+4+4+4+4];
+ msg[0] = (char) RemoteTCPProtocol::dataPosition;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1], 4+4+4);
+ RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4], latitude);
+ RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4], longitude);
+ RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4+4], altitude);
+
+ int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
+ for (int i = 0; i < clients; i++)
+ {
+ Socket *client = m_clients[i];
+ client->write(msg, sizeof(msg));
+ m_bytesTransmitted += sizeof(msg);
+ client->flush();
+ }
+}
+
+void RemoteTCPSinkSink::sendDirection(bool isotropic, float azimuth, float elevation)
+{
+ char msg[1+4+4+4+4];
+ msg[0] = (char) RemoteTCPProtocol::dataDirection;
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1], 4+4+4);
+ RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1+4], isotropic);
+ RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4], azimuth);
+ RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4+4], elevation);
+
+ int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
+ for (int i = 0; i < clients; i++)
+ {
+ Socket *client = m_clients[i];
+ client->write(msg, sizeof(msg));
+ m_bytesTransmitted += sizeof(msg);
+ client->flush();
+ }
+}
+
+void RemoteTCPSinkSink::sendPosition()
+{
+ float latitude = MainCore::instance()->getSettings().getLatitude();
+ float longitude = MainCore::instance()->getSettings().getLongitude();
+ float altitude = MainCore::instance()->getSettings().getAltitude();
+
+ // Use device postion in preference to My Position
+ ChannelWebAPIUtils::getDevicePosition(m_deviceIndex, latitude, longitude, altitude);
+
+ sendPosition(latitude, longitude, altitude);
+}
+
+void RemoteTCPSinkSink::sendRotatorDirection(bool force)
+{
+ unsigned int rotatorFeatureSetIndex;
+ unsigned int rotatorFeatureIndex;
+
+ if (MainCore::getFeatureIndexFromId(m_settings.m_rotator, rotatorFeatureSetIndex, rotatorFeatureIndex))
+ {
+ double azimuth;
+ double elevation;
+
+ if (ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentAzimuth", azimuth)
+ && ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentElevation", elevation))
+ {
+ if (force || ((azimuth != m_azimuth) || (elevation != m_elevation)))
{
- int v = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- int gain = (int)(qint16)(v & 0xffff);
- int stage = (v >> 16) & 0xffff;
- ChannelWebAPIUtils::setGain(m_deviceIndex, stage, gain);
- break;
+ sendDirection(false, (float) azimuth, (float) elevation);
+ m_azimuth = azimuth;
+ m_elevation = elevation;
}
- case RemoteTCPProtocol::setAGCMode:
+ }
+ }
+}
+
+void RemoteTCPSinkSink::preferenceChanged(int elementType)
+{
+ Preferences::ElementType pref = (Preferences::ElementType)elementType;
+
+ if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude)) {
+ sendPosition();
+ }
+}
+
+// Poll for changes to device settings - FIXME: Need a signal from DeviceAPI - reverseAPI?
+void RemoteTCPSinkSink::checkDeviceSettings()
+{
+ if ((m_settings.m_protocol != RemoteTCPSinkSettings::RTL0) && !m_settings.m_iqOnly)
+ {
+ // Forward device settings to clients if they've changed
+
+ double centerFrequency;
+ if (ChannelWebAPIUtils::getCenterFrequency(m_deviceIndex, centerFrequency))
+ {
+ if (centerFrequency != m_centerFrequency)
{
- int agc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set AGC " << agc;
- ChannelWebAPIUtils::setAGC(m_deviceIndex, agc);
- break;
+ m_centerFrequency = centerFrequency;
+ sendCommand(RemoteTCPProtocol::setCenterFrequency, m_centerFrequency);
}
- case RemoteTCPProtocol::setDirectSampling:
+ }
+
+ int ppmCorrection;
+ if (ChannelWebAPIUtils::getLOPpmCorrection(m_deviceIndex, ppmCorrection))
+ {
+ if (ppmCorrection != m_ppmCorrection)
{
- int ds = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set direct sampling " << ds;
- ChannelWebAPIUtils::patchDeviceSetting(m_deviceIndex, "noModMode", ds); // RTLSDR only
- break;
+ m_ppmCorrection = ppmCorrection;
+ sendCommand(RemoteTCPProtocol::setFrequencyCorrection, m_ppmCorrection);
}
- case RemoteTCPProtocol::setBiasTee:
+ }
+
+ int biasTeeEnabled;
+ if (ChannelWebAPIUtils::getBiasTee(m_deviceIndex, biasTeeEnabled))
+ {
+ if (biasTeeEnabled != m_biasTeeEnabled)
{
- int biasTee = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set bias tee " << biasTee;
- ChannelWebAPIUtils::setBiasTee(m_deviceIndex, biasTee);
- break;
+ m_biasTeeEnabled = biasTeeEnabled;
+ sendCommand(RemoteTCPProtocol::setBiasTee, m_biasTeeEnabled);
}
- case RemoteTCPProtocol::setTunerBandwidth:
+ }
+
+ int directSampling;
+ if (ChannelWebAPIUtils::getDeviceSetting(m_deviceIndex, "noModMode", directSampling))
+ {
+ if (directSampling != m_directSampling)
{
- int rfBW = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set tuner bandwidth " << rfBW;
- ChannelWebAPIUtils::setRFBandwidth(m_deviceIndex, rfBW);
- break;
+ m_directSampling = directSampling;
+ sendCommand(RemoteTCPProtocol::setDirectSampling, m_directSampling);
}
- case RemoteTCPProtocol::setDecimation:
+ }
+
+ int agc;
+ if (ChannelWebAPIUtils::getAGC(m_deviceIndex, agc))
+ {
+ if (agc != m_agc)
{
- int dec = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set decimation " << dec;
- ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, dec);
- break;
+ m_agc = agc;
+ sendCommand(RemoteTCPProtocol::setAGCMode, m_agc);
}
- case RemoteTCPProtocol::setDCOffsetRemoval:
+ }
+
+ int dcOffsetRemoval;
+ if (ChannelWebAPIUtils::getDCOffsetRemoval(m_deviceIndex, dcOffsetRemoval))
+ {
+ if (dcOffsetRemoval != m_dcOffsetRemoval)
{
- int dc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set DC offset removal " << dc;
- ChannelWebAPIUtils::setDCOffsetRemoval(m_deviceIndex, dc);
- break;
+ m_dcOffsetRemoval = dcOffsetRemoval;
+ sendCommand(RemoteTCPProtocol::setDCOffsetRemoval, m_dcOffsetRemoval);
}
- case RemoteTCPProtocol::setIQCorrection:
+ }
+
+ int iqCorrection;
+ if (ChannelWebAPIUtils::getIQCorrection(m_deviceIndex, iqCorrection))
+ {
+ if (iqCorrection != m_iqCorrection)
{
- int iq = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set IQ correction " << iq;
- ChannelWebAPIUtils::setIQCorrection(m_deviceIndex, iq);
- break;
+ m_iqCorrection = iqCorrection;
+ sendCommand(RemoteTCPProtocol::setIQCorrection, m_iqCorrection);
}
- case RemoteTCPProtocol::setChannelSampleRate:
+ }
+
+ qint32 devSampleRate;
+ if (ChannelWebAPIUtils::getDevSampleRate(m_deviceIndex, devSampleRate))
+ {
+ if (devSampleRate != m_devSampleRate)
{
- int channelSampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set channel sample rate " << channelSampleRate;
- settings.m_channelSampleRate = channelSampleRate;
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, true));
- }
- if (m_messageQueueToChannel) {
- m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, true));
- }
- break;
+ m_devSampleRate = devSampleRate;
+ sendCommand(RemoteTCPProtocol::setSampleRate, m_devSampleRate);
}
- case RemoteTCPProtocol::setChannelFreqOffset:
+ }
+
+ qint32 log2Decim;
+ if (ChannelWebAPIUtils::getSoftDecim(m_deviceIndex, log2Decim))
+ {
+ if (log2Decim != m_log2Decim)
{
- int offset = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set channel input frequency offset " << offset;
- settings.m_inputFrequencyOffset = offset;
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false, true));
- }
- if (m_messageQueueToChannel) {
- m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false, true));
- }
- break;
+ m_log2Decim = log2Decim;
+ sendCommand(RemoteTCPProtocol::setDecimation, m_log2Decim);
}
- case RemoteTCPProtocol::setChannelGain:
+ }
+
+
+ qint32 rfBW;
+ if (ChannelWebAPIUtils::getRFBandwidth(m_deviceIndex, rfBW))
+ {
+ if (rfBW != m_rfBW)
{
- int gain = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set channel gain " << gain;
- settings.m_gain = gain;
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false, true));
- }
- if (m_messageQueueToChannel) {
- m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false, true));
- }
- break;
+ m_rfBW = rfBW;
+ sendCommand(RemoteTCPProtocol::setTunerBandwidth, m_rfBW);
}
- case RemoteTCPProtocol::setSampleBitDepth:
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ qint32 gain;
+ if (ChannelWebAPIUtils::getGain(m_deviceIndex, i, gain))
{
- int bits = RemoteTCPProtocol::extractUInt32(&cmd[1]);
- qDebug() << "RemoteTCPSinkSink::processCommand: set sample bit depth " << bits;
- settings.m_sampleBits = bits;
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false, true));
- }
- if (m_messageQueueToChannel) {
- m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false, true));
+ if (gain != m_gain[i])
+ {
+ m_gain[i] = gain;
+ if (i == 0)
+ {
+ sendCommand(RemoteTCPProtocol::setTunerGain, gain);
+ }
+ else
+ {
+ int v = (gain & 0xffff) | (i << 16);
+ sendCommand(RemoteTCPProtocol::setTunerIFGain, v);
+ }
}
- break;
- }
- default:
- qDebug() << "RemoteTCPSinkSink::processCommand: unknown command " << cmd[0];
- break;
}
}
- else
- {
- qDebug() << "RemoteTCPSinkSink::processCommand: read only " << len << " bytes - Expecting " << sizeof(cmd);
+
+ if (!m_settings.m_isotropic && !m_settings.m_rotator.isEmpty() && (m_settings.m_rotator != "None")) {
+ sendRotatorDirection(false);
}
+
}
}
diff --git a/plugins/channelrx/remotetcpsink/remotetcpsinksink.h b/plugins/channelrx/remotetcpsink/remotetcpsinksink.h
index d012bdc6de..9972565abd 100644
--- a/plugins/channelrx/remotetcpsink/remotetcpsinksink.h
+++ b/plugins/channelrx/remotetcpsink/remotetcpsinksink.h
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
-// Copyright (C) 2022-2023 Jon Beniston, M7RCE //
+// Copyright (C) 2022-2024 Jon Beniston, M7RCE //
// Copyright (C) 2022 Jiřà Pinkava //
// //
// This program is free software; you can redistribute it and/or modify //
@@ -23,16 +23,25 @@
#include
#include
#include
+#include
#include
+#include
+
+#include
+#include
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "util/messagequeue.h"
+#include "util/movingaverage.h"
+#include "util/doublebufferfifo.h"
#include "remotetcpsinksettings.h"
#include "remotetcpprotocol.h"
+#include "util/socket.h"
+
class DeviceSampleSource;
class RemoteTCPSinkSink : public QObject, public ChannelSampleSink {
@@ -47,22 +56,75 @@ class RemoteTCPSinkSink : public QObject, public ChannelSampleSink {
void stop();
void init();
- void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool remoteChange = false);
+ void applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force = false, bool restartRequired = false);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void setDeviceIndex(uint32_t deviceIndex) { m_deviceIndex = deviceIndex; }
void setChannelIndex(uint32_t channelIndex) { m_channelIndex = channelIndex; }
void setMessageQueueToGUI(MessageQueue *queue) { m_messageQueueToGUI = queue; }
void setMessageQueueToChannel(MessageQueue *queue) { m_messageQueueToChannel = queue; }
+ void acceptConnection(Socket *client);
+ Socket *getSocket(QObject *object) const;
+ void sendCommand(RemoteTCPProtocol::Command cmd, quint32 value);
+ void sendCommandFloat(RemoteTCPProtocol::Command cmd, float value);
+ void sendMessage(QHostAddress address, quint16 port, const QString& callsign, const QString& text, bool broadcast);
+
+ FLAC__StreamEncoderWriteStatus flacWrite(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t currentFrame);
+
+ bool getSquelchOpen() const { return m_squelchOpen; }
+
+ 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;
+ }
+
+private:
+ void sendQueuePosition(Socket *client, int position);
+ void sendBlacklisted(Socket *client);
+ void sendTimeLimit(Socket *client);
+ void sendPosition(float latitude, float longitude, float altitude);
+ void sendDirection(bool isotropic, float azimuth, float elevation);
+ void sendPosition();
+ void sendRotatorDirection(bool force);
private slots:
- void acceptConnection();
+ void acceptTCPConnection();
+ void acceptWebConnection();
void disconnected();
void errorOccurred(QAbstractSocket::SocketError socketError);
void processCommand();
void started();
void finished();
+#ifndef QT_NO_OPENSSL
+ void onSslErrors(const QList &errors);
+#endif
+ void preferenceChanged(int elementType);
+ void checkDeviceSettings();
private:
+
+ struct MagSqLevelsStore
+ {
+ MagSqLevelsStore() :
+ m_magsq(1e-12),
+ m_magsqPeak(1e-12)
+ {}
+ double m_magsq;
+ double m_magsqPeak;
+ };
+
RemoteTCPSinkSettings m_settings;
bool m_running;
MessageQueue *m_messageQueueToGUI;
@@ -76,7 +138,9 @@ private slots:
QRecursiveMutex m_mutex;
QTcpServer *m_server;
- QList m_clients;
+ QWebSocketServer *m_webSocketServer;
+ QList m_clients;
+ QList m_timers;
QDateTime m_bwDateTime; //!< For calculating TX bandwidth
qint64 m_bwBytes;
@@ -86,6 +150,52 @@ private slots:
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
+ // FLAC compression
+ FLAC__StreamEncoder *m_encoder;
+ QByteArray m_flacHeader;
+ static const int m_flacHeaderSize = 4 + 38 + 51;
+
+ // Zlib compression
+ z_stream m_zStream;
+ bool m_zStreamInitialised;
+ QByteArray m_zInBuf;
+ QByteArray m_zOutBuf;
+ int m_zInBufCount;
+ static const int m_zBufSize = 32768+128; //
+
+ qint64 m_bytesUncompressed;
+ qint64 m_bytesCompressed;
+ qint64 m_bytesTransmitted;
+
+ Real m_squelchLevel;
+ int m_squelchCount;
+ bool m_squelchOpen;
+ DoubleBufferFIFO m_squelchDelayLine;
+ double m_magsq;
+ double m_magsqSum;
+ double m_magsqPeak;
+ int m_magsqCount;
+ MagSqLevelsStore m_magSqLevelStore;
+ MovingAverageUtil m_movingAverage;
+
+ // Device settings
+ double m_centerFrequency;
+ qint32 m_ppmCorrection;
+ int m_biasTeeEnabled;
+ int m_directSampling;
+ int m_agc;
+ int m_dcOffsetRemoval;
+ int m_iqCorrection;
+ qint32 m_devSampleRate;
+ qint32 m_log2Decim;
+ qint32 m_rfBW;
+ qint32 m_gain[4];
+ QTimer m_timer;
+
+ // Rotator setttings
+ double m_azimuth;
+ double m_elevation;
+
void startServer();
void stopServer();
void processOneSample(Complex &ci);
diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp
index e5105c7d9c..e6b67525ec 100644
--- a/plugins/feature/map/mapgui.cpp
+++ b/plugins/feature/map/mapgui.cpp
@@ -40,6 +40,7 @@
#include "gui/dialogpositioner.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
+#include "channel/channelwebapiutils.h"
#include "dsp/devicesamplesource.h"
#include "device/deviceenumerator.h"
#include "util/units.h"
@@ -55,8 +56,6 @@
#include "ui_mapgui.h"
#include "map.h"
#include "mapgui.h"
-#include "SWGMapItem.h"
-#include "SWGDeviceSettings.h"
#include "SWGKiwiSDRSettings.h"
#include "SWGRemoteTCPInputSettings.h"
@@ -295,6 +294,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
connect(&m_kiwiSDRList, &KiwiSDRList::dataUpdated, this, &MapGUI::kiwiSDRUpdated);
connect(&m_spyServerList, &SpyServerList::dataUpdated, this, &MapGUI::spyServerUpdated);
+ connect(&m_sdrangelServerList, &SDRangelServerList::dataUpdated, this, &MapGUI::sdrangelServerUpdated);
#ifdef QT_WEBENGINE_FOUND
QWebEngineSettings *settings = ui->web->settings();
@@ -358,7 +358,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
setBeacons(beacons);
}
addIBPBeacons();
-
+ addNAT();
addRadioTimeTransmitters();
addRadar();
addIonosonde();
@@ -371,6 +371,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
addVLF();
addKiwiSDR();
addSpyServer();
+ addSDRangelServer();
displaySettings();
applySettings(true);
@@ -490,6 +491,44 @@ void MapGUI::addIBPBeacons()
}
}
+// https://www.icao.int/EURNAT/EUR%20and%20NAT%20Documents/NAT%20Documents/NAT%20Documents/NAT%20Doc%20003/NAT%20Doc003%20-%20HF%20Guidance%20v3.0.0_2015.pdf
+// Coords aren't precise
+const QList MapGUI::m_natTransmitters = {
+ {"Bodo", 0, 67.26742f, 14.34990, 0},
+ {"Gander", 0, 48.993056f, -54.674444f, 0},
+ {"Iceland", 0, 64.08516f, -21.84531f, 0},
+ {"New York", 0, 40.881111f, -72.647778f, 0},
+ {"Santa Maria", 0, 36.995556f, -25.170556, 0},
+ {"Shanwick", 0, 52.75f, -8.933333f, 0},
+};
+
+// North Atlantic HF ATC ground stations
+void MapGUI::addNAT()
+{
+ for (int i = 0; i < m_natTransmitters.size(); i++)
+ {
+ SWGSDRangel::SWGMapItem natMapItem;
+ // Need to suffix frequency, as there are multiple becaons with same callsign at different locations
+ QString name = QString("%1").arg(m_natTransmitters[i].m_callsign);
+ natMapItem.setName(new QString(name));
+ natMapItem.setLatitude(m_natTransmitters[i].m_latitude);
+ natMapItem.setLongitude(m_natTransmitters[i].m_longitude);
+ natMapItem.setAltitude(0.0);
+ natMapItem.setImage(new QString("antenna.png"));
+ natMapItem.setImageRotation(0);
+ QString text = QString("NAT ATC Transmitter\nCallsign: %1")
+ .arg(m_natTransmitters[i].m_callsign);
+ natMapItem.setText(new QString(text));
+ natMapItem.setModel(new QString("antenna.glb"));
+ natMapItem.setFixedPosition(true);
+ natMapItem.setOrientation(0);
+ natMapItem.setLabel(new QString(name));
+ natMapItem.setLabelAltitudeOffset(4.5);
+ natMapItem.setAltitudeReference(1);
+ update(m_map, &natMapItem, "NAT ATC Transmitters");
+ }
+}
+
void MapGUI::addVLF()
{
for (int i = 0; i < VLFTransmitters::m_transmitters.size(); i++)
@@ -699,6 +738,81 @@ void MapGUI::spyServerUpdated(const QList& sdrs)
}
}
+void MapGUI::addSDRangelServer()
+{
+ m_sdrangelServerList.getDataPeriodically();
+}
+
+void MapGUI::sdrangelServerUpdated(const QList& sdrs)
+{
+ for (const auto& sdr : sdrs)
+ {
+ SWGSDRangel::SWGMapItem sdrangelServerMapItem;
+
+ QString address = QString("%1:%2").arg(sdr.m_address).arg(sdr.m_port);
+ sdrangelServerMapItem.setName(new QString(address));
+ sdrangelServerMapItem.setLatitude(sdr.m_latitude);
+ sdrangelServerMapItem.setLongitude(sdr.m_longitude);
+ sdrangelServerMapItem.setAltitude(sdr.m_altitude);
+ sdrangelServerMapItem.setImage(new QString("antennaangel.png"));
+ sdrangelServerMapItem.setImageRotation(0);
+ QStringList antenna;
+ if (!sdr.m_antenna.isEmpty()) {
+ antenna.append(sdr.m_antenna);
+ }
+ if (sdr.m_isotropic) {
+ antenna.append("Isotropic");
+ } else {
+ antenna.append(QString("Az: %1%3 El: %2%3").arg(sdr.m_azimuth).arg(sdr.m_elevation).arg(QChar(0x00b0)));
+ }
+
+ QString text = QString("%9\n\nStation: %1\nDevice: %2\nAntenna: %3\nFrequency: %4 - %5\nRemote control: %6\nUsers: %7/%8")
+ .arg(sdr.m_stationName)
+ .arg(sdr.m_device)
+ .arg(antenna.join(" - "))
+ .arg(formatFrequency(sdr.m_minFrequency))
+ .arg(formatFrequency(sdr.m_maxFrequency))
+ .arg(sdr.m_remoteControl ? "Yes" : "No")
+ .arg(sdr.m_clients)
+ .arg(sdr.m_maxClients)
+ .arg(sdr.m_protocol)
+ ;
+ if (sdr.m_timeLimit > 0) {
+ text.append(QString("\nTime limit: %1 mins").arg(sdr.m_timeLimit));
+ }
+ QString url;
+ if (sdr.m_protocol == "SDRangel wss") {
+ url = QString("sdrangel-wss-server://%1").arg(address);
+ } else {
+ url = QString("sdrangel-server://%1").arg(address);
+ }
+ QString link = QString("%2").arg(url).arg(address);
+ text.append(QString("\nURL: %1").arg(link));
+ sdrangelServerMapItem.setText(new QString(text));
+ sdrangelServerMapItem.setModel(new QString("antenna.glb"));
+ sdrangelServerMapItem.setFixedPosition(true);
+ sdrangelServerMapItem.setOrientation(0);
+ QStringList bands;
+ if (sdr.m_minFrequency < 30000000) {
+ bands.append("HF");
+ }
+ if ((sdr.m_minFrequency < 300000000) && (sdr.m_maxFrequency > 30000000)) {
+ bands.append("VHF");
+ }
+ if ((sdr.m_minFrequency < 3000000000) && (sdr.m_maxFrequency > 300000000)) {
+ bands.append("UHF");
+ }
+ if (sdr.m_maxFrequency > 3000000000) {
+ bands.append("SHF");
+ }
+ QString label = QString("%1 %2").arg(sdr.m_protocol).arg(bands.join(" "));
+ sdrangelServerMapItem.setLabel(new QString(label));
+ sdrangelServerMapItem.setLabelAltitudeOffset(4.5);
+ sdrangelServerMapItem.setAltitudeReference(1);
+ update(m_map, &sdrangelServerMapItem, "SDRangel");
+ }
+}
+
// Ionosonde stations
void MapGUI::addIonosonde()
{
@@ -1582,8 +1696,13 @@ void MapGUI::applyMap2DSettings(bool reloadMap)
if (!m_settings.m_osmURL.isEmpty()) {
parameters["osm.mapping.custom.host"] = m_settings.m_osmURL; // E.g: "http://a.tile.openstreetmap.fr/hot/"
}
+#ifdef __EMSCRIPTEN__
+ // Default is http://maps-redirect.qt.io/osm/5.8/ and Emscripten needs https
+ parameters["osm.mapping.providersrepository.address"] = QString("https://sdrangel.beniston.com/sdrangel/maps/");
+#else
// Use our repo, so we can append API key
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
+#endif
// Use application specific cache, as other apps may not use API key so will have different images
QString cachePath = osmCachePath();
parameters["osm.mapping.cache.directory"] = cachePath;
@@ -1685,6 +1804,9 @@ void MapGUI::displayToolbar()
bool narrow = this->screen()->availableGeometry().width() < 400;
ui->layersMenu->setVisible(narrow);
bool overlayButtons = !narrow && ((m_settings.m_mapProvider == "osm") || m_settings.m_map3DEnabled);
+#ifdef __EMSCRIPTEN__
+ overlayButtons = false;
+#endif
ui->displayRain->setVisible(overlayButtons);
ui->displayClouds->setVisible(overlayButtons);
ui->displaySeaMarks->setVisible(overlayButtons);
@@ -2600,135 +2722,67 @@ void MapGUI::linkClicked(const QString& url)
QString spyServerURL = url.mid(21);
openSpyServer(spyServerURL);
}
-}
-
-// Open a KiwiSDR RX device
-void MapGUI::openKiwiSDR(const QString& url)
-{
- // Create DeviceSet
- MainCore *mainCore = MainCore::instance();
- unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
- MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
- mainCore->getMainMessageQueue()->push(msg);
-
- // Switch to KiwiSDR
- int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
- bool found = false;
- QString hwType = "KiwiSDR";
- for (int i = 0; i < nbSamplingDevices; i++)
- {
- const PluginInterface::SamplingDevice *samplingDevice;
-
- samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
-
- if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
- continue;
- }
-
- int direction = 0;
- MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
- mainCore->getMainMessageQueue()->push(msg);
- found = true;
- break;
- }
- if (!found)
+ else if (url.startsWith("sdrangel-wss-server://"))
{
- qCritical() << "MapGUI::openKiwiSDR: Failed to find KiwiSDR";
- return;
+ QString sdrangelServerURL = url.mid(22);
+ openSDRangelServer(sdrangelServerURL, true);
}
-
- // Wait until device is created - is there a better way?
- DeviceSet *deviceSet = nullptr;
- do
+ else if (url.startsWith("sdrangel-server://"))
{
- QTime dieTime = QTime::currentTime().addMSecs(100);
- while (QTime::currentTime() < dieTime) {
- QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
- }
- if (mainCore->getDeviceSets().size() > deviceSetIndex)
- {
- deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
- }
+ QString sdrangelServerURL = url.mid(18);
+ openSDRangelServer(sdrangelServerURL, false);
}
- while (!deviceSet);
-
- // Move to same workspace
- //getWorkspaceIndex();
+}
- // Set address setting
+// Open a KiwiSDR RX device
+void MapGUI::openKiwiSDR(const QString& url)
+{
+ m_remoteDeviceAddress = url;
QStringList deviceSettingsKeys = {"serverAddress"};
- SWGSDRangel::SWGDeviceSettings response;
- response.init();
- SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response.getKiwiSdrSettings();
- deviceSettings->setServerAddress(new QString(url));
- QString errorMessage;
- deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
+ SWGSDRangel::SWGDeviceSettings *response = new SWGSDRangel::SWGDeviceSettings();
+ response->init();
+ SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response->getKiwiSdrSettings();
+ deviceSettings->setServerAddress(new QString(m_remoteDeviceAddress));
+
+ ChannelWebAPIUtils::addDevice("KiwiSDR", 0, deviceSettingsKeys, response);
}
// Open a RemoteTCPInput device to use for SpyServer
void MapGUI::openSpyServer(const QString& url)
{
- // Create DeviceSet
- MainCore *mainCore = MainCore::instance();
- unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
- MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
- mainCore->getMainMessageQueue()->push(msg);
-
- // Switch to RemoteTCPInput
- int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
- bool found = false;
- QString hwType = "RemoteTCPInput";
- for (int i = 0; i < nbSamplingDevices; i++)
- {
- const PluginInterface::SamplingDevice *samplingDevice;
-
- samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
-
- if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
- continue;
- }
+ QStringList address = url.split(":");
+ m_remoteDeviceAddress = address[0];
+ m_remoteDevicePort = address[1].toInt();
+
+ QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
+ SWGSDRangel::SWGDeviceSettings *response = new SWGSDRangel::SWGDeviceSettings();
+ response->init();
+ SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response->getRemoteTcpInputSettings();
+ deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
+ deviceSettings->setDataPort(m_remoteDevicePort);
+ deviceSettings->setProtocol(new QString("Spy Server"));
+ deviceSettings->setOverrideRemoteSettings(false);
- int direction = 0;
- MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
- mainCore->getMainMessageQueue()->push(msg);
- found = true;
- break;
- }
- if (!found)
- {
- qCritical() << "MapGUI::openSpyServer: Failed to find RemoteTCPInput";
- return;
- }
+ ChannelWebAPIUtils::addDevice("RemoteTCPInput", 0, deviceSettingsKeys, response);
+}
- // Wait until device is created - is there a better way?
- DeviceSet *deviceSet = 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];
- }
- }
- while (!deviceSet);
+// Open a RemoteTCPInput device to use for SDRangel
+void MapGUI::openSDRangelServer(const QString& url, bool wss)
+{
+ QStringList address = url.split(":");
+ m_remoteDeviceAddress = address[0];
+ m_remoteDevicePort = address[1].toInt();
- // Move to same workspace
- //getWorkspaceIndex();
+ QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
+ SWGSDRangel::SWGDeviceSettings *response = new SWGSDRangel::SWGDeviceSettings();
+ response->init();
+ SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response->getRemoteTcpInputSettings();
+ deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
+ deviceSettings->setDataPort(m_remoteDevicePort);
+ deviceSettings->setProtocol(new QString(wss ? "SDRangel wss" : "SDRangel"));
+ deviceSettings->setOverrideRemoteSettings(false);
- // Set address/port setting
- QStringList address = url.split(":");
- QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol"};
- SWGSDRangel::SWGDeviceSettings response;
- response.init();
- SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
- deviceSettings->setDataAddress(new QString(address[0]));
- deviceSettings->setDataPort(address[1].toInt());
- deviceSettings->setProtocol(new QString("Spy Server"));
- QString errorMessage;
- deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
+ ChannelWebAPIUtils::addDevice("RemoteTCPInput", 0, deviceSettingsKeys, response);
}
#ifdef QT_WEBENGINE_FOUND
@@ -2836,4 +2890,3 @@ void MapGUI::makeUIConnections()
QObject::connect(ui->ibpBeacons, &QToolButton::clicked, this, &MapGUI::on_ibpBeacons_clicked);
QObject::connect(ui->radiotime, &QToolButton::clicked, this, &MapGUI::on_radiotime_clicked);
}
-
diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h
index f54bda7788..dfe20cb6f2 100644
--- a/plugins/feature/map/mapgui.h
+++ b/plugins/feature/map/mapgui.h
@@ -47,10 +47,12 @@
#include "util/nasaglobalimagery.h"
#include "util/kiwisdrlist.h"
#include "util/spyserverlist.h"
+#include "util/sdrangelserverlist.h"
#include "settings/rollupstate.h"
#include "availablechannelorfeaturehandler.h"
#include "SWGMapItem.h"
+#include "SWGDeviceSettings.h"
#include "mapsettings.h"
#include "mapbeacondialog.h"
@@ -169,6 +171,7 @@ class MapGUI : public FeatureGUI {
void addIBPBeacons();
QList getRadioTimeTransmitters() { return m_radioTimeTransmitters; }
void addRadioTimeTransmitters();
+ void addNAT();
void addRadar();
void addIonosonde();
void addBroadcast();
@@ -182,6 +185,7 @@ class MapGUI : public FeatureGUI {
void addVLF();
void addKiwiSDR();
void addSpyServer();
+ void addSDRangelServer();
void find(const QString& target);
void track3D(const QString& target);
Q_INVOKABLE void supportedMapsChanged();
@@ -231,6 +235,7 @@ class MapGUI : public FeatureGUI {
QGeoCoordinate m_lastFullUpdatePosition;
KiwiSDRList m_kiwiSDRList;
SpyServerList m_spyServerList;
+ SDRangelServerList m_sdrangelServerList;
CesiumInterface *m_cesium;
WebServer *m_webServer;
@@ -257,6 +262,10 @@ class MapGUI : public FeatureGUI {
QTableWidget *m_overviewWidget;
QTextEdit *m_descriptionWidget;
+ // Settings for opening a device
+ QString m_remoteDeviceAddress;
+ quint16 m_remoteDevicePort;
+
explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~MapGUI();
@@ -284,11 +293,13 @@ class MapGUI : public FeatureGUI {
void displayNASAMetaData();
void openKiwiSDR(const QString& url);
void openSpyServer(const QString& url);
+ void openSDRangelServer(const QString& url, bool wss);
QString formatFrequency(qint64 frequency) const;
void updateGIRO(const QDateTime& mapDateTime);
static QString getDataDir();
static const QList m_radioTimeTransmitters;
+ static const QList m_natTransmitters;
static const QList m_vlfTransmitters;
enum NASARow {
@@ -360,8 +371,8 @@ private slots:
void waypointsUpdated();
void kiwiSDRUpdated(const QList& sdrs);
void spyServerUpdated(const QList& sdrs);
+ void sdrangelServerUpdated(const QList& sdrs);
void linkClicked(const QString& url);
-
};
#endif // INCLUDE_FEATURE_MAPGUI_H_
diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp
index 4ae6b6cbcd..6c1f4a8ab3 100644
--- a/plugins/feature/map/mapsettings.cpp
+++ b/plugins/feature/map/mapsettings.cpp
@@ -99,6 +99,7 @@ MapSettings::MapSettings() :
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", true, QColor(102, 0, 102), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8));
+ m_itemSettings.insert("NAT ATC Transmitters", new MapItemSettings("NAT ATC Transmitters", false, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("FT8Demod", new MapItemSettings("FT8Demod", true, QColor(0, 192, 255), true, true, 8));
m_itemSettings.insert("HeatMap", new MapItemSettings("HeatMap", true, QColor(102, 40, 220), true, true, 11));
m_itemSettings.insert("VLF", new MapItemSettings("VLF", false, QColor(255, 0, 0), false, true, 8));
@@ -157,8 +158,13 @@ MapSettings::MapSettings() :
waypointsSettings->m_filterDistance = 500000;
m_itemSettings.insert("Waypoints", waypointsSettings);
- m_itemSettings.insert("KiwiSDR", new MapItemSettings("KiwiSDR", true, QColor(0, 255, 0), false, true, 8));
- m_itemSettings.insert("SpyServer", new MapItemSettings("SpyServer", true, QColor(0, 0, 255), false, true, 8));
+ bool showOtherServers = true;
+#ifdef __EMSCRIPTEN__
+ showOtherServers = false; // Can't use without proxy
+#endif
+ m_itemSettings.insert("KiwiSDR", new MapItemSettings("KiwiSDR", showOtherServers, QColor(0, 255, 0), false, true, 8));
+ m_itemSettings.insert("SpyServer", new MapItemSettings("SpyServer", showOtherServers, QColor(0, 0, 255), false, true, 8));
+ m_itemSettings.insert("SDRangel", new MapItemSettings("SDRangel", true, QColor(255, 0, 255), false, true, 8));
resetToDefaults();
}
diff --git a/plugins/samplesource/remotetcpinput/CMakeLists.txt b/plugins/samplesource/remotetcpinput/CMakeLists.txt
index b7f2693b46..644c5c1443 100644
--- a/plugins/samplesource/remotetcpinput/CMakeLists.txt
+++ b/plugins/samplesource/remotetcpinput/CMakeLists.txt
@@ -57,6 +57,7 @@ endif()
target_link_libraries(${TARGET_NAME} PRIVATE
Qt::Core
+ Qt::WebSockets
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
diff --git a/plugins/samplesource/remotetcpinput/readme.md b/plugins/samplesource/remotetcpinput/readme.md
index 9158dfd408..48f929ebb8 100644
--- a/plugins/samplesource/remotetcpinput/readme.md
+++ b/plugins/samplesource/remotetcpinput/readme.md
@@ -93,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 and Spy Server protocol supports 8, 16, 24 and 32-bit samples.
+When the protocol is RTL0, only 8-bits are supported. SDRangel and Spy Server protocol supports 8, 16, 24 and 32-bit samples.
19: Server IP address
@@ -105,14 +105,16 @@ TCP port on the server to connect to.
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.
+Selects protocol to use. Set to SDRangel for rtl_tcp, rsp_tcp or SDRangel's own protocol.
+Set to SDRangel wss to use SDRangel's protocol over WebSocket Secure.
+Alternatively, Spy Server can be selected to connect to Spy Servers.
23: Connection settings
Determines which settings are used when connecting.
When checked, settings in the RemoteTCPInput GUI are written to the remote device upon connection.
-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.
+When unchecked, if the remote server is using the SDRangel 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.
24: Pre-fill
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinput.cpp b/plugins/samplesource/remotetcpinput/remotetcpinput.cpp
index 06619e3ace..ae27a4cf52 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinput.cpp
+++ b/plugins/samplesource/remotetcpinput/remotetcpinput.cpp
@@ -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::quiet_NaN()),
+ m_longitude(std::numeric_limits::quiet_NaN()),
+ m_altitude(std::numeric_limits::quiet_NaN()),
+ m_isotropic(false),
+ m_azimuth(std::numeric_limits::quiet_NaN()),
+ m_elevation(std::numeric_limits::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,39 @@ void RemoteTCPInput::destroy()
void RemoteTCPInput::init()
{
+ qDebug() << "RemoteTCPInput::init";
applySettings(m_settings, QList(), true);
}
bool RemoteTCPInput::start()
{
qDebug() << "RemoteTCPInput::start";
+ if (m_running) {
+ qDebug() << "RemoteTCPInput::stop - Already running";
+ return true;
+ }
m_remoteInputTCPPHandler->reset();
m_remoteInputTCPPHandler->start();
m_remoteInputTCPPHandler->getInputMessageQueue()->push(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler::create(m_settings, QList(), 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
+ return;
+ }
m_remoteInputTCPPHandler->stop();
m_thread.quit();
+#ifndef __EMSCRIPTEN__
m_thread.wait();
+#endif
+ m_running = false;
}
QByteArray RemoteTCPInput::serialize() const
@@ -119,7 +145,6 @@ bool RemoteTCPInput::deserialize(const QByteArray& data)
m_settings.resetToDefaults();
success = false;
}
-
MsgConfigureRemoteTCPInput* message = MsgConfigureRemoteTCPInput::create(m_settings, QList(), true);
m_inputMessageQueue.push(message);
@@ -195,14 +220,14 @@ bool RemoteTCPInput::handleMessage(const Message& message)
else if (MsgConfigureRemoteTCPInput::match(message))
{
qDebug() << "RemoteTCPInput::handleMessage:" << message.getIdentifier();
- MsgConfigureRemoteTCPInput& conf = (MsgConfigureRemoteTCPInput&) message;
+ const MsgConfigureRemoteTCPInput& conf = (const MsgConfigureRemoteTCPInput&) message;
applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce());
return true;
}
else if (RemoteTCPInputTCPHandler::MsgReportConnection::match(message))
{
qDebug() << "RemoteTCPInput::handleMessage:" << message.getIdentifier();
- RemoteTCPInputTCPHandler::MsgReportConnection& report = (RemoteTCPInputTCPHandler::MsgReportConnection&) message;
+ const RemoteTCPInputTCPHandler::MsgReportConnection& report = (const RemoteTCPInputTCPHandler::MsgReportConnection&) message;
if (report.getConnected())
{
qDebug() << "Disconnected - stopping DSP";
@@ -210,6 +235,42 @@ bool RemoteTCPInput::handleMessage(const Message& message)
}
return true;
}
+ else if (MsgSaveReplay::match(message))
+ {
+ const MsgSaveReplay& cmd = (const MsgSaveReplay&) message;
+ m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
+ return true;
+ }
+ else if (MsgSendMessage::match(message))
+ {
+ const MsgSendMessage& msg = (const MsgSendMessage&) message;
+ m_remoteInputTCPPHandler->getInputMessageQueue()->push(MsgSendMessage::create(msg.getCallsign(), msg.getText(), msg.getBroadcast()));
+ return true;
+ }
+ else if (MsgReportPosition::match(message))
+ {
+ const MsgReportPosition& report = (const 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))
+ {
+ const MsgReportDirection& report = (const 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 +303,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 +332,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 +538,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& deviceSettingsKeys, const RemoteTCPInputSettings& settings, bool force)
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinput.h b/plugins/samplesource/remotetcpinput/remotetcpinput.h
index 56a1e4f373..5ef3ec9e78 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinput.h
+++ b/plugins/samplesource/remotetcpinput/remotetcpinput.h
@@ -31,13 +31,14 @@
#include
#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
@@ -78,10 +79,10 @@ class RemoteTCPInput : public DeviceSampleSource {
return new MsgStartStop(startStop);
}
- protected:
+ private:
bool m_startStop;
- MsgStartStop(bool startStop) :
+ explicit MsgStartStop(bool startStop) :
Message(),
m_startStop(startStop)
{ }
@@ -104,7 +105,7 @@ class RemoteTCPInput : public DeviceSampleSource {
outBytesAvailable, outSize, outSeconds);
}
- protected:
+ private:
qint64 m_inBytesAvailable;
qint64 m_inSize;
float m_inSeconds;
@@ -124,6 +125,100 @@ class RemoteTCPInput : public DeviceSampleSource {
{ }
};
+ class MsgSaveReplay : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ QString getFilename() const { return m_filename; }
+
+ static MsgSaveReplay* create(const QString& filename) {
+ return new MsgSaveReplay(filename);
+ }
+
+ private:
+ QString m_filename;
+
+ explicit 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);
+ }
+
+ private:
+ 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 @@ class RemoteTCPInput : public DeviceSampleSource {
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 @@ class RemoteTCPInput : public DeviceSampleSource {
QString m_deviceDescription;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
+ ReplayBuffer 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& settingsKeys, bool force = false);
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp
index b1eec42ea4..7270b4c03b 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#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;
@@ -143,7 +151,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
{
if (RemoteTCPInput::MsgConfigureRemoteTCPInput::match(message))
{
- const RemoteTCPInput::MsgConfigureRemoteTCPInput& cfg = (RemoteTCPInput::MsgConfigureRemoteTCPInput&) message;
+ const RemoteTCPInput::MsgConfigureRemoteTCPInput& cfg = (const RemoteTCPInput::MsgConfigureRemoteTCPInput&) message;
if (cfg.getForce()) {
m_settings = cfg.getSettings();
@@ -158,7 +166,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
}
else if (RemoteTCPInput::MsgStartStop::match(message))
{
- RemoteTCPInput::MsgStartStop& notif = (RemoteTCPInput::MsgStartStop&) message;
+ const RemoteTCPInput::MsgStartStop& notif = (const RemoteTCPInput::MsgStartStop&) message;
m_connectionError = false;
blockApplySettings(true);
ui->startStop->setChecked(notif.getStartStop());
@@ -167,7 +175,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
}
else if (RemoteTCPInput::MsgReportTCPBuffer::match(message))
{
- const RemoteTCPInput::MsgReportTCPBuffer& report = (RemoteTCPInput::MsgReportTCPBuffer&) message;
+ const RemoteTCPInput::MsgReportTCPBuffer& report = (const RemoteTCPInput::MsgReportTCPBuffer&) message;
ui->inGauge->setMaximum((int)report.getInSize());
ui->inGauge->setValue((int)report.getInBytesAvailable());
ui->inBufferLenSecsText->setText(QString("%1s").arg(report.getInSeconds(), 0, 'f', 2));
@@ -178,7 +186,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
}
else if (RemoteTCPInputTCPHandler::MsgReportRemoteDevice::match(message))
{
- const RemoteTCPInputTCPHandler::MsgReportRemoteDevice& report = (RemoteTCPInputTCPHandler::MsgReportRemoteDevice&) message;
+ const RemoteTCPInputTCPHandler::MsgReportRemoteDevice& report = (const RemoteTCPInputTCPHandler::MsgReportRemoteDevice&) message;
QHash devices = {
{RemoteTCPProtocol::RTLSDR_E4000, "RTLSDR E4000"},
{RemoteTCPProtocol::RTLSDR_FC0012, "RTLSDR FC0012"},
@@ -224,24 +232,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 +261,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,36 +295,35 @@ 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;
}
else if (RemoteTCPInputTCPHandler::MsgReportConnection::match(message))
{
- const RemoteTCPInputTCPHandler::MsgReportConnection& report = (RemoteTCPInputTCPHandler::MsgReportConnection&) message;
+ const RemoteTCPInputTCPHandler::MsgReportConnection& report = (const RemoteTCPInputTCPHandler::MsgReportConnection&) message;
qDebug() << "RemoteTCPInputGui::handleMessage: MsgReportConnection connected: " << report.getConnected();
- if (report.getConnected())
- {
- m_connectionError = false;
- ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
- }
- else
- {
- m_connectionError = true;
- ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
- }
+ m_connectionError = !report.getConnected();
+ 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 +332,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 +470,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 +498,11 @@ void RemoteTCPInputGui::displaySettings()
}
displayGains();
+ displayReplayLength();
+ displayReplayOffset();
+ displayReplayStep();
+ ui->replayLoop->setChecked(m_settings.m_replayLoop);
+ displayEnabled();
blockApplySettings(false);
}
@@ -517,6 +608,7 @@ const QHash 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 +701,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,16 +886,43 @@ 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();
- m_settingsKeys.append("dataAddress");
- m_settings.m_addressList.clear();
- for (int i = 0; i < ui->dataAddress->count(); i++) {
- m_settings.m_addressList.append(ui->dataAddress->itemText(i));
+ QString text = ui->dataAddress->currentText();
+ if (text != m_settings.m_dataAddress)
+ {
+ m_settings.m_dataAddress = text;
+ m_settingsKeys.append("dataAddress");
+ m_settings.m_addressList.clear();
+ for (int i = 0; i < ui->dataAddress->count(); i++) {
+ m_settings.m_addressList.append(ui->dataAddress->itemText(i));
+ }
+ m_settingsKeys.append("addressList");
+ sendSettings();
}
- m_settingsKeys.append("addressList");
- sendSettings();
}
void RemoteTCPInputGui::on_dataAddress_currentIndexChanged(int index)
@@ -810,17 +934,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 +947,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 +984,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 +1010,28 @@ void RemoteTCPInputGui::updateStatus()
default:
break;
}
+ }
+ displayEnabled();
+}
- m_lastEngineState = state;
+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 +1039,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 +1051,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 +1067,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 +1192,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::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::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_dataAddress_currentIndexChanged);
- QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataPort_editingFinished);
+ QObject::connect(ui->dataPort, QOverload::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::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);
}
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.h b/plugins/samplesource/remotetcpinput/remotetcpinputgui.h
index 9afcec66ac..b252c45c83 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.h
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.h
@@ -108,12 +108,11 @@ class RemoteTCPInputGui : public DeviceGUI {
QList 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 @@ class RemoteTCPInputGui : public DeviceGUI {
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 @@ class RemoteTCPInputGui : public DeviceGUI {
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
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui b/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui
index ec526650d6..75e7f180b4 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputgui.ui
@@ -7,7 +7,7 @@
0
0
360
- 360
+ 586
@@ -19,13 +19,13 @@
360
- 360
+ 500
- 491
- 360
+ 533
+ 610
@@ -89,7 +89,7 @@
00000k
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
@@ -100,7 +100,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -134,7 +134,7 @@
PointingHandCursor
- Qt::StrongFocus
+ Qt::FocusPolicy::StrongFocus
Center frequency in kHz
@@ -153,14 +153,14 @@
kHz
- Qt::AlignCenter
+ Qt::AlignmentFlag::AlignCenter
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -199,7 +199,7 @@
1
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -215,7 +215,7 @@
0
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
@@ -224,7 +224,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -253,7 +253,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -288,7 +288,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -342,7 +342,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -415,7 +415,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -459,14 +459,14 @@
40.0dB
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -500,7 +500,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -534,7 +534,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -559,7 +559,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -609,7 +609,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -624,7 +624,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -669,7 +669,7 @@
PointingHandCursor
- Qt::StrongFocus
+ Qt::FocusPolicy::StrongFocus
Channel shift frequency from center in Hz
@@ -686,7 +686,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -804,7 +804,7 @@ Use to ensure full dynamic range of 8-bit data is used.
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -863,10 +863,188 @@ Use to ensure full dynamic range of 8-bit data is used.
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
-
+
+
+ dB
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 24
+
+
+
+
+ Liberation Mono
+ 8
+
+
+
+ Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold
+
+
+
+
+
+ -
+
+
-
+
+
+ Check to enable IQ squelch
+
+
+ SQ
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ IQ squelch power level in dB
+
+
+ -150
+
+
+ 0
+
+
+ 1
+
+
+
+ -
+
+
+
+ 32
+ 0
+
+
+
+ -150
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+ dB
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+
+ 40
+ 0
+
+
+
+ IQ squelch gate time
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 30
+ 0
+
+
+
+ Channel power
+
+
+ Qt::LayoutDirection::RightToLeft
+
+
+ 0.0
+
+
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+ dB
+
+
+
+
+
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -887,6 +1065,12 @@ Use to ensure full dynamic range of 8-bit data is used.
-
+
+
+ 0
+ 0
+
+
120
@@ -914,40 +1098,25 @@ Use to ensure full dynamic range of 8-bit data is used.
-
-
-
- true
-
-
-
- 60
- 0
-
-
-
-
- 60
- 16777215
-
-
+
Remote data port (rtl_tcp defaults to 1234)
-
- 00000
+
+ 1024
-
- 0
+
+ 65535
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+ 1234
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -961,7 +1130,7 @@ Use to ensure full dynamic range of 8-bit data is used.
- 75
+ 110
0
@@ -973,6 +1142,11 @@ Use to ensure full dynamic range of 8-bit data is used.
SDRangel
+ -
+
+ SDRangel wss
+
+
-
Spy Server
@@ -1037,7 +1211,7 @@ When unchecked, if remote device is using SDRA protocol, local settings are upda
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -1100,14 +1274,14 @@ When unchecked, if remote device is using SDRA protocol, local settings are upda
10.00s
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -1162,10 +1336,186 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Click to send message
+
+
+ TX
+
+
+
+ -
+
+
+ false
+
+
+ Who to send message to
+
+
-
+
+ Host
+
+
+ -
+
+ All
+
+
+
+
+ -
+
+
+ false
+
+
+ Message to transmit
+
+
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Messages
+
+
+ QListView::Movement::Static
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
-
+
+
+
+ 65
+ 0
+
+
+
+ Time Delay
+
+
+
+ -
+
+
+ Replay time delay in seconds
+
+
+ 500
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
+ Replay time delay in seconds
+
+
+ 0.0s
+
+
+
+ -
+
+
+ Set time delay to 0 seconds
+
+
+ Now
+
+
+
+ -
+
+
+ Add displayed number of seconds to time delay
+
+
+ +5s
+
+
+
+ -
+
+
+ Remove displayed number of seconds from time delay
+
+
+ -5s
+
+
+
+ -
+
+
+ Repeatedly replay data in replay buffer
+
+
+
+
+
+
+ :/playloop.png:/playloop.png
+
+
+
+ -
+
+
+ Save replay buffer to a file
+
+
+
+
+
+
+ :/save.png:/save.png
+
+
+
+
+
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -1181,7 +1531,7 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -1208,12 +1558,24 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
1
+
+ LevelMeterSignalDB
+ QWidget
+
+ 1
+
ValueDial
QWidget
1
+
+ PeriodDial
+ QWidget
+
+ 1
+
startStop
@@ -1232,9 +1594,23 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
channelGain
decimation
sampleBits
+ squelchEnabled
+ squelch
+ dataAddress
dataPort
+ protocol
overrideRemoteSettings
preFill
+ sendMessage
+ txAddress
+ txMessage
+ messages
+ replayOffset
+ replayNow
+ replayPlus
+ replayMinus
+ replayLoop
+ replaySave
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp
index ad52917941..b374b9fc1e 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.cpp
@@ -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++)
{
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h
index a95dc97732..bb4606fbb9 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputsettings.h
@@ -53,7 +53,14 @@ 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"
+ QString m_protocol; // "SDRangel", "SDRangel wss" 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();
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp
index 9eaf92038b..d383326b28 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.cpp
@@ -17,11 +17,12 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#include
#include
+#include
#include "device/deviceapi.h"
#include "util/message.h"
+#include "maincore.h"
#include "remotetcpinputtcphandler.h"
#include "remotetcpinput.h"
@@ -31,30 +32,54 @@ MESSAGE_CLASS_DEFINITION(RemoteTCPInputTCPHandler::MsgReportRemoteDevice, Messag
MESSAGE_CLASS_DEFINITION(RemoteTCPInputTCPHandler::MsgReportConnection, Message)
MESSAGE_CLASS_DEFINITION(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler, Message)
-RemoteTCPInputTCPHandler::RemoteTCPInputTCPHandler(SampleSinkFifo *sampleFifo, DeviceAPI *deviceAPI) :
+RemoteTCPInputTCPHandler::RemoteTCPInputTCPHandler(SampleSinkFifo *sampleFifo, DeviceAPI *deviceAPI, ReplayBuffer *replayBuffer) :
m_deviceAPI(deviceAPI),
m_running(false),
m_dataSocket(nullptr),
+ m_tcpSocket(nullptr),
+ m_webSocket(nullptr),
m_tcpBuf(nullptr),
m_sampleFifo(sampleFifo),
- m_messageQueueToGUI(0),
+ m_replayBuffer(replayBuffer),
+ m_messageQueueToInput(nullptr),
+ m_messageQueueToGUI(nullptr),
m_fillBuffer(true),
m_timer(this),
m_reconnectTimer(this),
m_sdra(false),
m_converterBuffer(nullptr),
m_converterBufferNbSamples(0),
- m_settings()
+ m_settings(),
+ m_remoteControl(true),
+ m_iqOnly(false),
+ m_decoder(nullptr),
+ m_zOutBuf(m_zBufSize, '\0'),
+ m_blacklisted(false),
+ m_magsq(0.0f),
+ m_magsqSum(0.0f),
+ m_magsqPeak(0.0f),
+ m_magsqCount(0)
{
m_sampleFifo->setSize(5000000); // Start with large FIFO, to avoid having to resize
m_tcpBuf = new char[m_sampleFifo->size()*2*4];
m_timer.setInterval(50); // Previously 125, but this results in an obviously slow spectrum refresh rate
connect(&m_reconnectTimer, SIGNAL(timeout()), this, SLOT(reconnect()));
m_reconnectTimer.setSingleShot(true);
+
+ // Initialise zlib decompressor
+ m_zStream.zalloc = nullptr;
+ m_zStream.zfree = nullptr;
+ m_zStream.opaque = nullptr;
+ m_zStream.avail_in = 0;
+ m_zStream.next_in = nullptr;
+ if (Z_OK != inflateInit(&m_zStream)) {
+ qDebug() << "RemoteTCPInputTCPHandler::RemoteTCPInputTCPHandler: inflateInit failed.";
+ }
}
RemoteTCPInputTCPHandler::~RemoteTCPInputTCPHandler()
{
+ qDebug() << "RemoteTCPInputTCPHandler::~RemoteTCPInputTCPHandler";
delete[] m_tcpBuf;
if (m_converterBuffer) {
delete[] m_converterBuffer;
@@ -66,6 +91,7 @@ void RemoteTCPInputTCPHandler::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
+ m_blacklisted = false;
}
// start() is called from DSPDeviceSourceEngine thread
@@ -101,39 +127,57 @@ void RemoteTCPInputTCPHandler::started()
// Don't connectToHost until we get settings
connect(&m_timer, SIGNAL(timeout()), this, SLOT(processData()));
- m_timer.start();
disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
}
void RemoteTCPInputTCPHandler::finished()
{
+ qDebug("RemoteTCPInputTCPHandler::finished");
QMutexLocker mutexLocker(&m_mutex);
m_timer.stop();
disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(processData()));
- disconnectFromHost();
+ //disconnectFromHost();
+ cleanup();
disconnect(thread(), SIGNAL(finished()), this, SLOT(finished()));
m_running = false;
}
-void RemoteTCPInputTCPHandler::connectToHost(const QString& address, quint16 port)
+void RemoteTCPInputTCPHandler::connectToHost(const QString& address, quint16 port, const QString& protocol)
{
- qDebug("RemoteTCPInputTCPHandler::connectToHost: connect to %s:%d", address.toStdString().c_str(), port);
- m_dataSocket = new QTcpSocket(this);
+ qDebug("RemoteTCPInputTCPHandler::connectToHost: connect to %s %s:%d", protocol.toStdString().c_str(), address.toStdString().c_str(), port);
m_fillBuffer = true;
m_readMetaData = false;
- connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
- connect(m_dataSocket, SIGNAL(connected()), this, SLOT(connected()));
- connect(m_dataSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));
+ if (protocol == "SDRangel wss")
+ {
+ m_webSocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
+ connect(m_webSocket, &QWebSocket::binaryFrameReceived, this, &RemoteTCPInputTCPHandler::dataReadyRead);
+ connect(m_webSocket, &QWebSocket::connected, this, &RemoteTCPInputTCPHandler::connected);
+ connect(m_webSocket, &QWebSocket::disconnected, this, &RemoteTCPInputTCPHandler::disconnected);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ connect(m_webSocket, &QWebSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
+#endif
+ connect(m_webSocket, &QWebSocket::sslErrors, this, &RemoteTCPInputTCPHandler::sslErrors);
+ m_webSocket->open(QUrl(QString("wss://%1:%2").arg(address).arg(port)));
+ m_dataSocket = new WebSocket(m_webSocket);
+ }
+ else
+ {
+ m_tcpSocket = new QTcpSocket(this);
+ connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
+ connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(connected()));
+ connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- connect(m_dataSocket, QOverload::of(&QAbstractSocket::error), this, &RemoteTCPInputTCPHandler::errorOccurred);
+ connect(m_tcpSocket, QOverload::of(&QAbstractSocket::error), this, &RemoteTCPInputTCPHandler::errorOccurred);
#else
- connect(m_dataSocket, &QAbstractSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
+ connect(m_tcpSocket, &QAbstractSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
#endif
- m_dataSocket->connectToHost(address, port);
+ m_tcpSocket->connectToHost(address, port);
+ m_dataSocket = new TCPSocket(m_tcpSocket);
+ }
}
-void RemoteTCPInputTCPHandler::disconnectFromHost()
+/*void RemoteTCPInputTCPHandler::disconnectFromHost()
{
if (m_dataSocket)
{
@@ -146,18 +190,57 @@ void RemoteTCPInputTCPHandler::disconnectFromHost()
#else
disconnect(m_dataSocket, &QAbstractSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
#endif
- m_dataSocket->disconnectFromHost();
+ //m_dataSocket->disconnectFromHost();
cleanup();
}
-}
+}*/
void RemoteTCPInputTCPHandler::cleanup()
{
+ if (m_decoder)
+ {
+ FLAC__stream_decoder_delete(m_decoder);
+ m_decoder = nullptr;
+ }
+ if (m_webSocket)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::cleanup: Closing and deleting web socket";
+ disconnect(m_webSocket, &QWebSocket::binaryFrameReceived, this, &RemoteTCPInputTCPHandler::dataReadyRead);
+ disconnect(m_webSocket, &QWebSocket::connected, this, &RemoteTCPInputTCPHandler::connected);
+ disconnect(m_webSocket, &QWebSocket::disconnected, this, &RemoteTCPInputTCPHandler::disconnected);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ disconnect(m_webSocket, &QWebSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
+#endif
+ }
+ if (m_tcpSocket)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::cleanup: Closing and deleting TCP socket";
+ // Disconnect disconnected, so don't get called recursively
+ disconnect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
+ disconnect(m_tcpSocket, SIGNAL(connected()), this, SLOT(connected()));
+ disconnect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));
+#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
+ disconnect(m_tcpSocket, QOverload::of(&QAbstractSocket::error), this, &RemoteTCPInputTCPHandler::errorOccurred);
+#else
+ disconnect(m_tcpSocket, &QAbstractSocket::errorOccurred, this, &RemoteTCPInputTCPHandler::errorOccurred);
+#endif
+ }
if (m_dataSocket)
{
+ m_dataSocket->close();
m_dataSocket->deleteLater();
m_dataSocket = nullptr;
}
+ if (m_webSocket)
+ {
+ m_webSocket->deleteLater();
+ m_webSocket = nullptr;
+ }
+ if (m_tcpSocket)
+ {
+ m_tcpSocket->deleteLater();
+ m_tcpSocket = nullptr;
+ }
}
// Clear input buffer when settings change that invalidate the data in it
@@ -176,225 +259,188 @@ void RemoteTCPInputTCPHandler::clearBuffer()
else
{
m_dataSocket->flush();
- m_dataSocket->readAll();
- m_fillBuffer = true;
+ if (!m_decoder) { // Can't throw away FLAC header
+ m_dataSocket->readAll();
+ m_fillBuffer = true;
+ }
}
}
}
-void RemoteTCPInputTCPHandler::setSampleRate(int sampleRate)
+void RemoteTCPInputTCPHandler::sendCommand(RemoteTCPProtocol::Command cmd, quint32 value)
{
QMutexLocker mutexLocker(&m_mutex);
-
quint8 request[5];
- request[0] = RemoteTCPProtocol::setSampleRate;
- RemoteTCPProtocol::encodeUInt32(&request[1], sampleRate);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
+
+ request[0] = (quint8) cmd;
+ RemoteTCPProtocol::encodeUInt32(&request[1], value);
+ if (m_dataSocket)
+ {
+ qint64 len = m_dataSocket->write((char*)request, sizeof(request));
+ if (len != sizeof(request)) {
+ qDebug() << "RemoteTCPInputTCPHandler::sendCommand: Failed to write all of request:" << len;
+ }
+ } else {
+ qDebug() << "RemoteTCPInputTCPHandler::sendCommand: No socket";
}
}
-void RemoteTCPInputTCPHandler::setCenterFrequency(quint64 frequency)
+void RemoteTCPInputTCPHandler::sendCommandFloat(RemoteTCPProtocol::Command cmd, float value)
{
QMutexLocker mutexLocker(&m_mutex);
-
quint8 request[5];
- request[0] = RemoteTCPProtocol::setCenterFrequency;
- RemoteTCPProtocol::encodeUInt32(&request[1], frequency);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
+
+ request[0] = (quint8) cmd;
+ RemoteTCPProtocol::encodeFloat(&request[1], value);
+ if (m_dataSocket)
+ {
+ qint64 len = m_dataSocket->write((char*)request, sizeof(request));
+ if (len != sizeof(request)) {
+ qDebug() << "RemoteTCPInputTCPHandler::sendCommand: Failed to write all of request:" << len;
+ }
+ } else {
+ qDebug() << "RemoteTCPInputTCPHandler::sendCommand: No socket";
}
}
-void RemoteTCPInputTCPHandler::setTunerAGC(bool agc)
+void RemoteTCPInputTCPHandler::setSampleRate(int sampleRate)
{
- QMutexLocker mutexLocker(&m_mutex);
+ sendCommand(RemoteTCPProtocol::setSampleRate, sampleRate);
+}
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setTunerGainMode;
- RemoteTCPProtocol::encodeUInt32(&request[1], agc);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+void RemoteTCPInputTCPHandler::setCenterFrequency(quint64 frequency)
+{
+ sendCommand(RemoteTCPProtocol::setCenterFrequency, frequency); // FIXME: Can't support >4GHz
}
-void RemoteTCPInputTCPHandler::setTunerGain(int gain)
+void RemoteTCPInputTCPHandler::setTunerAGC(bool agc)
{
- QMutexLocker mutexLocker(&m_mutex);
+ sendCommand(RemoteTCPProtocol::setTunerGainMode, agc);
+}
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setTunerGain;
- RemoteTCPProtocol::encodeUInt32(&request[1], gain);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+void RemoteTCPInputTCPHandler::setTunerGain(int gain)
+{
+ sendCommand(RemoteTCPProtocol::setTunerGain, gain);
}
void RemoteTCPInputTCPHandler::setGainByIndex(int index)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setGainByIndex;
- RemoteTCPProtocol::encodeUInt32(&request[1], index);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setGainByIndex, index);
}
void RemoteTCPInputTCPHandler::setFreqCorrection(int correction)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setFrequencyCorrection;
- RemoteTCPProtocol::encodeUInt32(&request[1], correction);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setFrequencyCorrection, correction);
}
void RemoteTCPInputTCPHandler::setIFGain(quint16 stage, quint16 gain)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setTunerIFGain;
- RemoteTCPProtocol::encodeUInt32(&request[1], (stage << 16) | gain);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setTunerIFGain, (stage << 16) | gain);
}
void RemoteTCPInputTCPHandler::setAGC(bool agc)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setAGCMode;
- RemoteTCPProtocol::encodeUInt32(&request[1], agc);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setAGCMode, agc);
}
void RemoteTCPInputTCPHandler::setDirectSampling(bool enabled)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setDirectSampling;
- RemoteTCPProtocol::encodeUInt32(&request[1], enabled ? 3 : 0);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setDirectSampling, enabled ? 3 : 0);
}
void RemoteTCPInputTCPHandler::setDCOffsetRemoval(bool enabled)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setDCOffsetRemoval;
- RemoteTCPProtocol::encodeUInt32(&request[1], enabled);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setDCOffsetRemoval, enabled);
}
void RemoteTCPInputTCPHandler::setIQCorrection(bool enabled)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setIQCorrection;
- RemoteTCPProtocol::encodeUInt32(&request[1], enabled);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setIQCorrection, enabled);
}
void RemoteTCPInputTCPHandler::setBiasTee(bool enabled)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setBiasTee;
- RemoteTCPProtocol::encodeUInt32(&request[1], enabled);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setBiasTee, enabled);
}
void RemoteTCPInputTCPHandler::setBandwidth(int bandwidth)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setTunerBandwidth;
- RemoteTCPProtocol::encodeUInt32(&request[1], bandwidth);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setTunerBandwidth, bandwidth);
}
void RemoteTCPInputTCPHandler::setDecimation(int dec)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setDecimation;
- RemoteTCPProtocol::encodeUInt32(&request[1], dec);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setDecimation, dec);
}
void RemoteTCPInputTCPHandler::setChannelSampleRate(int sampleRate)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setChannelSampleRate;
- RemoteTCPProtocol::encodeUInt32(&request[1], sampleRate);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setChannelSampleRate, sampleRate);
}
void RemoteTCPInputTCPHandler::setChannelFreqOffset(int offset)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setChannelFreqOffset;
- RemoteTCPProtocol::encodeUInt32(&request[1], offset);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setChannelFreqOffset, offset);
}
void RemoteTCPInputTCPHandler::setChannelGain(int gain)
{
- QMutexLocker mutexLocker(&m_mutex);
-
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setChannelGain;
- RemoteTCPProtocol::encodeUInt32(&request[1], gain);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
- }
+ sendCommand(RemoteTCPProtocol::setChannelGain, gain);
}
void RemoteTCPInputTCPHandler::setSampleBitDepth(int sampleBits)
+{
+ sendCommand(RemoteTCPProtocol::setSampleBitDepth, sampleBits);
+}
+
+void RemoteTCPInputTCPHandler::setSquelchEnabled(bool enabled)
+{
+ sendCommand(RemoteTCPProtocol::setIQSquelchEnabled, (quint32) enabled);
+}
+
+void RemoteTCPInputTCPHandler::setSquelch(float squelch)
+{
+ sendCommandFloat(RemoteTCPProtocol::setIQSquelch, squelch);
+}
+
+void RemoteTCPInputTCPHandler::setSquelchGate(float squelchGate)
+{
+ sendCommandFloat(RemoteTCPProtocol::setIQSquelchGate, squelchGate);
+}
+
+void RemoteTCPInputTCPHandler::sendMessage(const QString& callsign, const QString& text, bool broadcast)
{
QMutexLocker mutexLocker(&m_mutex);
- quint8 request[5];
- request[0] = RemoteTCPProtocol::setSampleBitDepth;
- RemoteTCPProtocol::encodeUInt32(&request[1], sampleBits);
- if (m_dataSocket) {
- m_dataSocket->write((char*)request, sizeof(request));
+ if (m_dataSocket)
+ {
+ qint64 len;
+ char cmd[1+4+1];
+ QByteArray callsignBytes = callsign.toUtf8();
+ QByteArray textBytes = text.toUtf8();
+ QByteArray bytes;
+
+ bytes.append(callsignBytes);
+ bytes.append('\0');
+ bytes.append(textBytes);
+ bytes.append('\0');
+
+ cmd[0] = (char) RemoteTCPProtocol::sendMessage;
+ RemoteTCPProtocol::encodeUInt32((quint8*) &cmd[1], bytes.size() + 1);
+ cmd[5] = (char) broadcast;
+
+ len = m_dataSocket->write(&cmd[0], sizeof(cmd));
+ if (len != sizeof(cmd)) {
+ qDebug() << "RemoteTCPInputTCPHandler::set: Failed to write all of message header:" << len;
+ }
+ len = m_dataSocket->write(bytes.data(), bytes.size());
+ if (len != bytes.size()) {
+ qDebug() << "RemoteTCPInputTCPHandler::set: Failed to write all of message:" << len;
+ }
+ m_dataSocket->flush();
+ qDebug() << "sendMessage" << text;
+ } else {
+ qDebug() << "RemoteTCPInputTCPHandler::sendMessage: No socket";
}
}
@@ -407,8 +453,10 @@ void RemoteTCPInputTCPHandler::spyServerConnect()
SpyServerProtocol::encodeUInt32(&request[4], 4+9);
SpyServerProtocol::encodeUInt32(&request[8], SpyServerProtocol::ProtocolID);
memcpy(&request[8+4], "SDRangel", 9);
- if (m_dataSocket) {
+ if (m_dataSocket)
+ {
m_dataSocket->write((char*)request, sizeof(request));
+ m_dataSocket->flush();
}
}
@@ -421,8 +469,10 @@ void RemoteTCPInputTCPHandler::spyServerSet(int setting, int value)
SpyServerProtocol::encodeUInt32(&request[4], 8);
SpyServerProtocol::encodeUInt32(&request[8], setting);
SpyServerProtocol::encodeUInt32(&request[12], value);
- if (m_dataSocket) {
+ if (m_dataSocket)
+ {
m_dataSocket->write((char*)request, sizeof(request));
+ m_dataSocket->flush();
}
}
@@ -577,13 +627,38 @@ void RemoteTCPInputTCPHandler::applySettings(const RemoteTCPInputSettings& setti
}
clearBuffer();
}
+ if (settingsKeys.contains("squelchEnabled") || force)
+ {
+ if (m_sdra) {
+ setSquelchEnabled(settings.m_squelchEnabled);
+ }
+ }
+ if (settingsKeys.contains("squelch") || force)
+ {
+ if (m_sdra) {
+ setSquelch(settings.m_squelch);
+ }
+ }
+ if (settingsKeys.contains("squelchGate") || force)
+ {
+ if (m_sdra) {
+ setSquelchGate(settings.m_squelchGate);
+ }
+ }
+ }
+
+ if (m_dataSocket) {
+ m_dataSocket->flush(); // Apparently needed for WebAssembly with proxy
}
// Don't use force, as disconnect can cause rtl_tcp to quit
- if (settingsKeys.contains("dataAddress") || settingsKeys.contains("dataPort") || (m_dataSocket == nullptr))
+ if (settingsKeys.contains("dataAddress")
+ || settingsKeys.contains("dataPort")
+ || ((m_dataSocket == nullptr) && !m_blacklisted))
{
- disconnectFromHost();
- connectToHost(settings.m_dataAddress, settings.m_dataPort);
+ //disconnectFromHost();
+ cleanup();
+ connectToHost(settings.m_dataAddress, settings.m_dataPort, settings.m_protocol);
}
if (force) {
@@ -593,6 +668,337 @@ void RemoteTCPInputTCPHandler::applySettings(const RemoteTCPInputSettings& setti
}
}
+static FLAC__StreamDecoderReadStatus flacReadCallback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *clientData)
+{
+ RemoteTCPInputTCPHandler *handler = (RemoteTCPInputTCPHandler *) clientData;
+
+ return handler->flacRead(decoder, buffer, bytes);
+}
+
+static FLAC__StreamDecoderWriteStatus flacWriteCallback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *clientData)
+{
+ RemoteTCPInputTCPHandler *handler = (RemoteTCPInputTCPHandler *) clientData;
+
+ return handler->flacWrite(decoder, frame, buffer);
+}
+
+static void flacErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *clientData)
+{
+ RemoteTCPInputTCPHandler *handler = (RemoteTCPInputTCPHandler *) clientData;
+
+ return handler->flacError(decoder, status);
+}
+
+FLAC__StreamDecoderReadStatus RemoteTCPInputTCPHandler::flacRead(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes)
+{
+ (void) decoder;
+
+ qsizetype bytesRequested = *bytes;
+ qsizetype bytesRead = std::min(bytesRequested, (qsizetype) m_compressedData.size());
+
+ memcpy(buffer, m_compressedData.constData(), bytesRead);
+ m_compressedData.remove(0, bytesRead);
+
+ if (bytesRead == 0)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::flacRead: Decoder will hang if we can't return data";
+ abort();
+ }
+
+ *bytes = (size_t) bytesRead;
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+FIFO::FIFO(qsizetype elements)
+{
+ m_data.resize(elements);
+ clear();
+}
+
+qsizetype FIFO::write(quint8 *data, qsizetype elements)
+{
+ qsizetype writeCount = std::min(elements, m_data.size() - m_fill);
+ qsizetype remaining = m_data.size() - m_writePtr;
+ qsizetype len2 = writeCount - remaining;
+
+ //qDebug() << "write" << write << remaining << len2;
+
+ if (len2 < 0)
+ {
+ std::memcpy(&m_data.data()[m_writePtr], data, writeCount);
+ m_writePtr += writeCount;
+ }
+ else if (len2 == 0)
+ {
+ std::memcpy(&m_data.data()[m_writePtr], data, writeCount);
+ m_writePtr = 0;
+ }
+ else
+ {
+ std::memcpy(&m_data.data()[m_writePtr], data, remaining);
+ std::memcpy(&m_data.data()[0], &data[remaining], len2);
+ m_writePtr = len2;
+ }
+
+ m_fill += writeCount;
+
+ return writeCount;
+}
+
+qsizetype FIFO::read(quint8 *data, qsizetype elements)
+{
+ qsizetype readCount = std::min(elements, m_fill);
+ qsizetype remaining = m_data.size() - m_readPtr;
+ qsizetype len2 = readCount - remaining;
+
+ // qDebug() << "read" << read << remaining << len2;
+
+ if (len2 < 0)
+ {
+ std::memcpy(data, &m_data.data()[m_readPtr], readCount);
+ m_readPtr += readCount;
+ }
+ else if (len2 == 0)
+ {
+ std::memcpy(data, &m_data.data()[m_readPtr], readCount);
+ m_readPtr = 0;
+ }
+ else
+ {
+ std::memcpy(&data[0], &m_data.data()[m_readPtr], remaining);
+ std::memcpy(&data[remaining], &m_data.data()[0], len2);
+ m_readPtr = len2;
+ }
+
+ m_fill -= readCount;
+
+ return readCount;
+}
+
+qsizetype FIFO::readPtr(quint8 **data, qsizetype elements)
+{
+ *data = (quint8 *) &m_data.data()[m_readPtr];
+
+ return std::min(elements, m_data.size() - m_readPtr);
+}
+
+void FIFO::read(qsizetype elements)
+{
+ m_readPtr = (m_readPtr + elements) % m_data.size();
+ m_fill -= elements;
+ if (m_fill < 0)
+ {
+ qDebug() << "FIFO::read: Underrun";
+ m_fill = 0;
+ }
+}
+
+void FIFO::resize(qsizetype elements)
+{
+ m_data.resize(elements);
+ m_data.squeeze();
+}
+
+void FIFO::clear()
+{
+ m_writePtr = 0;
+ m_readPtr = 0;
+ m_fill = 0;
+}
+
+void RemoteTCPInputTCPHandler::calcPower(const Sample *iq, int nbSamples)
+{
+ for (int i = 0; i < nbSamples; i++)
+ {
+ Real re = iq[i].real();// SDR_RX_SCALED;
+ Real im = iq[i].imag();// SDR_RX_SCALED;
+
+ Real magsq = (re*re + im*im) / (SDR_RX_SCALED*SDR_RX_SCALED);
+ m_movingAverage(magsq);
+ m_magsq = m_movingAverage.asDouble();
+ m_magsqSum += magsq;
+ m_magsqPeak = std::max(magsq, m_magsqPeak);
+ m_magsqCount++;
+ }
+}
+
+FLAC__StreamDecoderWriteStatus RemoteTCPInputTCPHandler::flacWrite(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[])
+{
+ (void) decoder;
+
+ m_uncompressedFrames++;
+
+ int nbSamples = frame->header.blocksize;
+ if (nbSamples > (int) m_converterBufferNbSamples)
+ {
+ if (m_converterBuffer) {
+ delete[] m_converterBuffer;
+ }
+ m_converterBuffer = new int32_t[nbSamples*2];
+ }
+
+ // Convert and interleave samples and output to FIFO
+ if ((frame->header.bits_per_sample == 8) && (SDR_RX_SAMP_SZ == 24) && (frame->header.channels == 2))
+ {
+ qint32 *out = (qint32 *)m_converterBuffer;
+ const qint32 *inI = buffer[0];
+ const qint32 *inQ = buffer[1];
+
+ for (int i = 0; i < nbSamples; i++)
+ {
+ *out++ = *inI++ << 16;
+ *out++ = *inQ++ << 16;
+ }
+ m_uncompressedData.write(reinterpret_cast(m_converterBuffer), nbSamples*sizeof(Sample));
+ }
+ else if ((frame->header.bits_per_sample == 16) && (SDR_RX_SAMP_SZ == 24) && (frame->header.channels == 2))
+ {
+ qint32 *out = (qint32 *)m_converterBuffer;
+ const qint32 *inI = buffer[0];
+ const qint32 *inQ = buffer[1];
+
+ for (int i = 0; i < nbSamples; i++)
+ {
+ *out++ = *inI++ << 8;
+ *out++ = *inQ++ << 8;
+ }
+ m_uncompressedData.write(reinterpret_cast(m_converterBuffer), nbSamples*sizeof(Sample));
+ }
+ else if ((frame->header.bits_per_sample == 24) && (SDR_RX_SAMP_SZ == 24) && (frame->header.channels == 2))
+ {
+ qint32 *out = (qint32 *)m_converterBuffer;
+ const qint32 *inI = buffer[0];
+ const qint32 *inQ = buffer[1];
+
+ for (int i = 0; i < nbSamples; i++)
+ {
+ *out++ = *inI++;
+ *out++ = *inQ++;
+ }
+ m_uncompressedData.write(reinterpret_cast(m_converterBuffer), nbSamples*sizeof(Sample));
+ }
+ else if ((frame->header.bits_per_sample == 32) && (SDR_RX_SAMP_SZ == 24) && (frame->header.channels == 2))
+ {
+ qint32 *out = (qint32 *)m_converterBuffer;
+ const qint32 *inI = buffer[0];
+ const qint32 *inQ = buffer[1];
+
+ for (int i = 0; i < nbSamples; i++)
+ {
+ *out++ = *inI++;
+ *out++ = *inQ++;
+ }
+ m_uncompressedData.write(reinterpret_cast(m_converterBuffer), nbSamples*sizeof(Sample));
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::flacWrite: Unsupported format";
+ }
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+
+// Convert from zlib uncompressed network format to Samples, to uncompressed data FIFO
+void RemoteTCPInputTCPHandler::processDecompressedZlibData(const char *inBuf, int nbSamples)
+{
+ // Ensure conversion buffer is large enough - FIXME: Don't use this buffer - just write in to FIFO
+ if (nbSamples > (int) m_converterBufferNbSamples)
+ {
+ if (m_converterBuffer) {
+ delete[] m_converterBuffer;
+ }
+ m_converterBuffer = new int32_t[nbSamples*2];
+ }
+
+ // Convert from network format to Sample
+ if ((m_settings.m_sampleBits == 8) && (SDR_RX_SAMP_SZ == 16))
+ {
+ const quint8 *in = (const quint8 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = (((qint16)in[is]) - 128) << 8;
+ }
+ }
+ else if ((m_settings.m_sampleBits == 8) && (SDR_RX_SAMP_SZ == 24))
+ {
+ const quint8 *in = (const quint8 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = (((qint32)in[is]) - 128) << 16;
+ }
+ }
+ else if ((m_settings.m_sampleBits == 16) && (SDR_RX_SAMP_SZ == 16))
+ {
+ const qint16 *in = (const qint16 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = in[is];
+ }
+ }
+ else if ((m_settings.m_sampleBits == 16) && (SDR_RX_SAMP_SZ == 24))
+ {
+ const qint16 *in = (const qint16 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = in[is] << 8;
+ }
+ }
+ else if ((m_settings.m_sampleBits == 24) && (SDR_RX_SAMP_SZ == 24))
+ {
+ const quint8 *in = (const quint8 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = (((in[3*is+2] << 16) | (in[3*is+1] << 8) | in[3*is]) << 8) >> 8;
+ }
+ }
+ else if ((m_settings.m_sampleBits == 24) && (SDR_RX_SAMP_SZ == 16))
+ {
+ const quint8 *in = (const quint8 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = (in[3*is+2] << 8) | in[3*is+1];
+ }
+ }
+ else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 16))
+ {
+ const qint32 *in = (const qint32 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = in[is] >> 8;
+ }
+ }
+ else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 24))
+ {
+ const qint32 *in = (const qint32 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
+
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = in[is];
+ }
+ }
+ else // invalid size
+ {
+ qWarning("RemoteTCPInputTCPHandler::convert: unexpected sample size in stream: %d bits", (int) m_settings.m_sampleBits);
+ }
+
+ m_uncompressedData.write(reinterpret_cast(m_converterBuffer), nbSamples*sizeof(Sample));
+}
+
+void RemoteTCPInputTCPHandler::flacError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status)
+{
+ (void) decoder;
+
+ qDebug() << "RemoteTCPInputTCPHandler::flacError: Error:" << status;
+}
+
void RemoteTCPInputTCPHandler::connected()
{
QMutexLocker mutexLocker(&m_mutex);
@@ -605,16 +1011,20 @@ void RemoteTCPInputTCPHandler::connected()
m_spyServer = m_settings.m_protocol == "Spy Server";
m_state = HEADER;
m_sdra = false;
+ m_remoteControl = true;
+ m_iqOnly = true;
if (m_spyServer) {
spyServerConnect();
}
+ // Start calls to processData
+ m_timer.start();
}
void RemoteTCPInputTCPHandler::reconnect()
{
QMutexLocker mutexLocker(&m_mutex);
if (!m_dataSocket) {
- connectToHost(m_settings.m_dataAddress, m_settings.m_dataPort);
+ connectToHost(m_settings.m_dataAddress, m_settings.m_dataPort, m_settings.m_protocol);
}
}
@@ -628,35 +1038,58 @@ void RemoteTCPInputTCPHandler::disconnected()
MsgReportConnection *msg = MsgReportConnection::create(false);
m_messageQueueToGUI->push(msg);
}
- // Try to reconnect
- m_reconnectTimer.start(500);
+ if (!m_blacklisted)
+ {
+ // Try to reconnect immediately - it may just be server settings changed
+ m_reconnectTimer.start(1);
+ }
+ else
+ {
+ // Stop device so we don't try to reconnect
+ RemoteTCPInput::MsgStartStop *msg = RemoteTCPInput::MsgStartStop::create(false);
+ m_messageQueueToInput->push(msg);
+ }
}
void RemoteTCPInputTCPHandler::errorOccurred(QAbstractSocket::SocketError socketError)
{
+ QMutexLocker mutexLocker(&m_mutex);
qDebug() << "RemoteTCPInputTCPHandler::errorOccurred: " << socketError;
- cleanup();
- if (m_messageQueueToGUI)
+
+ // For RemoteHostClosedError, disconnected() will be called afterwards, so don't try to reconnect here
+ // We try to reconnect here, for errors such as ConnectionRefusedError
+ if (socketError != QAbstractSocket::RemoteHostClosedError)
{
- MsgReportConnection *msg = MsgReportConnection::create(false);
- m_messageQueueToGUI->push(msg);
+ cleanup();
+ if (m_messageQueueToGUI)
+ {
+ MsgReportConnection *msg = MsgReportConnection::create(false);
+ m_messageQueueToGUI->push(msg);
+ }
+ // Try to reconnect
+ m_reconnectTimer.start(500);
}
- // Try to reconnect
- m_reconnectTimer.start(500);
+}
+
+void RemoteTCPInputTCPHandler::sslErrors(const QList &errors)
+{
+ qDebug() << "RemoteTCPInputTCPHandler::sslErrors: " << errors;
+ m_webSocket->ignoreSslErrors(); // FIXME: Add a setting whether to do this?
}
void RemoteTCPInputTCPHandler::dataReadyRead()
{
QMutexLocker mutexLocker(&m_mutex);
- if (!m_readMetaData && !m_spyServer)
- {
+ if (!m_readMetaData && !m_spyServer) {
processMetaData();
- }
- else if (!m_readMetaData && m_spyServer)
- {
+ } else if (!m_readMetaData && m_spyServer) {
processSpyServerMetaData();
}
+
+ if (m_readMetaData && !m_iqOnly) {
+ processCommands();
+ }
}
void RemoteTCPInputTCPHandler::processMetaData()
@@ -682,19 +1115,13 @@ void RemoteTCPInputTCPHandler::processMetaData()
m_device = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]);
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol));
+ m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol, false, true));
}
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));
- }
+ settings.m_sampleBits = 8;
+ sendSettings(settings, {"sampleBits"});
}
}
else if (protocol == "SDRA")
@@ -704,10 +1131,22 @@ void RemoteTCPInputTCPHandler::processMetaData()
bytesRead = m_dataSocket->read((char *)&metaData[4], RemoteTCPProtocol::m_sdraMetaDataSize-4);
m_device = (RemoteTCPProtocol::Device)RemoteTCPProtocol::extractUInt32(&metaData[4]);
+ quint32 protocolRevision = RemoteTCPProtocol::extractUInt32(&metaData[60]);
+ quint32 flags = RemoteTCPProtocol::extractUInt32(&metaData[20]);
+ if (protocolRevision >= 1)
+ {
+ m_iqOnly = !(bool) ((flags >> 7) & 1);
+ m_remoteControl = (bool) ((flags >> 6) & 1);
+ }
+ else
+ {
+ m_iqOnly = true;
+ m_remoteControl = true;
+ }
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol));
+ m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, protocol, m_iqOnly, m_remoteControl));
}
- if (!m_settings.m_overrideRemoteSettings)
+ if (!m_settings.m_overrideRemoteSettings || !m_remoteControl)
{
// Update local settings to match remote
RemoteTCPInputSettings& settings = m_settings;
@@ -716,7 +1155,6 @@ void RemoteTCPInputTCPHandler::processMetaData()
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;
@@ -752,19 +1190,57 @@ void RemoteTCPInputTCPHandler::processMetaData()
settings.m_channelDecimation = true;
settingsKeys.append("channelDecimation");
}
- if (m_messageQueueToInput) {
- m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys));
+ if (protocolRevision >= 1)
+ {
+ settings.m_squelchEnabled = (flags >> 5) & 1;
+ settingsKeys.append("squelchEnabled");
+ settings.m_squelch = RemoteTCPProtocol::extractFloat(&metaData[64]);
+ settingsKeys.append("squelch");
+ settings.m_squelchGate = RemoteTCPProtocol::extractFloat(&metaData[68]);
+ settingsKeys.append("squelchGate");
+ }
+ sendSettings(settings, settingsKeys);
+ }
+
+ if (!m_iqOnly)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler: Compression enabled";
+ // Create FLAC decoder for IQ decompression
+ m_decoder = FLAC__stream_decoder_new();
+ m_remainingSamples = 0;
+ m_compressedFrames = 0;
+ m_uncompressedFrames = 0;
+
+ int bytesPerSecond = m_settings.m_channelSampleRate * 2 * sizeof(Sample);
+ int fifoSize = 2 * m_settings.m_preFill * bytesPerSecond;
+ m_uncompressedData.resize(fifoSize);
+ m_uncompressedData.clear();
+
+ if (m_decoder)
+ {
+ FLAC__StreamDecoderInitStatus initStatus;
+ initStatus = FLAC__stream_decoder_init_stream(m_decoder, flacReadCallback, nullptr, nullptr, nullptr, nullptr, flacWriteCallback, nullptr, flacErrorCallback, this);
+ if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler: Failed to init FLAC decoder: " << initStatus;
+ }
}
- if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys));
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler: Failed to allocate FLAC decoder";
}
}
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler: Compression disabled";
+ }
}
else
{
qDebug() << "RemoteTCPInputTCPHandler::dataReadyRead: Unknown protocol: " << protocol;
+ m_dataSocket->close();
}
- if (m_settings.m_overrideRemoteSettings)
+ if (m_settings.m_overrideRemoteSettings && m_remoteControl)
{
// Force settings to be sent to remote device (this needs to be after m_sdra is determined above)
applySettings(m_settings, QList(), true);
@@ -868,7 +1344,7 @@ void RemoteTCPInputTCPHandler::processSpyServerDevice(const SpyServerProtocol::D
break;
}
if (m_messageQueueToGUI) {
- m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, "Spy Server", ssDevice->m_maxGainIndex));
+ m_messageQueueToGUI->push(MsgReportRemoteDevice::create(m_device, "Spy Server", false, true, ssDevice->m_maxGainIndex));
}
RemoteTCPInputSettings& settings = m_settings;
@@ -882,12 +1358,7 @@ void RemoteTCPInputTCPHandler::processSpyServerDevice(const SpyServerProtocol::D
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));
- }
+ sendSettings(settings, settingsKeys);
}
void RemoteTCPInputTCPHandler::processSpyServerState(const SpyServerProtocol::State* ssState, bool initial)
@@ -922,12 +1393,7 @@ void RemoteTCPInputTCPHandler::processSpyServerState(const SpyServerProtocol::St
}
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));
- }
+ sendSettings(settings, settingsKeys);
}
}
}
@@ -978,7 +1444,7 @@ void RemoteTCPInputTCPHandler::processSpyServerData(int requiredBytes, bool clea
if (!clear)
{
const int bytesPerIQPair = 2 * m_settings.m_sampleBits / 8;
- convert(bytesRead / bytesPerIQPair);
+ processUncompressedData(&m_tcpBuf[0], bytesRead / bytesPerIQPair);
}
m_spyServerHeader.m_size -= bytesRead;
requiredBytes -= bytesRead;
@@ -1013,19 +1479,483 @@ void RemoteTCPInputTCPHandler::processSpyServerData(int requiredBytes, bool clea
}
}
+void RemoteTCPInputTCPHandler::sendSettings(const RemoteTCPInputSettings& settings, const QStringList& settingsKeys)
+{
+ if (m_messageQueueToInput) {
+ m_messageQueueToInput->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys));
+ }
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPInput::MsgConfigureRemoteTCPInput::create(settings, settingsKeys));
+ }
+}
+
+void RemoteTCPInputTCPHandler::processCommands()
+{
+ bool done = false;
+
+ while (!done)
+ {
+ if (m_state == HEADER)
+ {
+ if (m_dataSocket->bytesAvailable() >= 5)
+ {
+ quint8 buf[5];
+ qint64 bytesRead = m_dataSocket->read((char *) buf, sizeof(buf));
+
+ if (bytesRead == sizeof(buf))
+ {
+ m_command = (RemoteTCPProtocol::Command) buf[0];
+
+ switch (m_command)
+ {
+ case RemoteTCPProtocol::setCenterFrequency:
+ {
+ quint32 centerFrequency = (quint32) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (centerFrequency != m_settings.m_centerFrequency)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_centerFrequency = centerFrequency;
+ sendSettings(settings, {"centerFrequency"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setSampleRate:
+ {
+ int devSampleRate = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (devSampleRate != m_settings.m_devSampleRate)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_devSampleRate = devSampleRate;
+ sendSettings(settings, {"devSampleRate"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setTunerGainMode:
+ {
+ // Currently fixed as 1
+ }
+ case RemoteTCPProtocol::setTunerGain:
+ {
+ int gain = RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (gain != m_settings.m_gain[0])
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_gain[0] = gain;
+ sendSettings(settings, {"gain[0]"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setFrequencyCorrection:
+ {
+ qint32 loPpmCorrection = (qint32) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (loPpmCorrection != m_settings.m_loPpmCorrection)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_loPpmCorrection = loPpmCorrection;
+ sendSettings(settings, {"loPpmCorrection"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setTunerIFGain:
+ {
+ int v = RemoteTCPProtocol::extractUInt32(&buf[1]);
+ int gain = (int)(qint16)(v & 0xffff);
+ int stage = (v >> 16) & 0xffff;
+ if ((stage < RemoteTCPInputSettings::m_maxGains) && (gain != m_settings.m_gain[stage]))
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_gain[stage] = gain;
+ sendSettings(settings, {QString("gain[%1]").arg(stage)});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setAGCMode:
+ {
+ bool agc = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (agc != m_settings.m_agc)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_agc = agc;
+ sendSettings(settings, {"agc"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setDirectSampling:
+ {
+ bool directSampling = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (directSampling != m_settings.m_directSampling)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_directSampling = directSampling;
+ sendSettings(settings, {"directSampling"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setBiasTee:
+ {
+ bool biasTee = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (biasTee != m_settings.m_biasTee)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_biasTee = biasTee;
+ sendSettings(settings, {"biasTee"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setTunerBandwidth:
+ {
+ int rfBW = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (rfBW != m_settings.m_rfBW)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_rfBW = rfBW;
+ sendSettings(settings, {"rfBW"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setDecimation:
+ {
+ int log2Decim = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (log2Decim != m_settings.m_log2Decim)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_log2Decim = log2Decim;
+ sendSettings(settings, {"log2Decim"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setDCOffsetRemoval:
+ {
+ bool dcBlock = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (dcBlock != m_settings.m_dcBlock)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_dcBlock = dcBlock;
+ sendSettings(settings, {"dcBlock"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQCorrection:
+ {
+ bool iqCorrection = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (iqCorrection != m_settings.m_iqCorrection)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_iqCorrection = iqCorrection;
+ sendSettings(settings, {"iqCorrection"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setChannelSampleRate:
+ {
+ qint32 channelSampleRate = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (channelSampleRate != m_settings.m_channelSampleRate)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_channelSampleRate = channelSampleRate;
+ sendSettings(settings, {"channelSampleRate"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setChannelFreqOffset:
+ {
+ qint32 inputFrequencyOffset = (qint32) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_inputFrequencyOffset = inputFrequencyOffset;
+ sendSettings(settings, {"inputFrequencyOffset"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setChannelGain:
+ {
+ qint32 channelGain = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (channelGain != m_settings.m_channelGain)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_channelGain = channelGain;
+ sendSettings(settings, {"channelGain"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setSampleBitDepth:
+ {
+ qint32 sampleBits = RemoteTCPProtocol::extractInt32(&buf[1]);
+ if (sampleBits != m_settings.m_sampleBits)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_sampleBits = sampleBits;
+ sendSettings(settings, {"sampleBits"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelchEnabled:
+ {
+ bool squelchEnabled = (bool) RemoteTCPProtocol::extractUInt32(&buf[1]);
+ if (squelchEnabled != m_settings.m_squelchEnabled)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_squelchEnabled = squelchEnabled;
+ sendSettings(settings, {"squelchEnabled"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelch:
+ {
+ float squelch = RemoteTCPProtocol::extractFloat(&buf[1]);
+ if (squelch != m_settings.m_squelch)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_squelch = squelch;
+ sendSettings(settings, {"squelch"});
+ }
+ break;
+ }
+ case RemoteTCPProtocol::setIQSquelchGate:
+ {
+ float squelchGate = RemoteTCPProtocol::extractFloat(&buf[1]);
+ if (squelchGate != m_settings.m_squelchGate)
+ {
+ RemoteTCPInputSettings settings = m_settings;
+ settings.m_squelchGate = squelchGate;
+ sendSettings(settings, {"squelchGate"});
+ }
+ break;
+ }
+ default:
+ m_commandLength = RemoteTCPProtocol::extractUInt32(&buf[1]);
+ m_state = DATA;
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << sizeof(buf);
+ }
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ if (m_state == DATA)
+ {
+ if (m_dataSocket->bytesAvailable() >= m_commandLength)
+ {
+ try
+ {
+ switch (m_command)
+ {
+
+
+ case RemoteTCPProtocol::dataIQ:
+ {
+ break;
+ }
+
+ case RemoteTCPProtocol::dataIQFLAC:
+ {
+ qsizetype s = m_compressedData.size();
+ m_compressedData.resize(s + m_commandLength);
+ qint64 bytesRead = m_dataSocket->read(&m_compressedData.data()[s], m_commandLength);
+ m_compressedFrames++;
+ if (bytesRead == m_commandLength)
+ {
+ // FLAC encoder writes out 4 (fLaC), 38 (STREAMINFO), 51 (?) byte headers, that are transmitted as one command block,
+ // then each command block will be a complete audio block (first two bytes will be 0xfff8)
+ // FLAC__stream_decoder_process_single will keep calling the read callback until it's decoded one metadata or audio block
+ // so we need to make sure there's enough data that it will be able to return
+
+ bool decodeDone = false;
+
+ while (!decodeDone)
+ {
+ //qDebug() << "m_compressedFrames" << m_compressedFrames << "m_uncompressedFrames" << m_uncompressedFrames;
+ if (m_compressedFrames - 1 > m_uncompressedFrames)
+ {
+ if (!FLAC__stream_decoder_process_single(m_decoder))
+ {
+ qDebug() << "FLAC decode failed";
+ decodeDone = true;
+ }
+ }
+ else
+ {
+ decodeDone = true;
+ }
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << m_commandLength;
+ }
+ break;
+ }
+
+ case RemoteTCPProtocol::dataIQzlib:
+ {
+ if (m_commandLength > (quint32) m_compressedData.size()) {
+ m_compressedData.resize(m_commandLength);
+ }
+ qint64 bytesRead = m_dataSocket->read(m_compressedData.data(), m_commandLength);
+ if (bytesRead == m_commandLength)
+ {
+ // Decompressing using zlib
+ m_zStream.next_in = (Bytef *) m_compressedData.data();
+ m_zStream.avail_in = m_commandLength;
+ m_zStream.next_out = (Bytef *) m_zOutBuf.data();
+ m_zStream.avail_out = m_zOutBuf.size();
+
+ int ret = inflate(&m_zStream, Z_NO_FLUSH);
+
+ if (ret == Z_STREAM_END) {
+ inflateReset(&m_zStream);
+ // Convert and write to uncompressed data FIFO
+ int uncompressedBytes = m_zOutBuf.size() - m_zStream.avail_out;
+ int nbSamples = uncompressedBytes / 2 / (m_settings.m_sampleBits / 8);
+ processDecompressedZlibData(m_zOutBuf.data(), nbSamples);
+ } else if (ret == Z_NEED_DICT) {
+ qDebug() << "zlib needs dict to inflate";
+ } else if (ret == Z_DATA_ERROR) {
+ qDebug() << "zlib data error";
+ } else if (ret == Z_MEM_ERROR) {
+ qDebug() << "zlib mem error";
+ } else {
+ qDebug() << "Unexpected zlib return value" << ret;
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << m_commandLength;
+ }
+ break;
+ }
+
+ case RemoteTCPProtocol::dataPosition:
+ {
+ char pos[4+4+4];
+ qint64 bytesRead = m_dataSocket->read(pos, m_commandLength);
+ if (bytesRead == m_commandLength)
+ {
+ float latitude = RemoteTCPProtocol::extractFloat((const quint8 *) &pos[0]);
+ float longitude = RemoteTCPProtocol::extractFloat((const quint8 *) &pos[4]);
+ float altitude = RemoteTCPProtocol::extractFloat((const quint8 *) &pos[8]);
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Position " << latitude << longitude << altitude;
+ if (m_messageQueueToInput) {
+ m_messageQueueToInput->push(RemoteTCPInput::MsgReportPosition::create(latitude, longitude, altitude));
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << m_commandLength;
+ }
+ break;
+ }
+
+ case RemoteTCPProtocol::dataDirection:
+ {
+ char dir[4+4+4];
+ qint64 bytesRead = m_dataSocket->read(dir, m_commandLength);
+ if (bytesRead == m_commandLength)
+ {
+ float isotropic = RemoteTCPProtocol::extractUInt32((const quint8 *) &dir[0]);
+ float azimuth = RemoteTCPProtocol::extractFloat((const quint8 *) &dir[4]);
+ float elevation = RemoteTCPProtocol::extractFloat((const quint8 *) &dir[8]);
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Direction " << isotropic << azimuth << elevation;
+ if (m_messageQueueToInput) {
+ m_messageQueueToInput->push(RemoteTCPInput::MsgReportDirection::create(isotropic, azimuth, elevation));
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << m_commandLength;
+ }
+ break;
+ }
+
+ case RemoteTCPProtocol::sendMessage:
+ {
+ char *buf = new char[m_commandLength];
+ qint64 bytesRead = m_dataSocket->read(buf, m_commandLength);
+
+ if (bytesRead == m_commandLength)
+ {
+ bool broadcast = (bool) buf[0];
+ int i;
+ for (i = 1; i < (int) m_commandLength; i++)
+ {
+ if (buf[i] == '\0') {
+ break;
+ }
+ }
+ QString callsign = QString::fromUtf8(&buf[1]);
+ QString text = QString::fromUtf8(&buf[i+1]);
+
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Message " << m_dataSocket->peerAddress() << m_dataSocket->peerPort() << callsign << broadcast << text;
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPInput::MsgSendMessage::create(callsign, text, broadcast));
+ }
+ }
+ else
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to read:" << bytesRead << "/" << m_commandLength;
+ }
+ delete[] buf;
+ break;
+ }
+
+ case RemoteTCPProtocol::sendBlacklistedMessage:
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Disconnecting as blacklisted";
+ if (m_messageQueueToGUI) {
+ m_messageQueueToGUI->push(RemoteTCPInput::MsgSendMessage::create("", "Disconnecting as IP address is blacklisted", false));
+ }
+ m_blacklisted = true;
+ qDebug() << "set m_blacklisted" << m_blacklisted;
+ break;
+ }
+
+ default:
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Unknown command" << m_command;
+ char *buf = new char[m_commandLength];
+ m_dataSocket->read(buf, m_commandLength);
+ delete[] buf;
+ break;
+ }
+ }
+ }
+ catch(std::bad_alloc&)
+ {
+ qDebug() << "RemoteTCPInputTCPHandler::processCommands: Failed to allocate memory";
+ done = true;
+ }
+ m_state = HEADER;
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ }
+}
+
// QTimer::timeout isn't guaranteed to be called on every timeout, so we need to look at the system clock
void RemoteTCPInputTCPHandler::processData()
{
QMutexLocker mutexLocker(&m_mutex);
- if (m_dataSocket && (m_dataSocket->state() == QAbstractSocket::ConnectedState))
+
+ if (m_dataSocket && m_dataSocket->isConnected())
{
int sampleRate = m_settings.m_channelSampleRate;
- int bytesPerIQPair = 2 * m_settings.m_sampleBits / 8;
+ int bytesPerIQPair = m_iqOnly ? (2 * m_settings.m_sampleBits / 8) : (2 * sizeof(Sample));
int bytesPerSecond = sampleRate * bytesPerIQPair;
- if (m_dataSocket->bytesAvailable() < (0.1f * m_settings.m_preFill * bytesPerSecond))
+ qint64 bytesAvailable = m_iqOnly ? m_dataSocket->bytesAvailable() : m_uncompressedData.fill();
+
+ if ((bytesAvailable < (0.1f * m_settings.m_preFill * bytesPerSecond)) && !m_fillBuffer)
{
- qDebug() << "RemoteTCPInputTCPHandler::processData: Buffering - bytesAvailable:" << m_dataSocket->bytesAvailable();
+ qDebug() << "RemoteTCPInputTCPHandler::processData: Buffering - bytesAvailable:" << bytesAvailable;
m_fillBuffer = true;
}
@@ -1033,9 +1963,9 @@ void RemoteTCPInputTCPHandler::processData()
// QTcpSockets buffer size should be unlimited - we pretend here it's twice as big as the point we start reading from it
if (m_messageQueueToGUI)
{
- qint64 size = std::max(m_dataSocket->bytesAvailable(), (qint64)(m_settings.m_preFill * bytesPerSecond));
+ qint64 size = std::max(bytesAvailable, (qint64)(m_settings.m_preFill * bytesPerSecond));
RemoteTCPInput::MsgReportTCPBuffer *report = RemoteTCPInput::MsgReportTCPBuffer::create(
- m_dataSocket->bytesAvailable(), size, m_dataSocket->bytesAvailable() / (float)bytesPerSecond,
+ bytesAvailable, size, bytesAvailable / (float)bytesPerSecond,
m_sampleFifo->fill(), m_sampleFifo->size(), m_sampleFifo->fill() / (float)bytesPerSecond
);
m_messageQueueToGUI->push(report);
@@ -1045,9 +1975,9 @@ void RemoteTCPInputTCPHandler::processData()
// Prime buffer, before we start reading
if (m_fillBuffer)
{
- if (m_dataSocket->bytesAvailable() >= m_settings.m_preFill * bytesPerSecond)
+ if (bytesAvailable >= m_settings.m_preFill * bytesPerSecond)
{
- qDebug() << "RemoteTCPInputTCPHandler::processData: Buffer primed - bytesAvailable:" << m_dataSocket->bytesAvailable();
+ qDebug() << "RemoteTCPInputTCPHandler::processData: Buffer primed - bytesAvailable:" << bytesAvailable;
m_fillBuffer = false;
m_prevDateTime = QDateTime::currentDateTime();
factor = 1.0f / 4.0f; // If this is too high, samples can just be dropped downstream
@@ -1056,22 +1986,31 @@ void RemoteTCPInputTCPHandler::processData()
else
{
QDateTime currentDateTime = QDateTime::currentDateTime();
- factor = m_prevDateTime.msecsTo(currentDateTime) / 1000.0f;
+ factor = m_prevDateTime.msecsTo(currentDateTime) / 1000.0f; // FIXME: Close skew.. Actual sample rate may differ
m_prevDateTime = currentDateTime;
}
unsigned int remaining = m_sampleFifo->size() - m_sampleFifo->fill();
- int requiredSamples = (int)std::min((unsigned int)(factor * sampleRate), remaining);
+ unsigned int maxRequired = (unsigned int) (factor * sampleRate);
+ int requiredSamples = (int)std::min(maxRequired, remaining);
+ int overflow = maxRequired - requiredSamples;
+ if (overflow > 0) {
+ qDebug() << "Not enough space in FIFO:" << overflow << maxRequired;
+ }
if (!m_fillBuffer)
{
- if (!m_spyServer)
+ if (!m_iqOnly)
+ {
+ processDecompressedData(requiredSamples);
+ }
+ else if (!m_spyServer)
{
- // rtl_tcp/SDRA stream is just IQ samples
if (m_dataSocket->bytesAvailable() >= requiredSamples*bytesPerIQPair)
{
+ // rtl_tcp stream is just IQ samples
m_dataSocket->read(&m_tcpBuf[0], requiredSamples*bytesPerIQPair);
- convert(requiredSamples);
+ processUncompressedData(&m_tcpBuf[0], requiredSamples);
}
}
else
@@ -1084,9 +2023,57 @@ void RemoteTCPInputTCPHandler::processData()
}
}
+// Copy from decompressed FIFO to replay buffer and sample FIFO
+void RemoteTCPInputTCPHandler::processDecompressedData(int requiredSamples)
+{
+ qint64 requiredBytes = requiredSamples * sizeof(Sample);
+
+ m_replayBuffer->lock();
+
+ while ((requiredBytes > 0) && !m_uncompressedData.empty())
+ {
+ quint8 *uncompressedPtr;
+ qsizetype uncompressedBytes = m_uncompressedData.readPtr(&uncompressedPtr, requiredSamples * sizeof(Sample));
+ qsizetype uncompressedSamples = 2 * uncompressedBytes / sizeof(Sample);
+
+ // Save data to replay buffer
+ bool replayEnabled = m_replayBuffer->getSize() > 0;
+ if (replayEnabled) {
+ m_replayBuffer->write((FixReal *) uncompressedPtr, (unsigned int) uncompressedSamples);
+ }
+
+ const FixReal *buf = (FixReal *) uncompressedPtr;
+ qint32 remaining = uncompressedSamples;
+
+ while (remaining > 0)
+ {
+ qint32 len;
+
+ // Choose between live data or replayed data
+ if (replayEnabled && m_replayBuffer->useReplay()) {
+ len = m_replayBuffer->read(remaining, buf);
+ } else {
+ len = remaining;
+ }
+ remaining -= len;
+
+ calcPower(reinterpret_cast(buf), len / 2);
+
+ m_sampleFifo->write(reinterpret_cast(buf), len * sizeof(FixReal));
+ }
+
+ m_uncompressedData.read(uncompressedBytes);
+ requiredBytes -= uncompressedBytes;
+ }
+
+ m_replayBuffer->unlock();
+}
+
+// Convert from uncompressed network format to Samples, then copy to replay buffer and sample FIFO
// The following code assumes host is little endian
-void RemoteTCPInputTCPHandler::convert(int nbSamples)
+void RemoteTCPInputTCPHandler::processUncompressedData(const char *inBuf, int nbSamples)
{
+ // Ensure conversion buffer is large enough
if (nbSamples > (int) m_converterBufferNbSamples)
{
if (m_converterBuffer) {
@@ -1095,102 +2082,121 @@ void RemoteTCPInputTCPHandler::convert(int nbSamples)
m_converterBuffer = new int32_t[nbSamples*2];
}
- if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 24) && !m_spyServer)
+ // Convert from network format to Sample
+ if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 24) && m_spyServer)
{
- m_sampleFifo->write(reinterpret_cast(m_tcpBuf), nbSamples*sizeof(Sample));
- }
- else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 24) && m_spyServer)
- {
- float *in = (float *)m_tcpBuf;
- qint32 *out = (qint32 *)m_converterBuffer;
+ const float *in = (const float *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (qint32)(in[is] * SDR_RX_SCALEF);
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 16) && m_spyServer)
{
- float *in = (float *)m_tcpBuf;
- qint16 *out = (qint16 *)m_converterBuffer;
+ const float *in = (const float *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (qint16)(in[is] * SDR_RX_SCALEF);
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 8) && (SDR_RX_SAMP_SZ == 16))
{
- quint8 *in = (quint8 *)m_tcpBuf;
- qint16 *out = (qint16 *)m_converterBuffer;
+ const quint8 *in = (const quint8 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (((qint16)in[is]) - 128) << 8;
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 8) && (SDR_RX_SAMP_SZ == 24))
{
- quint8 *in = (quint8 *)m_tcpBuf;
- qint32 *out = (qint32 *)m_converterBuffer;
+ const quint8 *in = (const quint8 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (((qint32)in[is]) - 128) << 16;
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 24) && (SDR_RX_SAMP_SZ == 24))
{
- quint8 *in = (quint8 *)m_tcpBuf;
- qint32 *out = (qint32 *)m_converterBuffer;
+ const quint8 *in = (const quint8 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (((in[3*is+2] << 16) | (in[3*is+1] << 8) | in[3*is]) << 8) >> 8;
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 24) && (SDR_RX_SAMP_SZ == 16))
{
- quint8 *in = (quint8 *)m_tcpBuf;
- qint16 *out = (qint16 *)m_converterBuffer;
+ const quint8 *in = (const quint8 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = (in[3*is+2] << 8) | in[3*is+1];
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 16) && (SDR_RX_SAMP_SZ == 24))
{
- qint16 *in = (qint16 *)m_tcpBuf;
- qint32 *out = (qint32 *)m_converterBuffer;
+ const qint16 *in = (const qint16 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = in[is] << 8;
}
-
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
}
else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 16))
{
- qint32 *in = (qint32 *)m_tcpBuf;
- qint16 *out = (qint16 *)m_converterBuffer;
+ const qint32 *in = (const qint32 *) inBuf;
+ qint16 *out = (qint16 *) m_converterBuffer;
for (int is = 0; is < nbSamples*2; is++) {
out[is] = in[is] >> 8;
}
+ }
+ else if ((m_settings.m_sampleBits == 32) && (SDR_RX_SAMP_SZ == 24))
+ {
+ const qint32 *in = (const qint32 *) inBuf;
+ qint32 *out = (qint32 *) m_converterBuffer;
- m_sampleFifo->write(reinterpret_cast(out), nbSamples*sizeof(Sample));
+ for (int is = 0; is < nbSamples*2; is++) {
+ out[is] = in[is];
+ }
}
else // invalid size
{
qWarning("RemoteTCPInputTCPHandler::convert: unexpected sample size in stream: %d bits", (int) m_settings.m_sampleBits);
}
+
+ qint32 len = nbSamples*2;
+
+ // Save data to replay buffer
+ m_replayBuffer->lock();
+ bool replayEnabled = m_replayBuffer->getSize() > 0;
+ if (replayEnabled) {
+ m_replayBuffer->write((const FixReal *) m_converterBuffer, len);
+ }
+
+ const FixReal *buf = (const FixReal *) m_converterBuffer;
+ qint32 remaining = len;
+
+ while (remaining > 0)
+ {
+ // Choose between live data or replayed data
+ if (replayEnabled && m_replayBuffer->useReplay()) {
+ len = m_replayBuffer->read(remaining, buf);
+ } else {
+ len = remaining;
+ }
+ remaining -= len;
+
+ calcPower(reinterpret_cast(buf), len / 2);
+
+ m_sampleFifo->write(reinterpret_cast(buf), len * sizeof(FixReal));
+ }
+
+ m_replayBuffer->unlock();
}
void RemoteTCPInputTCPHandler::handleInputMessages()
@@ -1210,10 +2216,18 @@ bool RemoteTCPInputTCPHandler::handleMessage(const Message& cmd)
if (MsgConfigureTcpHandler::match(cmd))
{
qDebug() << "RemoteTCPInputTCPHandler::handleMessage: MsgConfigureTcpHandler";
- MsgConfigureTcpHandler& notif = (MsgConfigureTcpHandler&) cmd;
+ const MsgConfigureTcpHandler& notif = (const MsgConfigureTcpHandler&) cmd;
applySettings(notif.getSettings(), notif.getSettingsKeys(), notif.getForce());
return true;
}
+ else if (RemoteTCPInput::MsgSendMessage::match(cmd))
+ {
+ const RemoteTCPInput::MsgSendMessage& msg = (const RemoteTCPInput::MsgSendMessage&) cmd;
+
+ sendMessage(MainCore::instance()->getSettings().getStationName(), msg.getText(), msg.getBroadcast());
+
+ return true;
+ }
else
{
return false;
diff --git a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h
index a829c93702..082c2a190d 100644
--- a/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h
+++ b/plugins/samplesource/remotetcpinput/remotetcpinputtcphandler.h
@@ -22,11 +22,18 @@
#include
#include
+#include
#include
#include
#include
+#include
+#include
+
#include "util/messagequeue.h"
+#include "util/movingaverage.h"
+#include "util/socket.h"
+#include "dsp/replaybuffer.h"
#include "remotetcpinputsettings.h"
#include "../../channelrx/remotetcpsink/remotetcpprotocol.h"
#include "spyserver.h"
@@ -35,6 +42,31 @@ class SampleSinkFifo;
class MessageQueue;
class DeviceAPI;
+class FIFO {
+public:
+
+ explicit 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 +103,28 @@ class RemoteTCPInputTCPHandler : public QObject
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 +149,7 @@ class RemoteTCPInputTCPHandler : public QObject
{ }
};
- RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI);
+ RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI, ReplayBuffer *replayBuffer);
~RemoteTCPInputTCPHandler();
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToInput(MessageQueue *queue) { m_messageQueueToInput = queue; }
@@ -120,20 +158,57 @@ class RemoteTCPInputTCPHandler : public QObject
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();
void connected();
void disconnected();
void errorOccurred(QAbstractSocket::SocketError socketError);
+ void sslErrors(const QList &errors);
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;
+ Socket *m_dataSocket;
+ QTcpSocket *m_tcpSocket;
+ QWebSocket *m_webSocket;
char *m_tcpBuf;
SampleSinkFifo *m_sampleFifo;
+ ReplayBuffer *m_replayBuffer;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToInput;
MessageQueue *m_messageQueueToGUI;
@@ -148,6 +223,8 @@ public slots:
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 +232,38 @@ public slots:
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 m_movingAverage;
+
bool handleMessage(const Message& message);
- void convert(int nbSamples);
- void connectToHost(const QString& address, quint16 port);
- void disconnectFromHost();
+ void connectToHost(const QString& address, quint16 port, const QString& protocol);
+ //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 +282,10 @@ public slots:
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& settingsKeys, bool force = false);
void processMetaData();
void spyServerConnect();
@@ -190,6 +296,11 @@ public slots:
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();
diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt
index 9255a4974f..bc66651f6b 100644
--- a/sdrbase/CMakeLists.txt
+++ b/sdrbase/CMakeLists.txt
@@ -266,8 +266,10 @@ set(sdrbase_SOURCES
util/rtpsink.cpp
util/syncmessenger.cpp
util/samplesourceserializer.cpp
+ util/sdrangelserverlist.cpp
util/simpleserializer.cpp
util/serialutil.cpp
+ util/socket.cpp
util/solardynamicsobservatory.cpp
util/sondehub.cpp
#util/spinlock.cpp
@@ -526,8 +528,10 @@ set(sdrbase_HEADERS
util/rtty.h
util/syncmessenger.h
util/samplesourceserializer.h
+ util/sdrangelserverlist.h
util/simpleserializer.h
util/serialutil.h
+ util/socket.h
util/solardynamicsobservatory.h
util/sondehub.h
#util/spinlock.h
diff --git a/sdrbase/channel/channelwebapiutils.cpp b/sdrbase/channel/channelwebapiutils.cpp
index f78f06841c..6a26b90188 100644
--- a/sdrbase/channel/channelwebapiutils.cpp
+++ b/sdrbase/channel/channelwebapiutils.cpp
@@ -33,6 +33,7 @@
#include "maincore.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
+#include "device/deviceenumerator.h"
#include "channel/channelapi.h"
#include "channel/channelutils.h"
#include "dsp/devicesamplesource.h"
@@ -1953,3 +1954,84 @@ bool ChannelWebAPIUtils::addChannel(unsigned int deviceSetIndex, const QString&
return false;
}
}
+
+// response will be deleted after device is opened.
+bool ChannelWebAPIUtils::addDevice(const QString hwType, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response)
+{
+ return DeviceOpener::open(hwType, direction, settingsKeys, response);
+}
+
+DeviceOpener::DeviceOpener(int deviceIndex, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response) :
+ m_deviceIndex(deviceIndex),
+ m_direction(direction),
+ m_settingsKeys(settingsKeys),
+ m_response(response),
+ m_device(nullptr)
+{
+ connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &DeviceOpener::deviceSetAdded);
+ // Create DeviceSet
+ MainCore *mainCore = MainCore::instance();
+ m_deviceSetIndex = mainCore->getDeviceSets().size();
+ MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(m_direction);
+ mainCore->getMainMessageQueue()->push(msg);
+}
+
+void DeviceOpener::deviceSetAdded(int index, DeviceAPI *device)
+{
+ if (index == m_deviceSetIndex)
+ {
+ disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &DeviceOpener::deviceSetAdded);
+
+ m_device = device;
+ // Set the correct device type
+ MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(m_deviceSetIndex, m_deviceIndex, m_direction);
+ MainCore::instance()->getMainMessageQueue()->push(msg);
+ // Wait until device has initialised - FIXME: Better way to do this other than polling?
+ m_timer.setInterval(250);
+ connect(&m_timer, &QTimer::timeout, this, &DeviceOpener::checkInitialised);
+ m_timer.start();
+ }
+}
+
+void DeviceOpener::checkInitialised()
+{
+ if (m_device && m_device->getSampleSource() && (m_device->state() >= DeviceAPI::EngineState::StIdle))
+ {
+ m_timer.stop();
+
+ QString errorMessage;
+ if (200 != m_device->getSampleSource()->webapiSettingsPutPatch(false, m_settingsKeys, *m_response, errorMessage)) {
+ qDebug() << "DeviceOpener::checkInitialised: webapiSettingsPutPatch failed: " << errorMessage;
+ }
+
+ delete m_response;
+ delete this;
+ }
+}
+
+bool DeviceOpener::open(const QString hwType, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response)
+{
+ if (direction) {
+ return false; // FIXME: Only RX support for now
+ }
+
+ int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
+
+ for (int i = 0; i < nbSamplingDevices; i++)
+ {
+ const PluginInterface::SamplingDevice *samplingDevice;
+
+ samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
+
+ if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
+ continue;
+ }
+
+ new DeviceOpener(i, direction, settingsKeys, response);
+
+ return true;
+ }
+
+ qWarning() << "DeviceOpener::open: Failed to find device with hwType " << hwType;
+ return false;
+}
diff --git a/sdrbase/channel/channelwebapiutils.h b/sdrbase/channel/channelwebapiutils.h
index 1bfc7e8087..be11eb02bd 100644
--- a/sdrbase/channel/channelwebapiutils.h
+++ b/sdrbase/channel/channelwebapiutils.h
@@ -23,6 +23,7 @@
#include
#include
+#include
#include "SWGDeviceSettings.h"
#include "SWGDeviceReport.h"
@@ -36,6 +37,28 @@
class DeviceSet;
class Feature;
class ChannelAPI;
+class DeviceAPI;
+
+// Use ChannelWebAPIUtils::addDevice rather than this directly
+class DeviceOpener : public QObject {
+ Q_OBJECT
+protected:
+ DeviceOpener(int deviceIndex, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response);
+private:
+ int m_deviceIndex;
+ int m_direction;
+ int m_deviceSetIndex;
+ QStringList m_settingsKeys;
+ SWGSDRangel::SWGDeviceSettings *m_response;
+ DeviceAPI *m_device;
+ QTimer m_timer;
+
+private slots:
+ void deviceSetAdded(int index, DeviceAPI *device);
+ void checkInitialised();
+public:
+ static bool open(const QString hwType, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response);
+};
class SDRBASE_API ChannelWebAPIUtils
{
@@ -103,6 +126,7 @@ class SDRBASE_API ChannelWebAPIUtils
static bool getChannelSettings(ChannelAPI *channel, SWGSDRangel::SWGChannelSettings &channelSettingsResponse);
static bool getChannelReport(unsigned int deviceIndex, unsigned int channelIndex, SWGSDRangel::SWGChannelReport &channelReport);
static bool addChannel(unsigned int deviceSetIndex, const QString& uri, int direction);
+ static bool addDevice(const QString hwType, int direction, const QStringList& settingsKeys, SWGSDRangel::SWGDeviceSettings *response);
protected:
static QString getDeviceHardwareId(unsigned int deviceIndex);
};
diff --git a/sdrbase/dsp/replaybuffer.h b/sdrbase/dsp/replaybuffer.h
index bacc966632..9e52ab0c16 100644
--- a/sdrbase/dsp/replaybuffer.h
+++ b/sdrbase/dsp/replaybuffer.h
@@ -209,6 +209,11 @@ class ReplayBuffer {
return data;
}
+ qint16 conv(qint32 data) const
+ {
+ return data >> 16;
+ }
+
qint16 conv(float data) const
{
return (qint16)(data * SDR_RX_SCALEF);
diff --git a/sdrbase/util/kiwisdrlist.cpp b/sdrbase/util/kiwisdrlist.cpp
index 0a01e562b7..695349aca4 100644
--- a/sdrbase/util/kiwisdrlist.cpp
+++ b/sdrbase/util/kiwisdrlist.cpp
@@ -97,6 +97,11 @@ void KiwiSDRList::handleHTML(const QString& url, const QByteArray& bytes)
QList sdrs;
QString html(bytes);
+
+ // Strip nested divs, as the following div regexp can't handle them
+ QRegularExpression divName("(.*?)<\\/div>", QRegularExpression::DotMatchesEverythingOption);
+ html.replace(divName, "\\1");
+
QRegularExpression div("
(.*?)<\\/div>", QRegularExpression::DotMatchesEverythingOption);
QRegularExpressionMatchIterator divItr = div.globalMatch(html);
diff --git a/sdrbase/util/sdrangelserverlist.cpp b/sdrbase/util/sdrangelserverlist.cpp
new file mode 100644
index 0000000000..eed5e9cd6d
--- /dev/null
+++ b/sdrbase/util/sdrangelserverlist.cpp
@@ -0,0 +1,186 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "sdrangelserverlist.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+SDRangelServerList::SDRangelServerList()
+{
+ m_networkManager = new QNetworkAccessManager();
+ QObject::connect(m_networkManager, &QNetworkAccessManager::finished, this, &SDRangelServerList::handleReply);
+
+ QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
+ QDir writeableDir(locations[0]);
+ if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("sdrangelserver"))) {
+ qDebug() << "Failed to create cache/sdrangelserver";
+ }
+
+ m_cache = new QNetworkDiskCache();
+ m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("sdrangelserver"));
+ m_cache->setMaximumCacheSize(100000000);
+ m_networkManager->setCache(m_cache);
+
+ connect(&m_timer, &QTimer::timeout, this, &SDRangelServerList::update);
+}
+
+SDRangelServerList::~SDRangelServerList()
+{
+ QObject::disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SDRangelServerList::handleReply);
+ delete m_networkManager;
+}
+
+void SDRangelServerList::getData()
+{
+ QUrl url = QUrl("https://sdrangel.org/websdr/websdrs.json");
+ m_networkManager->get(QNetworkRequest(url));
+}
+
+void SDRangelServerList::getDataPeriodically(int periodInMins)
+{
+ m_timer.setInterval(periodInMins*60*1000);
+ m_timer.start();
+ update();
+}
+
+void SDRangelServerList::update()
+{
+ getData();
+}
+
+void SDRangelServerList::handleReply(QNetworkReply* reply)
+{
+ if (reply)
+ {
+ if (!reply->error())
+ {
+ QString url = reply->url().toEncoded().constData();
+ QByteArray bytes = reply->readAll();
+
+ handleJSON(url, bytes);
+ }
+ else
+ {
+ qDebug() << "SDRangelServerList::handleReply: error: " << reply->error();
+ }
+ reply->deleteLater();
+ }
+ else
+ {
+ qDebug() << "SDRangelServerList::handleReply: reply is null";
+ }
+}
+
+void SDRangelServerList::handleJSON(const QString& url, const QByteArray& bytes)
+{
+ (void) url;
+
+ QList sdrs;
+ QJsonDocument document = QJsonDocument::fromJson(bytes);
+
+ if (document.isArray())
+ {
+ QJsonArray servers = document.array();
+
+ for (auto valRef : servers)
+ {
+ if (valRef.isObject())
+ {
+ QJsonObject serverObj = valRef.toObject();
+ SDRangelServer sdr;
+
+ if (serverObj.contains(QStringLiteral("address"))) {
+ sdr.m_address = serverObj.value(QStringLiteral("address")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("port"))) {
+ sdr.m_port = serverObj.value(QStringLiteral("port")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("protocol"))) {
+ sdr.m_protocol = serverObj.value(QStringLiteral("protocol")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("minFrequency"))) {
+ sdr.m_minFrequency = serverObj.value(QStringLiteral("minFrequency")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("maxFrequency"))) {
+ sdr.m_maxFrequency = serverObj.value(QStringLiteral("maxFrequency")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("maxSampleRate"))) {
+ sdr.m_maxSampleRate = serverObj.value(QStringLiteral("maxSampleRate")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("device"))) {
+ sdr.m_device = serverObj.value(QStringLiteral("device")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("antenna"))) {
+ sdr.m_antenna = serverObj.value(QStringLiteral("antenna")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("remoteControl"))) {
+ sdr.m_remoteControl = serverObj.value(QStringLiteral("remoteControl")).toInt() == 1;
+ }
+ if (serverObj.contains(QStringLiteral("stationName"))) {
+ sdr.m_stationName = serverObj.value(QStringLiteral("stationName")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("location"))) {
+ sdr.m_location = serverObj.value(QStringLiteral("location")).toString();
+ }
+ if (serverObj.contains(QStringLiteral("latitude"))) {
+ sdr.m_latitude = serverObj.value(QStringLiteral("latitude")).toDouble();
+ }
+ if (serverObj.contains(QStringLiteral("longitude"))) {
+ sdr.m_longitude = serverObj.value(QStringLiteral("longitude")).toDouble();
+ }
+ if (serverObj.contains(QStringLiteral("altitude"))) {
+ sdr.m_altitude = serverObj.value(QStringLiteral("altitude")).toDouble();
+ }
+ if (serverObj.contains(QStringLiteral("isotropic"))) {
+ sdr.m_isotropic = serverObj.value(QStringLiteral("isotropic")).toInt() == 1;
+ }
+ if (serverObj.contains(QStringLiteral("azimuth"))) {
+ sdr.m_azimuth = serverObj.value(QStringLiteral("azimuth")).toDouble();
+ }
+ if (serverObj.contains(QStringLiteral("elevation"))) {
+ sdr.m_elevation = serverObj.value(QStringLiteral("elevation")).toDouble();
+ }
+ if (serverObj.contains(QStringLiteral("clients"))) {
+ sdr.m_clients = serverObj.value(QStringLiteral("clients")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("maxClients"))) {
+ sdr.m_maxClients = serverObj.value(QStringLiteral("maxClients")).toInt();
+ }
+ if (serverObj.contains(QStringLiteral("timeLimit"))) {
+ sdr.m_timeLimit = serverObj.value(QStringLiteral("timeLimit")).toInt();
+ }
+
+ sdrs.append(sdr);
+ }
+ else
+ {
+ qDebug() << "SDRangelServerList::handleJSON: Element not an object:\n" << valRef;
+ }
+ }
+ }
+ else
+ {
+ qDebug() << "SDRangelServerList::handleJSON: Doc doesn't contain an array:\n" << document;
+ }
+
+ emit dataUpdated(sdrs);
+}
diff --git a/sdrbase/util/sdrangelserverlist.h b/sdrbase/util/sdrangelserverlist.h
new file mode 100644
index 0000000000..9cb08b483d
--- /dev/null
+++ b/sdrbase/util/sdrangelserverlist.h
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 INCLUDE_SDRANGELSERVERLIST_H
+#define INCLUDE_SDRANGELSERVERLIST_H
+
+#include
+#include
+
+#include "export.h"
+
+class QNetworkAccessManager;
+class QNetworkReply;
+class QNetworkDiskCache;
+
+// Gets a list of public SDRangel Servers from https://sdrangel.org/websdr/
+class SDRBASE_API SDRangelServerList : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ struct SDRangelServer {
+ QString m_address;
+ quint16 m_port;
+ QString m_protocol;
+ qint64 m_minFrequency;
+ qint64 m_maxFrequency;
+ int m_maxSampleRate;
+ QString m_device;
+ QString m_antenna;
+ bool m_remoteControl;
+ QString m_stationName;
+ QString m_location;
+ float m_latitude;
+ float m_longitude;
+ float m_altitude;
+ bool m_isotropic;
+ float m_azimuth;
+ float m_elevation;
+ int m_clients;
+ int m_maxClients;
+ int m_timeLimit; // In minutes
+ };
+
+ SDRangelServerList();
+ ~SDRangelServerList();
+
+ void getData();
+ void getDataPeriodically(int periodInMins=1);
+
+public slots:
+ void handleReply(QNetworkReply* reply);
+ void update();
+
+signals:
+ void dataUpdated(const QList& sdrs); // Emitted when data are available.
+
+private:
+ QNetworkAccessManager *m_networkManager;
+ QNetworkDiskCache *m_cache;
+ QTimer m_timer; // Timer for periodic updates
+
+ void handleJSON(const QString& url, const QByteArray& bytes);
+
+};
+
+#endif /* INCLUDE_SDRANGELSERVERLIST_H */
diff --git a/sdrbase/util/socket.cpp b/sdrbase/util/socket.cpp
new file mode 100644
index 0000000000..300a04e6f0
--- /dev/null
+++ b/sdrbase/util/socket.cpp
@@ -0,0 +1,193 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "socket.h"
+
+Socket::Socket(QObject *socket, QObject *parent) :
+ QObject(parent),
+ m_socket(socket)
+{
+}
+
+Socket::~Socket()
+{
+ delete m_socket;
+}
+
+TCPSocket::TCPSocket(QTcpSocket *socket) :
+ Socket(socket)
+{
+}
+
+qint64 TCPSocket::write(const char *data, qint64 length)
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->write(data, length);
+}
+
+qint64 TCPSocket::read(char *data, qint64 length)
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->read(data, length);
+}
+
+QByteArray TCPSocket::readAll()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->readAll();
+}
+
+void TCPSocket::close()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ socket->close();
+}
+
+qint64 TCPSocket::bytesAvailable()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->bytesAvailable();
+}
+
+void TCPSocket::flush()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ socket->flush();
+}
+
+QHostAddress TCPSocket::peerAddress()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->peerAddress();
+}
+
+quint16 TCPSocket::peerPort()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->peerPort();
+}
+
+bool TCPSocket::isConnected()
+{
+ QTcpSocket *socket = qobject_cast(m_socket);
+
+ return socket->state() == QAbstractSocket::ConnectedState;
+}
+
+WebSocket::WebSocket(QWebSocket *socket) :
+ Socket(socket)
+{
+ m_rxBuffer.reserve(64000);
+ m_txBuffer.reserve(64000);
+ connect(socket, &QWebSocket::binaryFrameReceived, this, &WebSocket::binaryFrameReceived);
+}
+
+ qint64 WebSocket::write(const char *data, qint64 length)
+{
+ //QWebSocket *socket = qobject_cast(m_socket);
+ //return socket->sendBinaryMessage(QByteArray(data, length));
+
+ m_txBuffer.append(data, length);
+ return length;
+}
+
+void WebSocket::flush()
+{
+ QWebSocket *socket = qobject_cast(m_socket);
+ if (m_txBuffer.size() > 0)
+ {
+ qint64 len = socket->sendBinaryMessage(m_txBuffer);
+ if (len != m_txBuffer.size()) {
+ qDebug() << "WebSocket::flush: Failed to send all of message" << len << "/" << m_txBuffer.size();
+ }
+ m_txBuffer.clear();
+ }
+ socket->flush();
+}
+
+qint64 WebSocket::read(char *data, qint64 length)
+{
+ length = std::min(length, (qint64)m_rxBuffer.size());
+
+ memcpy(data, m_rxBuffer.constData(), length);
+
+ m_rxBuffer = m_rxBuffer.mid(length); // Yep, not very efficient
+
+ return length;
+}
+
+QByteArray WebSocket::readAll()
+{
+ QByteArray b = m_rxBuffer;
+
+ m_rxBuffer.clear();
+
+ return b;
+}
+
+void WebSocket::close()
+{
+ QWebSocket *socket = qobject_cast(m_socket);
+
+ // Will crash if we call close on unopened socket
+ if (socket->state() != QAbstractSocket::UnconnectedState) {
+ socket->close();
+ }
+}
+
+qint64 WebSocket::bytesAvailable()
+{
+ return m_rxBuffer.size();
+}
+
+void WebSocket::binaryFrameReceived(const QByteArray &frame, bool isLastFrame)
+{
+ (void) isLastFrame;
+
+ m_rxBuffer.append(frame);
+}
+
+QHostAddress WebSocket::peerAddress()
+{
+ QWebSocket *socket = qobject_cast(m_socket);
+
+ return socket->peerAddress();
+}
+
+quint16 WebSocket::peerPort()
+{
+ QWebSocket *socket = qobject_cast(m_socket);
+
+ return socket->peerPort();
+}
+
+bool WebSocket::isConnected()
+{
+ QWebSocket *socket = qobject_cast(m_socket);
+
+ return socket->state() == QAbstractSocket::ConnectedState;
+}
diff --git a/sdrbase/util/socket.h b/sdrbase/util/socket.h
new file mode 100644
index 0000000000..28adb865d5
--- /dev/null
+++ b/sdrbase/util/socket.h
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 INCLUDE_SOCKET_H_
+#define INCLUDE_SOCKET_H_
+
+#include
+#include
+
+#include "export.h"
+
+// Class to allow easy use of either QTCPSocket or QWebSocket
+class SDRBASE_API Socket : public QObject {
+ Q_OBJECT
+protected:
+ Socket(QObject *socket, QObject *parent=nullptr);
+
+public:
+ virtual ~Socket();
+ virtual qint64 write(const char *data, qint64 length) = 0;
+ virtual void flush() = 0;
+ virtual qint64 read(char *data, qint64 length) = 0;
+ virtual qint64 bytesAvailable() = 0;
+ virtual QByteArray readAll() = 0;
+ virtual void close() = 0;
+ virtual QHostAddress peerAddress() = 0;
+ virtual quint16 peerPort() = 0;
+ virtual bool isConnected() = 0;
+
+ QObject *socket() { return m_socket; }
+
+protected:
+
+ QObject *m_socket;
+
+};
+
+class SDRBASE_API TCPSocket : public Socket {
+ Q_OBJECT
+
+public:
+
+ explicit TCPSocket(QTcpSocket *socket) ;
+ qint64 write(const char *data, qint64 length) override;
+ void flush() override;
+ qint64 read(char *data, qint64 length) override;
+ qint64 bytesAvailable() override;
+ QByteArray readAll() override;
+ void close() override;
+ QHostAddress peerAddress() override;
+ quint16 peerPort() override;
+ bool isConnected() override;
+
+};
+
+class SDRBASE_API WebSocket : public Socket {
+ Q_OBJECT
+
+public:
+
+ explicit WebSocket(QWebSocket *socket);
+ qint64 write(const char *data, qint64 length) override;
+ void flush() override;
+ qint64 read(char *data, qint64 length) override;
+ qint64 bytesAvailable() override;
+ QByteArray readAll() override;
+ void close() override;
+ QHostAddress peerAddress() override;
+ quint16 peerPort() override;
+ bool isConnected() override;
+
+private slots:
+
+ void binaryFrameReceived(const QByteArray &frame, bool isLastFrame);
+
+private:
+
+ QByteArray m_rxBuffer;
+ QByteArray m_txBuffer;
+
+};
+
+#endif // INCLUDE_SOCKET_H_
diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt
index c911490143..d524583d58 100644
--- a/sdrgui/CMakeLists.txt
+++ b/sdrgui/CMakeLists.txt
@@ -74,6 +74,7 @@ set(sdrgui_SOURCES
gui/mdiutils.cpp
gui/mypositiondialog.cpp
gui/nanosecondsdelegate.cpp
+ gui/perioddial.cpp
gui/pluginsdialog.cpp
gui/pluginpresetsdialog.cpp
gui/presetitem.cpp
@@ -103,6 +104,8 @@ set(sdrgui_SOURCES
gui/workspaceselectiondialog.cpp
gui/wsspectrumsettingsdialog.cpp
gui/wrappingdatetimeedit.cpp
+ gui/wrappingdial.cpp
+ gui/wrappingspinbox.cpp
dsp/scopevisxy.cpp
@@ -201,6 +204,7 @@ set(sdrgui_HEADERS
gui/mdiutils.h
gui/mypositiondialog.h
gui/nanosecondsdelegate.h
+ gui/perioddial.h
gui/physicalunit.h
gui/pluginsdialog.h
gui/pluginpresetsdialog.h
@@ -233,6 +237,8 @@ set(sdrgui_HEADERS
gui/workspaceselectiondialog.h
gui/wsspectrumsettingsdialog.h
gui/wrappingdatetimeedit.h
+ gui/wrappingdial.h
+ gui/wrappingspinbox.h
dsp/scopevisxy.h
diff --git a/sdrgui/gui/perioddial.cpp b/sdrgui/gui/perioddial.cpp
new file mode 100644
index 0000000000..e5658e8c81
--- /dev/null
+++ b/sdrgui/gui/perioddial.cpp
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "perioddial.h"
+#include "wrappingdial.h"
+#include "wrappingspinbox.h"
+
+#include
+#include
+
+PeriodDial::PeriodDial(QWidget *parent) :
+ QWidget(parent)
+{
+ m_layout = new QHBoxLayout(this);
+ m_layout->setContentsMargins(0, 0, 0, 0);
+
+ m_dial = new WrappingDial();
+ m_dial->setMinimum(1);
+ m_dial->setMaximum(999);
+ m_dial->setMaximumSize(24, 24);
+
+ m_spinBox = new WrappingSpinBox();
+ m_spinBox->setMinimum(1);
+ m_spinBox->setMaximum(999);
+ m_spinBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+
+ m_units = new QComboBox();
+ m_units->addItem(QString("%1s").arg(QChar(0x3bc)));
+ m_units->addItem("ms");
+ m_units->addItem("s");
+
+ m_layout->addWidget(m_dial);
+ m_layout->addWidget(m_spinBox);
+ m_layout->addWidget(m_units);
+
+ connect(m_dial, &WrappingDial::valueChanged, this, &PeriodDial::on_dial_valueChanged);
+ connect(m_dial, &WrappingDial::wrapUp, this, &PeriodDial::on_wrapUp);
+ connect(m_dial, &WrappingDial::wrapDown, this, &PeriodDial::on_wrapDown);
+ connect(m_spinBox, QOverload::of(&WrappingSpinBox::valueChanged), this, &PeriodDial::on_spinBox_valueChanged);
+ connect(m_spinBox, &WrappingSpinBox::wrapUp, this, &PeriodDial::on_wrapUp);
+ connect(m_spinBox, &WrappingSpinBox::wrapDown, this, &PeriodDial::on_wrapDown);
+ connect(m_units, QOverload::of(&QComboBox::currentIndexChanged), this, &PeriodDial::on_units_currentIndexChanged);
+}
+
+void PeriodDial::on_dial_valueChanged(int dialValue)
+{
+ m_spinBox->setValue(dialValue);
+ emit valueChanged(value());
+}
+
+void PeriodDial::on_spinBox_valueChanged(int boxValue)
+{
+ m_dial->setValue(boxValue);
+}
+
+void PeriodDial::on_units_currentIndexChanged(int index)
+{
+ (void) index;
+
+ emit valueChanged(value());
+}
+
+void PeriodDial::on_wrapUp()
+{
+ int index = m_units->currentIndex();
+ if (index < m_units->count() - 1) {
+ m_units->setCurrentIndex(index + 1);
+ }
+}
+
+void PeriodDial::on_wrapDown()
+{
+ int index = m_units->currentIndex();
+ if (index > 0) {
+ m_units->setCurrentIndex(index - 1);
+ }
+}
+
+void PeriodDial::setValue(double newValue)
+{
+ double oldValue = value();
+ int index;
+ if (newValue < 1e-3) {
+ index = 0;
+ } else if (newValue < 1.0) {
+ index = 1;
+ } else {
+ index = 2;
+ }
+ double scale = std::pow(10.0, 3 * (2 - index));
+ int mantissa = std::round(newValue * scale);
+
+ bool blocked = blockSignals(true);
+ m_dial->setValue(mantissa);
+ m_units->setCurrentIndex(index);
+ blockSignals(blocked);
+
+ if (newValue != oldValue) {
+ emit valueChanged(value());
+ }
+}
+
+double PeriodDial::value()
+{
+ int index = -3 * (2 - m_units->currentIndex()); // 0=s -3=ms -6=us
+ double scale = std::pow(10.0, index);
+ int mantissa = m_dial->value();
+ return mantissa * scale;
+}
diff --git a/sdrgui/gui/perioddial.h b/sdrgui/gui/perioddial.h
new file mode 100644
index 0000000000..18cf9e6ae2
--- /dev/null
+++ b/sdrgui/gui/perioddial.h
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 SDRGUI_GUI_PERIODDIAL_H
+#define SDRGUI_GUI_PERIODDIAL_H
+
+#include
+
+#include "export.h"
+
+class QHBoxLayout;
+class WrappingDial;
+class WrappingSpinBox;
+class QComboBox;
+
+// Combines QDial, QSpinBox and QComboBox to allow user to enter time period in s, ms or us
+class SDRGUI_API PeriodDial : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit PeriodDial(QWidget *parent = nullptr);
+
+ void setValue(double value);
+ double value();
+
+private:
+
+ QHBoxLayout *m_layout;
+ WrappingDial *m_dial;
+ WrappingSpinBox *m_spinBox;
+ QComboBox *m_units;
+
+private slots:
+
+ void on_dial_valueChanged(int value);
+ void on_spinBox_valueChanged(int value);
+ void on_units_currentIndexChanged(int index);
+ void on_wrapUp();
+ void on_wrapDown();
+
+signals:
+ void valueChanged(double value);
+
+};
+
+#endif // SDRGUI_GUI_PERIODDIAL_H
diff --git a/sdrgui/gui/wrappingdial.cpp b/sdrgui/gui/wrappingdial.cpp
new file mode 100644
index 0000000000..b21c076b31
--- /dev/null
+++ b/sdrgui/gui/wrappingdial.cpp
@@ -0,0 +1,63 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "wrappingdial.h"
+
+#include
+
+WrappingDial::WrappingDial(QWidget *parent) :
+ QDial(parent),
+ m_wheelEvent(false),
+ m_wheelUp(false)
+{
+ setWrapping(true);
+
+ connect(this, &QDial::actionTriggered, this, &WrappingDial::on_actionTriggered);
+}
+
+void WrappingDial::on_actionTriggered(int action)
+{
+ if (wrapping())
+ {
+ if ( ( (action == QAbstractSlider::SliderSingleStepSub)
+ || (action == QAbstractSlider::SliderPageStepSub)
+ || ((action == QAbstractSlider::SliderMove) && m_wheelEvent && !m_wheelUp)
+ )
+ && (value() < sliderPosition()))
+ {
+ emit wrapDown();
+ }
+ if ( ( (action == QAbstractSlider::SliderSingleStepAdd)
+ || (action == QAbstractSlider::SliderPageStepAdd)
+ || ((action == QAbstractSlider::SliderMove) && m_wheelEvent && m_wheelUp)
+ )
+ && (value() > sliderPosition()))
+ {
+ emit wrapUp();
+ }
+ }
+}
+
+// QAbstractSlider just generates SliderMove actions for wheel events, so we can't distinguish between
+// wheel and dial being clicked to a new position - so we set a flag here, before passing up the event
+void WrappingDial::wheelEvent(QWheelEvent *e)
+{
+ m_wheelEvent = true;
+ m_wheelUp = e->angleDelta().y() > 0;
+ QDial::wheelEvent(e);
+ m_wheelEvent = false;
+}
diff --git a/sdrgui/gui/wrappingdial.h b/sdrgui/gui/wrappingdial.h
new file mode 100644
index 0000000000..fa3b63ff0b
--- /dev/null
+++ b/sdrgui/gui/wrappingdial.h
@@ -0,0 +1,48 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 SDRGUI_GUI_WRAPPINGDDIAL_H
+#define SDRGUI_GUI_WRAPPINGDDIAL_H
+
+#include
+
+#include "export.h"
+
+// Extends QDial to generate a signal when dial wraps
+class SDRGUI_API WrappingDial : public QDial {
+ Q_OBJECT
+
+public:
+ explicit WrappingDial(QWidget *parent = nullptr);
+
+protected:
+ void wheelEvent(QWheelEvent *e) override;
+
+private:
+ bool m_wheelEvent;
+ bool m_wheelUp;
+
+private slots:
+ void on_actionTriggered(int action);
+
+signals:
+ void wrapUp();
+ void wrapDown();
+
+};
+
+#endif // SDRGUI_GUI_WRAPPINGDDIAL_H
diff --git a/sdrgui/gui/wrappingspinbox.cpp b/sdrgui/gui/wrappingspinbox.cpp
new file mode 100644
index 0000000000..3c2c7ee23b
--- /dev/null
+++ b/sdrgui/gui/wrappingspinbox.cpp
@@ -0,0 +1,53 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 "wrappingspinbox.h"
+
+#include
+
+WrappingSpinBox::WrappingSpinBox(QWidget *parent) :
+ QSpinBox(parent),
+ m_wheelEvent(false),
+ m_wheelUp(false)
+{
+ setWrapping(true);
+}
+
+void WrappingSpinBox::stepBy(int steps)
+{
+ int v = value();
+ QSpinBox::stepBy(steps);
+ if (wrapping())
+ {
+ if (v + steps > maximum()) {
+ emit wrapUp();
+ }
+ if (v + steps < minimum()) {
+ emit wrapDown();
+ }
+ }
+}
+
+// QAbstractSlider just generates SliderMove actions for wheel events, so we can't distinguish between
+// wheel and dial being clicked to a new position - so we set a flag here, before passing up the event
+void WrappingSpinBox::wheelEvent(QWheelEvent *e)
+{
+ m_wheelEvent = true;
+ m_wheelUp = e->angleDelta().y() > 0;
+ QSpinBox::wheelEvent(e);
+ m_wheelEvent = false;
+}
diff --git a/sdrgui/gui/wrappingspinbox.h b/sdrgui/gui/wrappingspinbox.h
new file mode 100644
index 0000000000..e775ecb86e
--- /dev/null
+++ b/sdrgui/gui/wrappingspinbox.h
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 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 //
+// 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 SDRGUI_GUI_WRAPPINGDSPINBOX_H
+#define SDRGUI_GUI_WRAPPINGDSPINBOX_H
+
+#include
+
+#include "export.h"
+
+// Extends QSpinBox to generate a signal when spinbox wraps
+class SDRGUI_API WrappingSpinBox : public QSpinBox {
+ Q_OBJECT
+
+public:
+ explicit WrappingSpinBox(QWidget *parent = nullptr);
+
+ void stepBy(int steps) override;
+
+protected:
+ void wheelEvent(QWheelEvent *e) override;
+
+private:
+ bool m_wheelEvent;
+ bool m_wheelUp;
+
+signals:
+ void wrapUp();
+ void wrapDown();
+
+};
+
+#endif // SDRGUI_GUI_WRAPPINGDSPINBOX_H
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 5e7b45b4c0..0b753bfce5 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -10,7 +10,7 @@
#
# To install sdrangel local snap:
#
-# sudo snap install sdrangel_7.19.0_amd64.snap --dangerous
+# sudo snap install sdrangel_7.22.1_amd64.snap --dangerous
#
# Users appear to need to grant h/w access manually from command line with:
#
@@ -36,11 +36,19 @@
# snapcraft clean uhd
# snapcraft stage uhd --shell-after
#
+#
+# To publish to snap store:
+#
+# snapcraft login
+# snapcraft push sdrangel_7.22.1_amd64.snap --release=stable,edge,candidate,beta
+#
+# Can also manage/promote releases at: https://snapcraft.io/sdrangel/releases
+
name: sdrangel
base: core22
type: app
-version: "7.19.0"
+adopt-info: sdrangel
summary: SDRangel
description: SDRangel is an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware. SSE 4.2 required.
confinement: strict
@@ -139,7 +147,9 @@ parts:
plugin: cmake
source: https://github.com/f4exb/sdrangel
source-type: git
- source-tag: v7.19.0
+ override-pull: |
+ snapcraftctl pull
+ snapcraftctl set-version "$(git describe --tags --abbrev=0 | sed 's/v//')"
after: [apt, libdab, mbelib, serialdv, dsdcc, codec2, sgp4, cm265cc, libsigmf, airspy, rtlsdr, pluto, bladerf, hackrf, limesuite, airspyhf, uhd, uhdfpga, soapysdr, soapyremote]
cmake-parameters:
- -DDEBUG_OUTPUT=OFF
@@ -167,6 +177,7 @@ parts:
- -DSGP4_DIR=$SNAPCRAFT_STAGE/opt/install/sdrangel
- -DLIBSIGMF_DIR=$SNAPCRAFT_STAGE/opt/install/sdrangel
- -DDAB_DIR=$SNAPCRAFT_STAGE/opt/install/sdrangel
+ - -DARCH_OPT=nehalem
#- -DQt5_DIR=/usr/lib/x86_64-linux-gnu/cmake/Qt5
build-packages:
- libfftw3-dev
@@ -192,6 +203,7 @@ parts:
- libqt5texttospeech5-dev
- libqt5gamepad5-dev
- libfaad-dev
+ - libflac-dev
- zlib1g-dev
- libboost-all-dev
- libasound2-dev
diff --git a/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml b/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml
index 09cc073e60..c27b4e712e 100644
--- a/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml
+++ b/swagger/sdrangel/api/swagger/include/RemoteTCPInput.yaml
@@ -61,3 +61,12 @@ RemoteTCPInputReport:
properties:
sampleRate:
type: integer
+ latitude:
+ type: number
+ format: float
+ longitude:
+ type: number
+ format: float
+ altitude:
+ type: number
+ format: float
diff --git a/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.cpp
index 74c4c5abf3..eb50dd12a4 100644
--- a/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.cpp
+++ b/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.cpp
@@ -1,6 +1,6 @@
/**
* SDRangel
- * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
+ * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
@@ -59,7 +59,7 @@ SWGRemoteTCPInputReport::fromJson(QString &json) {
void
SWGRemoteTCPInputReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", "");
-
+
}
QString
@@ -79,6 +79,9 @@ SWGRemoteTCPInputReport::asJsonObject() {
if(m_sample_rate_isSet){
obj->insert("sampleRate", QJsonValue(sample_rate));
}
+ obj->insert("latitude", QJsonValue(latitude));
+ obj->insert("longitude", QJsonValue(longitude));
+ obj->insert("altitude", QJsonValue(altitude));
return obj;
}
diff --git a/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.h b/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.h
index 1dd6df6a8c..92ea400e01 100644
--- a/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.h
+++ b/swagger/sdrangel/code/qt5/client/SWGRemoteTCPInputReport.h
@@ -1,6 +1,6 @@
/**
* SDRangel
- * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
+ * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
@@ -43,13 +43,21 @@ class SWG_API SWGRemoteTCPInputReport: public SWGObject {
qint32 getSampleRate();
void setSampleRate(qint32 sample_rate);
-
+ float getLatitude() { return latitude; }
+ float getLongitude() { return longitude; }
+ float getAltitude() { return altitude; }
+ void setLatitude(float latitude) { this->latitude = latitude; }
+ void setLongitude(float longitude) { this->longitude = longitude; }
+ void setAltitude(float altitude) { this->altitude = altitude; }
virtual bool isSet() override;
private:
qint32 sample_rate;
bool m_sample_rate_isSet;
+ float latitude;
+ float longitude;
+ float altitude;
};