mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-22 16:08:39 -05:00
1763 lines
70 KiB
C++
1763 lines
70 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// 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 //
|
|
// 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 <QMutexLocker>
|
|
#include <QThread>
|
|
#include <QSslConfiguration>
|
|
#include <QSslCertificate>
|
|
#include <QSslKey>
|
|
|
|
#include "channel/channelwebapiutils.h"
|
|
#include "device/deviceapi.h"
|
|
#include "maincore.h"
|
|
|
|
#include "remotetcpsinksink.h"
|
|
#include "remotetcpsink.h"
|
|
|
|
static FLAC__StreamEncoderWriteStatus flacWriteCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t currentFrame, void *clientData)
|
|
{
|
|
RemoteTCPSinkSink *sink = (RemoteTCPSinkSink*) clientData;
|
|
|
|
return sink->flacWrite(encoder, buffer, bytes, samples, currentFrame);
|
|
}
|
|
|
|
RemoteTCPSinkSink::RemoteTCPSinkSink() :
|
|
m_running(false),
|
|
m_messageQueueToGUI(nullptr),
|
|
m_messageQueueToChannel(nullptr),
|
|
m_channelFrequencyOffset(0),
|
|
m_channelSampleRate(48000),
|
|
m_linearGain(1.0f),
|
|
m_server(nullptr),
|
|
m_webSocketServer(nullptr),
|
|
m_encoder(nullptr),
|
|
m_zStreamInitialised(false),
|
|
m_zInBuf(m_zBufSize, '\0'),
|
|
m_zOutBuf(m_zBufSize, '\0'),
|
|
m_zInBufCount(0),
|
|
m_bytesUncompressed(0),
|
|
m_bytesCompressed(0),
|
|
m_bytesTransmitted(0),
|
|
m_squelchLevel(-150.0f),
|
|
m_squelchCount(0),
|
|
m_squelchOpen(false),
|
|
m_squelchDelayLine(48000/2),
|
|
m_magsq(0.0f),
|
|
m_magsqSum(0.0f),
|
|
m_magsqPeak(0.0f),
|
|
m_magsqCount(0),
|
|
m_centerFrequency(0.0),
|
|
m_ppmCorrection(0),
|
|
m_biasTeeEnabled(false),
|
|
m_directSampling(false),
|
|
m_agc(false),
|
|
m_dcOffsetRemoval(false),
|
|
m_iqCorrection(false),
|
|
m_devSampleRate(0),
|
|
m_log2Decim(0),
|
|
m_rfBW(0),
|
|
m_gain(),
|
|
m_timer(this),
|
|
m_azimuth(std::numeric_limits<float>::quiet_NaN()),
|
|
m_elevation(std::numeric_limits<float>::quiet_NaN())
|
|
{
|
|
qDebug("RemoteTCPSinkSink::RemoteTCPSinkSink");
|
|
applySettings(m_settings, QStringList(), true);
|
|
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
|
|
|
// Get updated when position changes
|
|
connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &RemoteTCPSinkSink::preferenceChanged);
|
|
|
|
m_timer.setSingleShot(false);
|
|
m_timer.setInterval(500);
|
|
connect(&m_timer, &QTimer::timeout, this, &RemoteTCPSinkSink::checkDeviceSettings);
|
|
}
|
|
|
|
RemoteTCPSinkSink::~RemoteTCPSinkSink()
|
|
{
|
|
qDebug("RemoteTCPSinkSink::~RemoteTCPSinkSink");
|
|
disconnect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &RemoteTCPSinkSink::preferenceChanged);
|
|
stop();
|
|
}
|
|
|
|
void RemoteTCPSinkSink::start()
|
|
{
|
|
qDebug("RemoteTCPSinkSink::start");
|
|
|
|
if (m_running) {
|
|
return;
|
|
}
|
|
|
|
connect(thread(), SIGNAL(started()), this, SLOT(started()));
|
|
connect(thread(), SIGNAL(finished()), this, SLOT(finished()));
|
|
|
|
m_running = true;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::stop()
|
|
{
|
|
qDebug("RemoteTCPSinkSink::stop");
|
|
|
|
m_running = false;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::started()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
startServer();
|
|
disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
|
|
m_timer.start();
|
|
}
|
|
|
|
void RemoteTCPSinkSink::finished()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
stopServer();
|
|
disconnect(thread(), SIGNAL(finished()), this, SLOT(finished()));
|
|
m_timer.stop();
|
|
m_running = false;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::init()
|
|
{
|
|
}
|
|
|
|
void RemoteTCPSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
|
|
if (m_clients.size() > 0)
|
|
{
|
|
Complex ci;
|
|
int bytes = 0;
|
|
|
|
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
|
{
|
|
Complex c(it->real(), it->imag());
|
|
c *= m_nco.nextIQ();
|
|
|
|
if (m_interpolatorDistance < 1.0f) // interpolate
|
|
{
|
|
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
|
|
{
|
|
processOneSample(ci);
|
|
bytes += 2 * m_settings.m_sampleBits / 8;
|
|
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
|
}
|
|
}
|
|
else // decimate
|
|
{
|
|
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
|
|
{
|
|
processOneSample(ci);
|
|
bytes += 2 * m_settings.m_sampleBits / 8;
|
|
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto client : m_clients) {
|
|
client->flush();
|
|
}
|
|
|
|
QDateTime currentDateTime = QDateTime::currentDateTime();
|
|
if (m_bwDateTime.isValid())
|
|
{
|
|
qint64 msecs = m_bwDateTime.msecsTo(currentDateTime) ;
|
|
if (msecs >= 1000)
|
|
{
|
|
float secs = msecs / 1000.0f;
|
|
float bw = (8 * m_bwBytes) / secs;
|
|
float networkBW = (8 * m_bytesTransmitted) / secs;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgReportBW::create(bw, networkBW, m_bytesUncompressed, m_bytesCompressed, m_bytesTransmitted));
|
|
}
|
|
m_bwDateTime = currentDateTime;
|
|
m_bwBytes = bytes;
|
|
m_bytesUncompressed = 0;
|
|
m_bytesCompressed = 0;
|
|
m_bytesTransmitted = 0;
|
|
}
|
|
else
|
|
{
|
|
m_bwBytes += bytes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bwDateTime = currentDateTime;
|
|
m_bwBytes = bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
static qint32 clamp8(qint32 x)
|
|
{
|
|
x = std::max(x, -128);
|
|
x = std::min(x, 127);
|
|
return x;
|
|
}
|
|
|
|
static qint32 clamp16(qint32 x)
|
|
{
|
|
x = std::max(x, -32768);
|
|
x = std::min(x, 32767);
|
|
return x;
|
|
}
|
|
|
|
static qint32 clamp24(qint32 x)
|
|
{
|
|
x = std::max(x, -8388608);
|
|
x = std::min(x, 8388607);
|
|
return x;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::processOneSample(Complex &ci)
|
|
{
|
|
// Apply gain
|
|
ci = ci * m_linearGain;
|
|
|
|
// Calculate channel power
|
|
Real re = ci.real();
|
|
Real im = ci.imag();
|
|
Real magsq = (re*re + im*im) / (SDR_RX_SCALED*SDR_RX_SCALED);
|
|
m_movingAverage(magsq);
|
|
m_magsq = m_movingAverage.asDouble();
|
|
m_magsqSum += magsq;
|
|
m_magsqPeak = std::max<double>(magsq, m_magsqPeak);
|
|
m_magsqCount++;
|
|
|
|
// Squelch
|
|
if (m_settings.m_squelchEnabled)
|
|
{
|
|
// Convert gate time from seconds to samples
|
|
int squelchGate = m_settings.m_squelchGate * m_channelSampleRate;
|
|
|
|
m_squelchDelayLine.write(ci);
|
|
|
|
if (m_magsq < m_squelchLevel)
|
|
{
|
|
if (m_squelchCount > 0) {
|
|
m_squelchCount--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_squelchCount = squelchGate;
|
|
}
|
|
m_squelchOpen = m_squelchCount > 0;
|
|
|
|
if (m_squelchOpen) {
|
|
ci = m_squelchDelayLine.readBack(squelchGate);
|
|
} else {
|
|
ci = 0.0;
|
|
}
|
|
}
|
|
|
|
if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC) && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0))
|
|
{
|
|
// Compress using FLAC
|
|
FLAC__int32 iqBuf[2];
|
|
|
|
if (m_settings.m_sampleBits == 8)
|
|
{
|
|
iqBuf[0] = (qint32) (ci.real() / 65536.0f);
|
|
iqBuf[1] = (qint32) (ci.imag() / 65536.0f);
|
|
iqBuf[0] = clamp8(iqBuf[0]);
|
|
iqBuf[1] = clamp8(iqBuf[1]);
|
|
}
|
|
else if (m_settings.m_sampleBits == 16)
|
|
{
|
|
iqBuf[0] = (qint32) (ci.real() / 256.0f);
|
|
iqBuf[1] = (qint32) (ci.imag() / 256.0f);
|
|
iqBuf[0] = clamp16(iqBuf[0]);
|
|
iqBuf[1] = clamp16(iqBuf[1]);
|
|
}
|
|
else if (m_settings.m_sampleBits == 24)
|
|
{
|
|
iqBuf[0] = (qint32) ci.real();
|
|
iqBuf[1] = (qint32) ci.imag();
|
|
iqBuf[0] = clamp24(iqBuf[0]);
|
|
iqBuf[1] = clamp24(iqBuf[1]);
|
|
}
|
|
else
|
|
{
|
|
iqBuf[0] = (qint32) ci.real();
|
|
iqBuf[1] = (qint32) ci.imag();
|
|
}
|
|
int bytes = 2 * m_settings.m_sampleBits / 8;
|
|
m_bytesUncompressed += bytes;
|
|
|
|
if (m_encoder && !FLAC__stream_encoder_process_interleaved(m_encoder, iqBuf, 1)) { // Number of samples in one channel
|
|
qDebug() << "RemoteTCPSinkSink::processOneSample: FLAC failed to encode:" << FLAC__stream_encoder_get_state(m_encoder);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
quint8 iqBuf[4*2];
|
|
|
|
if (m_settings.m_sampleBits == 8)
|
|
{
|
|
// Transmit data as per rtl_tcp - Interleaved unsigned 8-bit IQ
|
|
iqBuf[0] = clamp8((qint32) (ci.real() / 65536.0f)) + 128;
|
|
iqBuf[1] = clamp8((qint32) (ci.imag() / 65536.0f)) + 128;
|
|
}
|
|
else if (m_settings.m_sampleBits == 16)
|
|
{
|
|
// Interleaved little-endian signed 16-bit IQ
|
|
qint32 i, q;
|
|
i = clamp16((qint32) (ci.real() / 256.0f));
|
|
q = clamp16((qint32) (ci.imag() / 256.0f));
|
|
iqBuf[1] = (i >> 8) & 0xff;
|
|
iqBuf[0] = i & 0xff;
|
|
iqBuf[3] = (q >> 8) & 0xff;
|
|
iqBuf[2] = q & 0xff;
|
|
}
|
|
else if (m_settings.m_sampleBits == 24)
|
|
{
|
|
// Interleaved little-endian signed 24-bit IQ
|
|
qint32 i, q;
|
|
i = clamp24((qint32) ci.real());
|
|
q = clamp24((qint32) ci.imag());
|
|
iqBuf[2] = (i >> 16) & 0xff;
|
|
iqBuf[1] = (i >> 8) & 0xff;
|
|
iqBuf[0] = i & 0xff;
|
|
iqBuf[5] = (q >> 16) & 0xff;
|
|
iqBuf[4] = (q >> 8) & 0xff;
|
|
iqBuf[3] = q & 0xff;
|
|
}
|
|
else
|
|
{
|
|
// Interleaved little-endian signed 32-bit IQ
|
|
qint32 i, q;
|
|
i = (qint32) ci.real();
|
|
q = (qint32) ci.imag();
|
|
iqBuf[3] = (i >> 24) & 0xff;
|
|
iqBuf[2] = (i >> 16) & 0xff;
|
|
iqBuf[1] = (i >> 8) & 0xff;
|
|
iqBuf[0] = i & 0xff;
|
|
iqBuf[7] = (q >> 24) & 0xff;
|
|
iqBuf[6] = (q >> 16) & 0xff;
|
|
iqBuf[5] = (q >> 8) & 0xff;
|
|
iqBuf[4] = q & 0xff;
|
|
}
|
|
|
|
int bytes = 2 * m_settings.m_sampleBits / 8;
|
|
m_bytesUncompressed += bytes;
|
|
|
|
if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::ZLIB) && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0))
|
|
{
|
|
if (m_zStreamInitialised)
|
|
{
|
|
// Store in block buffer
|
|
memcpy(&m_zInBuf.data()[m_zInBufCount], iqBuf, bytes);
|
|
m_zInBufCount += bytes;
|
|
|
|
if (m_zInBufCount >= m_settings.m_blockSize)
|
|
{
|
|
// Compress using zlib
|
|
m_zStream.next_in = (Bytef *) m_zInBuf.data();
|
|
m_zStream.avail_in = m_zInBufCount;
|
|
m_zStream.next_out = (Bytef *) m_zOutBuf.data();
|
|
m_zStream.avail_out = m_zOutBuf.size();
|
|
int ret = deflate(&m_zStream, Z_FINISH);
|
|
|
|
if (ret == Z_STREAM_END) {
|
|
deflateReset(&m_zStream);
|
|
} else if (ret != Z_OK) {
|
|
qDebug() << "RemoteTCPSinkSink::processOneSample: Failed to deflate" << ret;
|
|
}
|
|
if (m_zStream.avail_in != 0) {
|
|
qDebug() << "RemoteTCPSinkSink::processOneSample: Data still in input buffer";
|
|
}
|
|
int compressedBytes = m_zOutBuf.size() - m_zStream.avail_out;
|
|
|
|
//qDebug() << "zlib ret" << ret << "m_settings.m_blockSize" << m_settings.m_blockSize << "m_zInBufCount" << m_zInBufCount << "compressedBytes" << compressedBytes << "avail_in" << m_zStream.avail_in << "avail_out" << m_zStream.avail_out << " % " << round(100.0 * compressedBytes / (float) m_zInBufCount );
|
|
|
|
m_zInBufCount = 0;
|
|
|
|
// Send to clients
|
|
|
|
int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
|
|
char header[1+4];
|
|
|
|
header[0] = (char) RemoteTCPProtocol::dataIQzlib;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], compressedBytes);
|
|
|
|
for (int i = 0; i < clients; i++)
|
|
{
|
|
m_clients[i]->write(header, sizeof(header));
|
|
m_bytesTransmitted += sizeof(header);
|
|
m_clients[i]->write((const char *)m_zOutBuf.data(), compressedBytes);
|
|
m_bytesTransmitted += compressedBytes;
|
|
}
|
|
m_bytesCompressed += sizeof(header) + compressedBytes;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Send uncompressed
|
|
int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
|
|
|
|
for (int i = 0; i < clients; i++)
|
|
{
|
|
m_clients[i]->write((const char *)iqBuf, bytes);
|
|
m_bytesTransmitted += bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::applyChannelSettings:"
|
|
<< " channelSampleRate: " << channelSampleRate
|
|
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
|
|
|
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
|
|
(m_channelSampleRate != channelSampleRate) || force)
|
|
{
|
|
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
|
}
|
|
|
|
if ((m_channelSampleRate != channelSampleRate) || force)
|
|
{
|
|
m_interpolator.create(16, channelSampleRate, m_settings.m_channelSampleRate / 2.0);
|
|
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_settings.m_channelSampleRate;
|
|
m_interpolatorDistanceRemain = m_interpolatorDistance;
|
|
}
|
|
|
|
m_channelSampleRate = channelSampleRate;
|
|
m_channelFrequencyOffset = channelFrequencyOffset;
|
|
|
|
m_squelchDelayLine.resize(m_settings.m_squelchGate * m_channelSampleRate + 1);
|
|
}
|
|
|
|
void RemoteTCPSinkSink::applySettings(const RemoteTCPSinkSettings& settings, const QStringList& settingsKeys, bool force, bool restartRequired)
|
|
{
|
|
bool initFLAC = false;
|
|
bool initZLib = false;
|
|
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
|
|
qDebug() << "RemoteTCPSinkSink::applySettings:" << settings.getDebugString(settingsKeys, force) << " force: " << force;
|
|
|
|
if (settingsKeys.contains("gain") || force)
|
|
{
|
|
m_linearGain = powf(10.0f, settings.m_gain/20.0f);
|
|
}
|
|
|
|
if (settingsKeys.contains("channelSampleRate") || force)
|
|
{
|
|
m_interpolator.create(16, m_channelSampleRate, settings.m_channelSampleRate / 2.0);
|
|
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) settings.m_channelSampleRate;
|
|
m_interpolatorDistanceRemain = m_interpolatorDistance;
|
|
}
|
|
|
|
// Update time limit for connected clients
|
|
if (settingsKeys.contains("timeLimit") && (m_settings.m_timeLimit != settings.m_timeLimit))
|
|
{
|
|
if (settings.m_timeLimit > 0)
|
|
{
|
|
// Set new timelimit
|
|
for (int i = 0; i < m_timers.size(); i++) {
|
|
m_timers[i]->setInterval(settings.m_timeLimit * 60 * 1000);
|
|
}
|
|
// Start timers if they weren't previously started
|
|
if (m_settings.m_timeLimit == 0)
|
|
{
|
|
for (int i = 0; i < std::min((int) m_timers.size(), m_settings.m_maxClients); i++) {
|
|
m_timers[i]->start();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Stop any existing timers
|
|
for (int i = 0; i < m_timers.size(); i++) {
|
|
m_timers[i]->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((settingsKeys.contains("compressionLevel") && (settings.m_compressionLevel != m_settings.m_compressionLevel))
|
|
|| (settingsKeys.contains("compression") && (settings.m_compression != m_settings.m_compression))
|
|
|| (settingsKeys.contains("sampleBits") && (settings.m_sampleBits != m_settings.m_sampleBits))
|
|
|| (settingsKeys.contains("blockSize") && (settings.m_blockSize != m_settings.m_blockSize))
|
|
|| (settingsKeys.contains("channelSampleRate") && (settings.m_channelSampleRate != m_settings.m_channelSampleRate))
|
|
|| force)
|
|
{
|
|
initFLAC = true;
|
|
}
|
|
|
|
if ((settingsKeys.contains("compressionLevel") && (settings.m_compressionLevel != m_settings.m_compressionLevel))
|
|
|| (settingsKeys.contains("compression") && (settings.m_compression != m_settings.m_compression))
|
|
|| force)
|
|
{
|
|
initZLib = true;
|
|
}
|
|
|
|
if (settingsKeys.contains("squelch") || force)
|
|
{
|
|
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
|
|
m_movingAverage.reset();
|
|
m_squelchCount = 0;
|
|
}
|
|
|
|
if (settingsKeys.contains("squelchGate") || force) {
|
|
m_squelchDelayLine.resize(settings.m_squelchGate * m_channelSampleRate + 1);
|
|
}
|
|
|
|
// Do clients need to reconnect to get these updated settings?
|
|
// settingsKeys will be empty if force is set
|
|
bool restart = (settingsKeys.contains("dataAddress") && (m_settings.m_dataAddress != settings.m_dataAddress))
|
|
|| (settingsKeys.contains("dataPort") && (m_settings.m_dataPort != settings.m_dataPort))
|
|
|| (settingsKeys.contains("certificate") && (m_settings.m_certificate != settings.m_certificate))
|
|
|| (settingsKeys.contains("key") && (m_settings.m_key!= settings.m_key))
|
|
|| (settingsKeys.contains("sampleBits") && (m_settings.m_sampleBits != settings.m_sampleBits))
|
|
|| (settingsKeys.contains("protocol") && (m_settings.m_protocol != settings.m_protocol))
|
|
|| (settingsKeys.contains("compression") && (m_settings.m_compression != settings.m_compression))
|
|
|| (settingsKeys.contains("remoteControl") && (m_settings.m_remoteControl != settings.m_remoteControl))
|
|
|| initFLAC
|
|
|| restartRequired
|
|
;
|
|
|
|
if (!restart && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0) && !m_settings.m_iqOnly)
|
|
{
|
|
// Forward settings to clients if they've changed
|
|
if ((settingsKeys.contains("channelSampleRate") || force) && (settings.m_channelSampleRate != m_settings.m_channelSampleRate)) {
|
|
sendCommand(RemoteTCPProtocol::setChannelSampleRate, settings.m_channelSampleRate);
|
|
}
|
|
if ((settingsKeys.contains("inputFrequencyOffset") || force) && (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)) {
|
|
sendCommand(RemoteTCPProtocol::setChannelFreqOffset, settings.m_inputFrequencyOffset);
|
|
}
|
|
if ((settingsKeys.contains("gain") || force) && (settings.m_gain != m_settings.m_gain)) {
|
|
sendCommand(RemoteTCPProtocol::setChannelGain, settings.m_gain);
|
|
}
|
|
if ((settingsKeys.contains("sampleBits") || force) && (settings.m_sampleBits != m_settings.m_sampleBits)) {
|
|
sendCommand(RemoteTCPProtocol::setSampleBitDepth, settings.m_sampleBits);
|
|
}
|
|
if ((settingsKeys.contains("squelchEnabled") || force) && (settings.m_squelchEnabled != m_settings.m_squelchEnabled)) {
|
|
sendCommand(RemoteTCPProtocol::setIQSquelchEnabled, (quint32) settings.m_squelchEnabled);
|
|
}
|
|
if ((settingsKeys.contains("squelch") || force) && (settings.m_squelch != m_settings.m_squelch)) {
|
|
sendCommandFloat(RemoteTCPProtocol::setIQSquelch, settings.m_squelch);
|
|
}
|
|
if ((settingsKeys.contains("squelchGate") || force) && (settings.m_squelchGate != m_settings.m_squelchGate)) {
|
|
sendCommandFloat(RemoteTCPProtocol::setIQSquelchGate, settings.m_squelchGate);
|
|
}
|
|
// m_remoteControl rather than restart?
|
|
}
|
|
|
|
if (force) {
|
|
m_settings = settings;
|
|
} else {
|
|
m_settings.applySettings(settingsKeys, settings);
|
|
}
|
|
|
|
if (m_running && (restart || force)) {
|
|
startServer();
|
|
}
|
|
|
|
if (initFLAC && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC))
|
|
{
|
|
if (m_encoder)
|
|
{
|
|
// Delete existing decoder
|
|
FLAC__stream_encoder_finish(m_encoder);
|
|
FLAC__stream_encoder_delete(m_encoder);
|
|
m_encoder = nullptr;
|
|
m_flacHeader.clear();
|
|
}
|
|
|
|
// Create FLAC encoder
|
|
FLAC__StreamEncoderInitStatus init_status;
|
|
m_encoder = FLAC__stream_encoder_new();
|
|
if (m_encoder)
|
|
{
|
|
const int maxSampleRate = 176400; // Spec says max is 655350, but doesn't seem to work
|
|
FLAC__bool ok = true;
|
|
|
|
ok &= FLAC__stream_encoder_set_verify(m_encoder, false);
|
|
ok &= FLAC__stream_encoder_set_compression_level(m_encoder, m_settings.m_compressionLevel);
|
|
ok &= FLAC__stream_encoder_set_channels(m_encoder, 2);
|
|
ok &= FLAC__stream_encoder_set_bits_per_sample(m_encoder, m_settings.m_sampleBits);
|
|
// We'll get FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE if we use the real sample rate
|
|
if (m_settings.m_channelSampleRate < maxSampleRate) {
|
|
ok &= FLAC__stream_encoder_set_sample_rate(m_encoder, m_settings.m_channelSampleRate);
|
|
} else {
|
|
ok &= FLAC__stream_encoder_set_sample_rate(m_encoder, maxSampleRate);
|
|
}
|
|
ok &= FLAC__stream_encoder_set_total_samples_estimate(m_encoder, 0);
|
|
//ok &= FLAC__stream_encoder_set_do_mid_side_stereo(m_encoder, false);
|
|
// FLAC__MAX_BLOCK_SIZE is 65536
|
|
// However, FLAC__format_blocksize_is_subset says anything over 16384 is not streamable
|
|
// Also, if sampleRate <= 48000, then max block size is 4608
|
|
if (FLAC__format_blocksize_is_subset(m_settings.m_blockSize, m_settings.m_channelSampleRate)) {
|
|
ok &= FLAC__stream_encoder_set_blocksize(m_encoder, m_settings.m_blockSize);
|
|
} else {
|
|
ok &= FLAC__stream_encoder_set_blocksize(m_encoder, 4096);
|
|
}
|
|
if (ok)
|
|
{
|
|
init_status = FLAC__stream_encoder_init_stream(m_encoder, flacWriteCallback, nullptr, nullptr, nullptr, this);
|
|
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::applySettings: Error initializing FLAC encoder:" << FLAC__StreamEncoderInitStatusString[init_status];
|
|
FLAC__stream_encoder_delete(m_encoder);
|
|
m_encoder = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::applySettings: Failed to configure FLAC encoder";
|
|
FLAC__stream_encoder_delete(m_encoder);
|
|
m_encoder = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::applySettings: Failed to allocate FLAC encoder";
|
|
}
|
|
|
|
m_bytesUncompressed = 0;
|
|
m_bytesCompressed = 0;
|
|
}
|
|
|
|
if (initZLib && (m_settings.m_compression == RemoteTCPSinkSettings::ZLIB))
|
|
{
|
|
// Intialise zlib compression
|
|
m_zStream.zalloc = nullptr;
|
|
m_zStream.zfree = nullptr;
|
|
m_zStream.opaque = nullptr;
|
|
m_zStream.data_type = Z_BINARY;
|
|
int windowBits = log2(m_settings.m_blockSize);
|
|
|
|
if (Z_OK == deflateInit2(&m_zStream, m_settings.m_compressionLevel, Z_DEFLATED, windowBits, 9, Z_DEFAULT_STRATEGY))
|
|
{
|
|
m_zStreamInitialised = true;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::applySettings: deflateInit failed";
|
|
m_zStreamInitialised = false;
|
|
}
|
|
|
|
m_bytesUncompressed = 0;
|
|
m_bytesCompressed = 0;
|
|
}
|
|
|
|
}
|
|
|
|
void RemoteTCPSinkSink::startServer()
|
|
{
|
|
stopServer();
|
|
|
|
if (m_settings.m_protocol == RemoteTCPSinkSettings::SDRA_WSS)
|
|
{
|
|
#ifndef QT_NO_OPENSSL
|
|
QSslConfiguration sslConfiguration;
|
|
qDebug() << "RemoteTCPSinkSink::startServer: SSL config: " << m_settings.m_certificate << m_settings.m_key;
|
|
if (m_settings.m_certificate.isEmpty())
|
|
{
|
|
QString msg = "RemoteTCPSink requires an SSL certificate in order to use wss protocol";
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
return;
|
|
}
|
|
if (m_settings.m_certificate.isEmpty())
|
|
{
|
|
QString msg = "RemoteTCPSink requires an SSL key in order to use wss protocol";
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
return;
|
|
}
|
|
QFile certFile(m_settings.m_certificate);
|
|
if (!certFile.open(QIODevice::ReadOnly))
|
|
{
|
|
QString msg = QString("RemoteTCPSink failed to open certificate %1: %2").arg(m_settings.m_certificate).arg(certFile.errorString());
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
return;
|
|
}
|
|
QFile keyFile(m_settings.m_key);
|
|
if (!keyFile.open(QIODevice::ReadOnly))
|
|
{
|
|
QString msg = QString("RemoteTCPSink failed to open key %1: %2").arg(m_settings.m_key).arg(keyFile.errorString());
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
return;
|
|
}
|
|
QSslCertificate certificate(&certFile, QSsl::Pem);
|
|
QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
|
|
certFile.close();
|
|
keyFile.close();
|
|
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
|
|
sslConfiguration.setLocalCertificate(certificate);
|
|
sslConfiguration.setPrivateKey(sslKey);
|
|
|
|
m_webSocketServer = new QWebSocketServer(QStringLiteral("Remote TCP Sink"),
|
|
QWebSocketServer::SecureMode,
|
|
this);
|
|
m_webSocketServer->setSslConfiguration(sslConfiguration);
|
|
|
|
QHostAddress address(m_settings.m_dataAddress);
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
|
|
m_webSocketServer->setSupportedSubprotocols({"binary"}); // Chrome wont connect without this - "Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received"
|
|
#endif
|
|
if (!m_webSocketServer->listen(address, m_settings.m_dataPort))
|
|
{
|
|
QString msg = QString("RemoteTCPSink failed to listen on %1 port %2: %3").arg(m_settings.m_dataAddress).arg(m_settings.m_dataPort).arg(m_webSocketServer->errorString());
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qInfo() << "RemoteTCPSink listening on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
|
|
connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &RemoteTCPSinkSink::acceptWebConnection);
|
|
connect(m_webSocketServer, &QWebSocketServer::sslErrors, this, &RemoteTCPSinkSink::onSslErrors);
|
|
}
|
|
#else
|
|
QString msg = "RemoteTCPSink unable to use wss protocol as SSL is not supported";
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
m_server = new QTcpServer(this);
|
|
if (!m_server->listen(QHostAddress(m_settings.m_dataAddress), m_settings.m_dataPort))
|
|
{
|
|
QString msg = QString("RemoteTCPSink failed to listen on %1 port %2: %3").arg(m_settings.m_dataAddress).arg(m_settings.m_dataPort).arg(m_webSocketServer->errorString());
|
|
qWarning() << msg;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgError::create(msg));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qInfo() << "RemoteTCPSink listening on" << m_settings.m_dataAddress << "port" << m_settings.m_dataPort;
|
|
connect(m_server, &QTcpServer::newConnection, this, &RemoteTCPSinkSink::acceptTCPConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::stopServer()
|
|
{
|
|
// Close connections to any existing clients
|
|
while (m_clients.size() > 0) {
|
|
m_clients[0]->close(); // This results in disconnected() being called, where we delete and remove from m_clients
|
|
}
|
|
|
|
// Close server sockets
|
|
if (m_server)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::stopServer: Closing old socket server";
|
|
m_server->close();
|
|
m_server->deleteLater();
|
|
m_server = nullptr;
|
|
}
|
|
if (m_webSocketServer)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::stopServer: Closing old web socket server";
|
|
m_webSocketServer->close();
|
|
m_webSocketServer->deleteLater();
|
|
m_webSocketServer = nullptr;
|
|
}
|
|
}
|
|
|
|
RemoteTCPProtocol::Device RemoteTCPSinkSink::getDevice()
|
|
{
|
|
DeviceAPI *deviceAPI = MainCore::instance()->getDevice(m_deviceIndex);
|
|
if (deviceAPI)
|
|
{
|
|
QString id = deviceAPI->getHardwareId();
|
|
QHash<QString, RemoteTCPProtocol::Device> map = {
|
|
{"Airspy", RemoteTCPProtocol::AIRSPY},
|
|
{"AirspyHF", RemoteTCPProtocol::AIRSPY_HF},
|
|
{"AudioInput", RemoteTCPProtocol::AUDIO_INPUT},
|
|
{"BladeRF1", RemoteTCPProtocol::BLADE_RF1},
|
|
{"BladeRF2", RemoteTCPProtocol::BLADE_RF2},
|
|
{"FCDPro", RemoteTCPProtocol::FCD_PRO},
|
|
{"FCDProPlus", RemoteTCPProtocol::FCD_PRO_PLUS},
|
|
{"FileInput", RemoteTCPProtocol::FILE_INPUT},
|
|
{"HackRF", RemoteTCPProtocol::HACK_RF},
|
|
{"KiwiSDR", RemoteTCPProtocol::KIWI_SDR},
|
|
{"LimeSDR", RemoteTCPProtocol::LIME_SDR},
|
|
{"LocalInput", RemoteTCPProtocol::LOCAL_INPUT},
|
|
{"Perseus", RemoteTCPProtocol::PERSEUS},
|
|
{"PlutoSDR", RemoteTCPProtocol::PLUTO_SDR},
|
|
{"RemoteInput", RemoteTCPProtocol::REMOTE_INPUT},
|
|
{"RemoteTCPInput", RemoteTCPProtocol::REMOTE_TCP_INPUT},
|
|
{"SDRplay1", RemoteTCPProtocol::SDRPLAY_1},
|
|
{"SigMFFileInput", RemoteTCPProtocol::SIGMF_FILE_INPUT},
|
|
{"SoapySDR", RemoteTCPProtocol::SOAPY_SDR},
|
|
{"TestSource", RemoteTCPProtocol::TEST_SOURCE},
|
|
{"USRP", RemoteTCPProtocol::USRP},
|
|
{"XTRX", RemoteTCPProtocol::XTRX},
|
|
};
|
|
if (map.contains(id))
|
|
{
|
|
return map.value(id);
|
|
}
|
|
else if (id == "SDRplayV3")
|
|
{
|
|
QString deviceType;
|
|
if (ChannelWebAPIUtils::getDeviceReportValue(m_deviceIndex, "deviceType", deviceType))
|
|
{
|
|
QHash<QString, RemoteTCPProtocol::Device> sdrplayMap = {
|
|
{"RSP1", RemoteTCPProtocol::SDRPLAY_V3_RSP1},
|
|
{"RSP1A", RemoteTCPProtocol::SDRPLAY_V3_RSP1A},
|
|
{"RSP1B", RemoteTCPProtocol::SDRPLAY_V3_RSP1B},
|
|
{"RSP2", RemoteTCPProtocol::SDRPLAY_V3_RSP2},
|
|
{"RSPduo", RemoteTCPProtocol::SDRPLAY_V3_RSPDUO},
|
|
{"RSPdx", RemoteTCPProtocol::SDRPLAY_V3_RSPDX},
|
|
};
|
|
if (sdrplayMap.contains(deviceType)) {
|
|
return sdrplayMap.value(deviceType);
|
|
}
|
|
qDebug() << "RemoteTCPSinkSink::getDevice: Unknown SDRplayV3 deviceType: " << deviceType;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::getDevice: Failed to get deviceType for SDRplayV3";
|
|
}
|
|
}
|
|
else if (id == "RTLSDR")
|
|
{
|
|
QString tunerType;
|
|
if (ChannelWebAPIUtils::getDeviceReportValue(m_deviceIndex, "tunerType", tunerType))
|
|
{
|
|
QHash<QString, RemoteTCPProtocol::Device> rtlsdrMap = {
|
|
{"E4000", RemoteTCPProtocol::RTLSDR_E4000},
|
|
{"FC0012", RemoteTCPProtocol::RTLSDR_FC0012},
|
|
{"FC0013", RemoteTCPProtocol::RTLSDR_FC0013},
|
|
{"FC2580", RemoteTCPProtocol::RTLSDR_FC2580},
|
|
{"R820T", RemoteTCPProtocol::RTLSDR_R820T},
|
|
{"R828D", RemoteTCPProtocol::RTLSDR_R828D},
|
|
};
|
|
if (rtlsdrMap.contains(tunerType)) {
|
|
return rtlsdrMap.value(tunerType);
|
|
}
|
|
qDebug() << "RemoteTCPSinkSink::getDevice: Unknown RTLSDR tunerType: " << tunerType;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::getDevice: Failed to get tunerType for RTLSDR";
|
|
}
|
|
}
|
|
}
|
|
return RemoteTCPProtocol::UNKNOWN;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::acceptWebConnection()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
QWebSocket *client = m_webSocketServer->nextPendingConnection();
|
|
|
|
connect(client, &QWebSocket::binaryMessageReceived, this, &RemoteTCPSinkSink::processCommand);
|
|
connect(client, &QWebSocket::disconnected, this, &RemoteTCPSinkSink::disconnected);
|
|
qDebug() << "RemoteTCPSinkSink::acceptWebConnection: client connected";
|
|
|
|
// https://bugreports.qt.io/browse/QTBUG-125874
|
|
QTimer::singleShot(200, this, [this, client] () {
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
m_clients.append(new WebSocket(client));
|
|
acceptConnection(m_clients.last());
|
|
});
|
|
}
|
|
|
|
void RemoteTCPSinkSink::acceptTCPConnection()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
QTcpSocket *client = m_server->nextPendingConnection();
|
|
|
|
connect(client, &QIODevice::readyRead, this, &RemoteTCPSinkSink::processCommand);
|
|
connect(client, &QTcpSocket::disconnected, this, &RemoteTCPSinkSink::disconnected);
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
connect(client, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &RemoteTCPSinkSink::errorOccurred);
|
|
#else
|
|
connect(client, &QAbstractSocket::errorOccurred, this, &RemoteTCPSinkSink::errorOccurred);
|
|
#endif
|
|
qDebug() << "RemoteTCPSinkSink::acceptTCPConnection: client connected";
|
|
|
|
QTimer::singleShot(200, this, [this, client] () {
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
m_clients.append(new TCPSocket(client));
|
|
acceptConnection(m_clients.last());
|
|
});
|
|
}
|
|
|
|
FLAC__StreamEncoderWriteStatus RemoteTCPSinkSink::flacWrite(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t currentFrame)
|
|
{
|
|
(void) encoder;
|
|
|
|
char header[1+4];
|
|
|
|
// Save FLAC header for clients that connect later
|
|
if ((currentFrame == 0) && (samples == 0))
|
|
{
|
|
m_flacHeader.append((const char *) buffer, bytes);
|
|
|
|
// Write complete header to all clients
|
|
if (m_flacHeader.size() == m_flacHeaderSize)
|
|
{
|
|
header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], m_flacHeader.size());
|
|
|
|
for (auto client : m_clients)
|
|
{
|
|
client->write(header, sizeof(header));
|
|
client->write(m_flacHeader.constData(), m_flacHeader.size());
|
|
m_bytesTransmitted += sizeof(header) + m_flacHeader.size();
|
|
client->flush();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Send compressed IQ data to max number of clients
|
|
header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], bytes);
|
|
int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
|
|
for (int i = 0; i < clients; i++)
|
|
{
|
|
Socket *client = m_clients[i];
|
|
client->write(header, sizeof(header));
|
|
client->write((const char *) buffer, bytes);
|
|
m_bytesTransmitted += sizeof(header) + bytes;
|
|
client->flush();
|
|
}
|
|
}
|
|
m_bytesCompressed += sizeof(header) + bytes;
|
|
|
|
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::acceptConnection(Socket *client)
|
|
{
|
|
if (m_settings.m_protocol == RemoteTCPSinkSettings::RTL0)
|
|
{
|
|
quint8 metaData[RemoteTCPProtocol::m_rtl0MetaDataSize] = {'R', 'T', 'L', '0'};
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[4], getDevice()); // Tuner ID
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[8], 1); // Gain stages
|
|
client->write((const char *)metaData, sizeof(metaData));
|
|
m_bytesTransmitted += sizeof(metaData);
|
|
client->flush();
|
|
}
|
|
else
|
|
{
|
|
quint8 metaData[RemoteTCPProtocol::m_sdraMetaDataSize] = {'S', 'D', 'R', 'A'};
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[4], getDevice());
|
|
|
|
// Send device/channel settings, so they can be displayed in the remote GUI
|
|
ChannelWebAPIUtils::getCenterFrequency(m_deviceIndex, m_centerFrequency);
|
|
ChannelWebAPIUtils::getLOPpmCorrection(m_deviceIndex, m_ppmCorrection);
|
|
ChannelWebAPIUtils::getDevSampleRate(m_deviceIndex, m_devSampleRate);
|
|
ChannelWebAPIUtils::getSoftDecim(m_deviceIndex, m_log2Decim);
|
|
for (int i = 0; i < 4; i++) {
|
|
ChannelWebAPIUtils::getGain(m_deviceIndex, i, m_gain[i]);
|
|
}
|
|
ChannelWebAPIUtils::getRFBandwidth(m_deviceIndex, m_rfBW);
|
|
ChannelWebAPIUtils::getBiasTee(m_deviceIndex, m_biasTeeEnabled);
|
|
ChannelWebAPIUtils::getDeviceSetting(m_deviceIndex, "noModMode", m_directSampling);
|
|
ChannelWebAPIUtils::getAGC(m_deviceIndex, m_agc);
|
|
ChannelWebAPIUtils::getDCOffsetRemoval(m_deviceIndex, m_dcOffsetRemoval);
|
|
ChannelWebAPIUtils::getIQCorrection(m_deviceIndex, m_iqCorrection);
|
|
|
|
quint32 flags = ((!m_settings.m_iqOnly) << 7)
|
|
| (m_settings.m_remoteControl << 6)
|
|
| (m_settings.m_squelchEnabled << 5)
|
|
| (m_iqCorrection << 4)
|
|
| (m_dcOffsetRemoval << 3)
|
|
| (m_agc << 2)
|
|
| (m_directSampling << 1)
|
|
| m_biasTeeEnabled;
|
|
|
|
RemoteTCPProtocol::encodeUInt64(&metaData[8], (quint64)m_centerFrequency);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[16], m_ppmCorrection);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[20], flags);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[24], m_devSampleRate);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[28], m_log2Decim);
|
|
RemoteTCPProtocol::encodeInt16(&metaData[32], m_gain[0]);
|
|
RemoteTCPProtocol::encodeInt16(&metaData[34], m_gain[1]);
|
|
RemoteTCPProtocol::encodeInt16(&metaData[36], m_gain[2]);
|
|
RemoteTCPProtocol::encodeInt16(&metaData[38], m_gain[3]);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[40], m_rfBW);
|
|
RemoteTCPProtocol::encodeInt32(&metaData[44], m_settings.m_inputFrequencyOffset);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[48], m_settings.m_gain);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[52], m_settings.m_channelSampleRate);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[56], m_settings.m_sampleBits);
|
|
RemoteTCPProtocol::encodeUInt32(&metaData[60], 1); // Protocol revision. 0=64 byte meta data, 1=128 byte meta data
|
|
RemoteTCPProtocol::encodeFloat(&metaData[64], m_settings.m_squelch);
|
|
RemoteTCPProtocol::encodeFloat(&metaData[68], m_settings.m_squelchGate);
|
|
// Send API port? Not accessible via MainCore
|
|
|
|
client->write((const char *)metaData, sizeof(metaData));
|
|
m_bytesTransmitted += sizeof(metaData);
|
|
client->flush();
|
|
|
|
// Inform client if they are in a queue
|
|
if (!m_settings.m_iqOnly && (m_clients.size() > m_settings.m_maxClients) && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0)) {
|
|
sendQueuePosition(client, m_clients.size() - m_settings.m_maxClients);
|
|
}
|
|
|
|
// Send existing FLAC header to new client
|
|
if (!m_settings.m_iqOnly && (m_settings.m_compression == RemoteTCPSinkSettings::FLAC) && (m_flacHeader.size() == m_flacHeaderSize) && (m_settings.m_protocol != RemoteTCPSinkSettings::RTL0))
|
|
{
|
|
char header[1+4];
|
|
|
|
header[0] = (char) RemoteTCPProtocol::dataIQFLAC;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &header[1], m_flacHeader.size());
|
|
client->write(header, sizeof(header));
|
|
client->write(m_flacHeader.constData(), m_flacHeader.size());
|
|
m_bytesTransmitted += sizeof(header) + m_flacHeader.size();
|
|
client->flush();
|
|
}
|
|
|
|
// Send position / direction of antenna
|
|
sendPosition();
|
|
if (m_settings.m_isotropic) {
|
|
sendDirection(true, std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN());
|
|
} else if (m_settings.m_rotator == "None") {
|
|
sendDirection(false, m_settings.m_azimuth, m_settings.m_elevation);
|
|
} else {
|
|
sendRotatorDirection(true);
|
|
}
|
|
}
|
|
|
|
// Create timer to disconnect client after timelimit reached
|
|
QTimer *timer = new QTimer();
|
|
timer->setSingleShot(true);
|
|
timer->callOnTimeout(this, [this, client] () {
|
|
qDebug() << "Disconnecting" << client->peerAddress() << "as time limit reached";
|
|
if (m_settings.m_compression) {
|
|
sendTimeLimit(client);
|
|
}
|
|
client->close();
|
|
});
|
|
if (m_settings.m_timeLimit > 0)
|
|
{
|
|
timer->setInterval(m_settings.m_timeLimit * 60 * 1000);
|
|
// Only start timer if we will receive data immediately
|
|
if (m_clients.size() <= m_settings.m_maxClients) {
|
|
timer->start();
|
|
}
|
|
}
|
|
m_timers.append(timer);
|
|
|
|
// Close connection if blacklisted
|
|
for (const auto& ip : m_settings.m_ipBlacklist)
|
|
{
|
|
QHostAddress address(ip);
|
|
if (address == client->peerAddress())
|
|
{
|
|
qDebug() << "Disconnecting" << client->peerAddress() << "as blacklisted";
|
|
if (m_settings.m_compression) {
|
|
sendBlacklisted(client);
|
|
}
|
|
client->close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size(), client->peerAddress(), client->peerPort()));
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgReportConnection::create(m_clients.size(), client->peerAddress(), client->peerPort()));
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::disconnected()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
qDebug() << "RemoteTCPSinkSink::disconnected";
|
|
QObject *object = sender();
|
|
QMutableListIterator<Socket *> itr(m_clients);
|
|
|
|
// Remove from list of clients
|
|
int idx = 0;
|
|
while (itr.hasNext())
|
|
{
|
|
Socket *socket = itr.next();
|
|
if (socket->socket() == object)
|
|
{
|
|
itr.remove();
|
|
delete m_timers.takeAt(idx);
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgReportDisconnect::create(m_clients.size(), socket->peerAddress(), socket->peerPort()));
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgReportDisconnect::create(m_clients.size(), socket->peerAddress(), socket->peerPort()));
|
|
}
|
|
socket->deleteLater();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
// Start timer for next waiting client
|
|
if ((idx < m_settings.m_maxClients) && (m_settings.m_timeLimit > 0))
|
|
{
|
|
int newActiveIdx = m_settings.m_maxClients - 1;
|
|
if (newActiveIdx < m_clients.size()) {
|
|
m_timers[newActiveIdx]->start();
|
|
}
|
|
}
|
|
|
|
// Update other clients waiting with current queue position
|
|
for (int i = m_settings.m_maxClients; i < m_clients.size(); i++) {
|
|
sendQueuePosition(m_clients[i], i - m_settings.m_maxClients + 1);
|
|
}
|
|
}
|
|
|
|
#ifndef QT_NO_OPENSSL
|
|
void RemoteTCPSinkSink::onSslErrors(const QList<QSslError> &errors)
|
|
{
|
|
qWarning() << "RemoteTCPSinkSink::onSslErrors: " << errors;
|
|
}
|
|
#endif
|
|
|
|
void RemoteTCPSinkSink::errorOccurred(QAbstractSocket::SocketError socketError)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::errorOccurred: " << socketError;
|
|
/*if (m_msgQueueToFeature)
|
|
{
|
|
RemoteTCPSinkSink::MsgReportWorker *msg = RemoteTCPSinkSink::MsgReportWorker::create(m_socket.errorString() + " " + socketError);
|
|
m_msgQueueToFeature->push(msg);
|
|
}*/
|
|
}
|
|
|
|
Socket *RemoteTCPSinkSink::getSocket(QObject *object) const
|
|
{
|
|
for (const auto client : m_clients)
|
|
{
|
|
if (client->socket() == object) {
|
|
return client;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void RemoteTCPSinkSink::processCommand()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
Socket *client = getSocket(sender());
|
|
RemoteTCPSinkSettings settings = m_settings;
|
|
quint8 cmd[5];
|
|
|
|
while (client && (client->bytesAvailable() >= (qint64)sizeof(cmd)))
|
|
{
|
|
int len = client->read((char *)cmd, sizeof(cmd));
|
|
|
|
if (len == sizeof(cmd))
|
|
{
|
|
if (cmd[0] == RemoteTCPProtocol::sendMessage)
|
|
{
|
|
quint32 msgLen = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
try
|
|
{
|
|
char *buf = new char[msgLen];
|
|
len = client->read((char *)buf, msgLen);
|
|
if (len == (int) msgLen)
|
|
{
|
|
bool broadcast = (bool) buf[0];
|
|
int i;
|
|
for (i = 1; i < (int) msgLen; i++)
|
|
{
|
|
if (buf[i] == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
QString callsign = QString::fromUtf8(&buf[1]);
|
|
QString text = QString::fromUtf8(&buf[i+1]);
|
|
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgSendMessage::create(client->peerAddress(), client->peerPort(), callsign, text, broadcast));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: sendMessage: Failed to read" << msgLen;
|
|
}
|
|
delete[] buf;
|
|
}
|
|
catch(std::bad_alloc&)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: sendMessage - Failed to allocate" << msgLen;
|
|
}
|
|
}
|
|
else if (!m_settings.m_remoteControl)
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: Ignoring command from client as remote control disabled";
|
|
}
|
|
else
|
|
{
|
|
switch (cmd[0])
|
|
{
|
|
case RemoteTCPProtocol::setCenterFrequency:
|
|
{
|
|
quint64 centerFrequency = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set center frequency " << centerFrequency;
|
|
ChannelWebAPIUtils::setCenterFrequency(m_deviceIndex, (double)centerFrequency);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setSampleRate:
|
|
{
|
|
int sampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set sample rate " << sampleRate;
|
|
ChannelWebAPIUtils::setDevSampleRate(m_deviceIndex, sampleRate);
|
|
if (m_settings.m_protocol == RemoteTCPSinkSettings::RTL0)
|
|
{
|
|
// Match channel sample rate with device sample rate for RTL0 protocol
|
|
ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, 0);
|
|
settings.m_channelSampleRate = sampleRate;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setTunerGainMode:
|
|
// SDRangel's rtlsdr sample source always has this fixed as 1, so nothing to do
|
|
break;
|
|
case RemoteTCPProtocol::setTunerGain: // gain is gain in 10th of a dB
|
|
{
|
|
int gain = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set gain " << gain;
|
|
ChannelWebAPIUtils::setGain(m_deviceIndex, 0, gain);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setFrequencyCorrection:
|
|
{
|
|
int ppm = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set LO ppm correction " << ppm;
|
|
ChannelWebAPIUtils::setLOPpmCorrection(m_deviceIndex, ppm);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setTunerIFGain:
|
|
{
|
|
int v = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
int gain = (int)(qint16)(v & 0xffff);
|
|
int stage = (v >> 16) & 0xffff;
|
|
ChannelWebAPIUtils::setGain(m_deviceIndex, stage, gain);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setAGCMode:
|
|
{
|
|
int agc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set AGC " << agc;
|
|
ChannelWebAPIUtils::setAGC(m_deviceIndex, agc);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setDirectSampling:
|
|
{
|
|
int ds = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set direct sampling " << ds;
|
|
ChannelWebAPIUtils::patchDeviceSetting(m_deviceIndex, "noModMode", ds); // RTLSDR only
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setBiasTee:
|
|
{
|
|
int biasTee = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set bias tee " << biasTee;
|
|
ChannelWebAPIUtils::setBiasTee(m_deviceIndex, biasTee);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setTunerBandwidth:
|
|
{
|
|
int rfBW = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set tuner bandwidth " << rfBW;
|
|
ChannelWebAPIUtils::setRFBandwidth(m_deviceIndex, rfBW);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setDecimation:
|
|
{
|
|
int dec = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set decimation " << dec;
|
|
ChannelWebAPIUtils::setSoftDecim(m_deviceIndex, dec);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setDCOffsetRemoval:
|
|
{
|
|
int dc = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set DC offset removal " << dc;
|
|
ChannelWebAPIUtils::setDCOffsetRemoval(m_deviceIndex, dc);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setIQCorrection:
|
|
{
|
|
int iq = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set IQ correction " << iq;
|
|
ChannelWebAPIUtils::setIQCorrection(m_deviceIndex, iq);
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setChannelSampleRate:
|
|
{
|
|
int channelSampleRate = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set channel sample rate " << channelSampleRate;
|
|
bool restartRequired;
|
|
if (channelSampleRate <= m_settings.m_maxSampleRate)
|
|
{
|
|
settings.m_channelSampleRate = channelSampleRate;
|
|
restartRequired = false;
|
|
}
|
|
else
|
|
{
|
|
settings.m_channelSampleRate = m_settings.m_maxSampleRate;
|
|
restartRequired = true; // Need to restart so client gets max sample rate
|
|
}
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, restartRequired));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"channelSampleRate"}, false, restartRequired));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setChannelFreqOffset:
|
|
{
|
|
int offset = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set channel input frequency offset " << offset;
|
|
settings.m_inputFrequencyOffset = offset;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"inputFrequencyOffset"}, false));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setChannelGain:
|
|
{
|
|
int gain = (int)RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set channel gain " << gain;
|
|
settings.m_gain = gain;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"gain"}, false));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setSampleBitDepth:
|
|
{
|
|
int bits = RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set sample bit depth " << bits;
|
|
settings.m_sampleBits = bits;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"sampleBits"}, false));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setIQSquelchEnabled:
|
|
{
|
|
bool enabled = (bool) RemoteTCPProtocol::extractUInt32(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch enabled " << enabled;
|
|
settings.m_squelchEnabled = enabled;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchEnabled"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchEnabled"}, false));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setIQSquelch:
|
|
{
|
|
float squelch = RemoteTCPProtocol::extractFloat(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch " << squelch;
|
|
settings.m_squelch = squelch;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelch"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelch"}, false));
|
|
}
|
|
break;
|
|
}
|
|
case RemoteTCPProtocol::setIQSquelchGate:
|
|
{
|
|
float squelchGate = RemoteTCPProtocol::extractFloat(&cmd[1]);
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: set IQ squelch gate " << squelchGate;
|
|
settings.m_squelchGate = squelchGate;
|
|
if (m_messageQueueToGUI) {
|
|
m_messageQueueToGUI->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchGate"}, false));
|
|
}
|
|
if (m_messageQueueToChannel) {
|
|
m_messageQueueToChannel->push(RemoteTCPSink::MsgConfigureRemoteTCPSink::create(settings, {"squelchGate"}, false));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: unknown command " << cmd[0];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "RemoteTCPSinkSink::processCommand: read only " << len << " bytes - Expecting " << sizeof(cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendCommand(RemoteTCPProtocol::Command cmdId, quint32 value)
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
quint8 cmd[5];
|
|
|
|
cmd[0] = (quint8) cmdId;
|
|
RemoteTCPProtocol::encodeUInt32(&cmd[1], value);
|
|
|
|
for (const auto client : m_clients)
|
|
{
|
|
qint64 len = client->write((char *) cmd, sizeof(cmd));
|
|
if (len != sizeof(cmd)) {
|
|
qDebug() << "RemoteTCPSinkSink::sendCommand: Failed to write all of message:" << len;
|
|
}
|
|
m_bytesTransmitted += sizeof(cmd);
|
|
client->flush();
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendCommandFloat(RemoteTCPProtocol::Command cmdId, float value)
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
quint8 cmd[5];
|
|
|
|
cmd[0] = (quint8) cmdId;
|
|
RemoteTCPProtocol::encodeFloat(&cmd[1], value);
|
|
|
|
for (const auto client : m_clients)
|
|
{
|
|
qint64 len = client->write((char *) cmd, sizeof(cmd));
|
|
if (len != sizeof(cmd)) {
|
|
qDebug() << "RemoteTCPSinkSink::sendCommand: Failed to write all of message:" << len;
|
|
}
|
|
m_bytesTransmitted += sizeof(cmd);
|
|
client->flush();
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendMessage(QHostAddress address, quint16 port, const QString& callsign, const QString& text, bool broadcast)
|
|
{
|
|
qint64 len;
|
|
char cmd[1+4+1];
|
|
QByteArray callsignBytes = callsign.toUtf8();
|
|
QByteArray textBytes = text.toUtf8();
|
|
QByteArray bytes;
|
|
|
|
bytes.append(callsignBytes);
|
|
bytes.append('\0');
|
|
bytes.append(textBytes);
|
|
bytes.append('\0');
|
|
|
|
cmd[0] = (char) RemoteTCPProtocol::sendMessage;
|
|
RemoteTCPProtocol::encodeUInt32((quint8*) &cmd[1], bytes.size() + 1);
|
|
cmd[5] = (char) broadcast;
|
|
|
|
for (const auto client : m_clients)
|
|
{
|
|
bool addressMatch = (address == client->peerAddress()) && (port == client->peerPort());
|
|
if ((broadcast && !addressMatch) || (!broadcast && addressMatch))
|
|
{
|
|
len = client->write(cmd, sizeof(cmd));
|
|
if (len != sizeof(cmd)) {
|
|
qDebug() << "RemoteTCPSinkSink::sendMessage: Failed to write all of message header:" << len;
|
|
}
|
|
len = client->write(bytes.data(), bytes.size());
|
|
if (len != bytes.size()) {
|
|
qDebug() << "RemoteTCPSinkSink::sendMessage: Failed to write all of message:" << len;
|
|
}
|
|
m_bytesTransmitted += sizeof(cmd) + bytes.size();
|
|
client->flush();
|
|
qDebug() << "RemoteTCPSinkSink::sendMessage:" << client->peerAddress() << client->peerPort() << text;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendQueuePosition(Socket *client, int position)
|
|
{
|
|
QString callsign = MainCore::instance()->getSettings().getStationName();
|
|
|
|
sendMessage(client->peerAddress(), client->peerPort(), callsign, QString("Server busy. You are number %1 in the queue.").arg(position), false);
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendBlacklisted(Socket *client)
|
|
{
|
|
char cmd[1+4];
|
|
|
|
cmd[0] = (char) RemoteTCPProtocol::sendBlacklistedMessage;
|
|
RemoteTCPProtocol::encodeUInt32((quint8*) &cmd[1], 0);
|
|
|
|
qint64 len = client->write(cmd, sizeof(cmd));
|
|
if (len != sizeof(cmd)) {
|
|
qDebug() << "RemoteTCPSinkSink::sendBlacklisted: Failed to write all of message:" << len;
|
|
}
|
|
m_bytesTransmitted += sizeof(cmd);
|
|
client->flush();
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendTimeLimit(Socket *client)
|
|
{
|
|
QString callsign = MainCore::instance()->getSettings().getStationName();
|
|
|
|
sendMessage(client->peerAddress(), client->peerPort(), callsign, "Time limit reached.", false);
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendPosition(float latitude, float longitude, float altitude)
|
|
{
|
|
char msg[1+4+4+4+4];
|
|
msg[0] = (char) RemoteTCPProtocol::dataPosition;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1], 4+4+4);
|
|
RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4], latitude);
|
|
RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4], longitude);
|
|
RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4+4], altitude);
|
|
|
|
int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
|
|
for (int i = 0; i < clients; i++)
|
|
{
|
|
Socket *client = m_clients[i];
|
|
client->write(msg, sizeof(msg));
|
|
m_bytesTransmitted += sizeof(msg);
|
|
client->flush();
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendDirection(bool isotropic, float azimuth, float elevation)
|
|
{
|
|
char msg[1+4+4+4+4];
|
|
msg[0] = (char) RemoteTCPProtocol::dataDirection;
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1], 4+4+4);
|
|
RemoteTCPProtocol::encodeUInt32((quint8 *) &msg[1+4], isotropic);
|
|
RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4], azimuth);
|
|
RemoteTCPProtocol::encodeFloat((quint8 *) &msg[1+4+4+4], elevation);
|
|
|
|
int clients = std::min((int) m_clients.size(), m_settings.m_maxClients);
|
|
for (int i = 0; i < clients; i++)
|
|
{
|
|
Socket *client = m_clients[i];
|
|
client->write(msg, sizeof(msg));
|
|
m_bytesTransmitted += sizeof(msg);
|
|
client->flush();
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendPosition()
|
|
{
|
|
float latitude = MainCore::instance()->getSettings().getLatitude();
|
|
float longitude = MainCore::instance()->getSettings().getLongitude();
|
|
float altitude = MainCore::instance()->getSettings().getAltitude();
|
|
|
|
// Use device postion in preference to My Position
|
|
ChannelWebAPIUtils::getDevicePosition(m_deviceIndex, latitude, longitude, altitude);
|
|
|
|
sendPosition(latitude, longitude, altitude);
|
|
}
|
|
|
|
void RemoteTCPSinkSink::sendRotatorDirection(bool force)
|
|
{
|
|
unsigned int rotatorFeatureSetIndex;
|
|
unsigned int rotatorFeatureIndex;
|
|
|
|
if (MainCore::getFeatureIndexFromId(m_settings.m_rotator, rotatorFeatureSetIndex, rotatorFeatureIndex))
|
|
{
|
|
double azimuth;
|
|
double elevation;
|
|
|
|
if (ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentAzimuth", azimuth)
|
|
&& ChannelWebAPIUtils::getFeatureReportValue(rotatorFeatureSetIndex, rotatorFeatureIndex, "currentElevation", elevation))
|
|
{
|
|
if (force || ((azimuth != m_azimuth) || (elevation != m_elevation)))
|
|
{
|
|
sendDirection(false, (float) azimuth, (float) elevation);
|
|
m_azimuth = azimuth;
|
|
m_elevation = elevation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteTCPSinkSink::preferenceChanged(int elementType)
|
|
{
|
|
Preferences::ElementType pref = (Preferences::ElementType)elementType;
|
|
|
|
if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude)) {
|
|
sendPosition();
|
|
}
|
|
}
|
|
|
|
// Poll for changes to device settings - FIXME: Need a signal from DeviceAPI - reverseAPI?
|
|
void RemoteTCPSinkSink::checkDeviceSettings()
|
|
{
|
|
if ((m_settings.m_protocol != RemoteTCPSinkSettings::RTL0) && !m_settings.m_iqOnly)
|
|
{
|
|
// Forward device settings to clients if they've changed
|
|
|
|
double centerFrequency;
|
|
if (ChannelWebAPIUtils::getCenterFrequency(m_deviceIndex, centerFrequency))
|
|
{
|
|
if (centerFrequency != m_centerFrequency)
|
|
{
|
|
m_centerFrequency = centerFrequency;
|
|
sendCommand(RemoteTCPProtocol::setCenterFrequency, m_centerFrequency);
|
|
}
|
|
}
|
|
|
|
int ppmCorrection;
|
|
if (ChannelWebAPIUtils::getLOPpmCorrection(m_deviceIndex, ppmCorrection))
|
|
{
|
|
if (ppmCorrection != m_ppmCorrection)
|
|
{
|
|
m_ppmCorrection = ppmCorrection;
|
|
sendCommand(RemoteTCPProtocol::setFrequencyCorrection, m_ppmCorrection);
|
|
}
|
|
}
|
|
|
|
int biasTeeEnabled;
|
|
if (ChannelWebAPIUtils::getBiasTee(m_deviceIndex, biasTeeEnabled))
|
|
{
|
|
if (biasTeeEnabled != m_biasTeeEnabled)
|
|
{
|
|
m_biasTeeEnabled = biasTeeEnabled;
|
|
sendCommand(RemoteTCPProtocol::setBiasTee, m_biasTeeEnabled);
|
|
}
|
|
}
|
|
|
|
int directSampling;
|
|
if (ChannelWebAPIUtils::getDeviceSetting(m_deviceIndex, "noModMode", directSampling))
|
|
{
|
|
if (directSampling != m_directSampling)
|
|
{
|
|
m_directSampling = directSampling;
|
|
sendCommand(RemoteTCPProtocol::setDirectSampling, m_directSampling);
|
|
}
|
|
}
|
|
|
|
int agc;
|
|
if (ChannelWebAPIUtils::getAGC(m_deviceIndex, agc))
|
|
{
|
|
if (agc != m_agc)
|
|
{
|
|
m_agc = agc;
|
|
sendCommand(RemoteTCPProtocol::setAGCMode, m_agc);
|
|
}
|
|
}
|
|
|
|
int dcOffsetRemoval;
|
|
if (ChannelWebAPIUtils::getDCOffsetRemoval(m_deviceIndex, dcOffsetRemoval))
|
|
{
|
|
if (dcOffsetRemoval != m_dcOffsetRemoval)
|
|
{
|
|
m_dcOffsetRemoval = dcOffsetRemoval;
|
|
sendCommand(RemoteTCPProtocol::setDCOffsetRemoval, m_dcOffsetRemoval);
|
|
}
|
|
}
|
|
|
|
int iqCorrection;
|
|
if (ChannelWebAPIUtils::getIQCorrection(m_deviceIndex, iqCorrection))
|
|
{
|
|
if (iqCorrection != m_iqCorrection)
|
|
{
|
|
m_iqCorrection = iqCorrection;
|
|
sendCommand(RemoteTCPProtocol::setIQCorrection, m_iqCorrection);
|
|
}
|
|
}
|
|
|
|
qint32 devSampleRate;
|
|
if (ChannelWebAPIUtils::getDevSampleRate(m_deviceIndex, devSampleRate))
|
|
{
|
|
if (devSampleRate != m_devSampleRate)
|
|
{
|
|
m_devSampleRate = devSampleRate;
|
|
sendCommand(RemoteTCPProtocol::setSampleRate, m_devSampleRate);
|
|
}
|
|
}
|
|
|
|
qint32 log2Decim;
|
|
if (ChannelWebAPIUtils::getSoftDecim(m_deviceIndex, log2Decim))
|
|
{
|
|
if (log2Decim != m_log2Decim)
|
|
{
|
|
m_log2Decim = log2Decim;
|
|
sendCommand(RemoteTCPProtocol::setDecimation, m_log2Decim);
|
|
}
|
|
}
|
|
|
|
|
|
qint32 rfBW;
|
|
if (ChannelWebAPIUtils::getRFBandwidth(m_deviceIndex, rfBW))
|
|
{
|
|
if (rfBW != m_rfBW)
|
|
{
|
|
m_rfBW = rfBW;
|
|
sendCommand(RemoteTCPProtocol::setTunerBandwidth, m_rfBW);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
qint32 gain;
|
|
if (ChannelWebAPIUtils::getGain(m_deviceIndex, i, gain))
|
|
{
|
|
if (gain != m_gain[i])
|
|
{
|
|
m_gain[i] = gain;
|
|
if (i == 0)
|
|
{
|
|
sendCommand(RemoteTCPProtocol::setTunerGain, gain);
|
|
}
|
|
else
|
|
{
|
|
int v = (gain & 0xffff) | (i << 16);
|
|
sendCommand(RemoteTCPProtocol::setTunerIFGain, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_settings.m_isotropic && !m_settings.m_rotator.isEmpty() && (m_settings.m_rotator != "None")) {
|
|
sendRotatorDirection(false);
|
|
}
|
|
|
|
}
|
|
}
|