1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-30 03:38:55 -05:00
sdrangel/plugins/samplesource/aaroniartsainput/aaroniartsainputworker.cpp
2024-07-10 23:06:38 +02:00

424 lines
14 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Daniele Forsi <iu5hkx@gmail.com> //
// Copyright (C) 2023 Frank Werner <mail@hb9fxq.ch> //
// Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.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 <boost/endian/conversion.hpp>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include "util/messagequeue.h"
#include "aaroniartsainputsettings.h"
#include "aaroniartsainputworker.h"
MESSAGE_CLASS_DEFINITION(AaroniaRTSAInputWorker::MsgReportSampleRateAndFrequency, Message)
AaroniaRTSAInputWorker::AaroniaRTSAInputWorker(SampleSinkFifo* sampleFifo) :
QObject(),
m_timer(this),
m_samplesBuf(),
m_sampleFifo(sampleFifo),
m_centerFrequency(0),
m_sampleRate(1),
m_inputMessageQueue(nullptr),
m_status(AaroniaRTSAInputSettings::ConnectionIdle),
mReply(nullptr),
m_convertBuffer(64e6)
{
// Initialize network managers
m_networkAccessManager = new QNetworkAccessManager(this);
m_networkAccessManagerConfig = new QNetworkAccessManager(this);
QObject::connect(
m_networkAccessManagerConfig,
&QNetworkAccessManager::finished,
this,
&AaroniaRTSAInputWorker::handleConfigReply
);
// Request 16bit raw samples
// m_serverAddress = "localhost:55123";
// QUrl url(tr("http://%1/stream?format=float32").arg(m_serverAddress));
// QNetworkRequest req(url);
// mReply = m_networkAccessManager->get(req);
// // Connect Qt slots to network events
// connect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
// connect(mReply, SIGNAL(finished()), this, SLOT(onFinished()));
// connect(mReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
mPrevTime = 0;
mPacketSamples = 0;
}
AaroniaRTSAInputWorker::~AaroniaRTSAInputWorker()
{
if (mReply)
{
// disconnect previous sugnals
disconnect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
disconnect(mReply, SIGNAL(finished()), this, SLOT(onFinished()));
disconnect(mReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
mReply->abort();
mReply->deleteLater();
}
m_networkAccessManager->deleteLater();
QObject::disconnect(
m_networkAccessManagerConfig,
&QNetworkAccessManager::finished,
this,
&AaroniaRTSAInputWorker::handleConfigReply
);
m_networkAccessManagerConfig->deleteLater();
}
void AaroniaRTSAInputWorker::onSocketError(QAbstractSocket::SocketError error)
{
(void) error;
m_status = AaroniaRTSAInputSettings::ConnectionError;
emit updateStatus(m_status);
}
void AaroniaRTSAInputWorker::sendCenterFrequencyAndSampleRate()
{
if (m_iqDemodName.size() == 0) {
return;
}
qDebug("AaroniaRTSAInputWorker::sendCenterFrequencyAndSampleRate: %llu samplerate: %d", m_centerFrequency, m_sampleRate);
QJsonObject object {
{"receiverName", m_iqDemodName},
{"simpleconfig", QJsonObject({
{"main", QJsonObject({
{"centerfreq", QJsonValue((qint64) m_centerFrequency)},
{"samplerate", QJsonValue(m_sampleRate)},
{"spanfreq", QJsonValue(m_sampleRate)},
})}
})}
};
QJsonDocument document;
document.setObject(object);
QUrl url(tr("http://%1/remoteconfig").arg(m_serverAddress));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
m_networkAccessManagerConfig->put(request, document.toJson());
}
void AaroniaRTSAInputWorker::getConfig()
{
QUrl url(tr("http://%1/remoteconfig").arg(m_serverAddress));
QNetworkRequest request(url);
m_networkAccessManagerConfig->get(request);
}
void AaroniaRTSAInputWorker::onCenterFrequencyChanged(quint64 centerFrequency)
{
if (m_centerFrequency == centerFrequency) {
return;
}
m_centerFrequency = centerFrequency;
sendCenterFrequencyAndSampleRate();
}
void AaroniaRTSAInputWorker::onSampleRateChanged(int sampleRate)
{
if (m_sampleRate == sampleRate) {
return;
}
m_sampleRate = sampleRate;
sendCenterFrequencyAndSampleRate();
}
void AaroniaRTSAInputWorker::onServerAddressChanged(QString serverAddress)
{
m_status = AaroniaRTSAInputSettings::ConnectionDisconnected;
updateStatus(m_status);
if (mReply)
{
// disconnect previous sugnals
disconnect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
disconnect(mReply, SIGNAL(finished()), this, SLOT(onFinished()));
disconnect(mReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
mReply->abort();
mReply->deleteLater();
}
QUrl url(tr("http://%1/stream?format=float32").arg(serverAddress));
QNetworkRequest req(url);
mReply = m_networkAccessManager->get(req);
// Connect Qt slots to network events
connect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
connect(mReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(mReply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
mPrevTime = 0;
mPacketSamples = 0;
m_serverAddress = serverAddress;
getConfig();
}
void AaroniaRTSAInputWorker::tick()
{
}
/**************************CPY ********************************* */
void AaroniaRTSAInputWorker::onError(QNetworkReply::NetworkError code)
{
(void) code;
qWarning() << "AaroniaRTSAInputWorker::onError: network Error: " << mReply->errorString();
m_status = 3;
emit updateStatus(3);
}
void AaroniaRTSAInputWorker::onFinished()
{
qDebug() << "AaroniaRTSAInputWorker::onFinished(: finished: " << mReply->errorString();
mBuffer.append(mReply->readAll());
mReply->deleteLater();
mReply = nullptr;
}
// bytes received from the socket
void AaroniaRTSAInputWorker::onReadyRead()
{
if (m_status != AaroniaRTSAInputSettings::ConnectionOK)
{
m_status = AaroniaRTSAInputSettings::ConnectionOK;
emit updateStatus(m_status);
}
// read as many bytes as possible into input buffer
qint64 n = mReply->bytesAvailable();
qint64 bs = mBuffer.size();
mBuffer.resize(bs + n);
qint64 done = mReply->read(mBuffer.data() + bs, n);
mBuffer.resize(bs + done);
// initialize parsing
int offset = 0;
int avail = mBuffer.size();
// consume all input data if possible
while (offset < avail)
{
// any samples so far (not looking for meta data)
if (mPacketSamples)
{
// enough samples
if (offset + mPacketSamples * 2 * sizeof(float) <= (unsigned long) avail)
{
// do something with the IQ data
const float *sp = (const float *)(mBuffer.constData() + offset);
SampleVector::iterator it = m_convertBuffer.begin();
m_decimatorsFloatIQ.decimate1(&it, sp, 2*mPacketSamples);
/*m_samplesBuf.clear();
for (int i = 0; i < mPacketSamples*2; i+=2)
{
m_samplesBuf.push_back(Sample(
sp[i] << (SDR_RX_SAMP_SZ - 8),
sp[i+1] << (SDR_RX_SAMP_SZ - 8)
));
}*/
//m_sampleFifo->write(m_samplesBuf.begin(), m_samplesBuf.end());
m_sampleFifo->write(m_convertBuffer.begin(), it);
//qDebug() << "IQ " << sp[0] << ", " << sp[1];
//m_sampleFifo->write()
// consume all samples from the input buffer
offset += mPacketSamples * 2 * sizeof(float);
mPacketSamples = 0;
}
else
{
break;
}
}
else
{
// is there a complete JSON metadata object in the buffer
int split = mBuffer.indexOf('\x1e', offset);
if (split != -1)
{
// Extract it
QByteArray data = mBuffer.mid(offset, split - offset);
offset = split + 1;
// Parse the JSON data
QJsonParseError error;
QJsonDocument jdoc = QJsonDocument::fromJson(data, &error);
if (error.error == QJsonParseError::NoError)
{
// Extract fields of interest
// double startTime = jdoc["startTime"].toDouble(), endTime = jdoc["endTime"].toDouble();
int samples = jdoc["samples"].toInt();
// Dump packet loss
// if (startTime != mPrevTime)
// {
// qDebug() << "AaroniaRTSAInputWorker::onReadyRead: packet loss: "
// << QDateTime::fromMSecsSinceEpoch(startTime * 1000).toString()
// << " D " << endTime - startTime
// << " O " << startTime - mPrevTime
// << " S " << samples
// << " L " << QDateTime::currentMSecsSinceEpoch() / 1000.0 - startTime;
// if (m_status != AaroniaRTSAInputSettings::ConnectionUnstable)
// {
// m_status = AaroniaRTSAInputSettings::ConnectionUnstable;
// emit updateStatus(m_status);
// }
// }
// Switch to data phase
// mPrevTime = endTime;
mPacketSamples = samples;
// qDebug() << jdoc.toJson();
quint64 endFreq = jdoc["endFrequency"].toDouble();
quint64 startFreq = jdoc["startFrequency"].toDouble();
int bw = jdoc["sampleFrequency"].toInt();
quint64 midFreq = (endFreq + startFreq) / 2;
if ((bw != m_sampleRate) || (midFreq != m_centerFrequency))
{
if (m_inputMessageQueue)
{
MsgReportSampleRateAndFrequency *msg = MsgReportSampleRateAndFrequency::create(bw, midFreq);
m_inputMessageQueue->push(msg);
}
}
m_sampleRate = bw;
m_centerFrequency = midFreq;
}
else
{
QTextStream qerr(stderr);
qerr << "Json Parse Error: " + error.errorString();
}
}
else
{
break;
}
}
}
// Remove consumed data from the buffer
mBuffer.remove(0, offset);
}
void AaroniaRTSAInputWorker::handleConfigReply(QNetworkReply* reply)
{
if (reply->operation() == QNetworkAccessManager::GetOperation) // return from GET to /remoteconfig
{
parseConfig(reply->readAll());
}
else if (reply->operation() == QNetworkAccessManager::PutOperation) // return from PUT to /remoteconfig
{
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if ((httpStatusCode / 100) == 2) {
qDebug("AaroniaRTSAInputWorker::handleConfigReply: remoteconfig OK (%d)", httpStatusCode);
} else {
qWarning("AaroniaRTSAInputWorker::handleConfigReply: remoteconfig ended with error (%d)", httpStatusCode);
}
}
reply->deleteLater();
}
void AaroniaRTSAInputWorker::parseConfig(QByteArray bytes)
{
QJsonDocument document = QJsonDocument::fromJson(bytes);
m_iqDemodName = "";
if (document.isObject())
{
QJsonObject documentObject = document.object();
if (documentObject.contains(QStringLiteral("config")))
{
QJsonObject config = documentObject.value(QStringLiteral("config")).toObject();
if (config.contains(QStringLiteral("items")))
{
QJsonArray configItems = config.value(QStringLiteral("items")).toArray();
for (const auto& configIem : configItems)
{
QJsonObject configIemObject = configIem.toObject();
if (configIemObject.contains(QStringLiteral("name")))
{
QString nameItem = configIemObject.value(QStringLiteral("name")).toString();
if (nameItem.startsWith("Block_IQDemodulator"))
{
m_iqDemodName = nameItem;
break;
}
}
}
}
else
{
qDebug() << "AaroniaRTSAInputWorker::parseConfig: config has no items: " << config;
}
}
else
{
qDebug() << "AaroniaRTSAInputWorker::parseConfig: document has no config object: " << documentObject;
}
}
else
{
qDebug() << "AaroniaRTSAInputWorker::parseConfig: document is not an object: " << document;
}
if (m_iqDemodName == "") {
qWarning("AaroniaRTSAInputWorker.parseConfig: could not find IQ demdulator");
} else {
qDebug("AaroniaRTSAInputWorker::parseConfig: IQ demdulator name: %s", qPrintable(m_iqDemodName));
}
}