1
0
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:
srcejon 2024-09-22 10:44:30 +01:00 committed by f4exb
parent 246735918b
commit 8b8867d343
41 changed files with 7182 additions and 801 deletions

View File

@ -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 \

View 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
View File

@ -45,6 +45,7 @@ Build-Depends: debhelper (>= 9),
flex,
ffmpeg,
libfaad-dev,
libflac-dev,
libavcodec-dev,
libavformat-dev,
libopus-dev,

View File

@ -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)

View File

@ -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})

View File

@ -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.

View File

@ -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_ */

View File

@ -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);
}

View File

@ -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);

View File

@ -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 {

View File

@ -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();

View File

@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2022-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
@ -15,19 +15,25 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QLocale>
#include <QHostAddress>
#include <QNetworkInterface>
#include <QTableWidgetItem>
#include "device/deviceuiset.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
#include "gui/perioddial.h"
#include "dsp/dspcommands.h"
#include "util/db.h"
#include "maincore.h"
#include "remotetcpsinkgui.h"
#include "remotetcpsink.h"
#include "ui_remotetcpsinkgui.h"
#include "remotetcpsinksettingsdialog.h"
const QString RemoteTCPSinkGUI::m_dateTimeFormat = "yyyy.MM.dd hh:mm:ss";
RemoteTCPSinkGUI* RemoteTCPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx)
{
@ -102,11 +108,74 @@ QString RemoteTCPSinkGUI::displayScaledF(float value, char type, int precision,
}
}
void RemoteTCPSinkGUI::resizeTable()
{
QDateTime dateTime = QDateTime::currentDateTime();
QString dateTimeString = dateTime.toString(m_dateTimeFormat);
int row = ui->connections->rowCount();
ui->connections->setRowCount(row + 1);
ui->connections->setItem(row, CONNECTIONS_COL_ADDRESS, new QTableWidgetItem("255.255.255.255"));
ui->connections->setItem(row, CONNECTIONS_COL_PORT, new QTableWidgetItem("65535"));
ui->connections->setItem(row, CONNECTIONS_COL_CONNECTED, new QTableWidgetItem(dateTimeString));
ui->connections->setItem(row, CONNECTIONS_COL_DISCONNECTED, new QTableWidgetItem(dateTimeString));
ui->connections->setItem(row, CONNECTIONS_COL_TIME, new QTableWidgetItem("1000 d"));
ui->connections->resizeColumnsToContents();
ui->connections->removeRow(row);
}
void RemoteTCPSinkGUI::addConnection(const QHostAddress& address, int port)
{
QDateTime dateTime = QDateTime::currentDateTime();
int row = ui->connections->rowCount();
ui->connections->setRowCount(row + 1);
ui->connections->setItem(row, CONNECTIONS_COL_ADDRESS, new QTableWidgetItem(address.toString()));
ui->connections->setItem(row, CONNECTIONS_COL_PORT, new QTableWidgetItem(QString::number(port)));
ui->connections->setItem(row, CONNECTIONS_COL_CONNECTED, new QTableWidgetItem(dateTime.toString(m_dateTimeFormat)));
ui->connections->setItem(row, CONNECTIONS_COL_DISCONNECTED, new QTableWidgetItem(""));
ui->connections->setItem(row, CONNECTIONS_COL_TIME, new QTableWidgetItem(""));
}
void RemoteTCPSinkGUI::removeConnection(const QHostAddress& address, int port)
{
QString addressString = address.toString();
QString portString = QString::number(port);
for (int row = 0; row < ui->connections->rowCount(); row++)
{
if ((ui->connections->item(row, CONNECTIONS_COL_ADDRESS)->text() == addressString)
&& (ui->connections->item(row, CONNECTIONS_COL_PORT)->text() == portString)
&& (ui->connections->item(row, CONNECTIONS_COL_DISCONNECTED)->text().isEmpty()))
{
QDateTime connected = QDateTime::fromString(ui->connections->item(row, CONNECTIONS_COL_CONNECTED)->text(), m_dateTimeFormat);
QDateTime disconnected = QDateTime::currentDateTime();
QString dateTimeString = disconnected.toString(m_dateTimeFormat);
QString time;
int secs = connected.secsTo(disconnected);
if (secs < 60) {
time = QString("%1 s").arg(secs);
} else if (secs < 60 * 60) {
time = QString("%1 m").arg(secs / 60);
} else if (secs < 60 * 60 * 24) {
time = QString("%1 h").arg(secs / 60 / 60);
} else {
time = QString("%1 d").arg(secs / 60 / 60 / 24);
}
ui->connections->item(row, CONNECTIONS_COL_DISCONNECTED)->setText(dateTimeString);
ui->connections->item(row, CONNECTIONS_COL_TIME)->setText(time);
break;
}
}
}
bool RemoteTCPSinkGUI::handleMessage(const Message& message)
{
if (RemoteTCPSink::MsgConfigureRemoteTCPSink::match(message))
{
const RemoteTCPSink::MsgConfigureRemoteTCPSink& cfg = (RemoteTCPSink::MsgConfigureRemoteTCPSink&) message;
if ((cfg.getSettings().m_channelSampleRate != m_settings.m_channelSampleRate)
|| (cfg.getSettings().m_sampleBits != m_settings.m_sampleBits)) {
m_bwAvg.reset();
@ -126,14 +195,52 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
else if (RemoteTCPSink::MsgReportConnection::match(message))
{
const RemoteTCPSink::MsgReportConnection& report = (RemoteTCPSink::MsgReportConnection&) message;
ui->clients->setText(QString("%1").arg(report.getClients()));
ui->clients->setText(QString("%1/%2").arg(report.getClients()).arg(m_settings.m_maxClients));
QString ip = QString("%1:%2").arg(report.getAddress().toString()).arg(report.getPort());
if (ui->txAddress->findText(ip) == -1) {
ui->txAddress->addItem(ip);
}
addConnection(report.getAddress(), report.getPort());
return true;
}
else if (RemoteTCPSink::MsgReportDisconnect::match(message))
{
const RemoteTCPSink::MsgReportDisconnect& report = (RemoteTCPSink::MsgReportDisconnect&) message;
ui->clients->setText(QString("%1/%2").arg(report.getClients()).arg(m_settings.m_maxClients));
QString ip = QString("%1:%2").arg(report.getAddress().toString()).arg(report.getPort());
int idx = ui->txAddress->findText(ip);
if (idx != -1) {
ui->txAddress->removeItem(idx);
}
removeConnection(report.getAddress(), report.getPort());
return true;
}
else if (RemoteTCPSink::MsgReportBW::match(message))
{
const RemoteTCPSink::MsgReportBW& report = (RemoteTCPSink::MsgReportBW&) message;
m_bwAvg(report.getBW());
ui->bw->setText(QString("%1bps").arg(displayScaledF(m_bwAvg.instantAverage(), 'f', 3, true)));
m_networkBWAvg(report.getNetworkBW());
QString text = QString("%1bps").arg(displayScaledF(m_bwAvg.instantAverage(), 'f', 1, true));
if (!m_settings.m_iqOnly && (report.getBytesUncompressed() > 0))
{
float compressionSaving = 1.0f - (report.getBytesCompressed() / (float) report.getBytesUncompressed());
m_compressionAvg(compressionSaving);
QString compressionText = QString(" %1%").arg((int) std::round(m_compressionAvg.instantAverage() * 100.0f));
text.append(compressionText);
}
QString networkBWText = QString(" %1bps").arg(displayScaledF(m_networkBWAvg.instantAverage(), 'f', 1, true));
text.append(networkBWText);
ui->bw->setText(text);
return true;
}
else if (DSPSignalNotification::match(message))
@ -150,6 +257,25 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
return true;
}
else if (RemoteTCPSink::MsgSendMessage::match(message))
{
RemoteTCPSink::MsgSendMessage& msg = (RemoteTCPSink::MsgSendMessage&) message;
QString address = QString("%1:%2").arg(msg.getAddress().toString()).arg(msg.getPort());
QString callsign = msg.getCallsign();
QString text = msg.getText();
bool broadcast = msg.getBroadcast();
// Display received message in GUI
ui->messages->addItem(QString("%1/%2> %3").arg(address).arg(callsign).arg(text));
ui->messages->scrollToBottom();
// Forward to other clients
if (broadcast) {
m_remoteSink->getInputMessageQueue()->push(RemoteTCPSink::MsgSendMessage::create(msg.getAddress(), msg.getPort(), callsign, text, broadcast));
}
return true;
}
else
{
return false;
@ -157,12 +283,14 @@ bool RemoteTCPSinkGUI::handleMessage(const Message& message)
}
RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::RemoteTCPSinkGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_basebandSampleRate(0),
m_deviceCenterFrequency(0)
ChannelGUI(parent),
ui(new Ui::RemoteTCPSinkGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_basebandSampleRate(0),
m_deviceCenterFrequency(0),
m_tickCount(0),
m_squelchOpen(false)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/remotetcpsink/readme.md";
@ -177,6 +305,8 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
m_remoteSink->setMessageQueueToGUI(getInputMessageQueue());
m_basebandSampleRate = m_remoteSink->getBasebandSampleRate();
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(m_settings.m_rgbColor);
m_channelMarker.setCenterFrequency(0);
@ -189,12 +319,17 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
m_deviceUISet->addChannelMarker(&m_channelMarker);
ui->txAddress->clear();
ui->txAddress->addItem("All");
ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->channelSampleRate->setValueRange(8, 0, 99999999);
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
#ifndef __EMSCRIPTEN__
// Add all IP addresses
for (const QHostAddress& address: QNetworkInterface::allAddresses())
{
@ -202,11 +337,14 @@ RemoteTCPSinkGUI::RemoteTCPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe
ui->dataAddress->addItem(address.toString());
}
}
#endif
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
resizeTable();
displaySettings();
makeUIConnections();
applyAllSettings();
@ -269,12 +407,39 @@ void RemoteTCPSinkGUI::displaySettings()
ui->dataAddress->addItem(m_settings.m_dataAddress);
}
ui->dataAddress->setCurrentText(m_settings.m_dataAddress);
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
ui->dataPort->setValue(m_settings.m_dataPort);
ui->protocol->setCurrentIndex((int)m_settings.m_protocol);
ui->remoteControl->setChecked(m_settings.m_remoteControl);
ui->squelchEnabled->setChecked(m_settings.m_squelchEnabled);
displayIQOnly();
displaySquelch();
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
}
void RemoteTCPSinkGUI::displayIQOnly()
{
ui->messagesLayout->setEnabled(!m_settings.m_iqOnly);
ui->sendMessage->setEnabled(!m_settings.m_iqOnly);
ui->txAddress->setEnabled(!m_settings.m_iqOnly);
ui->txMessage->setEnabled(!m_settings.m_iqOnly);
ui->messagesContainer->setVisible(!m_settings.m_iqOnly);
}
void RemoteTCPSinkGUI::displaySquelch()
{
ui->squelch->setValue(m_settings.m_squelch);
ui->squelchText->setText(QString::number(m_settings.m_squelch));
ui->squelch->setEnabled(m_settings.m_squelchEnabled);
ui->squelchText->setEnabled(m_settings.m_squelchEnabled);
ui->squelchUnits->setEnabled(m_settings.m_squelchEnabled);
ui->squelchGate->setValue(m_settings.m_squelchGate);
ui->squelchGate->setEnabled(m_settings.m_squelchEnabled);
ui->audioMute->setEnabled(m_settings.m_squelchEnabled);
}
void RemoteTCPSinkGUI::displayRateAndShift()
{
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
@ -430,20 +595,9 @@ void RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged(int index)
applySetting("dataAddress");
}
void RemoteTCPSinkGUI::on_dataPort_editingFinished()
void RemoteTCPSinkGUI::on_dataPort_valueChanged(int value)
{
bool dataOk;
int dataPort = ui->dataPort->text().toInt(&dataOk);
if((!dataOk) || (dataPort < 1024) || (dataPort > 65535))
{
return;
}
else
{
m_settings.m_dataPort = dataPort;
}
m_settings.m_dataPort = value;
applySetting("dataPort");
}
@ -453,6 +607,104 @@ void RemoteTCPSinkGUI::on_protocol_currentIndexChanged(int index)
applySetting("protocol");
}
void RemoteTCPSinkGUI::on_remoteControl_toggled(bool checked)
{
m_settings.m_remoteControl = checked;
applySetting("remoteControl");
}
void RemoteTCPSinkGUI::on_squelchEnabled_toggled(bool checked)
{
m_settings.m_squelchEnabled = checked;
applySetting("squelchEnabled");
displaySquelch();
}
void RemoteTCPSinkGUI::on_squelch_valueChanged(int value)
{
m_settings.m_squelch = value;
ui->squelchText->setText(QString::number(m_settings.m_squelch));
applySetting("squelch");
}
void RemoteTCPSinkGUI::on_squelchGate_valueChanged(double value)
{
m_settings.m_squelchGate = value;
applySetting("squelchGate");
}
void RemoteTCPSinkGUI::on_displaySettings_clicked()
{
RemoteTCPSinkSettingsDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
applySettings(dialog.getSettingsKeys());
displayIQOnly();
}
}
void RemoteTCPSinkGUI::on_sendMessage_clicked()
{
QString message = ui->txMessage->text().trimmed();
if (!message.isEmpty())
{
ui->messages->addItem(QString("< %1").arg(message));
ui->messages->scrollToBottom();
bool broadcast = ui->txAddress->currentText() == "All";
QHostAddress address;
quint16 port = 0;
if (!broadcast)
{
QStringList parts = ui->txAddress->currentText().split(':');
address = QHostAddress(parts[0]);
port = parts[1].toInt();
}
QString callsign = MainCore::instance()->getSettings().getStationName();
m_remoteSink->getInputMessageQueue()->push(RemoteTCPSink::MsgSendMessage::create(address, port, callsign, message, broadcast));
}
}
void RemoteTCPSinkGUI::on_txMessage_returnPressed()
{
on_sendMessage_clicked();
ui->txMessage->selectAll();
}
void RemoteTCPSinkGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_remoteSink->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(tr("%1").arg(powDbAvg, 0, 'f', 1));
}
bool squelchOpen = m_remoteSink->getSquelchOpen() || !m_settings.m_squelchEnabled;
if (squelchOpen != m_squelchOpen)
{
/*if (squelchOpen) {
ui->audioMute->setStyleSheet("QToolButton { background-color : green; }");
} else {
ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}*/
ui->audioMute->setChecked(!squelchOpen);
m_squelchOpen = squelchOpen;
}
m_tickCount++;
}
void RemoteTCPSinkGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &RemoteTCPSinkGUI::on_deltaFrequency_changed);
@ -461,8 +713,15 @@ void RemoteTCPSinkGUI::makeUIConnections()
QObject::connect(ui->sampleBits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_sampleBits_currentIndexChanged);
QObject::connect(ui->dataAddress->lineEdit(), &QLineEdit::editingFinished, this, &RemoteTCPSinkGUI::on_dataAddress_editingFinished);
QObject::connect(ui->dataAddress, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_dataAddress_currentIndexChanged);
QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPSinkGUI::on_dataPort_editingFinished);
QObject::connect(ui->dataPort, QOverload<int>::of(&QSpinBox::valueChanged), this, &RemoteTCPSinkGUI::on_dataPort_valueChanged);
QObject::connect(ui->protocol, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPSinkGUI::on_protocol_currentIndexChanged);
QObject::connect(ui->remoteControl, &ButtonSwitch::toggled, this, &RemoteTCPSinkGUI::on_remoteControl_toggled);
QObject::connect(ui->squelchEnabled, &ButtonSwitch::toggled, this, &RemoteTCPSinkGUI::on_squelchEnabled_toggled);
QObject::connect(ui->squelch, &QDial::valueChanged, this, &RemoteTCPSinkGUI::on_squelch_valueChanged);
QObject::connect(ui->squelchGate, &PeriodDial::valueChanged, this, &RemoteTCPSinkGUI::on_squelchGate_valueChanged);
QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &RemoteTCPSinkGUI::on_displaySettings_clicked);
QObject::connect(ui->sendMessage, &QToolButton::clicked, this, &RemoteTCPSinkGUI::on_sendMessage_clicked);
QObject::connect(ui->txMessage, &QLineEdit::returnPressed, this, &RemoteTCPSinkGUI::on_txMessage_returnPressed);
}
void RemoteTCPSinkGUI::updateAbsoluteCenterFrequency()

View File

@ -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_ */

View File

@ -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"/>

View File

@ -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;
}

View File

@ -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).

View 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();
}

View File

@ -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

View 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

View File

@ -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);

View 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();
}

View 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_

View File

@ -295,6 +295,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
connect(&m_kiwiSDRList, &KiwiSDRList::dataUpdated, this, &MapGUI::kiwiSDRUpdated);
connect(&m_spyServerList, &SpyServerList::dataUpdated, this, &MapGUI::spyServerUpdated);
connect(&m_sdrangelServerList, &SDRangelServerList::dataUpdated, this, &MapGUI::sdrangelServerUpdated);
#ifdef QT_WEBENGINE_FOUND
QWebEngineSettings *settings = ui->web->settings();
@ -309,6 +310,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
connect(profile, &QWebEngineProfile::downloadRequested, this, &MapGUI::downloadRequested);
#endif
qDebug() << "Get station position";
// Get station position
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
@ -320,6 +322,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
m_polygonMapFilter.setPosition(stationPosition);
m_polylineMapFilter.setPosition(stationPosition);
qDebug() << "Centre map";
// Centre map at My Position
QQuickItem *item = ui->map->rootObject();
QObject *object = item->findChild<QObject*>("map");
@ -331,6 +334,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
object->setProperty("center", QVariant::fromValue(coords));
}
qDebug() << "Creating antenna";
// Create antenna at My Position
m_antennaMapItem.setName(new QString("Station"));
m_antennaMapItem.setLatitude(stationLatitude);
@ -358,7 +362,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
setBeacons(beacons);
}
addIBPBeacons();
addNAT();
addRadioTimeTransmitters();
addRadar();
addIonosonde();
@ -371,6 +375,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
addVLF();
addKiwiSDR();
addSpyServer();
addSDRangelServer();
displaySettings();
applySettings(true);
@ -490,6 +495,44 @@ void MapGUI::addIBPBeacons()
}
}
// https://www.icao.int/EURNAT/EUR%20and%20NAT%20Documents/NAT%20Documents/NAT%20Documents/NAT%20Doc%20003/NAT%20Doc003%20-%20HF%20Guidance%20v3.0.0_2015.pdf
// Coords aren't precise
const QList<RadioTimeTransmitter> MapGUI::m_natTransmitters = {
{"Bodo", 0, 67.26742f, 14.34990, 0},
{"Gander", 0, 48.993056f, -54.674444f, 0},
{"Iceland", 0, 64.08516f, -21.84531f, 0},
{"New York", 0, 40.881111f, -72.647778f, 0},
{"Santa Maria", 0, 36.995556f, -25.170556, 0},
{"Shanwick", 0, 52.75f, -8.933333f, 0},
};
// North Atlantic HF ATC ground stations
void MapGUI::addNAT()
{
for (int i = 0; i < m_natTransmitters.size(); i++)
{
SWGSDRangel::SWGMapItem natMapItem;
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
QString name = QString("%1").arg(m_natTransmitters[i].m_callsign);
natMapItem.setName(new QString(name));
natMapItem.setLatitude(m_natTransmitters[i].m_latitude);
natMapItem.setLongitude(m_natTransmitters[i].m_longitude);
natMapItem.setAltitude(0.0);
natMapItem.setImage(new QString("antenna.png"));
natMapItem.setImageRotation(0);
QString text = QString("NAT ATC Transmitter\nCallsign: %1")
.arg(m_natTransmitters[i].m_callsign);
natMapItem.setText(new QString(text));
natMapItem.setModel(new QString("antenna.glb"));
natMapItem.setFixedPosition(true);
natMapItem.setOrientation(0);
natMapItem.setLabel(new QString(name));
natMapItem.setLabelAltitudeOffset(4.5);
natMapItem.setAltitudeReference(1);
update(m_map, &natMapItem, "NAT ATC Transmitters");
}
}
void MapGUI::addVLF()
{
for (int i = 0; i < VLFTransmitters::m_transmitters.size(); i++)
@ -699,6 +742,75 @@ void MapGUI::spyServerUpdated(const QList<SpyServerList::SpyServer>& sdrs)
}
}
void MapGUI::addSDRangelServer()
{
m_sdrangelServerList.getDataPeriodically();
}
void MapGUI::sdrangelServerUpdated(const QList<SDRangelServerList::SDRangelServer>& sdrs)
{
for (const auto& sdr : sdrs)
{
SWGSDRangel::SWGMapItem sdrangelServerMapItem;
QString address = QString("%1:%2").arg(sdr.m_address).arg(sdr.m_port);
sdrangelServerMapItem.setName(new QString(address));
sdrangelServerMapItem.setLatitude(sdr.m_latitude);
sdrangelServerMapItem.setLongitude(sdr.m_longitude);
sdrangelServerMapItem.setAltitude(sdr.m_altitude);
sdrangelServerMapItem.setImage(new QString("antennaangel.png"));
sdrangelServerMapItem.setImageRotation(0);
QStringList antenna;
if (!sdr.m_antenna.isEmpty()) {
antenna.append(sdr.m_antenna);
}
if (sdr.m_isotropic) {
antenna.append("Isotropic");
} else {
antenna.append(QString("Az: %1%3 El: %2%3").arg(sdr.m_azimuth).arg(sdr.m_elevation).arg(QChar(0x00b0)));
}
QString text = QString("SDRangel\n\nStation: %1\nDevice: %2\nAntenna: %3\nFrequency: %4 - %5\nRemote control: %6\nUsers: %7/%8")
.arg(sdr.m_stationName)
.arg(sdr.m_device)
.arg(antenna.join(" - "))
.arg(formatFrequency(sdr.m_minFrequency))
.arg(formatFrequency(sdr.m_maxFrequency))
.arg(sdr.m_remoteControl ? "Yes" : "No")
.arg(sdr.m_clients)
.arg(sdr.m_maxClients)
;
if (sdr.m_timeLimit > 0) {
text.append(QString("\nTime limit: %1 mins").arg(sdr.m_timeLimit));
}
QString url = QString("sdrangel-server://%1").arg(address);
QString link = QString("<a href=%1 onclick=\"return parent.infoboxLink('%1')\">%2</a>").arg(url).arg(address);
text.append(QString("\nURL: %1").arg(link));
sdrangelServerMapItem.setText(new QString(text));
sdrangelServerMapItem.setModel(new QString("antenna.glb"));
sdrangelServerMapItem.setFixedPosition(true);
sdrangelServerMapItem.setOrientation(0);
QStringList bands;
if (sdr.m_minFrequency < 30000000) {
bands.append("HF");
}
if ((sdr.m_minFrequency < 300000000) && (sdr.m_maxFrequency > 30000000)) {
bands.append("VHF");
}
if ((sdr.m_minFrequency < 3000000000) && (sdr.m_maxFrequency > 300000000)) {
bands.append("UHF");
}
if (sdr.m_maxFrequency > 3000000000) {
bands.append("SHF");
}
QString label = QString("SDRangel %1").arg(bands.join(" "));
sdrangelServerMapItem.setLabel(new QString(label));
sdrangelServerMapItem.setLabelAltitudeOffset(4.5);
sdrangelServerMapItem.setAltitudeReference(1);
update(m_map, &sdrangelServerMapItem, "SDRangel");
}
}
// Ionosonde stations
void MapGUI::addIonosonde()
{
@ -1582,8 +1694,13 @@ void MapGUI::applyMap2DSettings(bool reloadMap)
if (!m_settings.m_osmURL.isEmpty()) {
parameters["osm.mapping.custom.host"] = m_settings.m_osmURL; // E.g: "http://a.tile.openstreetmap.fr/hot/"
}
#ifdef __EMSCRIPTEN__
// Default is http://maps-redirect.qt.io/osm/5.8/ and Emscripten needs https
parameters["osm.mapping.providersrepository.address"] = QString("https://sdrangel.beniston.com/sdrangel/maps/");
#else
// Use our repo, so we can append API key
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
#endif
// Use application specific cache, as other apps may not use API key so will have different images
QString cachePath = osmCachePath();
parameters["osm.mapping.cache.directory"] = cachePath;
@ -1685,6 +1802,9 @@ void MapGUI::displayToolbar()
bool narrow = this->screen()->availableGeometry().width() < 400;
ui->layersMenu->setVisible(narrow);
bool overlayButtons = !narrow && ((m_settings.m_mapProvider == "osm") || m_settings.m_map3DEnabled);
#ifdef __EMSCRIPTEN__
overlayButtons = false;
#endif
ui->displayRain->setVisible(overlayButtons);
ui->displayClouds->setVisible(overlayButtons);
ui->displaySeaMarks->setVisible(overlayButtons);
@ -2600,12 +2720,16 @@ void MapGUI::linkClicked(const QString& url)
QString spyServerURL = url.mid(21);
openSpyServer(spyServerURL);
}
else if (url.startsWith("sdrangel-server://"))
{
QString sdrangelServerURL = url.mid(18);
openSDRangelServer(sdrangelServerURL);
}
}
// Open a KiwiSDR RX device
void MapGUI::openKiwiSDR(const QString& url)
bool MapGUI::openKiwiSDRInput()
{
// Create DeviceSet
// Create DeviceSet
MainCore *mainCore = MainCore::instance();
unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
@ -2634,39 +2758,43 @@ void MapGUI::openKiwiSDR(const QString& url)
if (!found)
{
qCritical() << "MapGUI::openKiwiSDR: Failed to find KiwiSDR";
return;
return false;
}
// Wait until device is created - is there a better way?
DeviceSet *deviceSet = nullptr;
do
{
QTime dieTime = QTime::currentTime().addMSecs(100);
while (QTime::currentTime() < dieTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (mainCore->getDeviceSets().size() > deviceSetIndex)
{
deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
}
}
while (!deviceSet);
// Move to same workspace
//getWorkspaceIndex();
// Set address setting
QStringList deviceSettingsKeys = {"serverAddress"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response.getKiwiSdrSettings();
deviceSettings->setServerAddress(new QString(url));
QString errorMessage;
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
return true;
}
// Open a RemoteTCPInput device to use for SpyServer
void MapGUI::openSpyServer(const QString& url)
// Open a KiwiSDR RX device
void MapGUI::openKiwiSDR(const QString& url)
{
m_remoteDeviceAddress = url;
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
if (!openKiwiSDRInput()) {
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
}
}
void MapGUI::kiwiSDRDeviceSetAdded(int index, DeviceAPI *device)
{
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::kiwiSDRDeviceSetAdded);
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
QTimer::singleShot(200, [=] {
// Set address setting
QStringList deviceSettingsKeys = {"serverAddress"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response.getKiwiSdrSettings();
deviceSettings->setServerAddress(new QString(m_remoteDeviceAddress));
QString errorMessage;
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
});
}
bool MapGUI::openRemoteTCPInput()
{
// Create DeviceSet
MainCore *mainCore = MainCore::instance();
@ -2696,39 +2824,79 @@ void MapGUI::openSpyServer(const QString& url)
}
if (!found)
{
qCritical() << "MapGUI::openSpyServer: Failed to find RemoteTCPInput";
return;
qCritical() << "MapGUI::openRemoteTCPInput: Failed to find RemoteTCPInput";
return false;
}
// Wait until device is created - is there a better way?
DeviceSet *deviceSet = nullptr;
do
{
QTime dieTime = QTime::currentTime().addMSecs(100);
while (QTime::currentTime() < dieTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (mainCore->getDeviceSets().size() > deviceSetIndex)
{
deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
}
}
while (!deviceSet);
// Move to same workspace
//getWorkspaceIndex();
// Set address/port setting
return true;
}
// Open a RemoteTCPInput device to use for SpyServer
void MapGUI::openSpyServer(const QString& url)
{
QStringList address = url.split(":");
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
deviceSettings->setDataAddress(new QString(address[0]));
deviceSettings->setDataPort(address[1].toInt());
deviceSettings->setProtocol(new QString("Spy Server"));
QString errorMessage;
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
m_remoteDeviceAddress = address[0];
m_remoteDevicePort = address[1].toInt();
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
if (!openRemoteTCPInput()) {
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
}
}
void MapGUI::spyServerDeviceSetAdded(int index, DeviceAPI *device)
{
qDebug() << "**************** MapGUI::spyServerDeviceSetAdded";
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::spyServerDeviceSetAdded);
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
QTimer::singleShot(200, [=] {
// Set address/port setting
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
deviceSettings->setDataPort(m_remoteDevicePort);
deviceSettings->setProtocol(new QString("Spy Server"));
deviceSettings->setOverrideRemoteSettings(false);
QString errorMessage;
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
});
}
// Open a RemoteTCPInput device to use for SDRangel
void MapGUI::openSDRangelServer(const QString& url)
{
QStringList address = url.split(":");
m_remoteDeviceAddress = address[0];
m_remoteDevicePort = address[1].toInt();
connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
if (!openRemoteTCPInput()) {
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
}
}
void MapGUI::sdrangelServerDeviceSetAdded(int index, DeviceAPI *device)
{
disconnect(MainCore::instance(), &MainCore::deviceSetAdded, this, &MapGUI::sdrangelServerDeviceSetAdded);
// FIXME: Doesn't work if we do it immediately. Settings overwritten?
QTimer::singleShot(200, [=] {
// Set address/port setting
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol", "overrideRemoteSettings"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
deviceSettings->setDataAddress(new QString(m_remoteDeviceAddress));
deviceSettings->setDataPort(m_remoteDevicePort);
deviceSettings->setProtocol(new QString("SDRangel"));
deviceSettings->setOverrideRemoteSettings(false);
QString errorMessage;
device->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
});
}
#ifdef QT_WEBENGINE_FOUND
@ -2836,4 +3004,3 @@ void MapGUI::makeUIConnections()
QObject::connect(ui->ibpBeacons, &QToolButton::clicked, this, &MapGUI::on_ibpBeacons_clicked);
QObject::connect(ui->radiotime, &QToolButton::clicked, this, &MapGUI::on_radiotime_clicked);
}

View File

@ -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_

View File

@ -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();
}

View File

@ -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)

View File

@ -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);

View File

@ -20,6 +20,7 @@
#include <QMessageBox>
#include <QDateTime>
#include <QString>
#include <QFileDialog>
#include "ui_remotetcpinputgui.h"
#include "gui/colormapper.h"
@ -30,17 +31,19 @@
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
#include "util/db.h"
#include "remotetcpinputgui.h"
#include "remotetcpinputtcphandler.h"
#include "maincore.h"
RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
DeviceGUI(parent),
ui(new Ui::RemoteTCPInputGui),
m_settings(),
m_sampleSource(0),
m_lastEngineState(DeviceAPI::StNotStarted),
m_sampleSource(nullptr),
m_sampleRate(0),
m_centerFrequency(0),
m_tickCount(0),
m_doApplySettings(true),
m_forceSettings(true),
m_deviceGains(nullptr),
@ -71,12 +74,15 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
displaySettings();
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(deviceUISet->m_deviceAPI, &DeviceAPI::stateChanged, this, &RemoteTCPInputGui::updateStatus);
updateStatus();
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
m_sampleSource = (RemoteTCPInput*) m_deviceUISet->m_deviceAPI->getSampleSource();
@ -89,11 +95,12 @@ RemoteTCPInputGui::RemoteTCPInputGui(DeviceUISet *deviceUISet, QWidget* parent)
makeUIConnections();
DialPopup::addPopupsToChildDials(this);
m_resizer.enableChildMouseTracking();
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
}
RemoteTCPInputGui::~RemoteTCPInputGui()
{
m_statusTimer.stop();
m_updateTimer.stop();
delete ui;
}
@ -110,6 +117,7 @@ void RemoteTCPInputGui::destroy()
void RemoteTCPInputGui::resetToDefaults()
{
qDebug() << "*************** RemoteTCPInputGui::resetToDefaults";
m_settings.resetToDefaults();
displaySettings();
m_forceSettings = true;
@ -150,6 +158,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
} else {
m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
}
qDebug() << "********* RemoteTCPInputGui::handleMessage MsgConfigureRemoteTCPInput" << m_settings.m_dataAddress;
blockApplySettings(true);
displaySettings();
@ -224,24 +233,27 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
ui->detectedProtocol->setText(QString("Protocol: %1").arg(report.getProtocol()));
// Update GUI so we only show widgets available for the protocol in use
bool sdra = report.getProtocol() == "SDRA";
bool spyServer = report.getProtocol() == "Spy Server";
if (spyServer) {
m_sdra = report.getProtocol() == "SDRA";
m_spyServer = report.getProtocol() == "Spy Server";
m_remoteControl = report.getRemoteControl();
m_iqOnly = report.getIQOnly();
if (m_spyServer) {
m_spyServerGains.m_gains[0].m_max = report.getMaxGain();
}
if ((sdra || spyServer) && (ui->sampleBits->count() < 4))
if ((m_sdra || m_spyServer) && (ui->sampleBits->count() < 4))
{
ui->sampleBits->addItem("16");
ui->sampleBits->addItem("24");
ui->sampleBits->addItem("32");
}
else if (!(sdra || spyServer) && (ui->sampleBits->count() != 1))
else if (!(m_sdra || m_spyServer) && (ui->sampleBits->count() != 1))
{
while (ui->sampleBits->count() > 1) {
ui->sampleBits->removeItem(ui->sampleBits->count() - 1);
}
}
if ((sdra || spyServer) && (ui->decim->count() != 7))
if ((m_sdra || m_spyServer) && (ui->decim->count() != 7))
{
ui->decim->addItem("2");
ui->decim->addItem("4");
@ -250,26 +262,19 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
ui->decim->addItem("32");
ui->decim->addItem("64");
}
else if (!(sdra || spyServer) && (ui->decim->count() != 1))
else if (!(m_sdra || m_spyServer) && (ui->decim->count() != 1))
{
while (ui->decim->count() > 1) {
ui->decim->removeItem(ui->decim->count() - 1);
}
}
if (!sdra)
if (!m_sdra)
{
ui->deltaFrequency->setValue(0);
ui->channelGain->setValue(0);
ui->decimation->setChecked(true);
}
ui->deltaFrequencyLabel->setEnabled(sdra);
ui->deltaFrequency->setEnabled(sdra);
ui->deltaUnits->setEnabled(sdra);
ui->channelGainLabel->setEnabled(sdra);
ui->channelGain->setEnabled(sdra);
ui->channelGainText->setEnabled(sdra);
ui->decimation->setEnabled(sdra);
if (sdra) {
if (m_sdra) {
ui->centerFrequency->setValueRange(9, 0, 999999999); // Should add transverter control to protocol in the future
} else {
ui->centerFrequency->setValueRange(7, 0, 9999999);
@ -291,19 +296,7 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
{
ui->devSampleRate->setValueRange(8, 0, 99999999);
}
ui->devSampleRateLabel->setEnabled(!spyServer);
ui->devSampleRate->setEnabled(!spyServer);
ui->devSampleRateUnits->setEnabled(!spyServer);
ui->agc->setEnabled(!spyServer);
ui->rfBWLabel->setEnabled(!spyServer);
ui->rfBW->setEnabled(!spyServer);
ui->rfBWUnits->setEnabled(!spyServer);
ui->dcOffset->setEnabled(!spyServer);
ui->iqImbalance->setEnabled(!spyServer);
ui->ppm->setEnabled(!spyServer);
ui->ppmLabel->setEnabled(!spyServer);
ui->ppmText->setEnabled(!spyServer);
displayEnabled();
displayGains();
return true;
}
@ -314,13 +307,33 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
if (report.getConnected())
{
m_connectionError = false;
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
//ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
}
else
{
m_connectionError = true;
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
//ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
}
updateStatus();
return true;
}
else if (RemoteTCPInput::MsgSendMessage::match(message))
{
const RemoteTCPInput::MsgSendMessage& msg = (const RemoteTCPInput::MsgSendMessage&) message;
ui->messages->addItem(QString("%1> %2").arg(msg.getCallsign()).arg(msg.getText()));
ui->messages->scrollToBottom();
return true;
}
else if (RemoteTCPInput::MsgReportPosition::match(message))
{
// Could display in future
return true;
}
else if (RemoteTCPInput::MsgReportDirection::match(message))
{
// Could display in future
return true;
}
else
@ -329,6 +342,87 @@ bool RemoteTCPInputGui::handleMessage(const Message& message)
}
}
void RemoteTCPInputGui::displayEnabled()
{
int state = m_deviceUISet->m_deviceAPI->state();
bool remoteControl;
bool enableMessages;
bool enableSquelchEnable;
bool enableSquelch;
bool sdra;
if (state == DeviceAPI::StRunning)
{
sdra = m_sdra;
remoteControl = m_remoteControl;
enableMessages = !m_iqOnly;
enableSquelchEnable = !m_iqOnly;
enableSquelch = !m_iqOnly && m_settings.m_squelchEnabled;
}
else
{
sdra = m_settings.m_protocol == "SDRangel";
remoteControl = m_settings.m_overrideRemoteSettings;
enableMessages = false;
enableSquelchEnable = m_settings.m_overrideRemoteSettings;
enableSquelch = m_settings.m_overrideRemoteSettings && m_settings.m_squelchEnabled;
}
ui->deltaFrequencyLabel->setEnabled(sdra && remoteControl);
ui->deltaFrequency->setEnabled(sdra && remoteControl);
ui->deltaUnits->setEnabled(sdra && remoteControl);
ui->channelGainLabel->setEnabled(sdra && remoteControl);
ui->channelGain->setEnabled(sdra && remoteControl);
ui->channelGainText->setEnabled(sdra && remoteControl);
ui->decimation->setEnabled(sdra && remoteControl);
ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation && sdra && remoteControl);
ui->devSampleRateLabel->setEnabled(!m_spyServer && remoteControl);
ui->devSampleRate->setEnabled(!m_spyServer && remoteControl);
ui->devSampleRateUnits->setEnabled(!m_spyServer && remoteControl);
ui->agc->setEnabled(!m_spyServer && remoteControl);
ui->rfBWLabel->setEnabled(!m_spyServer && remoteControl);
ui->rfBW->setEnabled(!m_spyServer && remoteControl);
ui->rfBWUnits->setEnabled(!m_spyServer && remoteControl);
ui->dcOffset->setEnabled(!m_spyServer && remoteControl);
ui->iqImbalance->setEnabled(!m_spyServer && remoteControl);
ui->ppm->setEnabled(!m_spyServer && remoteControl);
ui->ppmLabel->setEnabled(!m_spyServer && remoteControl);
ui->ppmText->setEnabled(!m_spyServer && remoteControl);
ui->centerFrequency->setEnabled(remoteControl);
ui->biasTee->setEnabled(remoteControl);
ui->directSampling->setEnabled(remoteControl);
ui->decimLabel->setEnabled(remoteControl);
ui->decim->setEnabled(remoteControl);
ui->gain1Label->setEnabled(remoteControl);
ui->gain1->setEnabled(remoteControl);
ui->gain1Text->setEnabled(remoteControl);
ui->gain2Label->setEnabled(remoteControl);
ui->gain2->setEnabled(remoteControl);
ui->gain2Text->setEnabled(remoteControl);
ui->gain3Label->setEnabled(remoteControl);
ui->gain3->setEnabled(remoteControl);
ui->gain3Text->setEnabled(remoteControl);
ui->sampleBitsLabel->setEnabled(remoteControl);
ui->sampleBits->setEnabled(remoteControl);
ui->sampleBitsUnits->setEnabled(remoteControl);
ui->squelchEnabled->setEnabled(enableSquelchEnable);
ui->squelch->setEnabled(enableSquelch);
ui->squelchText->setEnabled(enableSquelch);
ui->squelchUnits->setEnabled(enableSquelch);
ui->squelchGate->setEnabled(enableSquelch);
ui->sendMessage->setEnabled(enableMessages);
ui->txAddress->setEnabled(enableMessages);
ui->txMessage->setEnabled(enableMessages);
ui->messages->setEnabled(enableMessages);
}
void RemoteTCPInputGui::handleInputMessages()
{
Message* message;
@ -386,12 +480,14 @@ void RemoteTCPInputGui::displaySettings()
ui->channelSampleRate->setValue(m_settings.m_channelSampleRate);
ui->deviceRateText->setText(tr("%1k").arg(m_settings.m_channelSampleRate / 1000.0));
ui->decimation->setChecked(!m_settings.m_channelDecimation);
ui->channelSampleRate->setEnabled(m_settings.m_channelDecimation);
ui->channelSampleRateLabel->setEnabled(m_settings.m_channelDecimation);
ui->channelSampleRateUnit->setEnabled(m_settings.m_channelDecimation);
ui->sampleBits->setCurrentText(QString::number(m_settings.m_sampleBits));
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
ui->squelchEnabled->setChecked(m_settings.m_squelchEnabled);
ui->squelch->setValue(m_settings.m_squelch);
ui->squelchText->setText(QString::number(m_settings.m_squelch));
ui->squelchGate->setValue(m_settings.m_squelchGate);
ui->dataPort->setValue(m_settings.m_dataPort);
ui->dataAddress->blockSignals(true);
ui->dataAddress->clear();
for (const auto& address : m_settings.m_addressList) {
@ -412,6 +508,11 @@ void RemoteTCPInputGui::displaySettings()
}
displayGains();
displayReplayLength();
displayReplayOffset();
displayReplayStep();
ui->replayLoop->setChecked(m_settings.m_replayLoop);
displayEnabled();
blockApplySettings(false);
}
@ -517,6 +618,7 @@ const QHash<RemoteTCPProtocol::Device, const RemoteTCPInputGui::DeviceGains *> R
{
{RemoteTCPProtocol::RTLSDR_E4000, &m_rtlSDRe4kGains},
{RemoteTCPProtocol::RTLSDR_R820T, &m_rtlSDRR820Gains},
{RemoteTCPProtocol::RTLSDR_R828D, &m_rtlSDRR820Gains},
{RemoteTCPProtocol::AIRSPY, &m_airspyGains},
{RemoteTCPProtocol::AIRSPY_HF, &m_airspyHFGains},
{RemoteTCPProtocol::BLADE_RF1, &m_baldeRF1Gains},
@ -609,7 +711,12 @@ void RemoteTCPInputGui::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
m_connectionError = false;
if (m_connectionError)
{
// Clear previous error
m_connectionError = false;
updateStatus();
}
RemoteTCPInput::MsgStartStop *message = RemoteTCPInput::MsgStartStop::create(checked);
m_sampleSource->getInputMessageQueue()->push(message);
}
@ -789,6 +896,29 @@ void RemoteTCPInputGui::on_sampleBits_currentIndexChanged(int index)
sendSettings();
}
void RemoteTCPInputGui::on_squelchEnabled_toggled(bool checked)
{
m_settings.m_squelchEnabled = checked;
m_settingsKeys.append("squelchEnabled");
displayEnabled();
sendSettings();
}
void RemoteTCPInputGui::on_squelch_valueChanged(int value)
{
m_settings.m_squelch = value;
ui->squelchText->setText(QString::number(m_settings.m_squelch));
m_settingsKeys.append("squelch");
sendSettings();
}
void RemoteTCPInputGui::on_squelchGate_valueChanged(double value)
{
m_settings.m_squelchGate = value;
m_settingsKeys.append("squelchGate");
sendSettings();
}
void RemoteTCPInputGui::on_dataAddress_editingFinished()
{
m_settings.m_dataAddress = ui->dataAddress->currentText();
@ -810,17 +940,9 @@ void RemoteTCPInputGui::on_dataAddress_currentIndexChanged(int index)
sendSettings();
}
void RemoteTCPInputGui::on_dataPort_editingFinished()
void RemoteTCPInputGui::on_dataPort_valueChanged(int value)
{
bool ok;
quint16 udpPort = ui->dataPort->text().toInt(&ok);
if ((!ok) || (udpPort < 1024)) {
udpPort = 9998;
}
m_settings.m_dataPort = udpPort;
ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort));
m_settings.m_dataPort = value;
m_settingsKeys.append("dataPort");
sendSettings();
@ -831,6 +953,7 @@ void RemoteTCPInputGui::on_overrideRemoteSettings_toggled(bool checked)
m_settings.m_overrideRemoteSettings = checked;
m_settingsKeys.append("overrideRemoteSettings");
sendSettings();
displayEnabled();
}
void RemoteTCPInputGui::on_preFill_valueChanged(int value)
@ -867,10 +990,14 @@ void RemoteTCPInputGui::updateHardware()
void RemoteTCPInputGui::updateStatus()
{
int state = m_deviceUISet->m_deviceAPI->state();
if (!m_connectionError && (m_lastEngineState != state))
if (m_connectionError)
{
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
}
else
{
int state = m_deviceUISet->m_deviceAPI->state();
switch(state)
{
case DeviceAPI::StNotStarted:
@ -889,9 +1016,28 @@ void RemoteTCPInputGui::updateStatus()
default:
break;
}
m_lastEngineState = state;
}
displayEnabled();
}
void RemoteTCPInputGui::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_sampleSource->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(tr("%1").arg(powDbAvg, 0, 'f', 1));
}
m_tickCount++;
}
void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
@ -899,6 +1045,9 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
if (m_contextMenuType == ContextMenuDeviceSettings)
{
BasicDeviceSettingsDialog dialog(this);
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2 * sizeof(FixReal));
dialog.setReplayLength(m_settings.m_replayLength);
dialog.setReplayStep(m_settings.m_replayStep);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
@ -908,6 +1057,11 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_replayLength = dialog.getReplayLength();
m_settings.m_replayStep = dialog.getReplayStep();
displayReplayLength();
displayReplayOffset();
displayReplayStep();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
@ -919,6 +1073,110 @@ void RemoteTCPInputGui::openDeviceSettingsDialog(const QPoint& p)
resetContextMenuType();
}
void RemoteTCPInputGui::displayReplayLength()
{
bool replayEnabled = m_settings.m_replayLength > 0.0f;
if (!replayEnabled) {
ui->replayOffset->setMaximum(0);
} else {
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
}
ui->replayLabel->setEnabled(replayEnabled);
ui->replayOffset->setEnabled(replayEnabled);
ui->replayOffsetText->setEnabled(replayEnabled);
ui->replaySave->setEnabled(replayEnabled);
}
void RemoteTCPInputGui::displayReplayOffset()
{
bool replayEnabled = m_settings.m_replayLength > 0.0f;
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
}
void RemoteTCPInputGui::displayReplayStep()
{
QString step;
float intpart;
float frac = modf(m_settings.m_replayStep, &intpart);
if (frac == 0.0f) {
step = QString::number((int)intpart);
} else {
step = QString::number(m_settings.m_replayStep, 'f', 1);
}
ui->replayPlus->setText(QString("+%1s").arg(step));
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
ui->replayMinus->setText(QString("-%1s").arg(step));
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
}
void RemoteTCPInputGui::on_replayOffset_valueChanged(int value)
{
m_settings.m_replayOffset = value / 10.0f;
displayReplayOffset();
m_settingsKeys.append("replayOffset");
sendSettings();
}
void RemoteTCPInputGui::on_replayNow_clicked()
{
ui->replayOffset->setValue(0);
}
void RemoteTCPInputGui::on_replayPlus_clicked()
{
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
}
void RemoteTCPInputGui::on_replayMinus_clicked()
{
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
}
void RemoteTCPInputGui::on_replaySave_clicked()
{
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
RemoteTCPInput::MsgSaveReplay *message = RemoteTCPInput ::MsgSaveReplay::create(fileNames[0]);
m_sampleSource->getInputMessageQueue()->push(message);
}
}
}
void RemoteTCPInputGui::on_replayLoop_toggled(bool checked)
{
m_settings.m_replayLoop = checked;
m_settingsKeys.append("replayLoop");
sendSettings();
}
void RemoteTCPInputGui::on_sendMessage_clicked()
{
QString message = ui->txMessage->text().trimmed();
if (!message.isEmpty())
{
ui->messages->addItem(QString("< %1").arg(message));
ui->messages->scrollToBottom();
bool broadcast = ui->txAddress->currentText() == "All";
QString callsign = MainCore::instance()->getSettings().getStationName();
m_sampleSource->getInputMessageQueue()->push(RemoteTCPInput::MsgSendMessage::create(callsign, message, broadcast));
}
}
void RemoteTCPInputGui::on_txMessage_returnPressed()
{
on_sendMessage_clicked();
ui->txMessage->selectAll();
}
void RemoteTCPInputGui::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_startStop_toggled);
@ -940,10 +1198,21 @@ void RemoteTCPInputGui::makeUIConnections()
QObject::connect(ui->channelSampleRate, &ValueDial::changed, this, &RemoteTCPInputGui::on_channelSampleRate_changed);
QObject::connect(ui->decimation, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_decimation_toggled);
QObject::connect(ui->sampleBits, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_sampleBits_currentIndexChanged);
QObject::connect(ui->squelchEnabled, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_squelchEnabled_toggled);
QObject::connect(ui->squelch, &QDial::valueChanged, this, &RemoteTCPInputGui::on_squelch_valueChanged);
QObject::connect(ui->squelchGate, &PeriodDial::valueChanged, this, &RemoteTCPInputGui::on_squelchGate_valueChanged);
QObject::connect(ui->dataAddress->lineEdit(), &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataAddress_editingFinished);
QObject::connect(ui->dataAddress, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_dataAddress_currentIndexChanged);
QObject::connect(ui->dataPort, &QLineEdit::editingFinished, this, &RemoteTCPInputGui::on_dataPort_editingFinished);
QObject::connect(ui->dataPort, QOverload<int>::of(&QSpinBox::valueChanged), this, &RemoteTCPInputGui::on_dataPort_valueChanged);
QObject::connect(ui->overrideRemoteSettings, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_overrideRemoteSettings_toggled);
QObject::connect(ui->preFill, &QDial::valueChanged, this, &RemoteTCPInputGui::on_preFill_valueChanged);
QObject::connect(ui->protocol, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RemoteTCPInputGui::on_protocol_currentIndexChanged);
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &RemoteTCPInputGui::on_replayOffset_valueChanged);
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayNow_clicked);
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayPlus_clicked);
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replayMinus_clicked);
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &RemoteTCPInputGui::on_replaySave_clicked);
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &RemoteTCPInputGui::on_replayLoop_toggled);
QObject::connect(ui->sendMessage, &QToolButton::clicked, this, &RemoteTCPInputGui::on_sendMessage_clicked);
QObject::connect(ui->txMessage, &QLineEdit::returnPressed, this, &RemoteTCPInputGui::on_txMessage_returnPressed);
}

View File

@ -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

View File

@ -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>

View File

@ -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++)
{

View File

@ -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();

View File

@ -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();

View File

@ -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

View 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);
}

View 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 */

View File

@ -61,3 +61,12 @@ RemoteTCPInputReport:
properties:
sampleRate:
type: integer
latitude:
type: number
format: float
longitude:
type: number
format: float
altitude:
type: number
format: float

View File

@ -1,6 +1,6 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
@ -59,7 +59,7 @@ SWGRemoteTCPInputReport::fromJson(QString &json) {
void
SWGRemoteTCPInputReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", "");
}
QString
@ -79,6 +79,9 @@ SWGRemoteTCPInputReport::asJsonObject() {
if(m_sample_rate_isSet){
obj->insert("sampleRate", QJsonValue(sample_rate));
}
obj->insert("latitude", QJsonValue(latitude));
obj->insert("longitude", QJsonValue(longitude));
obj->insert("altitude", QJsonValue(altitude));
return obj;
}

View File

@ -1,6 +1,6 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
@ -43,13 +43,21 @@ public:
qint32 getSampleRate();
void setSampleRate(qint32 sample_rate);
float getLatitude() { return latitude; }
float getLongitude() { return longitude; }
float getAltitude() { return altitude; }
void setLatitude(float latitude) { this->latitude = latitude; }
void setLongitude(float longitude) { this->longitude = longitude; }
void setAltitude(float altitude) { this->altitude = altitude; }
virtual bool isSet() override;
private:
qint32 sample_rate;
bool m_sample_rate_isSet;
float latitude;
float longitude;
float altitude;
};