1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-01 21:54:55 -04:00

Merge branch 'aaroniartsa'

This commit is contained in:
f4exb
2023-04-06 22:55:54 +02:00
47 changed files with 3417 additions and 438 deletions
+1
View File
@@ -73,3 +73,4 @@ endif()
add_subdirectory(audioinput)
add_subdirectory(kiwisdr)
add_subdirectory(remotetcpinput)
add_subdirectory(aaroniartsainput)
@@ -0,0 +1,63 @@
project(aaroniartsainput)
set(aaroniartsainput_SOURCES
aaroniartsainput.cpp
aaroniartsainputplugin.cpp
aaroniartsainputworker.cpp
aaroniartsainputsettings.cpp
aaroniartsainputwebapiadapter.cpp
)
set(aaroniartsainput_HEADERS
aaroniartsainput.h
aaroniartsainputplugin.h
aaroniartsainputworker.h
aaroniartsainputsettings.h
aaroniartsainputwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
set(aaroniartsainput_SOURCES
${aaroniartsainput_SOURCES}
aaroniartsainputgui.cpp
aaroniartsainputgui.ui
)
set(aaroniartsainput_HEADERS
${aaroniartsainput_HEADERS}
aaroniartsainputgui.h
)
set(TARGET_NAME inputaaroniartsa)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME inputaaroniartsasrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${aaroniartsainput_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt::Core
Qt::WebSockets
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()
@@ -0,0 +1,535 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 <string.h>
#include <errno.h>
#include <QDebug>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QBuffer>
#include <QThread>
#include "SWGDeviceSettings.h"
#include "SWGDeviceState.h"
#include "SWGDeviceReport.h"
#include "SWGAaroniaRTSAReport.h"
#include "aaroniartsainput.h"
#include "device/deviceapi.h"
#include "aaroniartsainputworker.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
MESSAGE_CLASS_DEFINITION(AaroniaRTSAInput::MsgConfigureAaroniaRTSA, Message)
MESSAGE_CLASS_DEFINITION(AaroniaRTSAInput::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(AaroniaRTSAInput::MsgSetStatus, Message)
AaroniaRTSAInput::AaroniaRTSAInput(DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_sampleRate(10000000),
m_centerFrequency(1450000),
m_settings(),
m_aaroniaRTSAWorker(nullptr),
m_aaroniaRTSAWorkerThread(nullptr),
m_deviceDescription("AaroniaRTSA"),
m_running(false),
m_masterTimer(deviceAPI->getMasterTimer())
{
m_sampleFifo.setLabel(m_deviceDescription);
m_deviceAPI->setNbSourceStreams(1);
if (!m_sampleFifo.setSize(getSampleRate() * 2)) {
qCritical("AaroniaRTSAInput::AaroniaRTSAInput: Could not allocate SampleFifo");
}
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&AaroniaRTSAInput::networkManagerFinished
);
}
AaroniaRTSAInput::~AaroniaRTSAInput()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&AaroniaRTSAInput::networkManagerFinished
);
delete m_networkManager;
if (m_running) {
stop();
}
}
void AaroniaRTSAInput::destroy()
{
delete this;
}
void AaroniaRTSAInput::init()
{
applySettings(m_settings, QList<QString>(), true);
}
bool AaroniaRTSAInput::start()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_running) {
return true;
}
m_aaroniaRTSAWorkerThread = new QThread();
m_aaroniaRTSAWorker = new AaroniaRTSAInputWorker(&m_sampleFifo);
m_aaroniaRTSAWorker->setInputMessageQueue(getInputMessageQueue());
m_aaroniaRTSAWorker->moveToThread(m_aaroniaRTSAWorkerThread);
QObject::connect(m_aaroniaRTSAWorkerThread, &QThread::finished, m_aaroniaRTSAWorker, &QObject::deleteLater);
QObject::connect(m_aaroniaRTSAWorkerThread, &QThread::finished, m_aaroniaRTSAWorkerThread, &QThread::deleteLater);
connect(this, &AaroniaRTSAInput::setWorkerCenterFrequency, m_aaroniaRTSAWorker, &AaroniaRTSAInputWorker::onCenterFrequencyChanged);
connect(this, &AaroniaRTSAInput::setWorkerSampleRate, m_aaroniaRTSAWorker, &AaroniaRTSAInputWorker::onSampleRateChanged);
connect(this, &AaroniaRTSAInput::setWorkerServerAddress, m_aaroniaRTSAWorker, &AaroniaRTSAInputWorker::onServerAddressChanged);
connect(m_aaroniaRTSAWorker, &AaroniaRTSAInputWorker::updateStatus, this, &AaroniaRTSAInput::setWorkerStatus);
m_aaroniaRTSAWorkerThread->start();
m_running = true;
mutexLocker.unlock();
applySettings(m_settings, QList<QString>(), true);
return true;
}
void AaroniaRTSAInput::stop()
{
QMutexLocker mutexLocker(&m_mutex);
if (!m_running) {
return;
}
m_running = false;
setWorkerStatus(0);
if (m_aaroniaRTSAWorkerThread)
{
m_aaroniaRTSAWorkerThread->quit();
m_aaroniaRTSAWorkerThread->wait();
m_aaroniaRTSAWorker = nullptr;
m_aaroniaRTSAWorkerThread = nullptr;
}
}
QByteArray AaroniaRTSAInput::serialize() const
{
return m_settings.serialize();
}
bool AaroniaRTSAInput::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureAaroniaRTSA* message = MsgConfigureAaroniaRTSA::create(m_settings, QList<QString>(), true);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureAaroniaRTSA* messageToGUI = MsgConfigureAaroniaRTSA::create(m_settings, QList<QString>(), true);
m_guiMessageQueue->push(messageToGUI);
}
return success;
}
const QString& AaroniaRTSAInput::getDeviceDescription() const
{
return m_deviceDescription;
}
int AaroniaRTSAInput::getSampleRate() const
{
return m_sampleRate;
}
quint64 AaroniaRTSAInput::getCenterFrequency() const
{
return m_settings.m_centerFrequency;
}
void AaroniaRTSAInput::setCenterFrequency(qint64 centerFrequency)
{
AaroniaRTSAInputSettings settings = m_settings;
settings.m_centerFrequency = centerFrequency;
MsgConfigureAaroniaRTSA* message = MsgConfigureAaroniaRTSA::create(settings, QList<QString>{"centerFrequency"}, false);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureAaroniaRTSA* messageToGUI = MsgConfigureAaroniaRTSA::create(settings, QList<QString>{"centerFrequency"}, false);
m_guiMessageQueue->push(messageToGUI);
}
}
void AaroniaRTSAInput::setWorkerStatus(int status)
{
if (m_guiMessageQueue) {
m_guiMessageQueue->push(MsgSetStatus::create(status));
}
}
bool AaroniaRTSAInput::handleMessage(const Message& message)
{
if (MsgConfigureAaroniaRTSA::match(message))
{
MsgConfigureAaroniaRTSA& conf = (MsgConfigureAaroniaRTSA&) message;
qDebug() << "AaroniaRTSAInput::handleMessage: MsgConfigureAaroniaRTSA";
bool success = applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce());
if (!success) {
qDebug("AaroniaRTSAInput::handleMessage: config error");
}
return true;
}
else if (AaroniaRTSAInputWorker::MsgReportSampleRateAndFrequency::match(message))
{
AaroniaRTSAInputWorker::MsgReportSampleRateAndFrequency& report = (AaroniaRTSAInputWorker::MsgReportSampleRateAndFrequency&) message;
m_sampleRate = report.getSampleRate();
m_centerFrequency = report.getCenterFrequency();
qDebug() << "AaroniaRTSAInput::handleMessage: AaroniaRTSAInputWorker::MsgReportSampleRateAndFrequency:"
<< " m_sampleRate: " << m_sampleRate
<< " m-centerFrequency" << m_centerFrequency;
if (!m_sampleFifo.setSize(m_sampleRate * 2)) {
qCritical("AaroniaRTSAInput::AaroniaRTSAInput: Could not allocate SampleFifo");
}
DSPSignalNotification *notif = new DSPSignalNotification(
m_sampleRate, m_centerFrequency);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
return true;
}
else if (MsgStartStop::match(message))
{
MsgStartStop& cmd = (MsgStartStop&) message;
qDebug() << "AaroniaRTSAInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop");
if (cmd.getStartStop())
{
if (m_deviceAPI->initDeviceEngine()) {
m_deviceAPI->startDeviceEngine();
}
}
else
{
m_deviceAPI->stopDeviceEngine();
}
if (m_settings.m_useReverseAPI) {
webapiReverseSendStartStop(cmd.getStartStop());
}
return true;
}
else
{
return false;
}
}
int AaroniaRTSAInput::getStatus() const
{
if (m_aaroniaRTSAWorker) {
return m_aaroniaRTSAWorker->getStatus();
} else {
return 0;
}
}
bool AaroniaRTSAInput::applySettings(const AaroniaRTSAInputSettings& settings, const QList<QString>& settingsKeys, bool force)
{
qDebug() << "AaroniaRTSAInput::applySettings: force: "<< force << settings.getDebugString(settingsKeys, force);
if (settingsKeys.contains("serverAddress") || force)
{
emit setWorkerServerAddress(settings.m_serverAddress);
}
if (settingsKeys.contains("centerFrequency") || force)
{
emit setWorkerCenterFrequency(settings.m_centerFrequency);
// DSPSignalNotification *notif = new DSPSignalNotification(
// getSampleRate(), settings.m_centerFrequency);
// m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
}
if (settingsKeys.contains("sampleRate")) {
emit setWorkerSampleRate(settings.m_sampleRate);
}
if (settingsKeys.contains("useReverseAPI"))
{
bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) ||
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIDeviceIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
return true;
}
int AaroniaRTSAInput::webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
return 200;
}
int AaroniaRTSAInput::webapiRun(
bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
MsgStartStop *message = MsgStartStop::create(run);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgStartStop *msgToGUI = MsgStartStop::create(run);
m_guiMessageQueue->push(msgToGUI);
}
return 200;
}
int AaroniaRTSAInput::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAaroniaRtsaSettings(new SWGSDRangel::SWGAaroniaRTSASettings());
response.getAaroniaRtsaSettings()->init();
webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int AaroniaRTSAInput::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) errorMessage;
AaroniaRTSAInputSettings settings = m_settings;
webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response);
MsgConfigureAaroniaRTSA *msg = MsgConfigureAaroniaRTSA::create(settings, deviceSettingsKeys, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAaroniaRTSA *msgToGUI = MsgConfigureAaroniaRTSA::create(settings, deviceSettingsKeys, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatDeviceSettings(response, settings);
return 200;
}
void AaroniaRTSAInput::webapiUpdateDeviceSettings(
AaroniaRTSAInputSettings& settings,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response)
{
if (deviceSettingsKeys.contains("centerFrequency")) {
settings.m_centerFrequency = response.getAaroniaRtsaSettings()->getCenterFrequency();
}
if (deviceSettingsKeys.contains("centerFrequency")) {
settings.m_sampleRate = response.getAaroniaRtsaSettings()->getSampleRate();
}
if (deviceSettingsKeys.contains("serverAddress")) {
settings.m_serverAddress = *response.getAaroniaRtsaSettings()->getServerAddress();
}
if (deviceSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAaroniaRtsaSettings()->getUseReverseApi() != 0;
}
if (deviceSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAaroniaRtsaSettings()->getReverseApiAddress();
}
if (deviceSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAaroniaRtsaSettings()->getReverseApiPort();
}
if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getAaroniaRtsaSettings()->getReverseApiDeviceIndex();
}
}
int AaroniaRTSAInput::webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAaroniaSdrReport(new SWGSDRangel::SWGAaroniaRTSAReport());
response.getAirspyHfReport()->init();
webapiFormatDeviceReport(response);
return 200;
}
void AaroniaRTSAInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AaroniaRTSAInputSettings& settings)
{
response.getAaroniaRtsaSettings()->setCenterFrequency(settings.m_centerFrequency);
response.getAaroniaRtsaSettings()->setSampleRate(settings.m_sampleRate);
if (response.getAaroniaRtsaSettings()->getServerAddress()) {
*response.getAaroniaRtsaSettings()->getServerAddress() = settings.m_serverAddress;
} else {
response.getAaroniaRtsaSettings()->setServerAddress(new QString(settings.m_serverAddress));
}
response.getAaroniaRtsaSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAaroniaRtsaSettings()->getReverseApiAddress()) {
*response.getAaroniaRtsaSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAaroniaRtsaSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAaroniaRtsaSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getAaroniaRtsaSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
}
void AaroniaRTSAInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
{
response.getAaroniaSdrReport()->setStatus(getStatus());
}
void AaroniaRTSAInput::webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const AaroniaRTSAInputSettings& settings, bool force)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(0); // single Rx
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("AaroniaRTSA"));
swgDeviceSettings->setAaroniaRtsaSettings(new SWGSDRangel::SWGAaroniaRTSASettings());
SWGSDRangel::SWGAaroniaRTSASettings *swgAaroniaRTSASettings = swgDeviceSettings->getAaroniaRtsaSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (deviceSettingsKeys.contains("centerFrequency") || force) {
swgAaroniaRTSASettings->setCenterFrequency(settings.m_centerFrequency);
}
if (deviceSettingsKeys.contains("serverAddress") || force) {
swgAaroniaRTSASettings->setServerAddress(new QString(settings.m_serverAddress));
}
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex);
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgDeviceSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgDeviceSettings;
}
void AaroniaRTSAInput::webapiReverseSendStartStop(bool start)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(0); // single Rx
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("AaroniaRTSA"));
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run")
.arg(m_settings.m_reverseAPIAddress)
.arg(m_settings.m_reverseAPIPort)
.arg(m_settings.m_reverseAPIDeviceIndex);
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgDeviceSettings->asJson().toUtf8());
buffer->seek(0);
QNetworkReply *reply;
if (start) {
reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer);
} else {
reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer);
}
buffer->setParent(reply);
delete swgDeviceSettings;
}
void AaroniaRTSAInput::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AaroniaRTSAInput::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AaroniaRTSAInput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
@@ -0,0 +1,186 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 _AARONIARTSA_AARONIARTSAINPUT_H_
#define _AARONIARTSA_AARONIARTSAINPUT_H_
#include <QString>
#include <QByteArray>
#include <QTimer>
#include <QNetworkRequest>
#include <dsp/devicesamplesource.h>
#include "aaroniartsainputsettings.h"
class DeviceAPI;
class AaroniaRTSAInputWorker;
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class AaroniaRTSAInput : public DeviceSampleSource {
Q_OBJECT
public:
class MsgConfigureAaroniaRTSA : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AaroniaRTSAInputSettings& getSettings() const { return m_settings; }
const QList<QString>& getSettingsKeys() const { return m_settingsKeys; }
bool getForce() const { return m_force; }
static MsgConfigureAaroniaRTSA* create(const AaroniaRTSAInputSettings& settings, const QList<QString>& settingsKeys, bool force)
{
return new MsgConfigureAaroniaRTSA(settings, settingsKeys, force);
}
private:
AaroniaRTSAInputSettings m_settings;
QList<QString> m_settingsKeys;
bool m_force;
MsgConfigureAaroniaRTSA(const AaroniaRTSAInputSettings& settings, const QList<QString>& settingsKeys, bool force) :
Message(),
m_settings(settings),
m_settingsKeys(settingsKeys),
m_force(force)
{ }
};
class MsgStartStop : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getStartStop() const { return m_startStop; }
static MsgStartStop* create(bool startStop) {
return new MsgStartStop(startStop);
}
protected:
bool m_startStop;
MsgStartStop(bool startStop) :
Message(),
m_startStop(startStop)
{ }
};
class MsgSetStatus : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getStatus() const { return m_status; }
static MsgSetStatus* create(int status) {
return new MsgSetStatus(status);
}
protected:
int m_status;
MsgSetStatus(int status) :
Message(),
m_status(status)
{ }
};
AaroniaRTSAInput(DeviceAPI *deviceAPI);
virtual ~AaroniaRTSAInput();
virtual void destroy();
virtual void init();
virtual bool start();
virtual void stop();
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; }
virtual const QString& getDeviceDescription() const;
virtual int getSampleRate() const;
virtual void setSampleRate(int sampleRate) { (void) sampleRate; }
virtual quint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
virtual bool handleMessage(const Message& message);
virtual int webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage);
virtual int webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiRun(
bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage);
static void webapiFormatDeviceSettings(
SWGSDRangel::SWGDeviceSettings& response,
const AaroniaRTSAInputSettings& settings);
static void webapiUpdateDeviceSettings(
AaroniaRTSAInputSettings& settings,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response);
private:
DeviceAPI *m_deviceAPI;
QMutex m_mutex;
int m_sampleRate;
quint64 m_centerFrequency;
AaroniaRTSAInputSettings m_settings;
AaroniaRTSAInputWorker* m_aaroniaRTSAWorker;
QThread *m_aaroniaRTSAWorkerThread;
QString m_deviceDescription;
bool m_running;
const QTimer& m_masterTimer;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
int getStatus() const;
bool applySettings(const AaroniaRTSAInputSettings& settings, const QList<QString>& settingsKeys, bool force);
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
void webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const AaroniaRTSAInputSettings& settings, bool force);
void webapiReverseSendStartStop(bool start);
signals:
void startWorker();
void stopWorker();
void setWorkerCenterFrequency(quint64 centerFrequency);
void setWorkerSampleRate(int sampleRate);
void setWorkerServerAddress(QString serverAddress);
private slots:
void setWorkerStatus(int status);
void networkManagerFinished(QNetworkReply *reply);
};
#endif // _AARONIARTSA_AARONIARTSAINPUT_H_
@@ -0,0 +1,347 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 <QDebug>
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QMessageBox>
#include <QFileDialog>
#include "ui_aaroniartsainputgui.h"
#include "plugin/pluginapi.h"
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "gui/basicdevicesettingsdialog.h"
#include "gui/dialogpositioner.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "util/db.h"
#include "mainwindow.h"
#include "aaroniartsainputgui.h"
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
AaroniaRTSAInputGui::AaroniaRTSAInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
DeviceGUI(parent),
ui(new Ui::AaroniaRTSAInputGui),
m_settings(),
m_doApplySettings(true),
m_forceSettings(true),
m_sampleSource(0),
m_tickCount(0),
m_lastEngineState(DeviceAPI::StNotStarted)
{
qDebug("AaroniaRTSAInputGui::AaroniaRTSAInputGui");
m_deviceUISet = deviceUISet;
setAttribute(Qt::WA_DeleteOnClose, true);
m_sampleSource = m_deviceUISet->m_deviceAPI->getSampleSource();
m_statusTooltips.push_back("Idle"); // 0
m_statusTooltips.push_back("Unstable"); // 1
m_statusTooltips.push_back("Connected"); // 2
m_statusTooltips.push_back("Error"); // 3
m_statusTooltips.push_back("Disconnected"); // 4
m_statusColors.push_back("gray"); // Idle
m_statusColors.push_back("rgb(232, 212, 35)"); // Unstable (yellow)
m_statusColors.push_back("rgb(35, 138, 35)"); // Connected (green)
m_statusColors.push_back("rgb(232, 85, 85)"); // Error (red)
m_statusColors.push_back("rgb(232, 85, 232)"); // Disconnected (magenta)
ui->setupUi(getContents());
sizeToContents();
getContents()->setStyleSheet("#AaroniaRTSAInputGui { background-color: rgb(64, 64, 64); }");
m_helpURL = "plugins/samplesource/aaroniartsainput/readme.md";
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->centerFrequency->setValueRange(9, 0, 999999999);
ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow));
ui->sampleRate->setValueRange(8, 2000U, 20000000U);
displaySettings();
makeUIConnections();
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
}
AaroniaRTSAInputGui::~AaroniaRTSAInputGui()
{
delete ui;
}
void AaroniaRTSAInputGui::destroy()
{
delete this;
}
void AaroniaRTSAInputGui::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
m_forceSettings = true;
sendSettings();
}
QByteArray AaroniaRTSAInputGui::serialize() const
{
return m_settings.serialize();
}
bool AaroniaRTSAInputGui::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
m_forceSettings = true;
sendSettings();
return true;
} else {
resetToDefaults();
return false;
}
}
void AaroniaRTSAInputGui::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
AaroniaRTSAInput::MsgStartStop *message = AaroniaRTSAInput::MsgStartStop::create(checked);
m_sampleSource->getInputMessageQueue()->push(message);
}
}
void AaroniaRTSAInputGui::on_centerFrequency_changed(quint64 value)
{
m_settings.m_centerFrequency = value * 1000;
m_settingsKeys.append("centerFrequency");
sendSettings();
}
void AaroniaRTSAInputGui::on_sampleRate_changed(quint64 value)
{
m_settings.m_sampleRate = value;
m_settingsKeys.append("sampleRate");
sendSettings();
}
void AaroniaRTSAInputGui::on_serverAddress_returnPressed()
{
on_serverAddressApplyButton_clicked();
}
void AaroniaRTSAInputGui::on_serverAddressApplyButton_clicked()
{
QString serverAddress = ui->serverAddress->text();
QUrl url(serverAddress);
if (QStringList{"ws", "wss", "http", "https"}.contains(url.scheme())) {
m_settings.m_serverAddress = QString("%1:%2").arg(url.host()).arg(url.port());
} else {
m_settings.m_serverAddress = serverAddress;
}
m_settingsKeys.append("serverAddress");
sendSettings();
}
void AaroniaRTSAInputGui::displaySettings()
{
blockApplySettings(true);
ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000);
ui->serverAddress->setText(m_settings.m_serverAddress);
blockApplySettings(false);
}
void AaroniaRTSAInputGui::sendSettings()
{
if (!m_updateTimer.isActive()) {
m_updateTimer.start(100);
}
}
void AaroniaRTSAInputGui::updateHardware()
{
if (m_doApplySettings)
{
AaroniaRTSAInput::MsgConfigureAaroniaRTSA* message = AaroniaRTSAInput::MsgConfigureAaroniaRTSA::create(m_settings, m_settingsKeys, m_forceSettings);
m_sampleSource->getInputMessageQueue()->push(message);
m_forceSettings = false;
m_settingsKeys.clear();
m_updateTimer.stop();
}
}
void AaroniaRTSAInputGui::updateStatus()
{
int state = m_deviceUISet->m_deviceAPI->state();
if (m_lastEngineState != state)
{
switch (state)
{
case DeviceAPI::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case DeviceAPI::StIdle:
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case DeviceAPI::StRunning:
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case DeviceAPI::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage());
break;
default:
break;
}
m_lastEngineState = state;
}
}
bool AaroniaRTSAInputGui::handleMessage(const Message& message)
{
if (AaroniaRTSAInput::MsgConfigureAaroniaRTSA::match(message))
{
qDebug("AaroniaRTSAInputGui::handleMessage: MsgConfigureAaroniaRTSA");
const AaroniaRTSAInput::MsgConfigureAaroniaRTSA& cfg = (AaroniaRTSAInput::MsgConfigureAaroniaRTSA&) message;
if (cfg.getForce()) {
m_settings = cfg.getSettings();
} else {
m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
}
displaySettings();
return true;
}
else if (AaroniaRTSAInput::MsgStartStop::match(message))
{
qDebug("AaroniaRTSAInputGui::handleMessage: MsgStartStop");
AaroniaRTSAInput::MsgStartStop& notif = (AaroniaRTSAInput::MsgStartStop&) message;
blockApplySettings(true);
ui->startStop->setChecked(notif.getStartStop());
blockApplySettings(false);
return true;
}
else if (AaroniaRTSAInput::MsgSetStatus::match(message))
{
qDebug("AaroniaRTSAInputGui::handleMessage: MsgSetStatus");
AaroniaRTSAInput::MsgSetStatus& notif = (AaroniaRTSAInput::MsgSetStatus&) message;
int status = notif.getStatus();
ui->statusIndicator->setToolTip(m_statusTooltips[status]);
ui->statusIndicator->setStyleSheet("QLabel { background-color: " +
m_statusColors[status] + "; border-radius: 7px; }");
return true;
}
else
{
return false;
}
}
void AaroniaRTSAInputGui::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (DSPSignalNotification::match(*message))
{
DSPSignalNotification* notif = (DSPSignalNotification*) message;
m_deviceSampleRate = notif->getSampleRate();
m_deviceCenterFrequency = notif->getCenterFrequency();
qDebug("AaroniaRTSAInputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu",
notif->getSampleRate(),
notif->getCenterFrequency());
updateSampleRateAndFrequency();
delete message;
}
else
{
if (handleMessage(*message))
{
delete message;
}
}
}
}
void AaroniaRTSAInputGui::updateSampleRateAndFrequency()
{
m_deviceUISet->getSpectrum()->setSampleRate(m_deviceSampleRate);
m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency);
// ui->deviceRateText->setText(tr("%1M").arg((float)m_deviceSampleRate / 1000 / 1000));
ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_deviceSampleRate / 1000.0f, 'g', 5)));
blockApplySettings(true);
ui->centerFrequency->setValue(m_deviceCenterFrequency / 1000);
ui->sampleRate->setValue(m_deviceSampleRate);
blockApplySettings(false);
}
void AaroniaRTSAInputGui::openDeviceSettingsDialog(const QPoint& p)
{
if (m_contextMenuType == ContextMenuDeviceSettings)
{
BasicDeviceSettingsDialog dialog(this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.move(p);
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settingsKeys.append("useReverseAPI");
m_settingsKeys.append("reverseAPIAddress");
m_settingsKeys.append("reverseAPIPort");
m_settingsKeys.append("reverseAPIDeviceIndex");
sendSettings();
}
resetContextMenuType();
}
void AaroniaRTSAInputGui::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &AaroniaRTSAInputGui::on_startStop_toggled);
QObject::connect(ui->centerFrequency, &ValueDial::changed, this, &AaroniaRTSAInputGui::on_centerFrequency_changed);
QObject::connect(ui->sampleRate, &ValueDial::changed, this, &AaroniaRTSAInputGui::on_sampleRate_changed);
QObject::connect(ui->serverAddress, &QLineEdit::returnPressed, this, &AaroniaRTSAInputGui::on_serverAddress_returnPressed);
QObject::connect(ui->serverAddressApplyButton, &QPushButton::clicked, this, &AaroniaRTSAInputGui::on_serverAddressApplyButton_clicked);
}
@@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 _AARONIARTSA_AARONIARTSAGUI_H_
#define _AARONIARTSA_AARONIARTSAGUI_H_
#include <QTimer>
#include <QWidget>
#include "device/devicegui.h"
#include "util/messagequeue.h"
#include "aaroniartsainputsettings.h"
#include "aaroniartsainput.h"
class DeviceUISet;
namespace Ui {
class AaroniaRTSAInputGui;
}
class AaroniaRTSAInputGui : public DeviceGUI {
Q_OBJECT
public:
explicit AaroniaRTSAInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0);
virtual ~AaroniaRTSAInputGui();
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
private:
Ui::AaroniaRTSAInputGui* ui;
AaroniaRTSAInputSettings m_settings;
QList<QString> m_settingsKeys;
QTimer m_updateTimer;
QTimer m_statusTimer;
bool m_doApplySettings;
bool m_forceSettings;
DeviceSampleSource* m_sampleSource;
std::size_t m_tickCount;
int m_deviceSampleRate;
quint64 m_deviceCenterFrequency; //!< Center frequency in device
int m_lastEngineState;
MessageQueue m_inputMessageQueue;
std::vector<QString> m_statusColors;
std::vector<QString> m_statusTooltips;
void blockApplySettings(bool block) { m_doApplySettings = !block; }
void displaySettings();
void sendSettings();
void updateSampleRateAndFrequency();
bool handleMessage(const Message& message);
void makeUIConnections();
private slots:
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_centerFrequency_changed(quint64 value);
void on_sampleRate_changed(quint64 value);
void on_serverAddress_returnPressed();
void on_serverAddressApplyButton_clicked();
void openDeviceSettingsDialog(const QPoint& p);
void updateStatus();
void updateHardware();
};
#endif // _AARONIARTSA_AARONIARTSAGUI_H_
@@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AaroniaRTSAInputGui</class>
<widget class="QWidget" name="AaroniaRTSAInputGui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>131</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>130</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>380</width>
<height>131</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="windowTitle">
<string>AaroniaRTSA</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_freq">
<item>
<layout class="QVBoxLayout" name="deviceUILayout">
<item>
<layout class="QHBoxLayout" name="deviceButtonsLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="deviceRateLayout">
<item>
<widget class="QLabel" name="deviceRateText">
<property name="minimumSize">
<size>
<width>58</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>I/Q sample rate kS/s</string>
</property>
<property name="text">
<string>0000.00k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ValueDial" name="centerFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>16</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Tuner center frequency in kHz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="freqUnits">
<property name="text">
<string> kHz</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="sampleRateLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="sampleRateLabel">
<property name="text">
<string>SR</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDial" name="sampleRate" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>Device sample rate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rateUnits">
<property name="text">
<string>S/s</string>
</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>
<layout class="QHBoxLayout" name="serverAddressLayout">
<item>
<widget class="QLabel" name="serverAddressLabel">
<property name="text">
<string>Addr</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="serverAddress">
<property name="toolTip">
<string>Server address</string>
</property>
<property name="text">
<string>127.0.0.1:8073</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusIndicator">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>14</width>
<height>14</height>
</size>
</property>
<property name="toolTip">
<string>Idle</string>
</property>
<property name="styleSheet">
<string notr="true">QLabel { background-color: gray; border-radius: 7px; }</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="serverAddressApplyButton">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Set</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ValueDial</class>
<extends>QWidget</extends>
<header>gui/valuedial.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>
@@ -0,0 +1,145 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 <QtPlugin>
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#ifdef SERVER_MODE
#include "aaroniartsainput.h"
#else
#include "aaroniartsainputgui.h"
#endif
#include "aaroniartsainputplugin.h"
#include "aaroniartsainputwebapiadapter.h"
const PluginDescriptor AaroniaRTSAInputPlugin::m_pluginDescriptor = {
QStringLiteral("AaroniaRTSA"),
QStringLiteral("AaroniaRTSA input"),
QStringLiteral("7.12.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
static constexpr const char* const m_hardwareID = "AaroniaRTSA";
static constexpr const char* const m_deviceTypeID = AARONIARTSA_DEVICE_TYPE_ID;
AaroniaRTSAInputPlugin::AaroniaRTSAInputPlugin(QObject* parent) :
QObject(parent)
{
}
const PluginDescriptor& AaroniaRTSAInputPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AaroniaRTSAInputPlugin::initPlugin(PluginAPI* pluginAPI)
{
pluginAPI->registerSampleSource(m_deviceTypeID, this);
}
void AaroniaRTSAInputPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices)
{
if (listedHwIds.contains(m_hardwareID)) { // check if it was done
return;
}
originDevices.append(OriginDevice(
"AaroniaRTSA",
m_hardwareID,
QString(),
0,
1, // nb Rx
0 // nb Tx
));
listedHwIds.append(m_hardwareID);
}
PluginInterface::SamplingDevices AaroniaRTSAInputPlugin::enumSampleSources(const OriginDevices& originDevices)
{
SamplingDevices result;
for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it)
{
if (it->hardwareId == m_hardwareID)
{
result.append(SamplingDevice(
it->displayableName,
m_hardwareID,
m_deviceTypeID,
it->serial,
it->sequence,
PluginInterface::SamplingDevice::BuiltInDevice,
PluginInterface::SamplingDevice::StreamSingleRx,
1,
0
));
}
}
return result;
}
#ifdef SERVER_MODE
DeviceGUI* AaroniaRTSAInputPlugin::createSampleSourcePluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
(void) sourceId;
(void) widget;
(void) deviceUISet;
return 0;
}
#else
DeviceGUI* AaroniaRTSAInputPlugin::createSampleSourcePluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
if(sourceId == m_deviceTypeID) {
AaroniaRTSAInputGui* gui = new AaroniaRTSAInputGui(deviceUISet);
*widget = gui;
return gui;
} else {
return 0;
}
}
#endif
DeviceSampleSource *AaroniaRTSAInputPlugin::createSampleSourcePluginInstance(const QString& sourceId, DeviceAPI *deviceAPI)
{
if (sourceId == m_deviceTypeID)
{
AaroniaRTSAInput* input = new AaroniaRTSAInput(deviceAPI);
return input;
}
else
{
return 0;
}
}
DeviceWebAPIAdapter *AaroniaRTSAInputPlugin::createDeviceWebAPIAdapter() const
{
return new AaroniaRTSAInputWebAPIAdapter();
}
@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 _AARONIARTSA_AARONIARTSAPLUGIN_H
#define _AARONIARTSA_AARONIARTSAPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class PluginAPI;
#define AARONIARTSA_DEVICE_TYPE_ID "sdrangel.samplesource.aaroniartsasource"
class AaroniaRTSAInputPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID AARONIARTSA_DEVICE_TYPE_ID)
public:
explicit AaroniaRTSAInputPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices);
virtual SamplingDevices enumSampleSources(const OriginDevices& originDevices);
virtual DeviceGUI* createSampleSourcePluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet);
virtual DeviceSampleSource* createSampleSourcePluginInstance(const QString& sourceId, DeviceAPI *deviceAPI);
virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
};
#endif // _AARONIARTSA_AARONIARTSAPLUGIN_H
@@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 "util/simpleserializer.h"
#include "aaroniartsainputsettings.h"
AaroniaRTSAInputSettings::AaroniaRTSAInputSettings()
{
resetToDefaults();
}
void AaroniaRTSAInputSettings::resetToDefaults()
{
m_centerFrequency = 1450000;
m_sampleRate = 200000;
m_serverAddress = "127.0.0.1:8073";
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
}
QByteArray AaroniaRTSAInputSettings::serialize() const
{
SimpleSerializer s(2);
s.writeString(2, m_serverAddress);
s.writeS32(3, m_sampleRate);
s.writeBool(100, m_useReverseAPI);
s.writeString(101, m_reverseAPIAddress);
s.writeU32(102, m_reverseAPIPort);
s.writeU32(103, m_reverseAPIDeviceIndex);
return s.final();
}
bool AaroniaRTSAInputSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 2)
{
uint32_t utmp;
d.readString(2, &m_serverAddress, "127.0.0.1:8073");
d.readS32(3, &m_sampleRate, 200000);
d.readBool(100, &m_useReverseAPI, false);
d.readString(101, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(102, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
}
else {
m_reverseAPIPort = 8888;
}
d.readU32(103, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}
void AaroniaRTSAInputSettings::applySettings(const QStringList& settingsKeys, const AaroniaRTSAInputSettings& settings)
{
if (settingsKeys.contains("centerFrequency")) {
m_centerFrequency = settings.m_centerFrequency;
}
if (settingsKeys.contains("sampleRate")) {
m_sampleRate = settings.m_sampleRate;
}
if (settingsKeys.contains("serverAddress")) {
m_serverAddress = settings.m_serverAddress;
}
if (settingsKeys.contains("useReverseAPI")) {
m_useReverseAPI = settings.m_useReverseAPI;
}
if (settingsKeys.contains("reverseAPIAddress")) {
m_reverseAPIAddress = settings.m_reverseAPIAddress;
}
if (settingsKeys.contains("reverseAPIPort")) {
m_reverseAPIPort = settings.m_reverseAPIPort;
}
if (settingsKeys.contains("reverseAPIDeviceIndex")) {
m_reverseAPIDeviceIndex = settings.m_reverseAPIDeviceIndex;
}
}
QString AaroniaRTSAInputSettings::getDebugString(const QStringList& settingsKeys, bool force) const
{
std::ostringstream ostr;
if (settingsKeys.contains("centerFrequency") || force) {
ostr << " m_centerFrequency: " << m_centerFrequency;
}
if (settingsKeys.contains("sampleRate") || force) {
ostr << " m_sampleRate: " << m_sampleRate;
}
if (settingsKeys.contains("serverAddress") || force) {
ostr << " m_serverAddress: " << m_serverAddress.toStdString();
}
if (settingsKeys.contains("useReverseAPI") || force) {
ostr << " m_useReverseAPI: " << m_useReverseAPI;
}
if (settingsKeys.contains("reverseAPIAddress") || force) {
ostr << " m_reverseAPIAddress: " << m_reverseAPIAddress.toStdString();
}
if (settingsKeys.contains("reverseAPIPort") || force) {
ostr << " m_reverseAPIPort: " << m_reverseAPIPort;
}
if (settingsKeys.contains("reverseAPIDeviceIndex") || force) {
ostr << " m_reverseAPIDeviceIndex: " << m_reverseAPIDeviceIndex;
}
return QString(ostr.str().c_str());
}
@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 _AARONIARTSA_AARONIARTSASETTINGS_H_
#define _AARONIARTSA_AARONIARTSASETTINGS_H_
#include <QString>
#include <QByteArray>
struct AaroniaRTSAInputSettings {
enum ConnectionStatus
{
ConnectionIdle, // 0 - gray
ConnectionUnstable, // 1 - yellow
ConnectionOK, // 2 - green
ConnectionError, // 3 - red
ConnectionDisconnected // 4 - magenta
};
quint64 m_centerFrequency;
int m_sampleRate;
QString m_serverAddress;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
AaroniaRTSAInputSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void applySettings(const QStringList& settingsKeys, const AaroniaRTSAInputSettings& settings);
QString getDebugString(const QStringList& settingsKeys, bool force=false) const;
};
#endif /* _AARONIARTSA_AARONIARTSASETTINGS_H_ */
@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// Implementation of static web API adapters used for preset serialization and //
// deserialization //
// //
// 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 "SWGDeviceSettings.h"
#include "aaroniartsainput.h"
#include "aaroniartsainputwebapiadapter.h"
AaroniaRTSAInputWebAPIAdapter::AaroniaRTSAInputWebAPIAdapter()
{}
AaroniaRTSAInputWebAPIAdapter::~AaroniaRTSAInputWebAPIAdapter()
{}
int AaroniaRTSAInputWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAaroniaRtsaSettings(new SWGSDRangel::SWGAaroniaRTSASettings());
response.getAaroniaRtsaSettings()->init();
AaroniaRTSAInput::webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int AaroniaRTSAInputWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
AaroniaRTSAInput::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response);
return 200;
}
@@ -0,0 +1,44 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// Implementation of static web API adapters used for preset serialization and //
// deserialization //
// //
// 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 "device/devicewebapiadapter.h"
#include "aaroniartsainputsettings.h"
class AaroniaRTSAInputWebAPIAdapter : public DeviceWebAPIAdapter
{
public:
AaroniaRTSAInputWebAPIAdapter();
virtual ~AaroniaRTSAInputWebAPIAdapter();
virtual QByteArray serialize() { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage);
private:
AaroniaRTSAInputSettings m_settings;
};
@@ -0,0 +1,422 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 "dsp/dspcommands.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);
// intialize 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 = endFreq - startFreq;
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 obhect: " << 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));
}
}
@@ -0,0 +1,123 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB //
// //
// 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 _AARONIARTSA_AARONIARTSAWORKER_H_
#define _AARONIARTSA_AARONIARTSAWORKER_H_
#include <QTimer>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QJsonDocument>
#include <QObject>
#include "dsp/decimatorsfi.h"
class MessageQueue;
class AaroniaRTSAInputWorker : public QObject {
Q_OBJECT
public:
class MsgReportSampleRateAndFrequency : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
quint64 getCenterFrequency() const { return m_centerFrequency; }
static MsgReportSampleRateAndFrequency* create(int sampleRate, quint64 centerFrequency) {
return new MsgReportSampleRateAndFrequency(sampleRate, centerFrequency);
}
private:
int m_sampleRate;
quint64 m_centerFrequency;
MsgReportSampleRateAndFrequency(int sampleRate, qint64 centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
};
AaroniaRTSAInputWorker(SampleSinkFifo* sampleFifo);
~AaroniaRTSAInputWorker();
int getStatus() const { return m_status; }
void setInputMessageQueue(MessageQueue *messageQueue) { m_inputMessageQueue = messageQueue; }
private:
QTimer m_timer;
SampleVector m_samplesBuf;
SampleSinkFifo* m_sampleFifo;
QString m_serverAddress;
quint64 m_centerFrequency;
int m_sampleRate;
MessageQueue *m_inputMessageQueue;
int m_status; //!< See GUI for status number detail
void sendCenterFrequencyAndSampleRate();
void getConfig();
void parseConfig(QByteArray bytes);
// QT htttp clients
QNetworkAccessManager *m_networkAccessManager;
QNetworkAccessManager *m_networkAccessManagerConfig;
// Reply from the HTTP server
QNetworkReply *mReply;
// Input buffer
QByteArray mBuffer;
// Number of IQ sample pairs in the current packet
int mPacketSamples;
// Previous sample end time to check for packet loss
double mPrevTime;
// Current iQ demodulator name
QString m_iqDemodName;
//Decimators<qint32, float, SDR_RX_SAMP_SZ, 32, true> m_decimatorsIQ;
DecimatorsFI<true> m_decimatorsFloatIQ;
SampleVector m_convertBuffer;
//void workIQ(unsigned int n_items);
signals:
void updateStatus(int status);
public slots:
void onCenterFrequencyChanged(quint64 centerFrequency);
void onSampleRateChanged(int sampleRate);
void onServerAddressChanged(QString serverAddress);
private slots:
void onSocketError(QAbstractSocket::SocketError error);
void onError(QNetworkReply::NetworkError code);
void onFinished(void);
void onReadyRead(void);
void handleConfigReply(QNetworkReply* reply);
void tick();
};
#endif // _AARONIARTSA_AARONIARTSAWORKER_H_
@@ -0,0 +1,66 @@
<h1>AaroniaRTSA input plugin</h1>
<h2>Introduction</h2>
You can use this plugin to interface with a http server block in the Aaronia RTSA suite connected to a Spectran V6 device. It is assumed that you have prior knowledge of the Aaronia RTSA suite software and operation of the Spectran V6 RTSA (Real Time Spectrum Analyzer). However in this context there are some specificities i.e. it assumes that the "mission" (in RTSA suite terms) that is the equivalent of a "configuration" in SDRangel has a `HTTP Server` block preceded by a `IQ Demodulator` block (the equivalent of the "Frequency translating FIR filter" in GNU radio). The center frequency and span (equal to decimated sample rate) can be controlled from either RTSA sutie or SDRangel.
An example flow graph could be the following (with two http servers) hence two possible Aaronia receivers in SDRangel:
![Aaronia RTSA dual server](../../../doc/img/AaroniaRTSAInput_mission.png)
On SDRangel side two instances of the plugin can listen to each one of the servers:
![Aaronia RTSA dual server](../../../doc/img/AaroniaRTSAInput_sdrangel.png)
Of course the `IQ Demodulator` span should fit in the bandwidth set in the Spectran V6 block else it will yield no output.
You can obviously run the RTSA suite and SDRangel on the same machine and connect via localhost but there are advantages on a split setup:
- The workload can be split between RTSA suite and SDRangel on two different machines. RTSA suite has to ingurgitate data from the Spectran and process it at a very high sample rate so this is demanding on CPU and latency. Often it is better to run it on its own dedicated machine.
- You can have the RTSA suite run on a machine close to the Spectran. This is in fact mandatory due to the length of the USB cables. And you can run SDRangel on another machine (possibly quite more lightweight) anywhere on the network. You can also multiply the client machines running SDRangel.
<h2>Interface</h2>
![Aaronia RTSA dual server](../../../doc/img/AaroniaRTSAInput.png)
The top and bottom bars of the device window are described [here](../../../sdrgui/device/readme.md)
<h3>1: Start/Stop</h3>
This button is used to start the "device" i.e connect to the remote and start stream or stop the "device" i.e. stop stream and disconnect from the remote. The button shows the following faces:
- Blue triangle icon: device is ready and can be started
- Green square icon: device is running and can be stopped
<h2>2: Stream sample rate</h2>
This is the stream sample rate in S/s with multiplier. It should be equal to what is displayed in (4).
<h3>3: Frequency</h3>
This is the center frequency received in he stream meta data. When setting it it will try to set the center frequency of the `IQ Demodulator` in RTSA suite the closest to the `HTTP server`.
<h3>4: Stream sample rate</h3>
This is sample rate (actually the frequency span) received in the stream meta data. When setting it it will try to set the sample rate and frequency span of the IQ Demodulator` in RTSA suite the closest to the `HTTP server`.
<h3>5: Remote address and port</h3>
This is the remote address and port of the `HTTP server` block in RTSA suite. The address has to be in the form of an IPv4 address.
Press button (7) to validate your change.
<h3>6: Status indicator</h3>
This ball can take the following colors depending on the status:
- <span style="color:gray">**Gray**</span>: Idle
- <span style="color:yellow">**Yellow**</span>: Unstable
- <span style="color:green">**Green**</span>: Connected
- <span style="color:red">**Red**</span>: Error
- <span style="color:magenta">**Magenta**</span>: Disconnected
<h3>7: Set address</h3>
When you change the address in (5) you have to push this button to validate the change.
+2 -2
View File
@@ -399,8 +399,8 @@ void AirspyGui::on_transverter_clicked()
updateFrequencyLimits();
m_settings.m_centerFrequency = ui->centerFrequency->getValueNew()*1000;
m_settingsKeys.append("transverterMode");
m_settingsKeys.append("m_transverterDeltaFrequency");
m_settingsKeys.append("m_iqOrder");
m_settingsKeys.append("transverterDeltaFrequency");
m_settingsKeys.append("iqOrder");
m_settingsKeys.append("centerFrequency");
sendSettings();
}
+1 -1
View File
@@ -55,7 +55,7 @@ private:
int m_fcPos;
bool m_iqOrder;
Decimators<qint32, qint16, SDR_RX_SAMP_SZ, 12, true> m_decimatorsIQ;
Decimators<qint32, qint16, SDR_RX_SAMP_SZ, 12, true> m_decimatorsIQ;
Decimators<qint32, qint16, SDR_RX_SAMP_SZ, 12, false> m_decimatorsQI;
void run();