mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-21 23:55:13 -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;
|
||||
@ -162,7 +288,9 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_basebandSampleRate(0),
|
||||
m_deviceCenterFrequency(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,10 +2720,14 @@ 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
|
||||
MainCore *mainCore = MainCore::instance();
|
||||
@ -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();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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(url));
|
||||
deviceSettings->setServerAddress(new QString(m_remoteDeviceAddress));
|
||||
QString errorMessage;
|
||||
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
|
||||
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
// Open a RemoteTCPInput device to use for SpyServer
|
||||
void MapGUI::openSpyServer(const QString& url)
|
||||
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"};
|
||||
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(address[0]));
|
||||
deviceSettings->setDataPort(address[1].toInt());
|
||||
deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
|
||||
deviceSettings->setDataPort(m_remoteDevicePort);
|
||||
deviceSettings->setProtocol(new QString("Spy Server"));
|
||||
deviceSettings->setOverrideRemoteSettings(false);
|
||||
QString errorMessage;
|
||||
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, 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)
|
||||
{
|
||||
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)
|
||||
@ -866,11 +989,15 @@ void RemoteTCPInputGui::updateHardware()
|
||||
}
|
||||
|
||||
void RemoteTCPInputGui::updateStatus()
|
||||
{
|
||||
if (m_connectionError)
|
||||
{
|
||||
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
|
||||
}
|
||||
else
|
||||
{
|
||||
int state = m_deviceUISet->m_deviceAPI->state();
|
||||
|
||||
if (!m_connectionError && (m_lastEngineState != 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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