| 
									
										
										
										
											2023-05-23 10:23:18 +02:00
										 |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							| 
									
										
										
										
											2023-11-18 09:58:55 +01:00
										 |  |  | // Copyright (C) 2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com>               //
 | 
					
						
							| 
									
										
										
										
											2023-05-23 10:23:18 +02:00
										 |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // 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 <QJsonObject>
 | 
					
						
							|  |  |  | #include "aaroniartsaoutputsettings.h"
 | 
					
						
							|  |  |  | #include "aaroniartsaoutputworker.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AaroniaRTSAOutputWorker::AaroniaRTSAOutputWorker(SampleSourceFifo* sampleFifo, QObject* parent) : | 
					
						
							|  |  |  | 	QObject(parent), | 
					
						
							|  |  |  |     m_running(false), | 
					
						
							|  |  |  | 	m_sampleFifo(sampleFifo), | 
					
						
							|  |  |  |     m_sampleRate(100000), | 
					
						
							|  |  |  |     m_packetsPerSecond(10), | 
					
						
							|  |  |  |     m_samplesArrayInt16(nullptr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_samplesPerPacket = m_sampleRate / m_packetsPerSecond; | 
					
						
							|  |  |  |     m_networkAccessManager = new QNetworkAccessManager(this); | 
					
						
							|  |  |  |     m_timer = new QTimer(this); | 
					
						
							|  |  |  |     m_timer->setTimerType(Qt::PreciseTimer); | 
					
						
							|  |  |  |     m_centerFrequency = 145000000; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AaroniaRTSAOutputWorker::~AaroniaRTSAOutputWorker() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (m_running) { | 
					
						
							|  |  |  | 		stopWork(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::startWork() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug("aroniaRTSAOutputWorker::startWork"); | 
					
						
							|  |  |  |     m_samplesPerPacket = m_sampleRate / m_packetsPerSecond; | 
					
						
							|  |  |  |     m_sampleResendTime = 1000 / (m_sampleRate / m_samplesPerPacket) ; | 
					
						
							|  |  |  | 	m_streamStartTime = ( QDateTime::currentDateTime().currentMSecsSinceEpoch() ); | 
					
						
							|  |  |  | 	m_lastPacketEnd = m_streamStartTime / 1000.0; | 
					
						
							|  |  |  | 	m_sumSamples = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     connect(m_timer, SIGNAL(timeout()), this, SLOT(onGeneratePacket())); | 
					
						
							|  |  |  |     qDebug("AaroniaRTSAOutputWorker::startWork: m_sampleResendTime: %f", m_sampleResendTime); | 
					
						
							|  |  |  | 	m_timer->start(m_sampleResendTime); //send period
 | 
					
						
							|  |  |  |     m_running = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::stopWork() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug("aroniaRTSAOutputWorker::stopWork"); | 
					
						
							|  |  |  |     m_running = false; | 
					
						
							|  |  |  |     m_status = AaroniaRTSAOutputSettings::ConnectionIdle; | 
					
						
							|  |  |  |     emit updateStatus(m_status); | 
					
						
							|  |  |  |     disconnect(m_timer, SIGNAL(timeout()), this, SLOT(onGeneratePacket())); | 
					
						
							|  |  |  |     m_timer->stop(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::setSampleRate(int sampleRate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug("aroniaRTSAOutputWorker::setSampleRate: %d", sampleRate); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (sampleRate == m_sampleRate) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_samplesPerPacket = sampleRate / m_packetsPerSecond; | 
					
						
							|  |  |  |     m_sampleResendTime = 1000 / (sampleRate / m_samplesPerPacket) ; | 
					
						
							|  |  |  | 	m_timer->start(m_sampleResendTime); //send period
 | 
					
						
							|  |  |  |     m_sampleRate = sampleRate; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::onGeneratePacket() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	double sampleTimeUS = 1000000.0 / m_sampleRate; | 
					
						
							|  |  |  | 	qint64 deltaT = QDateTime::currentDateTime().currentMSecsSinceEpoch(); | 
					
						
							|  |  |  |     deltaT = deltaT - (m_lastPacketEnd*1000); | 
					
						
							|  |  |  | 	double ndiff =  1000*((double)deltaT + 1 ) / sampleTimeUS ; | 
					
						
							|  |  |  | 	double reawaketime = m_sampleResendTime -  (sampleTimeUS*(ndiff - m_samplesPerPacket ))/1000 ; | 
					
						
							|  |  |  |     // qDebug("AaroniaRTSAOutputWorker::onGeneratePacket: %f", reawaketime);
 | 
					
						
							|  |  |  | 	m_timer->setInterval( reawaketime ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	double newStart = m_lastPacketEnd; | 
					
						
							|  |  |  | 	m_lastPacketEnd = newStart + ((m_samplesPerPacket + 1) *  sampleTimeUS)/1000000 ; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	double timeOffset = 0.4; //put it into future
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     buildSamples( newStart + timeOffset , m_lastPacketEnd + timeOffset); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::buildSamples(double startTime, double stopTime) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End; | 
					
						
							|  |  |  |     SampleVector& data = m_sampleFifo->getData(); | 
					
						
							|  |  |  |     m_sampleFifo->read(m_samplesPerPacket, iPart1Begin, iPart1End, iPart2Begin, iPart2End); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (m_samplesArrayInt16 == nullptr) { | 
					
						
							|  |  |  | 		m_samplesArrayInt16 = new int16_t[2*m_samplesPerPacket]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (iPart1Begin != iPart1End) { | 
					
						
							|  |  |  |         callbackPart(m_samplesArrayInt16, data, iPart1Begin, iPart1End); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (iPart2Begin != iPart2End) { | 
					
						
							|  |  |  |         callbackPart(&m_samplesArrayInt16[(iPart1End - iPart1Begin)*2], data, iPart2Begin, iPart2End); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     double startFrequency = m_centerFrequency -  m_sampleRate/2; | 
					
						
							|  |  |  |     double endFrequency = m_centerFrequency + m_sampleRate/2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	QJsonDocument jdoc(QJsonObject({ | 
					
						
							|  |  |  | 		{"startTime", startTime }, | 
					
						
							|  |  |  | 		{"endTime" , stopTime }, | 
					
						
							|  |  |  | 		{"startFrequency", startFrequency }, | 
					
						
							|  |  |  | 		{"endFrequency" , endFrequency }, | 
					
						
							|  |  |  | 		{"minPower", -2}, | 
					
						
							|  |  |  | 		{"maxPower" , 2}, | 
					
						
							|  |  |  | 		{"sampleSize", 2}, | 
					
						
							|  |  |  | 		{"sampleDepth" , 1}, | 
					
						
							|  |  |  | 		{"payload", "iq"}, | 
					
						
							|  |  |  | 		{"format", "int16"}, | 
					
						
							|  |  |  | 		{"scale", 512.0}, | 
					
						
							|  |  |  | 		{"unit" , "volt"}, | 
					
						
							|  |  |  | 		{"samples" , 2*m_samplesPerPacket}, | 
					
						
							|  |  |  | 	})); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     postData(jdoc, m_samplesArrayInt16, 2*m_samplesPerPacket); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::callbackPart(int16_t *buf, SampleVector& data, unsigned int iBegin, unsigned int iEnd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     for (unsigned int j = 0, i = iBegin; i < iEnd; j++, i++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         buf[2*j]   = data[i].m_real; | 
					
						
							|  |  |  |         buf[2*j+1] = data[i].m_imag; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::postData(QJsonDocument jdoc, int16_t *samplesArray, int nSamples) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     QUrl url(tr("http://%1/sample").arg(m_serverAddress)); | 
					
						
							| 
									
										
										
										
											2023-05-23 13:51:27 +02:00
										 |  |  |     // qDebug() << "AaroniaRTSAOutputWorker::postData:" << url;
 | 
					
						
							| 
									
										
										
										
											2023-05-23 10:23:18 +02:00
										 |  |  | 	QNetworkRequest request(url); | 
					
						
							|  |  |  |     request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json")); | 
					
						
							|  |  |  |     QByteArray byteArray = jdoc.toJson(QJsonDocument::Compact); | 
					
						
							|  |  |  |     byteArray.append(0x1e); | 
					
						
							|  |  |  |     byteArray.append(QByteArray::fromRawData(reinterpret_cast<char *>(samplesArray), nSamples*sizeof(int16_t))); | 
					
						
							|  |  |  | 	QNetworkReply *networkReply = m_networkAccessManager->post(request, byteArray); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError))); | 
					
						
							|  |  |  | 	connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::onError(QNetworkReply::NetworkError) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	QNetworkReply* nReply = qobject_cast<QNetworkReply*>( sender() ); | 
					
						
							|  |  |  | 	qDebug() << "AaroniaRTSAOutputWorker::onError: Network Error: " + nReply->errorString(); | 
					
						
							|  |  |  | 	m_timer->stop(); | 
					
						
							|  |  |  |     m_status = AaroniaRTSAOutputSettings::ConnectionError; | 
					
						
							|  |  |  | 	emit updateStatus(m_status); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void AaroniaRTSAOutputWorker::onFinished(QNetworkReply *reply) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if ((m_status != AaroniaRTSAOutputSettings::ConnectionOK) && (reply->error() == QNetworkReply::NoError)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_status = AaroniaRTSAOutputSettings::ConnectionOK; | 
					
						
							|  |  |  |         emit updateStatus(m_status); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reply->deleteLater(); | 
					
						
							|  |  |  | } |