mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-21 15:51:47 -05:00
Remote TCP updates:
Add support for public list of SDRangel servers that can be displayed on Map. Add FLAC and zlib IQ compression. Add IQ squelch for compression. Add remote device/antenna position and direction reporting. Add text messaging.
This commit is contained in:
parent
246735918b
commit
8b8867d343
@ -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 \
|
||||
|
31
cmake/Modules/FindFLAC.cmake
Normal file
31
cmake/Modules/FindFLAC.cmake
Normal file
@ -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)
|
1
debian/control
vendored
1
debian/control
vendored
@ -45,6 +45,7 @@ Build-Depends: debhelper (>= 9),
|
||||
flex,
|
||||
ffmpeg,
|
||||
libfaad-dev,
|
||||
libflac-dev,
|
||||
libavcodec-dev,
|
||||
libavformat-dev,
|
||||
libopus-dev,
|
||||
|
31
external/CMakeLists.txt
vendored
31
external/CMakeLists.txt
vendored
@ -851,6 +851,37 @@ 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}/lib${LIB_SUFFIX}/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)
|
||||
set(FLAC_LIBRARIES "${binary_dir}/flac/FLAC${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE INTERNAL "")
|
||||
install(DIRECTORY "${binary_dir}/libFLAC" DESTINATION "${INSTALL_LIB_DIR}"
|
||||
FILES_MATCHING PATTERN "libFLAC*${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||
set(MACOS_EXTERNAL_LIBS_FIXUP "${MACOS_EXTERNAL_LIBS_FIXUP};${binary_dir}/libFLAC")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# For Morse Decoder feature
|
||||
if(ENABLE_FEATURE_MORSEDECODER)
|
||||
if (WIN32)
|
||||
|
@ -7,6 +7,7 @@ set(remotetcpsink_SOURCES
|
||||
remotetcpsinksettings.cpp
|
||||
remotetcpsinkwebapiadapter.cpp
|
||||
remotetcpsinkplugin.cpp
|
||||
socket.cpp
|
||||
)
|
||||
|
||||
set(remotetcpsink_HEADERS
|
||||
@ -17,10 +18,13 @@ set(remotetcpsink_HEADERS
|
||||
remotetcpsinkwebapiadapter.h
|
||||
remotetcpsinkplugin.h
|
||||
remotetcpprotocol.h
|
||||
socket.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
${FLAC_INCLUDE_DIR}
|
||||
${ZLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
@ -28,10 +32,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 +51,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()
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
set_property(GLOBAL APPEND PROPERTY STATIC_PLUGINS_PROPERTY ${TARGET_NAME})
|
||||
endif()
|
||||
|
||||
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})
|
||||
|
@ -1,11 +1,25 @@
|
||||
<h1>Remote TCP sink channel plugin</h1>
|
||||
<h1>Remote TCP Sink Channel Plugin</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
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 the 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.
|
||||
|
||||
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).
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
![Remote TCP sink channel plugin GUI](../../../doc/img/RemoteTCPSink.png)
|
||||
@ -20,25 +34,85 @@ 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.
|
||||
|
||||
<h3>3: Sample rate</h3>
|
||||
<h3>3: Channel power</h3>
|
||||
|
||||
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
|
||||
|
||||
<h3>4: Level meter in dB</h3>
|
||||
|
||||
- top bar (green): average value
|
||||
- bottom bar (blue green): instantaneous peak value
|
||||
- tip vertical bar (bright green): peak hold value
|
||||
|
||||
<h3>5: IQ Squelch</h3>
|
||||
|
||||
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.
|
||||
|
||||
<h3>6: IQ Squelch power level</h3>
|
||||
|
||||
Sets the power level in dB, below which, IQ data will be squelched.
|
||||
|
||||
<h3>7: IQ Squelch gate time</h3>
|
||||
|
||||
Sets the IQ squelch gate time. The units can be us (microseconds), ms (milliseconds) or s (seconds).
|
||||
|
||||
<h3>8: IQ Squelch indicator</h3>
|
||||
|
||||
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.
|
||||
|
||||
<h3>9: Sample rate</h3>
|
||||
|
||||
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.
|
||||
|
||||
<h3>4: Sample bit depth</h3>
|
||||
<h3>10: Sample bit depth</h3>
|
||||
|
||||
Specifies number of bits per I/Q sample transmitted via TCP/IP.
|
||||
|
||||
<h3>5: IP address</h3>
|
||||
<h3>11: IP address</h3>
|
||||
|
||||
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.
|
||||
|
||||
<h3>6: Port</h3>
|
||||
<h3>12: Port</h3>
|
||||
|
||||
TCP port on which the server will listen for connections.
|
||||
|
||||
<h3>7: Protocol</h3>
|
||||
<h3>13: Protocol</h3>
|
||||
|
||||
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).
|
||||
- SDRA: Enhanced version of protocol via TCP Socket.
|
||||
- SDRA wss: SDRA protocol via a Secure Web Socket instead of a TCP Socket. You should use this with the WebAssembly version of SDRangel.
|
||||
|
||||
<h3>14: Display Settings</h3>
|
||||
|
||||
Click to open the Settings Dialog.
|
||||
|
||||
<h3>15: Remote Control</h3>
|
||||
|
||||
When checked, remote clients will be able to change device settings. When unchecked, client requests to change settings will be ignored.
|
||||
|
||||
<h3>16: TX</h3>
|
||||
|
||||
When pressed, the text message (18) will be transmitted to the clients specified by (17).
|
||||
|
||||
<h3>17: TX Address</h3>
|
||||
|
||||
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.
|
||||
|
||||
<h3>18: TX Message</h3>
|
||||
|
||||
Specifies a text message to transmit to clients, when the TX button (16) is pressed.
|
||||
|
||||
<h3>19: RX Messages</h3>
|
||||
|
||||
Displays text messages received from clients.
|
||||
|
||||
<h3>20: Connection Log</h3>
|
||||
|
||||
Displays a 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.
|
||||
|
@ -105,13 +105,23 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
return data;
|
||||
}
|
||||
|
||||
static quint32 extractUInt32(quint8 *p)
|
||||
static quint32 extractUInt32(const quint8 *p)
|
||||
{
|
||||
quint32 data;
|
||||
data = (p[3] & 0xff)
|
||||
@ -150,7 +160,7 @@ public:
|
||||
return data;
|
||||
}
|
||||
|
||||
static qint32 extractInt32(quint8 *p)
|
||||
static qint32 extractInt32(const quint8 *p)
|
||||
{
|
||||
return (qint32)extractUInt32(p);
|
||||
}
|
||||
@ -167,7 +177,7 @@ public:
|
||||
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 @@ public:
|
||||
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_ */
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
#include <QJsonDocument>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGWorkspaceInfo.h"
|
||||
@ -33,13 +35,16 @@
|
||||
#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)
|
||||
|
||||
const char* const RemoteTCPSink::m_channelIdURI = "sdrangel.channel.remotetcpsink";
|
||||
const char* const RemoteTCPSink::m_channelId = "RemoteTCPSink";
|
||||
@ -47,7 +52,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 +84,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 +109,6 @@ RemoteTCPSink::~RemoteTCPSink()
|
||||
m_deviceAPI->removeChannelSinkAPI(this);
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
|
||||
if (m_basebandSink->isRunning()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
m_basebandSink->deleteLater();
|
||||
}
|
||||
|
||||
@ -130,6 +147,8 @@ void RemoteTCPSink::start()
|
||||
if (m_basebandSampleRate != 0) {
|
||||
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||
}
|
||||
|
||||
updatePublicListing();
|
||||
}
|
||||
|
||||
void RemoteTCPSink::stop()
|
||||
@ -138,6 +157,9 @@ 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)
|
||||
@ -146,7 +168,7 @@ bool RemoteTCPSink::handleMessage(const Message& cmd)
|
||||
{
|
||||
MsgConfigureRemoteTCPSink& cfg = (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;
|
||||
}
|
||||
@ -166,6 +188,28 @@ bool RemoteTCPSink::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSendMessage::match(cmd))
|
||||
{
|
||||
MsgSendMessage& msg = (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))
|
||||
{
|
||||
MsgReportConnection& msg = (MsgReportConnection&) cmd;
|
||||
m_clients = msg.getClients();
|
||||
updatePublicListing();
|
||||
return true;
|
||||
}
|
||||
else if (MsgReportDisconnect::match(cmd))
|
||||
{
|
||||
MsgReportDisconnect& msg = (MsgReportDisconnect&) cmd;
|
||||
m_clients = msg.getClients();
|
||||
updatePublicListing();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -208,7 +252,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 +265,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 +280,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 +300,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 +635,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 +654,88 @@ 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();
|
||||
|
||||
QJsonObject json;
|
||||
json.insert("address", m_settings.m_publicAddress);
|
||||
json.insert("port", m_settings.m_publicPort);
|
||||
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);
|
||||
}
|
||||
|
@ -42,25 +42,25 @@ public:
|
||||
const RemoteTCPSinkSettings& getSettings() const { return m_settings; }
|
||||
const QList<QString>& 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<QString>& settingsKeys, bool force, bool remoteChange = false)
|
||||
static MsgConfigureRemoteTCPSink* create(const RemoteTCPSinkSettings& settings, const QList<QString>& 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<QString> 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<QString>& settingsKeys, bool force, bool remoteChange) :
|
||||
MsgConfigureRemoteTCPSink(const RemoteTCPSinkSettings& settings, const QList<QString>& settingsKeys, bool force, bool restartRequired) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_settingsKeys(settingsKeys),
|
||||
m_force(force),
|
||||
m_remoteChange(remoteChange)
|
||||
m_restartRequired(restartRequired)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -69,39 +69,117 @@ public:
|
||||
|
||||
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);
|
||||
return new MsgReportConnection(clients, address, port);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_clients;
|
||||
QHostAddress m_address;
|
||||
quint16 m_port;
|
||||
|
||||
MsgReportConnection(int clients) :
|
||||
MsgReportConnection(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
|
||||
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 MsgReportDisconnect(clients, address, port);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_clients;
|
||||
QHostAddress m_address;
|
||||
quint16 m_port;
|
||||
|
||||
MsgReportDisconnect(int clients, const QHostAddress& address, quint16 port) :
|
||||
Message(),
|
||||
m_clients(clients),
|
||||
m_address(address),
|
||||
m_port(port)
|
||||
{ }
|
||||
};
|
||||
|
||||
// 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) :
|
||||
MsgReportBW(float bw, float networkBW, qint64 bytesUncompressed, qint64 bytesCompressed, qint64 bytesTransmitted) :
|
||||
Message(),
|
||||
m_bw(bw)
|
||||
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; }
|
||||
|
||||
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)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -164,7 +242,17 @@ public:
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
int getBasebandSampleRate() const { return m_basebandSampleRate; }
|
||||
|
||||
void setMessageQueueToGUI(MessageQueue* queue) override {
|
||||
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 +271,11 @@ private:
|
||||
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<ObjectPipe*>& pipes,
|
||||
@ -198,6 +289,8 @@ private:
|
||||
const RemoteTCPSinkSettings& settings,
|
||||
bool force
|
||||
);
|
||||
void removePublicListing(const QString& address, quint16 port);
|
||||
void updatePublicListing();
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
|
@ -128,7 +128,7 @@ bool RemoteTCPSinkBaseband::handleMessage(const Message& cmd)
|
||||
RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (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;
|
||||
}
|
||||
@ -141,13 +141,21 @@ bool RemoteTCPSinkBaseband::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (RemoteTCPSink::MsgSendMessage::match(cmd))
|
||||
{
|
||||
RemoteTCPSink::MsgSendMessage& msg = (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 {
|
||||
|
@ -47,6 +47,8 @@ public:
|
||||
|
||||
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 @@ private:
|
||||
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();
|
||||
|
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022-2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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,25 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QLocale>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkInterface>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#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 +108,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;
|
||||
|
||||
if ((cfg.getSettings().m_channelSampleRate != m_settings.m_channelSampleRate)
|
||||
|| (cfg.getSettings().m_sampleBits != m_settings.m_sampleBits)) {
|
||||
m_bwAvg.reset();
|
||||
@ -126,14 +195,52 @@ 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()));
|
||||
|
||||
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 = (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;
|
||||
|
||||
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))
|
||||
@ -150,6 +257,25 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (RemoteTCPSink::MsgSendMessage::match(message))
|
||||
{
|
||||
RemoteTCPSink::MsgSendMessage& msg = (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
|
||||
{
|
||||
return false;
|
||||
@ -157,12 +283,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 +305,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 +319,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 +337,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 +407,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,20 +595,9 @@ void RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged(int index)
|
||||
applySetting("dataAddress");
|
||||
}
|
||||
|
||||
void RemoteTCPSinkGUI::on_dataPort_editingFinished()
|
||||
void RemoteTCPSinkGUI::on_dataPort_valueChanged(int value)
|
||||
{
|
||||
bool dataOk;
|
||||
int dataPort = ui->dataPort->text().toInt(&dataOk);
|
||||
|
||||
if((!dataOk) || (dataPort < 1024) || (dataPort > 65535))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.m_dataPort = dataPort;
|
||||
}
|
||||
|
||||
m_settings.m_dataPort = value;
|
||||
applySetting("dataPort");
|
||||
}
|
||||
|
||||
@ -453,6 +607,104 @@ void RemoteTCPSinkGUI::on_protocol_currentIndexChanged(int 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)
|
||||
{
|
||||
m_settings.m_squelch = value;
|
||||
ui->squelchText->setText(QString::number(m_settings.m_squelch));
|
||||
applySetting("squelch");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
applySettings(dialog.getSettingsKeys());
|
||||
displayIQOnly();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTCPSinkGUI::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";
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTCPSinkGUI::on_txMessage_returnPressed()
|
||||
{
|
||||
on_sendMessage_clicked();
|
||||
ui->txMessage->selectAll();
|
||||
}
|
||||
|
||||
void RemoteTCPSinkGUI::tick()
|
||||
{
|
||||
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()
|
||||
{
|
||||
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &RemoteTCPSinkGUI::on_deltaFrequency_changed);
|
||||
@ -461,8 +713,15 @@ void RemoteTCPSinkGUI::makeUIConnections()
|
||||
QObject::connect(ui->sampleBits, QOverload<int>::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<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged);
|
||||
QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPSinkGUI::on_dataPort_editingFinished);
|
||||
QObject::connect(ui->dataPort, QOverload<int>::of(&QSpinBox::valueChanged), this, &RemoteTCPSinkGUI::on_dataPort_valueChanged);
|
||||
QObject::connect(ui->protocol, QOverload<int>::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()
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "dsp/channelmarker.h"
|
||||
#include "channel/channelgui.h"
|
||||
@ -82,9 +83,23 @@ private:
|
||||
bool m_doApplySettings;
|
||||
|
||||
RemoteTCPSink* m_remoteSink;
|
||||
uint32_t m_tickCount;
|
||||
bool m_squelchOpen;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
||||
MovingAverageUtil<float, float, 10> m_bwAvg;
|
||||
MovingAverageUtil<float, float, 10> m_compressionAvg;
|
||||
MovingAverageUtil<float, float, 10> 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 @@ private:
|
||||
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_ */
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>147</height>
|
||||
<height>646</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -24,7 +24,7 @@
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>560</width>
|
||||
<width>1000</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -41,8 +41,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>340</width>
|
||||
<height>141</height>
|
||||
<width>361</width>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -174,8 +174,217 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPower">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel power</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerUnits">
|
||||
<property name="text">
|
||||
<string> dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="signalLevelLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerMeterUnits">
|
||||
<property name="text">
|
||||
<string>dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="squelchLayout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="squelchEnabled">
|
||||
<property name="toolTip">
|
||||
<string>Check to enable IQ squelch</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>SQ</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="squelch">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IQ squelch power level in dB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-150</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="squelchText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-150</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="squelchUnits">
|
||||
<property name="text">
|
||||
<string>dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PeriodDial" name="squelchGate" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IQ squelch gate time</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="audioMute">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Indicates when IQ squelch is open</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/sound_on.png</normaloff>
|
||||
<normalon>:/sound_off.png</normalon>:/sound_on.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="samplesSettingsLayout">
|
||||
<property name="rightMargin">
|
||||
@ -325,21 +534,15 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="dataPort">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<widget class="QSpinBox" name="dataPort">
|
||||
<property name="toolTip">
|
||||
<string>TCP port for server to listen for connections on</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>00000</string>
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -365,9 +568,18 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="protocol">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Protocol to transmit data with</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RTL0</string>
|
||||
@ -378,12 +590,41 @@
|
||||
<string>SDRA</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>SDRA wss</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="displaySettings">
|
||||
<property name="toolTip">
|
||||
<string>Display settings dialog</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="statusLayout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="remoteControl">
|
||||
<property name="toolTip">
|
||||
<string>Allow remote control</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
@ -407,10 +648,10 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="clients">
|
||||
<property name="toolTip">
|
||||
<string>Number of clients connected</string>
|
||||
<string>Number of clients connected / maximum clients</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string>0/0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -431,10 +672,14 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="bw">
|
||||
<property name="toolTip">
|
||||
<string>Transmit bandwidth for a single TCP connection averaged over the last 10 seconds in bits per second</string>
|
||||
<string>- 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</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.000Mbps</string>
|
||||
<string>0.0Mbps 0% 0.0Mbps</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -442,8 +687,151 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="messagesContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>200</y>
|
||||
<width>351</width>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Messages</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="txMessageLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="sendMessage">
|
||||
<property name="text">
|
||||
<string>TX</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="txAddress">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>127.127.127.127:1234</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="txMessage"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="messagesLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="messages"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="connectionsContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>410</y>
|
||||
<width>351</width>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Connection Log</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="connectionsLayout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="connections">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IP address of client</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Port</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>TCP port number of client</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Connected</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Date and time client connected</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Disconnected</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Date and time client disconnected</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Time</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Time client was connected for</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RollupContents</class>
|
||||
<extends>QWidget</extends>
|
||||
@ -456,12 +844,24 @@
|
||||
<header>gui/valuedialz.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LevelMeterSignalDB</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/levelmeter.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDial</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PeriodDial</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/perioddial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
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).
|
||||
|
370
plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.cpp
Normal file
370
plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.cpp
Normal file
@ -0,0 +1,370 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#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)
|
||||
{
|
||||
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();
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_REMOTETCPSINKSETTINGSDIALOG_H
|
||||
#define INCLUDE_REMOTETCPSINKSETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#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();
|
||||
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
|
675
plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.ui
Normal file
675
plugins/channelrx/remotetcpsink/remotetcpsinksettingsdialog.ui
Normal file
@ -0,0 +1,675 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RemoteTCPSinkSettingsDialog</class>
|
||||
<widget class="QDialog" name="RemoteTCPSinkSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>409</width>
|
||||
<height>947</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="serverSettingsGroup">
|
||||
<property name="title">
|
||||
<string>Server Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="maxSampleRate">
|
||||
<property name="toolTip">
|
||||
<string>Maximum channel sample rate</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>2700</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100000000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>10000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="timeLimitLabel">
|
||||
<property name="text">
|
||||
<string>Time Limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="maxClientsLabel">
|
||||
<property name="text">
|
||||
<string>Max Clients</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="timeLimit">
|
||||
<property name="toolTip">
|
||||
<string>Connection time limit in minutes if max clients reached. 0 for no limit.</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="maxSampleRateLabel">
|
||||
<property name="text">
|
||||
<string>Max Ch. Sample Rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="maxSampleRateUnits">
|
||||
<property name="text">
|
||||
<string>S/s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="maxClients">
|
||||
<property name="toolTip">
|
||||
<string>Maximum number of simultaneous clients</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="timeLimitUnits">
|
||||
<property name="text">
|
||||
<string>mins</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="iqOnlyLabel">
|
||||
<property name="text">
|
||||
<string>IQ only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="iqOnly">
|
||||
<property name="toolTip">
|
||||
<string>Transmit uncompressed IQ only. Disables compression, position and messaging support.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="compressionSettings">
|
||||
<property name="title">
|
||||
<string>Compression</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="compressorLabel">
|
||||
<property name="text">
|
||||
<string>Compressor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="compressor">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>FLAC</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>zlib</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="compressionLevelLabel">
|
||||
<property name="text">
|
||||
<string>Compression Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="compressionLevel">
|
||||
<property name="toolTip">
|
||||
<string>0 - Least compression. 8 - Most compression Higher compression requires more CPU.</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="blockSizeLabel">
|
||||
<property name="text">
|
||||
<string>Block size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="blockSize">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4096</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>16384</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="sslSettingsGroup">
|
||||
<property name="title">
|
||||
<string>SSL Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="certificate">
|
||||
<property name="toolTip">
|
||||
<string>SSL certificate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="browseKey">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/load.png</normaloff>:/load.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="keyLabel">
|
||||
<property name="text">
|
||||
<string>Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="key">
|
||||
<property name="toolTip">
|
||||
<string>SSL certificate key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="certificateLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Certificate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="browseCertificate">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/load.png</normaloff>:/load.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="directorySettings">
|
||||
<property name="title">
|
||||
<string>Public Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="antennaLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Antenna</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="minFrequency">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Minimum recommend frequency in MHz</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>20000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="publicPort">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Publically accessible port number</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1234</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="directionLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="location">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Town and country where antenna is located</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>255</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="publicListingLabel">
|
||||
<property name="text">
|
||||
<string>List Server </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="isotropicLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Isotropic</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1" colspan="3">
|
||||
<layout class="QHBoxLayout" name="directionLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="azimuthLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Az</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="azimuth">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Antenna azimuth in degrees</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="elevationLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>El</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="elevation">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Antenna elevation in degrees</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-90.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="maxFrequency">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Maximum recommend frequency in MHz</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>20000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="publicAddressLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="locationLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QLabel" name="frequencyUnits">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>MHz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="publicAddress">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Publically accessible IP address or hostname</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="isotropic">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Check to indicate an antenna that is isotropic (non-directional)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="publicListing">
|
||||
<property name="toolTip">
|
||||
<string>Whether to list the server as publically accessible</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="frequencyLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Frequency Range</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="antenna">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Antenna description</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>255</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="rotatorLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rotator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="rotator">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rotator feature to get antenna direction from</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="ipBlacklistGroup">
|
||||
<property name="title">
|
||||
<string>IP Blacklist</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QListWidget" name="ipBlacklist">
|
||||
<property name="toolTip">
|
||||
<string>List of IP addresses from which connections should not be allowed</string>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::MultiSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="ipBlacklistLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="addIP">
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="removeIP">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>maxClients</tabstop>
|
||||
<tabstop>timeLimit</tabstop>
|
||||
<tabstop>maxSampleRate</tabstop>
|
||||
<tabstop>iqOnly</tabstop>
|
||||
<tabstop>compressor</tabstop>
|
||||
<tabstop>compressionLevel</tabstop>
|
||||
<tabstop>blockSize</tabstop>
|
||||
<tabstop>certificate</tabstop>
|
||||
<tabstop>browseCertificate</tabstop>
|
||||
<tabstop>key</tabstop>
|
||||
<tabstop>browseKey</tabstop>
|
||||
<tabstop>publicListing</tabstop>
|
||||
<tabstop>publicAddress</tabstop>
|
||||
<tabstop>publicPort</tabstop>
|
||||
<tabstop>minFrequency</tabstop>
|
||||
<tabstop>maxFrequency</tabstop>
|
||||
<tabstop>antenna</tabstop>
|
||||
<tabstop>location</tabstop>
|
||||
<tabstop>isotropic</tabstop>
|
||||
<tabstop>rotator</tabstop>
|
||||
<tabstop>azimuth</tabstop>
|
||||
<tabstop>elevation</tabstop>
|
||||
<tabstop>ipBlacklist</tabstop>
|
||||
<tabstop>addIP</tabstop>
|
||||
<tabstop>removeIP</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>RemoteTCPSinkSettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>RemoteTCPSinkSettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022-2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
@ -23,16 +23,25 @@
|
||||
#include <QThread>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocketServer>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include <FLAC/stream_encoder.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#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 "socket.h"
|
||||
|
||||
class DeviceSampleSource;
|
||||
|
||||
class RemoteTCPSinkSink : public QObject, public ChannelSampleSink {
|
||||
@ -47,22 +56,75 @@ public:
|
||||
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<QSslError> &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:
|
||||
|
||||
QRecursiveMutex m_mutex;
|
||||
QTcpServer *m_server;
|
||||
QList<QTcpSocket *> m_clients;
|
||||
QWebSocketServer *m_webSocketServer;
|
||||
QList<Socket *> m_clients;
|
||||
QList<QTimer *> m_timers;
|
||||
|
||||
QDateTime m_bwDateTime; //!< For calculating TX bandwidth
|
||||
qint64 m_bwBytes;
|
||||
@ -86,6 +150,52 @@ private:
|
||||
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<Complex> m_squelchDelayLine;
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MovingAverageUtil<Real, double, 16> 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);
|
||||
|
160
plugins/channelrx/remotetcpsink/socket.cpp
Normal file
160
plugins/channelrx/remotetcpsink/socket.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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<QTcpSocket *>(m_socket);
|
||||
|
||||
return socket->write(data, length);
|
||||
}
|
||||
|
||||
qint64 TCPSocket::read(char *data, qint64 length)
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
return socket->read(data, length);
|
||||
}
|
||||
|
||||
void TCPSocket::close()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
socket->close();
|
||||
}
|
||||
|
||||
qint64 TCPSocket::bytesAvailable()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
return socket->bytesAvailable();
|
||||
}
|
||||
|
||||
void TCPSocket::flush()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
socket->flush();
|
||||
}
|
||||
|
||||
QHostAddress TCPSocket::peerAddress()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
return socket->peerAddress();
|
||||
}
|
||||
|
||||
quint16 TCPSocket::peerPort()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket *>(m_socket);
|
||||
|
||||
return socket->peerPort();
|
||||
}
|
||||
|
||||
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<QWebSocket *>(m_socket);
|
||||
//return socket->sendBinaryMessage(QByteArray(data, length));
|
||||
|
||||
m_txBuffer.append(data, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
void WebSocket::flush()
|
||||
{
|
||||
QWebSocket *socket = qobject_cast<QWebSocket *>(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;
|
||||
}
|
||||
|
||||
void WebSocket::close()
|
||||
{
|
||||
QWebSocket *socket = qobject_cast<QWebSocket *>(m_socket);
|
||||
|
||||
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<QWebSocket *>(m_socket);
|
||||
|
||||
return socket->peerAddress();
|
||||
}
|
||||
|
||||
quint16 WebSocket::peerPort()
|
||||
{
|
||||
QWebSocket *socket = qobject_cast<QWebSocket *>(m_socket);
|
||||
|
||||
return socket->peerPort();
|
||||
}
|
89
plugins/channelrx/remotetcpsink/socket.h
Normal file
89
plugins/channelrx/remotetcpsink/socket.h
Normal file
@ -0,0 +1,89 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SOCKET_H_
|
||||
#define INCLUDE_SOCKET_H_
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocket>
|
||||
|
||||
// Class to allow easy use of either QTCPSocket or QWebSocket
|
||||
class 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 void close() = 0;
|
||||
virtual QHostAddress peerAddress() = 0;
|
||||
virtual quint16 peerPort() = 0;
|
||||
|
||||
QObject *socket() { return m_socket; }
|
||||
|
||||
protected:
|
||||
|
||||
QObject *m_socket;
|
||||
|
||||
};
|
||||
|
||||
class TCPSocket : public Socket {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
TCPSocket(QTcpSocket *socket) ;
|
||||
qint64 write(const char *data, qint64 length) override;
|
||||
void flush() override;
|
||||
qint64 read(char *data, qint64 length) override;
|
||||
qint64 bytesAvailable() override;
|
||||
void close() override;
|
||||
QHostAddress peerAddress() override;
|
||||
quint16 peerPort() override;
|
||||
|
||||
};
|
||||
|
||||
class WebSocket : public Socket {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
WebSocket(QWebSocket *socket);
|
||||
qint64 write(const char *data, qint64 length) override;
|
||||
void flush() override;
|
||||
qint64 read(char *data, qint64 length) override;
|
||||
qint64 bytesAvailable() override;
|
||||
void close() override;
|
||||
QHostAddress peerAddress() override;
|
||||
quint16 peerPort() override;
|
||||
|
||||
private slots:
|
||||
|
||||
void binaryFrameReceived(const QByteArray &frame, bool isLastFrame);
|
||||
|
||||
private:
|
||||
|
||||
QByteArray m_rxBuffer;
|
||||
QByteArray m_txBuffer;
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SOCKET_H_
|
@ -295,6 +295,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();
|
||||
@ -309,6 +310,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
connect(profile, &QWebEngineProfile::downloadRequested, this, &MapGUI::downloadRequested);
|
||||
#endif
|
||||
|
||||
qDebug() << "Get station position";
|
||||
// Get station position
|
||||
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
|
||||
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
|
||||
@ -320,6 +322,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
m_polygonMapFilter.setPosition(stationPosition);
|
||||
m_polylineMapFilter.setPosition(stationPosition);
|
||||
|
||||
qDebug() << "Centre map";
|
||||
// Centre map at My Position
|
||||
QQuickItem *item = ui->map->rootObject();
|
||||
QObject *object = item->findChild<QObject*>("map");
|
||||
@ -331,6 +334,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
object->setProperty("center", QVariant::fromValue(coords));
|
||||
}
|
||||
|
||||
qDebug() << "Creating antenna";
|
||||
// Create antenna at My Position
|
||||
m_antennaMapItem.setName(new QString("Station"));
|
||||
m_antennaMapItem.setLatitude(stationLatitude);
|
||||
@ -358,7 +362,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
setBeacons(beacons);
|
||||
}
|
||||
addIBPBeacons();
|
||||
|
||||
addNAT();
|
||||
addRadioTimeTransmitters();
|
||||
addRadar();
|
||||
addIonosonde();
|
||||
@ -371,6 +375,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
addVLF();
|
||||
addKiwiSDR();
|
||||
addSpyServer();
|
||||
addSDRangelServer();
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
@ -490,6 +495,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<RadioTimeTransmitter> 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 +742,75 @@ void MapGUI::spyServerUpdated(const QList<SpyServerList::SpyServer>& sdrs)
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::addSDRangelServer()
|
||||
{
|
||||
m_sdrangelServerList.getDataPeriodically();
|
||||
}
|
||||
|
||||
void MapGUI::sdrangelServerUpdated(const QList<SDRangelServerList::SDRangelServer>& 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("SDRangel\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)
|
||||
;
|
||||
if (sdr.m_timeLimit > 0) {
|
||||
text.append(QString("\nTime limit: %1 mins").arg(sdr.m_timeLimit));
|
||||
}
|
||||
QString url = QString("sdrangel-server://%1").arg(address);
|
||||
QString link = QString("<a href=%1 onclick=\"return parent.infoboxLink('%1')\">%2</a>").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("SDRangel %1").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 +1694,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 +1802,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,12 +2720,16 @@ void MapGUI::linkClicked(const QString& url)
|
||||
QString spyServerURL = url.mid(21);
|
||||
openSpyServer(spyServerURL);
|
||||
}
|
||||
else if (url.startsWith("sdrangel-server://"))
|
||||
{
|
||||
QString sdrangelServerURL = url.mid(18);
|
||||
openSDRangelServer(sdrangelServerURL);
|
||||
}
|
||||
}
|
||||
|
||||
// Open a KiwiSDR RX device
|
||||
void MapGUI::openKiwiSDR(const QString& url)
|
||||
bool MapGUI::openKiwiSDRInput()
|
||||
{
|
||||
// Create DeviceSet
|
||||
// Create DeviceSet
|
||||
MainCore *mainCore = MainCore::instance();
|
||||
unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
|
||||
MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
|
||||
@ -2634,39 +2758,43 @@ void MapGUI::openKiwiSDR(const QString& url)
|
||||
if (!found)
|
||||
{
|
||||
qCritical() << "MapGUI::openKiwiSDR: Failed to find KiwiSDR";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Move to same workspace
|
||||
//getWorkspaceIndex();
|
||||
|
||||
// Set address setting
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Open a RemoteTCPInput device to use for SpyServer
|
||||
void MapGUI::openSpyServer(const QString& url)
|
||||
// Open a KiwiSDR RX device
|
||||
void MapGUI::openKiwiSDR(const QString& url)
|
||||
{
|
||||
m_remoteDeviceAddress = url;
|
||||
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
|
||||
if (!openKiwiSDRInput()) {
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::kiwiSDRDeviceSetAdded(int index, DeviceAPI *device)
|
||||
{
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
|
||||
|
||||
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
|
||||
QTimer::singleShot(200, [=] {
|
||||
// Set address setting
|
||||
QStringList deviceSettingsKeys = {"serverAddress"};
|
||||
SWGSDRangel::SWGDeviceSettings response;
|
||||
response.init();
|
||||
SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response.getKiwiSdrSettings();
|
||||
deviceSettings->setServerAddress(new QString(m_remoteDeviceAddress));
|
||||
QString errorMessage;
|
||||
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
bool MapGUI::openRemoteTCPInput()
|
||||
{
|
||||
// Create DeviceSet
|
||||
MainCore *mainCore = MainCore::instance();
|
||||
@ -2696,39 +2824,79 @@ void MapGUI::openSpyServer(const QString& url)
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
qCritical() << "MapGUI::openSpyServer: Failed to find RemoteTCPInput";
|
||||
return;
|
||||
qCritical() << "MapGUI::openRemoteTCPInput: Failed to find RemoteTCPInput";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Move to same workspace
|
||||
//getWorkspaceIndex();
|
||||
|
||||
// Set address/port setting
|
||||
return true;
|
||||
}
|
||||
|
||||
// Open a RemoteTCPInput device to use for SpyServer
|
||||
void MapGUI::openSpyServer(const QString& url)
|
||||
{
|
||||
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);
|
||||
m_remoteDeviceAddress = address[0];
|
||||
m_remoteDevicePort = address[1].toInt();
|
||||
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
|
||||
if (!openRemoteTCPInput()) {
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::spyServerDeviceSetAdded(int index, DeviceAPI *device)
|
||||
{
|
||||
qDebug() << "**************** MapGUI::spyServerDeviceSetAdded";
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
|
||||
|
||||
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
|
||||
QTimer::singleShot(200, [=] {
|
||||
// Set address/port setting
|
||||
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
|
||||
SWGSDRangel::SWGDeviceSettings response;
|
||||
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);
|
||||
QString errorMessage;
|
||||
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
// Open a RemoteTCPInput device to use for SDRangel
|
||||
void MapGUI::openSDRangelServer(const QString& url)
|
||||
{
|
||||
QStringList address = url.split(":");
|
||||
m_remoteDeviceAddress = address[0];
|
||||
m_remoteDevicePort = address[1].toInt();
|
||||
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
|
||||
if (!openRemoteTCPInput()) {
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::sdrangelServerDeviceSetAdded(int index, DeviceAPI *device)
|
||||
{
|
||||
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
|
||||
|
||||
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
|
||||
QTimer::singleShot(200, [=] {
|
||||
// Set address/port setting
|
||||
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
|
||||
SWGSDRangel::SWGDeviceSettings response;
|
||||
response.init();
|
||||
SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
|
||||
deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
|
||||
deviceSettings->setDataPort(m_remoteDevicePort);
|
||||
deviceSettings->setProtocol(new QString("SDRangel"));
|
||||
deviceSettings->setOverrideRemoteSettings(false);
|
||||
QString errorMessage;
|
||||
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef QT_WEBENGINE_FOUND
|
||||
@ -2836,4 +3004,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);
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include "util/nasaglobalimagery.h"
|
||||
#include "util/kiwisdrlist.h"
|
||||
#include "util/spyserverlist.h"
|
||||
#include "util/sdrangelserverlist.h"
|
||||
#include "settings/rollupstate.h"
|
||||
#include "availablechannelorfeaturehandler.h"
|
||||
|
||||
@ -169,6 +170,7 @@ public:
|
||||
void addIBPBeacons();
|
||||
QList<RadioTimeTransmitter> getRadioTimeTransmitters() { return m_radioTimeTransmitters; }
|
||||
void addRadioTimeTransmitters();
|
||||
void addNAT();
|
||||
void addRadar();
|
||||
void addIonosonde();
|
||||
void addBroadcast();
|
||||
@ -182,6 +184,7 @@ public:
|
||||
void addVLF();
|
||||
void addKiwiSDR();
|
||||
void addSpyServer();
|
||||
void addSDRangelServer();
|
||||
void find(const QString& target);
|
||||
void track3D(const QString& target);
|
||||
Q_INVOKABLE void supportedMapsChanged();
|
||||
@ -231,6 +234,7 @@ private:
|
||||
QGeoCoordinate m_lastFullUpdatePosition;
|
||||
KiwiSDRList m_kiwiSDRList;
|
||||
SpyServerList m_spyServerList;
|
||||
SDRangelServerList m_sdrangelServerList;
|
||||
|
||||
CesiumInterface *m_cesium;
|
||||
WebServer *m_webServer;
|
||||
@ -257,6 +261,10 @@ private:
|
||||
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();
|
||||
|
||||
@ -282,13 +290,17 @@ private:
|
||||
void applyNASAGlobalImagerySettings();
|
||||
void createNASAGlobalImageryView();
|
||||
void displayNASAMetaData();
|
||||
bool openKiwiSDRInput();
|
||||
bool openRemoteTCPInput();
|
||||
void openKiwiSDR(const QString& url);
|
||||
void openSpyServer(const QString& url);
|
||||
void openSDRangelServer(const QString& url);
|
||||
QString formatFrequency(qint64 frequency) const;
|
||||
void updateGIRO(const QDateTime& mapDateTime);
|
||||
|
||||
static QString getDataDir();
|
||||
static const QList<RadioTimeTransmitter> m_radioTimeTransmitters;
|
||||
static const QList<RadioTimeTransmitter> m_natTransmitters;
|
||||
static const QList<RadioTimeTransmitter> m_vlfTransmitters;
|
||||
|
||||
enum NASARow {
|
||||
@ -359,9 +371,12 @@ private slots:
|
||||
void airportsUpdated();
|
||||
void waypointsUpdated();
|
||||
void kiwiSDRUpdated(const QList<KiwiSDRList::KiwiSDR>& sdrs);
|
||||
void kiwiSDRDeviceSetAdded(int index, DeviceAPI *device);
|
||||
void spyServerUpdated(const QList<SpyServerList::SpyServer>& sdrs);
|
||||
void spyServerDeviceSetAdded(int index, DeviceAPI *device);
|
||||
void sdrangelServerUpdated(const QList<SDRangelServerList::SDRangelServer>& sdrs);
|
||||
void sdrangelServerDeviceSetAdded(int index, DeviceAPI *device);
|
||||
void linkClicked(const QString& url);
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPGUI_H_
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -40,16 +40,27 @@
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgConfigureRemoteTCPInput, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportTCPBuffer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgSaveReplay, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgSendMessage, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportPosition, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RemoteTCPInput::MsgReportDirection, Message)
|
||||
|
||||
RemoteTCPInput::RemoteTCPInput(DeviceAPI *deviceAPI) :
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_settings(),
|
||||
m_remoteInputTCPPHandler(nullptr),
|
||||
m_deviceDescription("RemoteTCPInput")
|
||||
m_deviceDescription("RemoteTCPInput"),
|
||||
m_running(false),
|
||||
m_latitude(std::numeric_limits<float>::quiet_NaN()),
|
||||
m_longitude(std::numeric_limits<float>::quiet_NaN()),
|
||||
m_altitude(std::numeric_limits<float>::quiet_NaN()),
|
||||
m_isotropic(false),
|
||||
m_azimuth(std::numeric_limits<float>::quiet_NaN()),
|
||||
m_elevation(std::numeric_limits<float>::quiet_NaN())
|
||||
{
|
||||
m_sampleFifo.setLabel(m_deviceDescription);
|
||||
m_sampleFifo.setSize(48000 * 8);
|
||||
m_remoteInputTCPPHandler = new RemoteTCPInputTCPHandler(&m_sampleFifo, m_deviceAPI);
|
||||
m_remoteInputTCPPHandler = new RemoteTCPInputTCPHandler(&m_sampleFifo, m_deviceAPI, &m_replayBuffer);
|
||||
m_remoteInputTCPPHandler->moveToThread(&m_thread);
|
||||
m_remoteInputTCPPHandler->setMessageQueueToInput(&m_inputMessageQueue);
|
||||
|
||||
@ -66,6 +77,7 @@ RemoteTCPInput::RemoteTCPInput(DeviceAPI *deviceAPI) :
|
||||
|
||||
RemoteTCPInput::~RemoteTCPInput()
|
||||
{
|
||||
qDebug() << "RemoteTCPInput::~RemoteTCPInput";
|
||||
QObject::disconnect(
|
||||
m_networkManager,
|
||||
&QNetworkAccessManager::finished,
|
||||
@ -84,25 +96,45 @@ void RemoteTCPInput::destroy()
|
||||
|
||||
void RemoteTCPInput::init()
|
||||
{
|
||||
qDebug() << "*************** RemoteTCPInput::init";
|
||||
applySettings(m_settings, QList<QString>(), true);
|
||||
}
|
||||
|
||||
bool RemoteTCPInput::start()
|
||||
{
|
||||
qDebug() << "RemoteTCPInput::start";
|
||||
if (m_running) {
|
||||
qDebug() << "RemoteTCPInput::stop - Already running";
|
||||
return true;
|
||||
}
|
||||
m_remoteInputTCPPHandler->reset();
|
||||
m_remoteInputTCPPHandler->start();
|
||||
qDebug() << "************ RemoteTCPInput::start" << m_settings.m_dataAddress;
|
||||
m_remoteInputTCPPHandler->getInputMessageQueue()->push(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler::create(m_settings, QList<QString>(), true));
|
||||
m_thread.start();
|
||||
m_running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoteTCPInput::stop()
|
||||
{
|
||||
qDebug() << "RemoteTCPInput::stop";
|
||||
if (!m_running) {
|
||||
// For wasm, important not to call m_remoteInputTCPPHandler->stop() twice
|
||||
// as mutex can deadlock when this object is being deleted
|
||||
qDebug() << "RemoteTCPInput::stop - Not running";
|
||||
return;
|
||||
}
|
||||
m_remoteInputTCPPHandler->stop();
|
||||
qDebug() << "RemoteTCPInput::stop1";
|
||||
m_thread.quit();
|
||||
qDebug() << "RemoteTCPInput::stop2";
|
||||
#ifndef __EMSCRIPTEN__
|
||||
qDebug() << "RemoteTCPInput::stop3";
|
||||
m_thread.wait();
|
||||
#endif
|
||||
m_running = false;
|
||||
qDebug() << "RemoteTCPInput::stopped";
|
||||
}
|
||||
|
||||
QByteArray RemoteTCPInput::serialize() const
|
||||
@ -120,6 +152,8 @@ bool RemoteTCPInput::deserialize(const QByteArray& data)
|
||||
success = false;
|
||||
}
|
||||
|
||||
qDebug() << "************** RemoteTCPInput::deserialize" << m_settings.m_dataAddress;
|
||||
|
||||
MsgConfigureRemoteTCPInput* message = MsgConfigureRemoteTCPInput::create(m_settings, QList<QString>(), true);
|
||||
m_inputMessageQueue.push(message);
|
||||
|
||||
@ -196,6 +230,7 @@ bool RemoteTCPInput::handleMessage(const Message& message)
|
||||
{
|
||||
qDebug() << "RemoteTCPInput::handleMessage:" << message.getIdentifier();
|
||||
MsgConfigureRemoteTCPInput& conf = (MsgConfigureRemoteTCPInput&) message;
|
||||
qDebug() << "*********** RemoteTCPInput::handleMessage MsgConfigureRemoteTCPInput" << m_settings.m_dataAddress;
|
||||
applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce());
|
||||
return true;
|
||||
}
|
||||
@ -210,6 +245,42 @@ bool RemoteTCPInput::handleMessage(const Message& message)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (MsgSaveReplay::match(message))
|
||||
{
|
||||
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
|
||||
m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
|
||||
return true;
|
||||
}
|
||||
else if (MsgSendMessage::match(message))
|
||||
{
|
||||
MsgSendMessage& msg = (MsgSendMessage&) message;
|
||||
m_remoteInputTCPPHandler->getInputMessageQueue()->push(MsgSendMessage::create(msg.getCallsign(), msg.getText(), msg.getBroadcast()));
|
||||
return true;
|
||||
}
|
||||
else if (MsgReportPosition::match(message))
|
||||
{
|
||||
MsgReportPosition& report = (MsgReportPosition&) message;
|
||||
|
||||
m_latitude = report.getLatitude();
|
||||
m_longitude = report.getLongitude();
|
||||
m_altitude = report.getAltitude();
|
||||
|
||||
emit positionChanged(m_latitude, m_longitude, m_altitude);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgReportDirection::match(message))
|
||||
{
|
||||
MsgReportDirection& report = (MsgReportDirection&) message;
|
||||
|
||||
m_isotropic = report.getIsotropic();
|
||||
m_azimuth = report.getAzimuth();
|
||||
m_elevation = report.getElevation();
|
||||
|
||||
emit directionChanged(m_isotropic, m_azimuth, m_elevation);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -242,6 +313,12 @@ void RemoteTCPInput::applySettings(const RemoteTCPInputSettings& settings, const
|
||||
forwardChange = true;
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("channelSampleRate") || force)
|
||||
&& (settings.m_devSampleRate != m_settings.m_devSampleRate))
|
||||
{
|
||||
m_replayBuffer.clear();
|
||||
}
|
||||
|
||||
mutexLocker.unlock();
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
@ -265,6 +342,18 @@ void RemoteTCPInput::applySettings(const RemoteTCPInputSettings& settings, const
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setSize(m_settings.m_replayLength, m_settings.m_devSampleRate);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * m_settings.m_devSampleRate)) * 2);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
m_replayBuffer.setLoop(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
m_remoteInputTCPPHandler->getInputMessageQueue()->push(RemoteTCPInputTCPHandler::MsgConfigureTcpHandler::create(m_settings, settingsKeys, force));
|
||||
}
|
||||
|
||||
@ -459,6 +548,9 @@ int RemoteTCPInput::webapiReportGet(
|
||||
void RemoteTCPInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
|
||||
{
|
||||
response.getRemoteTcpInputReport()->setSampleRate(m_settings.m_channelSampleRate);
|
||||
response.getRemoteTcpInputReport()->setLatitude(m_latitude);
|
||||
response.getRemoteTcpInputReport()->setLongitude(m_longitude);
|
||||
response.getRemoteTcpInputReport()->setAltitude(m_altitude);
|
||||
}
|
||||
|
||||
void RemoteTCPInput::webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const RemoteTCPInputSettings& settings, bool force)
|
||||
|
@ -31,13 +31,14 @@
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
|
||||
#include "remotetcpinputsettings.h"
|
||||
#include "remotetcpinputtcphandler.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeviceAPI;
|
||||
class RemoteTCPInputTCPHandler;
|
||||
|
||||
class RemoteTCPInput : public DeviceSampleSource {
|
||||
Q_OBJECT
|
||||
@ -124,6 +125,100 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSaveReplay : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getFilename() const { return m_filename; }
|
||||
|
||||
static MsgSaveReplay* create(const QString& filename) {
|
||||
return new MsgSaveReplay(filename);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
|
||||
MsgSaveReplay(const QString& filename) :
|
||||
Message(),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSendMessage : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getCallsign() const { return m_callsign; }
|
||||
const QString& getText() const { return m_text; }
|
||||
bool getBroadcast() const { return m_broadcast; }
|
||||
|
||||
static MsgSendMessage* create(const QString& callsign, const QString& text, bool broadcast) {
|
||||
return new MsgSendMessage(callsign, text, broadcast);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_callsign;
|
||||
QString m_text;
|
||||
bool m_broadcast;
|
||||
|
||||
MsgSendMessage(const QString& callsign, const QString& text, bool broadcast) :
|
||||
Message(),
|
||||
m_callsign(callsign),
|
||||
m_text(text),
|
||||
m_broadcast(broadcast)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportPosition : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
float getLatitude() const { return m_latitude; }
|
||||
float getLongitude() const { return m_longitude; }
|
||||
float getAltitude() const { return m_altitude; }
|
||||
|
||||
static MsgReportPosition* create(float latitude, float longitude, float altitude) {
|
||||
return new MsgReportPosition(latitude, longitude, altitude);
|
||||
}
|
||||
|
||||
private:
|
||||
float m_latitude;
|
||||
float m_longitude;
|
||||
float m_altitude;
|
||||
|
||||
MsgReportPosition(float latitude, float longitude, float altitude) :
|
||||
Message(),
|
||||
m_latitude(latitude),
|
||||
m_longitude(longitude),
|
||||
m_altitude(altitude)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportDirection : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getIsotropic() const { return m_isotropic; }
|
||||
float getAzimuth() const { return m_azimuth; }
|
||||
float getElevation() const { return m_elevation; }
|
||||
|
||||
static MsgReportDirection* create(bool isotropic, float azimuth, float elevation) {
|
||||
return new MsgReportDirection(isotropic, azimuth, elevation);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_isotropic;
|
||||
float m_azimuth;
|
||||
float m_elevation;
|
||||
|
||||
MsgReportDirection(bool isotropic, float azimuth, float elevation) :
|
||||
Message(),
|
||||
m_isotropic(isotropic),
|
||||
m_azimuth(azimuth),
|
||||
m_elevation(elevation)
|
||||
{ }
|
||||
};
|
||||
|
||||
RemoteTCPInput(DeviceAPI *deviceAPI);
|
||||
virtual ~RemoteTCPInput();
|
||||
virtual void destroy();
|
||||
@ -177,6 +272,15 @@ public:
|
||||
const QStringList& deviceSettingsKeys,
|
||||
SWGSDRangel::SWGDeviceSettings& response);
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||
{
|
||||
if (m_remoteInputTCPPHandler) {
|
||||
m_remoteInputTCPPHandler->getMagSqLevels(avg, peak, nbSamples);
|
||||
} else {
|
||||
avg = 0.0; peak = 0.0; nbSamples = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DeviceAPI *m_deviceAPI;
|
||||
QRecursiveMutex m_mutex;
|
||||
@ -185,7 +289,15 @@ private:
|
||||
QString m_deviceDescription;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
ReplayBuffer<FixReal> m_replayBuffer;
|
||||
QThread m_thread;
|
||||
bool m_running;
|
||||
float m_latitude; // Position of remote device (antenna)
|
||||
float m_longitude;
|
||||
float m_altitude;
|
||||
bool m_isotropic; // Direction of remote anntenna
|
||||
float m_azimuth;
|
||||
float m_elevation;
|
||||
|
||||
void applySettings(const RemoteTCPInputSettings& settings, const QList<QString>& settingsKeys, bool force = false);
|
||||
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "ui_remotetcpinputgui.h"
|
||||
#include "gui/colormapper.h"
|
||||
@ -30,17 +31,19 @@
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "device/deviceuiset.h"
|
||||
#include "util/db.h"
|
||||
#include "remotetcpinputgui.h"
|
||||
#include "remotetcpinputtcphandler.h"
|
||||
#include "maincore.h"
|
||||
|
||||
RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
|
||||
DeviceGUI(parent),
|
||||
ui(new Ui::RemoteTCPInputGui),
|
||||
m_settings(),
|
||||
m_sampleSource(0),
|
||||
m_lastEngineState(DeviceAPI::StNotStarted),
|
||||
m_sampleSource(nullptr),
|
||||
m_sampleRate(0),
|
||||
m_centerFrequency(0),
|
||||
m_tickCount(0),
|
||||
m_doApplySettings(true),
|
||||
m_forceSettings(true),
|
||||
m_deviceGains(nullptr),
|
||||
@ -71,12 +74,15 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
|
||||
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
||||
|
||||
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
|
||||
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
|
||||
|
||||
displaySettings();
|
||||
|
||||
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
m_statusTimer.start(500);
|
||||
connect(deviceUISet->m_deviceAPI, &DeviceAPI::stateChanged, this, &RemoteTCPInputGui::updateStatus);
|
||||
updateStatus();
|
||||
|
||||
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
|
||||
|
||||
m_sampleSource = (RemoteTCPInput*) m_deviceUISet->m_deviceAPI->getSampleSource();
|
||||
@ -89,11 +95,12 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
|
||||
makeUIConnections();
|
||||
DialPopup::addPopupsToChildDials(this);
|
||||
m_resizer.enableChildMouseTracking();
|
||||
|
||||
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
|
||||
}
|
||||
|
||||
RemoteTCPInputGui::~RemoteTCPInputGui()
|
||||
{
|
||||
m_statusTimer.stop();
|
||||
m_updateTimer.stop();
|
||||
delete ui;
|
||||
}
|
||||
@ -110,6 +117,7 @@ void RemoteTCPInputGui::destroy()
|
||||
|
||||
void RemoteTCPInputGui::resetToDefaults()
|
||||
{
|
||||
qDebug() << "*************** RemoteTCPInputGui::resetToDefaults";
|
||||
m_settings.resetToDefaults();
|
||||
displaySettings();
|
||||
m_forceSettings = true;
|
||||
@ -150,6 +158,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
} else {
|
||||
m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
|
||||
}
|
||||
qDebug() << "********* RemoteTCPInputGui::handleMessage MsgConfigureRemoteTCPInput" << m_settings.m_dataAddress;
|
||||
|
||||
blockApplySettings(true);
|
||||
displaySettings();
|
||||
@ -224,24 +233,27 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
ui->detectedProtocol->setText(QString("Protocol: %1").arg(report.getProtocol()));
|
||||
|
||||
// Update GUI so we only show widgets available for the protocol in use
|
||||
bool sdra = report.getProtocol() == "SDRA";
|
||||
bool spyServer = report.getProtocol() == "Spy Server";
|
||||
if (spyServer) {
|
||||
m_sdra = report.getProtocol() == "SDRA";
|
||||
m_spyServer = report.getProtocol() == "Spy Server";
|
||||
m_remoteControl = report.getRemoteControl();
|
||||
m_iqOnly = report.getIQOnly();
|
||||
|
||||
if (m_spyServer) {
|
||||
m_spyServerGains.m_gains[0].m_max = report.getMaxGain();
|
||||
}
|
||||
if ((sdra || spyServer) && (ui->sampleBits->count() < 4))
|
||||
if ((m_sdra || m_spyServer) && (ui->sampleBits->count() < 4))
|
||||
{
|
||||
ui->sampleBits->addItem("16");
|
||||
ui->sampleBits->addItem("24");
|
||||
ui->sampleBits->addItem("32");
|
||||
}
|
||||
else if (!(sdra || spyServer) && (ui->sampleBits->count() != 1))
|
||||
else if (!(m_sdra || m_spyServer) && (ui->sampleBits->count() != 1))
|
||||
{
|
||||
while (ui->sampleBits->count() > 1) {
|
||||
ui->sampleBits->removeItem(ui->sampleBits->count() - 1);
|
||||
}
|
||||
}
|
||||
if ((sdra || spyServer) && (ui->decim->count() != 7))
|
||||
if ((m_sdra || m_spyServer) && (ui->decim->count() != 7))
|
||||
{
|
||||
ui->decim->addItem("2");
|
||||
ui->decim->addItem("4");
|
||||
@ -250,26 +262,19 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
ui->decim->addItem("32");
|
||||
ui->decim->addItem("64");
|
||||
}
|
||||
else if (!(sdra || spyServer) && (ui->decim->count() != 1))
|
||||
else if (!(m_sdra || m_spyServer) && (ui->decim->count() != 1))
|
||||
{
|
||||
while (ui->decim->count() > 1) {
|
||||
ui->decim->removeItem(ui->decim->count() - 1);
|
||||
}
|
||||
}
|
||||
if (!sdra)
|
||||
if (!m_sdra)
|
||||
{
|
||||
ui->deltaFrequency->setValue(0);
|
||||
ui->channelGain->setValue(0);
|
||||
ui->decimation->setChecked(true);
|
||||
}
|
||||
ui->deltaFrequencyLabel->setEnabled(sdra);
|
||||
ui->deltaFrequency->setEnabled(sdra);
|
||||
ui->deltaUnits->setEnabled(sdra);
|
||||
ui->channelGainLabel->setEnabled(sdra);
|
||||
ui->channelGain->setEnabled(sdra);
|
||||
ui->channelGainText->setEnabled(sdra);
|
||||
ui->decimation->setEnabled(sdra);
|
||||
if (sdra) {
|
||||
if (m_sdra) {
|
||||
ui->centerFrequency->setValueRange(9, 0, 999999999); // Should add transverter control to protocol in the future
|
||||
} else {
|
||||
ui->centerFrequency->setValueRange(7, 0, 9999999);
|
||||
@ -291,19 +296,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
{
|
||||
ui->devSampleRate->setValueRange(8, 0, 99999999);
|
||||
}
|
||||
ui->devSampleRateLabel->setEnabled(!spyServer);
|
||||
ui->devSampleRate->setEnabled(!spyServer);
|
||||
ui->devSampleRateUnits->setEnabled(!spyServer);
|
||||
ui->agc->setEnabled(!spyServer);
|
||||
ui->rfBWLabel->setEnabled(!spyServer);
|
||||
ui->rfBW->setEnabled(!spyServer);
|
||||
ui->rfBWUnits->setEnabled(!spyServer);
|
||||
ui->dcOffset->setEnabled(!spyServer);
|
||||
ui->iqImbalance->setEnabled(!spyServer);
|
||||
ui->ppm->setEnabled(!spyServer);
|
||||
ui->ppmLabel->setEnabled(!spyServer);
|
||||
ui->ppmText->setEnabled(!spyServer);
|
||||
|
||||
displayEnabled();
|
||||
displayGains();
|
||||
return true;
|
||||
}
|
||||
@ -314,13 +307,33 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
if (report.getConnected())
|
||||
{
|
||||
m_connectionError = false;
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
|
||||
//ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_connectionError = true;
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
|
||||
//ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
|
||||
}
|
||||
updateStatus();
|
||||
return true;
|
||||
}
|
||||
else if (RemoteTCPInput::MsgSendMessage::match(message))
|
||||
{
|
||||
const RemoteTCPInput::MsgSendMessage& msg = (const RemoteTCPInput::MsgSendMessage&) message;
|
||||
|
||||
ui->messages->addItem(QString("%1> %2").arg(msg.getCallsign()).arg(msg.getText()));
|
||||
ui->messages->scrollToBottom();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (RemoteTCPInput::MsgReportPosition::match(message))
|
||||
{
|
||||
// Could display in future
|
||||
return true;
|
||||
}
|
||||
else if (RemoteTCPInput::MsgReportDirection::match(message))
|
||||
{
|
||||
// Could display in future
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -329,6 +342,87 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::displayEnabled()
|
||||
{
|
||||
int state = m_deviceUISet->m_deviceAPI->state();
|
||||
bool remoteControl;
|
||||
bool enableMessages;
|
||||
bool enableSquelchEnable;
|
||||
bool enableSquelch;
|
||||
bool sdra;
|
||||
|
||||
if (state == DeviceAPI::StRunning)
|
||||
{
|
||||
sdra = m_sdra;
|
||||
remoteControl = m_remoteControl;
|
||||
enableMessages = !m_iqOnly;
|
||||
enableSquelchEnable = !m_iqOnly;
|
||||
enableSquelch = !m_iqOnly && m_settings.m_squelchEnabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
sdra = m_settings.m_protocol == "SDRangel";
|
||||
remoteControl = m_settings.m_overrideRemoteSettings;
|
||||
enableMessages = false;
|
||||
enableSquelchEnable = m_settings.m_overrideRemoteSettings;
|
||||
enableSquelch = m_settings.m_overrideRemoteSettings && m_settings.m_squelchEnabled;
|
||||
}
|
||||
|
||||
ui->deltaFrequencyLabel->setEnabled(sdra && remoteControl);
|
||||
ui->deltaFrequency->setEnabled(sdra && remoteControl);
|
||||
ui->deltaUnits->setEnabled(sdra && remoteControl);
|
||||
ui->channelGainLabel->setEnabled(sdra && remoteControl);
|
||||
ui->channelGain->setEnabled(sdra && remoteControl);
|
||||
ui->channelGainText->setEnabled(sdra && remoteControl);
|
||||
ui->decimation->setEnabled(sdra && remoteControl);
|
||||
|
||||
ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
|
||||
ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
|
||||
ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
|
||||
|
||||
ui->devSampleRateLabel->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->devSampleRate->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->devSampleRateUnits->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->agc->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->rfBWLabel->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->rfBW->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->rfBWUnits->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->dcOffset->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->iqImbalance->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->ppm->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->ppmLabel->setEnabled(!m_spyServer && remoteControl);
|
||||
ui->ppmText->setEnabled(!m_spyServer && remoteControl);
|
||||
|
||||
ui->centerFrequency->setEnabled(remoteControl);
|
||||
ui->biasTee->setEnabled(remoteControl);
|
||||
ui->directSampling->setEnabled(remoteControl);
|
||||
ui->decimLabel->setEnabled(remoteControl);
|
||||
ui->decim->setEnabled(remoteControl);
|
||||
ui->gain1Label->setEnabled(remoteControl);
|
||||
ui->gain1->setEnabled(remoteControl);
|
||||
ui->gain1Text->setEnabled(remoteControl);
|
||||
ui->gain2Label->setEnabled(remoteControl);
|
||||
ui->gain2->setEnabled(remoteControl);
|
||||
ui->gain2Text->setEnabled(remoteControl);
|
||||
ui->gain3Label->setEnabled(remoteControl);
|
||||
ui->gain3->setEnabled(remoteControl);
|
||||
ui->gain3Text->setEnabled(remoteControl);
|
||||
ui->sampleBitsLabel->setEnabled(remoteControl);
|
||||
ui->sampleBits->setEnabled(remoteControl);
|
||||
ui->sampleBitsUnits->setEnabled(remoteControl);
|
||||
|
||||
ui->squelchEnabled->setEnabled(enableSquelchEnable);
|
||||
ui->squelch->setEnabled(enableSquelch);
|
||||
ui->squelchText->setEnabled(enableSquelch);
|
||||
ui->squelchUnits->setEnabled(enableSquelch);
|
||||
ui->squelchGate->setEnabled(enableSquelch);
|
||||
|
||||
ui->sendMessage->setEnabled(enableMessages);
|
||||
ui->txAddress->setEnabled(enableMessages);
|
||||
ui->txMessage->setEnabled(enableMessages);
|
||||
ui->messages->setEnabled(enableMessages);
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
@ -386,12 +480,14 @@ void RemoteTCPInputGui::displaySettings()
|
||||
ui->channelSampleRate->setValue(m_settings.m_channelSampleRate);
|
||||
ui->deviceRateText->setText(tr("%1k").arg(m_settings.m_channelSampleRate / 1000.0));
|
||||
ui->decimation->setChecked(!m_settings.m_channelDecimation);
|
||||
ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation);
|
||||
ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation);
|
||||
ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation);
|
||||
ui->sampleBits->setCurrentText(QString::number(m_settings.m_sampleBits));
|
||||
|
||||
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
|
||||
ui->squelchEnabled->setChecked(m_settings.m_squelchEnabled);
|
||||
ui->squelch->setValue(m_settings.m_squelch);
|
||||
ui->squelchText->setText(QString::number(m_settings.m_squelch));
|
||||
ui->squelchGate->setValue(m_settings.m_squelchGate);
|
||||
|
||||
ui->dataPort->setValue(m_settings.m_dataPort);
|
||||
ui->dataAddress->blockSignals(true);
|
||||
ui->dataAddress->clear();
|
||||
for (const auto& address : m_settings.m_addressList) {
|
||||
@ -412,6 +508,11 @@ void RemoteTCPInputGui::displaySettings()
|
||||
}
|
||||
|
||||
displayGains();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
ui->replayLoop->setChecked(m_settings.m_replayLoop);
|
||||
displayEnabled();
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
@ -517,6 +618,7 @@ const QHash<RemoteTCPProtocol::Device, const RemoteTCPInputGui::DeviceGains *> R
|
||||
{
|
||||
{RemoteTCPProtocol::RTLSDR_E4000, &m_rtlSDRe4kGains},
|
||||
{RemoteTCPProtocol::RTLSDR_R820T, &m_rtlSDRR820Gains},
|
||||
{RemoteTCPProtocol::RTLSDR_R828D, &m_rtlSDRR820Gains},
|
||||
{RemoteTCPProtocol::AIRSPY, &m_airspyGains},
|
||||
{RemoteTCPProtocol::AIRSPY_HF, &m_airspyHFGains},
|
||||
{RemoteTCPProtocol::BLADE_RF1, &m_baldeRF1Gains},
|
||||
@ -609,7 +711,12 @@ void RemoteTCPInputGui::on_startStop_toggled(bool checked)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
m_connectionError = false;
|
||||
if (m_connectionError)
|
||||
{
|
||||
// Clear previous error
|
||||
m_connectionError = false;
|
||||
updateStatus();
|
||||
}
|
||||
RemoteTCPInput::MsgStartStop *message = RemoteTCPInput::MsgStartStop::create(checked);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
@ -789,6 +896,29 @@ void RemoteTCPInputGui::on_sampleBits_currentIndexChanged(int index)
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_squelchEnabled_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_squelchEnabled = checked;
|
||||
m_settingsKeys.append("squelchEnabled");
|
||||
displayEnabled();
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_squelch_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_squelch = value;
|
||||
ui->squelchText->setText(QString::number(m_settings.m_squelch));
|
||||
m_settingsKeys.append("squelch");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_squelchGate_valueChanged(double value)
|
||||
{
|
||||
m_settings.m_squelchGate = value;
|
||||
m_settingsKeys.append("squelchGate");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_dataAddress_editingFinished()
|
||||
{
|
||||
m_settings.m_dataAddress = ui->dataAddress->currentText();
|
||||
@ -810,17 +940,9 @@ void RemoteTCPInputGui::on_dataAddress_currentIndexChanged(int index)
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_dataPort_editingFinished()
|
||||
void RemoteTCPInputGui::on_dataPort_valueChanged(int value)
|
||||
{
|
||||
bool ok;
|
||||
quint16 udpPort = ui->dataPort->text().toInt(&ok);
|
||||
|
||||
if ((!ok) || (udpPort < 1024)) {
|
||||
udpPort = 9998;
|
||||
}
|
||||
|
||||
m_settings.m_dataPort = udpPort;
|
||||
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
|
||||
m_settings.m_dataPort = value;
|
||||
m_settingsKeys.append("dataPort");
|
||||
|
||||
sendSettings();
|
||||
@ -831,6 +953,7 @@ void RemoteTCPInputGui::on_overrideRemoteSettings_toggled(bool checked)
|
||||
m_settings.m_overrideRemoteSettings = checked;
|
||||
m_settingsKeys.append("overrideRemoteSettings");
|
||||
sendSettings();
|
||||
displayEnabled();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_preFill_valueChanged(int value)
|
||||
@ -867,10 +990,14 @@ void RemoteTCPInputGui::updateHardware()
|
||||
|
||||
void RemoteTCPInputGui::updateStatus()
|
||||
{
|
||||
int state = m_deviceUISet->m_deviceAPI->state();
|
||||
|
||||
if (!m_connectionError && (m_lastEngineState != state))
|
||||
if (m_connectionError)
|
||||
{
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
|
||||
}
|
||||
else
|
||||
{
|
||||
int state = m_deviceUISet->m_deviceAPI->state();
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case DeviceAPI::StNotStarted:
|
||||
@ -889,9 +1016,28 @@ void RemoteTCPInputGui::updateStatus()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_lastEngineState = state;
|
||||
}
|
||||
displayEnabled();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::tick()
|
||||
{
|
||||
double magsqAvg, magsqPeak;
|
||||
int nbMagsqSamples;
|
||||
m_sampleSource->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||
double powDbAvg = CalcDb::dbPower(magsqAvg);
|
||||
double powDbPeak = CalcDb::dbPower(magsqPeak);
|
||||
|
||||
ui->channelPowerMeter->levelChanged(
|
||||
(100.0f + powDbAvg) / 100.0f,
|
||||
(100.0f + powDbPeak) / 100.0f,
|
||||
nbMagsqSamples);
|
||||
|
||||
if (m_tickCount % 4 == 0) {
|
||||
ui->channelPower->setText(tr("%1").arg(powDbAvg, 0, 'f', 1));
|
||||
}
|
||||
|
||||
m_tickCount++;
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
@ -899,6 +1045,9 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
if (m_contextMenuType == ContextMenuDeviceSettings)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(this);
|
||||
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2 * sizeof(FixReal));
|
||||
dialog.setReplayLength(m_settings.m_replayLength);
|
||||
dialog.setReplayStep(m_settings.m_replayStep);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
@ -908,6 +1057,11 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
new DialogPositioner(&dialog, false);
|
||||
dialog.exec();
|
||||
|
||||
m_settings.m_replayLength = dialog.getReplayLength();
|
||||
m_settings.m_replayStep = dialog.getReplayStep();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
@ -919,6 +1073,110 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::displayReplayLength()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
if (!replayEnabled) {
|
||||
ui->replayOffset->setMaximum(0);
|
||||
} else {
|
||||
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
|
||||
}
|
||||
ui->replayLabel->setEnabled(replayEnabled);
|
||||
ui->replayOffset->setEnabled(replayEnabled);
|
||||
ui->replayOffsetText->setEnabled(replayEnabled);
|
||||
ui->replaySave->setEnabled(replayEnabled);
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::displayReplayOffset()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
|
||||
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
|
||||
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
|
||||
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::displayReplayStep()
|
||||
{
|
||||
QString step;
|
||||
float intpart;
|
||||
float frac = modf(m_settings.m_replayStep, &intpart);
|
||||
if (frac == 0.0f) {
|
||||
step = QString::number((int)intpart);
|
||||
} else {
|
||||
step = QString::number(m_settings.m_replayStep, 'f', 1);
|
||||
}
|
||||
ui->replayPlus->setText(QString("+%1s").arg(step));
|
||||
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
|
||||
ui->replayMinus->setText(QString("-%1s").arg(step));
|
||||
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replayOffset_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_replayOffset = value / 10.0f;
|
||||
displayReplayOffset();
|
||||
m_settingsKeys.append("replayOffset");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replayNow_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(0);
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replayPlus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replayMinus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replaySave_clicked()
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
RemoteTCPInput::MsgSaveReplay *message = RemoteTCPInput ::MsgSaveReplay::create(fileNames[0]);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_replayLoop_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_replayLoop = checked;
|
||||
m_settingsKeys.append("replayLoop");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_sendMessage_clicked()
|
||||
{
|
||||
QString message = ui->txMessage->text().trimmed();
|
||||
if (!message.isEmpty())
|
||||
{
|
||||
ui->messages->addItem(QString("< %1").arg(message));
|
||||
ui->messages->scrollToBottom();
|
||||
bool broadcast = ui->txAddress->currentText() == "All";
|
||||
QString callsign = MainCore::instance()->getSettings().getStationName();
|
||||
m_sampleSource->getInputMessageQueue()->push(RemoteTCPInput::MsgSendMessage::create(callsign, message, broadcast));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::on_txMessage_returnPressed()
|
||||
{
|
||||
on_sendMessage_clicked();
|
||||
ui->txMessage->selectAll();
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_startStop_toggled);
|
||||
@ -940,10 +1198,21 @@ void RemoteTCPInputGui::makeUIConnections()
|
||||
QObject::connect(ui->channelSampleRate, &ValueDial::changed, this, &RemoteTCPInputGui::on_channelSampleRate_changed);
|
||||
QObject::connect(ui->decimation, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_decimation_toggled);
|
||||
QObject::connect(ui->sampleBits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_sampleBits_currentIndexChanged);
|
||||
QObject::connect(ui->squelchEnabled, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_squelchEnabled_toggled);
|
||||
QObject::connect(ui->squelch, &QDial::valueChanged, this, &RemoteTCPInputGui::on_squelch_valueChanged);
|
||||
QObject::connect(ui->squelchGate, &PeriodDial::valueChanged, this, &RemoteTCPInputGui::on_squelchGate_valueChanged);
|
||||
QObject::connect(ui->dataAddress->lineEdit(), &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataAddress_editingFinished);
|
||||
QObject::connect(ui->dataAddress, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_dataAddress_currentIndexChanged);
|
||||
QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataPort_editingFinished);
|
||||
QObject::connect(ui->dataPort, QOverload<int>::of(&QSpinBox::valueChanged), this, &RemoteTCPInputGui::on_dataPort_valueChanged);
|
||||
QObject::connect(ui->overrideRemoteSettings, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_overrideRemoteSettings_toggled);
|
||||
QObject::connect(ui->preFill, &QDial::valueChanged, this, &RemoteTCPInputGui::on_preFill_valueChanged);
|
||||
QObject::connect(ui->protocol, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_protocol_currentIndexChanged);
|
||||
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &RemoteTCPInputGui::on_replayOffset_valueChanged);
|
||||
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayNow_clicked);
|
||||
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayPlus_clicked);
|
||||
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayMinus_clicked);
|
||||
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replaySave_clicked);
|
||||
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_replayLoop_toggled);
|
||||
QObject::connect(ui->sendMessage, &QToolButton::clicked, this, &RemoteTCPInputGui::on_sendMessage_clicked);
|
||||
QObject::connect(ui->txMessage, &QLineEdit::returnPressed, this, &RemoteTCPInputGui::on_txMessage_returnPressed);
|
||||
}
|
||||
|
@ -108,12 +108,11 @@ private:
|
||||
QList<QString> m_settingsKeys;
|
||||
RemoteTCPInput* m_sampleSource;
|
||||
QTimer m_updateTimer;
|
||||
QTimer m_statusTimer;
|
||||
int m_lastEngineState;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
||||
int m_sampleRate;
|
||||
quint64 m_centerFrequency;
|
||||
uint32_t m_tickCount;
|
||||
|
||||
bool m_doApplySettings;
|
||||
bool m_forceSettings;
|
||||
@ -125,6 +124,11 @@ private:
|
||||
DeviceGains::GainRange m_spyServerGainRange;
|
||||
DeviceGains m_spyServerGains;
|
||||
|
||||
bool m_sdra;
|
||||
bool m_spyServer;
|
||||
bool m_remoteControl;
|
||||
bool m_iqOnly;
|
||||
|
||||
static const DeviceGains::GainRange m_rtlSDR34kGainRange;
|
||||
static const DeviceGains m_rtlSDRe4kGains;
|
||||
static const DeviceGains::GainRange m_rtlSDRR820GainRange;
|
||||
@ -175,9 +179,13 @@ private:
|
||||
void blockApplySettings(bool block);
|
||||
void displaySettings();
|
||||
QString gainText(int stage);
|
||||
void displayEnabled();
|
||||
void displayGains();
|
||||
void displayRemoteSettings();
|
||||
void displayRemoteShift();
|
||||
void displayReplayLength();
|
||||
void displayReplayOffset();
|
||||
void displayReplayStep();
|
||||
void sendSettings();
|
||||
void updateSampleRateAndFrequency();
|
||||
void applyDecimation();
|
||||
@ -206,15 +214,27 @@ private slots:
|
||||
void on_channelSampleRate_changed(quint64 value);
|
||||
void on_decimation_toggled(bool checked);
|
||||
void on_sampleBits_currentIndexChanged(int index);
|
||||
void on_squelchEnabled_toggled(bool checked);
|
||||
void on_squelch_valueChanged(int value);
|
||||
void on_squelchGate_valueChanged(double value);
|
||||
void on_dataAddress_editingFinished();
|
||||
void on_dataAddress_currentIndexChanged(int index);
|
||||
void on_dataPort_editingFinished();
|
||||
void on_dataPort_valueChanged(int value);
|
||||
void on_overrideRemoteSettings_toggled(bool checked);
|
||||
void on_preFill_valueChanged(int value);
|
||||
void on_protocol_currentIndexChanged(int index);
|
||||
void on_replayOffset_valueChanged(int value);
|
||||
void on_replayNow_clicked();
|
||||
void on_replayPlus_clicked();
|
||||
void on_replayMinus_clicked();
|
||||
void on_replaySave_clicked();
|
||||
void on_replayLoop_toggled(bool checked);
|
||||
void on_sendMessage_clicked();
|
||||
void on_txMessage_returnPressed();
|
||||
void updateHardware();
|
||||
void updateStatus();
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
void tick();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_REMOTETCPINPUTGUI_H
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>360</height>
|
||||
<height>586</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,13 +19,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>360</height>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>491</width>
|
||||
<height>360</height>
|
||||
<width>533</width>
|
||||
<height>610</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -863,6 +863,184 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="signalLevelLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerMeterUnits">
|
||||
<property name="text">
|
||||
<string>dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="squelchLayout">
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="squelchEnabled">
|
||||
<property name="toolTip">
|
||||
<string>Check to enable IQ squelch</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>SQ</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_12">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="squelch">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IQ squelch power level in dB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-150</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="squelchText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-150</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="squelchUnits">
|
||||
<property name="text">
|
||||
<string>dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_13">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PeriodDial" name="squelchGate" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>IQ squelch gate time</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_14">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPower">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Channel power</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerUnits">
|
||||
<property name="text">
|
||||
<string> dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
@ -914,33 +1092,18 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="dataPort">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<widget class="QSpinBox" name="dataPort">
|
||||
<property name="toolTip">
|
||||
<string>Remote data port (rtl_tcp defaults to 1234)</string>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string>00000</string>
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<property name="value">
|
||||
<number>1234</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -961,7 +1124,7 @@ Use to ensure full dynamic range of 8-bit data is used.</string>
|
||||
<widget class="QComboBox" name="protocol">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>75</width>
|
||||
<width>92</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1162,6 +1325,182 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="txMessagesLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="sendMessage">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to send message</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TX</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="txAddress">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Who to send message to</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Host</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>All</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="txMessage">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Message to transmit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="messagesLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="messages">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Messages</string>
|
||||
</property>
|
||||
<property name="movement">
|
||||
<enum>QListView::Static</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time Delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="replayOffset">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayOffsetText">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayNow">
|
||||
<property name="toolTip">
|
||||
<string>Set time delay to 0 seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayPlus">
|
||||
<property name="toolTip">
|
||||
<string>Add displayed number of seconds to time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayMinus">
|
||||
<property name="toolTip">
|
||||
<string>Remove displayed number of seconds from time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="replayLoop">
|
||||
<property name="toolTip">
|
||||
<string>Repeatedly replay data in replay buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replaySave">
|
||||
<property name="toolTip">
|
||||
<string>Save replay buffer to a file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
@ -1214,6 +1553,18 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
|
||||
<header>gui/valuedial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LevelMeterSignalDB</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/levelmeter.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PeriodDial</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/perioddial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>startStop</tabstop>
|
||||
@ -1232,7 +1583,8 @@ This should typically be empty. If full, your CPU cannot keep up and data will b
|
||||
<tabstop>channelGain</tabstop>
|
||||
<tabstop>decimation</tabstop>
|
||||
<tabstop>sampleBits</tabstop>
|
||||
<tabstop>dataPort</tabstop>
|
||||
<tabstop>dataAddress</tabstop>
|
||||
<tabstop>protocol</tabstop>
|
||||
<tabstop>overrideRemoteSettings</tabstop>
|
||||
<tabstop>preFill</tabstop>
|
||||
</tabstops>
|
||||
|
@ -53,6 +53,13 @@ void RemoteTCPInputSettings::resetToDefaults()
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIDeviceIndex = 0;
|
||||
m_replayOffset = 0.0f;
|
||||
m_replayLength = 20.0f;
|
||||
m_replayStep = 5.0f;
|
||||
m_replayLoop = false;
|
||||
m_squelchEnabled = false;
|
||||
m_squelch = -100.0f;
|
||||
m_squelchGate = 0.001f;
|
||||
}
|
||||
|
||||
QByteArray RemoteTCPInputSettings::serialize() const
|
||||
@ -83,11 +90,19 @@ QByteArray RemoteTCPInputSettings::serialize() const
|
||||
s.writeU32(23, m_reverseAPIDeviceIndex);
|
||||
s.writeList(24, m_addressList);
|
||||
s.writeString(25, m_protocol);
|
||||
s.writeFloat(26, m_replayOffset);
|
||||
s.writeFloat(27, m_replayLength);
|
||||
s.writeFloat(28, m_replayStep);
|
||||
s.writeBool(29, m_replayLoop);
|
||||
|
||||
for (int i = 0; i < m_maxGains; i++) {
|
||||
s.writeS32(30+i, m_gain[i]);
|
||||
}
|
||||
|
||||
s.writeBool(40, m_squelchEnabled);
|
||||
s.writeFloat(41, m_squelch);
|
||||
s.writeFloat(42, m_squelchGate);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
@ -140,10 +155,19 @@ bool RemoteTCPInputSettings::deserialize(const QByteArray& data)
|
||||
d.readList(24, &m_addressList);
|
||||
d.readString(25, &m_protocol, "SDRangel");
|
||||
|
||||
d.readFloat(26, &m_replayOffset, 0.0f);
|
||||
d.readFloat(27, &m_replayLength, 20.0f);
|
||||
d.readFloat(28, &m_replayStep, 5.0f);
|
||||
d.readBool(29, &m_replayLoop, false);
|
||||
|
||||
for (int i = 0; i < m_maxGains; i++) {
|
||||
d.readS32(30+i, &m_gain[i], 0);
|
||||
}
|
||||
|
||||
d.readBool(40, &m_squelchEnabled, false);
|
||||
d.readFloat(41, &m_squelch, -100.0f);
|
||||
d.readFloat(42, &m_squelchGate, 0.001f);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -212,7 +236,7 @@ void RemoteTCPInputSettings::applySettings(const QStringList& settingsKeys, cons
|
||||
if (settingsKeys.contains("preFill")) {
|
||||
m_preFill = settings.m_preFill;
|
||||
}
|
||||
if (settingsKeys.contains("_useReverseAPI")) {
|
||||
if (settingsKeys.contains("useReverseAPI")) {
|
||||
m_useReverseAPI = settings.m_useReverseAPI;
|
||||
}
|
||||
if (settingsKeys.contains("reverseAPIAddress")) {
|
||||
@ -230,6 +254,27 @@ void RemoteTCPInputSettings::applySettings(const QStringList& settingsKeys, cons
|
||||
if (settingsKeys.contains("protocol")) {
|
||||
m_protocol = settings.m_protocol;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset")) {
|
||||
m_replayOffset = settings.m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength")) {
|
||||
m_replayLength = settings.m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep")) {
|
||||
m_replayStep = settings.m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop")) {
|
||||
m_replayLoop = settings.m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("squelchEnabled")) {
|
||||
m_squelchEnabled = settings.m_squelchEnabled;
|
||||
}
|
||||
if (settingsKeys.contains("squelch")) {
|
||||
m_squelch = settings.m_squelch;
|
||||
}
|
||||
if (settingsKeys.contains("squelchGate")) {
|
||||
m_squelchGate = settings.m_squelchGate;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_maxGains; i++)
|
||||
{
|
||||
@ -318,6 +363,27 @@ QString RemoteTCPInputSettings::getDebugString(const QStringList& settingsKeys,
|
||||
if (settingsKeys.contains("protocol") || force) {
|
||||
ostr << " m_protocol: " << m_protocol.toStdString();
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset") || force) {
|
||||
ostr << " m_replayOffset: " << m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength") || force) {
|
||||
ostr << " m_replayLength: " << m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep") || force) {
|
||||
ostr << " m_replayStep: " << m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
ostr << " m_replayLoop: " << m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("squelchEnabled") || force) {
|
||||
ostr << " m_squelchEnabled: " << m_squelchEnabled;
|
||||
}
|
||||
if (settingsKeys.contains("squelch") || force) {
|
||||
ostr << " m_squelch: " << m_squelch;
|
||||
}
|
||||
if (settingsKeys.contains("squelchGate") || force) {
|
||||
ostr << " m_squelchGate: " << m_squelchGate;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_maxGains; i++)
|
||||
{
|
||||
|
@ -54,6 +54,13 @@ struct RemoteTCPInputSettings
|
||||
uint16_t m_reverseAPIDeviceIndex;
|
||||
QStringList m_addressList; // List of dataAddresses that have been used in the past
|
||||
QString m_protocol; // "SDRangel" or "Spy Server"
|
||||
float m_replayOffset; //!< Replay offset in seconds
|
||||
float m_replayLength; //!< Replay buffer size in seconds
|
||||
float m_replayStep; //!< Replay forward/back step size in seconds
|
||||
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
|
||||
bool m_squelchEnabled;
|
||||
float m_squelch;
|
||||
float m_squelchGate;
|
||||
|
||||
RemoteTCPInputSettings();
|
||||
void resetToDefaults();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,12 @@
|
||||
#include <QRecursiveMutex>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "remotetcpinputsettings.h"
|
||||
#include "../../channelrx/remotetcpsink/remotetcpprotocol.h"
|
||||
#include "spyserver.h"
|
||||
@ -35,6 +40,31 @@ class SampleSinkFifo;
|
||||
class MessageQueue;
|
||||
class DeviceAPI;
|
||||
|
||||
class FIFO {
|
||||
public:
|
||||
|
||||
FIFO(qsizetype elements = 10);
|
||||
|
||||
qsizetype write(quint8 *data, qsizetype elements);
|
||||
qsizetype read(quint8 *data, qsizetype elements);
|
||||
qsizetype readPtr(quint8 **data, qsizetype elements);
|
||||
void read(qsizetype elements);
|
||||
void resize(qsizetype elements); // Sets capacity
|
||||
void clear();
|
||||
qsizetype fill() const { return m_fill; } // Number of elements in use
|
||||
bool empty() const { return m_fill == 0; }
|
||||
bool full() const { return m_fill == m_data.size(); }
|
||||
|
||||
private:
|
||||
|
||||
qsizetype m_readPtr;
|
||||
qsizetype m_writePtr;
|
||||
qsizetype m_fill;
|
||||
|
||||
QByteArray m_data;
|
||||
|
||||
};
|
||||
|
||||
class RemoteTCPInputTCPHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -71,22 +101,28 @@ public:
|
||||
public:
|
||||
RemoteTCPProtocol::Device getDevice() const { return m_device; }
|
||||
QString getProtocol() const { return m_protocol; }
|
||||
bool getIQOnly() const { return m_iqOnly; }
|
||||
bool getRemoteControl() const { return m_remoteControl; }
|
||||
int getMaxGain() const { return m_maxGain; }
|
||||
|
||||
static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain = 0)
|
||||
static MsgReportRemoteDevice* create(RemoteTCPProtocol::Device device, const QString& protocol, bool iqOnly, bool remoteControl, int maxGain = 0)
|
||||
{
|
||||
return new MsgReportRemoteDevice(device, protocol, maxGain);
|
||||
return new MsgReportRemoteDevice(device, protocol, iqOnly, remoteControl, maxGain);
|
||||
}
|
||||
|
||||
protected:
|
||||
RemoteTCPProtocol::Device m_device;
|
||||
QString m_protocol;
|
||||
bool m_iqOnly;
|
||||
bool m_remoteControl;
|
||||
int m_maxGain;
|
||||
|
||||
MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol, int maxGain) :
|
||||
MsgReportRemoteDevice(RemoteTCPProtocol::Device device, const QString& protocol, bool iqOnly, bool remoteControl, int maxGain) :
|
||||
Message(),
|
||||
m_device(device),
|
||||
m_protocol(protocol),
|
||||
m_iqOnly(iqOnly),
|
||||
m_remoteControl(remoteControl),
|
||||
m_maxGain(maxGain)
|
||||
{ }
|
||||
};
|
||||
@ -111,7 +147,7 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI);
|
||||
RemoteTCPInputTCPHandler(SampleSinkFifo* sampleFifo, DeviceAPI *deviceAPI, ReplayBuffer<FixReal> *replayBuffer);
|
||||
~RemoteTCPInputTCPHandler();
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
void setMessageQueueToInput(MessageQueue *queue) { m_messageQueueToInput = queue; }
|
||||
@ -120,6 +156,29 @@ public:
|
||||
void start();
|
||||
void stop();
|
||||
int getBufferGauge() const { return 0; }
|
||||
void processCommands();
|
||||
|
||||
FLAC__StreamDecoderReadStatus flacRead(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes);
|
||||
FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
|
||||
void flacError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status);
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||
{
|
||||
if (m_magsqCount > 0)
|
||||
{
|
||||
m_magsq = m_magsqSum / m_magsqCount;
|
||||
m_magSqLevelStore.m_magsq = m_magsq;
|
||||
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
|
||||
}
|
||||
|
||||
avg = m_magSqLevelStore.m_magsq;
|
||||
peak = m_magSqLevelStore.m_magsqPeak;
|
||||
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
|
||||
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void dataReadyRead();
|
||||
@ -129,11 +188,22 @@ public slots:
|
||||
|
||||
private:
|
||||
|
||||
struct MagSqLevelsStore
|
||||
{
|
||||
MagSqLevelsStore() :
|
||||
m_magsq(1e-12),
|
||||
m_magsqPeak(1e-12)
|
||||
{}
|
||||
double m_magsq;
|
||||
double m_magsqPeak;
|
||||
};
|
||||
|
||||
DeviceAPI *m_deviceAPI;
|
||||
bool m_running;
|
||||
QTcpSocket *m_dataSocket;
|
||||
char *m_tcpBuf;
|
||||
SampleSinkFifo *m_sampleFifo;
|
||||
ReplayBuffer<FixReal> *m_replayBuffer;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
MessageQueue *m_messageQueueToInput;
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
@ -148,6 +218,8 @@ private:
|
||||
SpyServerProtocol::Header m_spyServerHeader;
|
||||
enum {HEADER, DATA} m_state; //!< FSM for reading Spy Server packets
|
||||
|
||||
RemoteTCPProtocol::Command m_command;
|
||||
quint32 m_commandLength;
|
||||
|
||||
int32_t *m_converterBuffer;
|
||||
uint32_t m_converterBufferNbSamples;
|
||||
@ -155,13 +227,38 @@ private:
|
||||
QRecursiveMutex m_mutex;
|
||||
RemoteTCPInputSettings m_settings;
|
||||
|
||||
void applyTCPLink(const QString& address, quint16 port);
|
||||
bool m_remoteControl;
|
||||
bool m_iqOnly;
|
||||
QByteArray m_compressedData;
|
||||
|
||||
// FLAC decompression
|
||||
qint64 m_compressedFrames;
|
||||
qint64 m_uncompressedFrames;
|
||||
FIFO m_uncompressedData;
|
||||
FLAC__StreamDecoder *m_decoder;
|
||||
int m_remainingSamples;
|
||||
|
||||
// Zlib decompression
|
||||
z_stream m_zStream;
|
||||
QByteArray m_zOutBuf;
|
||||
static const int m_zBufSize = 32768+128; //
|
||||
|
||||
bool m_blacklisted;
|
||||
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
|
||||
bool handleMessage(const Message& message);
|
||||
void convert(int nbSamples);
|
||||
void connectToHost(const QString& address, quint16 port);
|
||||
void disconnectFromHost();
|
||||
//void disconnectFromHost();
|
||||
void cleanup();
|
||||
void clearBuffer();
|
||||
void sendCommand(RemoteTCPProtocol::Command cmd, quint32 value);
|
||||
void sendCommandFloat(RemoteTCPProtocol::Command cmd, float value);
|
||||
void setSampleRate(int sampleRate);
|
||||
void setCenterFrequency(quint64 frequency);
|
||||
void setTunerAGC(bool agc);
|
||||
@ -180,6 +277,10 @@ private:
|
||||
void setChannelFreqOffset(int offset);
|
||||
void setChannelGain(int gain);
|
||||
void setSampleBitDepth(int sampleBits);
|
||||
void setSquelchEnabled(bool enabled);
|
||||
void setSquelch(float squelch);
|
||||
void setSquelchGate(float squelchGate);
|
||||
void sendMessage(const QString& callsign, const QString& text, bool broadcast);
|
||||
void applySettings(const RemoteTCPInputSettings& settings, const QList<QString>& settingsKeys, bool force = false);
|
||||
void processMetaData();
|
||||
void spyServerConnect();
|
||||
@ -190,6 +291,11 @@ private:
|
||||
void processSpyServerDevice(const SpyServerProtocol::Device* ssDevice);
|
||||
void processSpyServerState(const SpyServerProtocol::State* ssState, bool initial);
|
||||
void processSpyServerData(int requiredBytes, bool clear);
|
||||
void processDecompressedData(int requiredSamples);
|
||||
void processUncompressedData(const char *inBuf, int nbSamples);
|
||||
void processDecompressedZlibData(const char *inBuf, int nbSamples);
|
||||
void calcPower(const Sample *iq, int nbSamples);
|
||||
void sendSettings(const RemoteTCPInputSettings& settings, const QStringList& settingsKeys);
|
||||
|
||||
private slots:
|
||||
void started();
|
||||
|
@ -266,6 +266,7 @@ set(sdrbase_SOURCES
|
||||
util/rtpsink.cpp
|
||||
util/syncmessenger.cpp
|
||||
util/samplesourceserializer.cpp
|
||||
util/sdrangelserverlist.cpp
|
||||
util/simpleserializer.cpp
|
||||
util/serialutil.cpp
|
||||
util/solardynamicsobservatory.cpp
|
||||
@ -526,6 +527,7 @@ set(sdrbase_HEADERS
|
||||
util/rtty.h
|
||||
util/syncmessenger.h
|
||||
util/samplesourceserializer.h
|
||||
util/sdrangelserverlist.h
|
||||
util/simpleserializer.h
|
||||
util/serialutil.h
|
||||
util/solardynamicsobservatory.h
|
||||
|
183
sdrbase/util/sdrangelserverlist.cpp
Normal file
183
sdrbase/util/sdrangelserverlist.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "sdrangelserverlist.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QRegularExpression>
|
||||
|
||||
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<SDRangelServer> 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("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);
|
||||
}
|
81
sdrbase/util/sdrangelserverlist.h
Normal file
81
sdrbase/util/sdrangelserverlist.h
Normal file
@ -0,0 +1,81 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SDRANGELSERVERLIST_H
|
||||
#define INCLUDE_SDRANGELSERVERLIST_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
|
||||
#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;
|
||||
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<SDRangelServer>& 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 */
|
@ -61,3 +61,12 @@ RemoteTCPInputReport:
|
||||
properties:
|
||||
sampleRate:
|
||||
type: integer
|
||||
latitude:
|
||||
type: number
|
||||
format: float
|
||||
longitude:
|
||||
type: number
|
||||
format: float
|
||||
altitude:
|
||||
type: number
|
||||
format: float
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user