1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-05 23:45:00 -04:00

New plugin pair LocalSink and LocalInput to pipe streams internally

This commit is contained in:
f4exb
2019-05-02 04:02:40 +02:00
parent 84dc7e0bb0
commit 9e5003eab9
333 changed files with 5869 additions and 267 deletions
@@ -0,0 +1,72 @@
project(localinput)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(localinput_SOURCES
localinputgui.cpp
localinput.cpp
localinputsettings.cpp
localinputplugin.cpp
)
set(localinput_HEADERS
localinputgui.h
localinput.h
localinputsettings.h
localinputplugin.h
)
set(localinput_FORMS
localinputgui.ui
)
#include(${QT_USE_FILE})
add_definitions(${QT_DEFINITIONS})
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_SHARED)
qt5_wrap_ui(localinput_FORMS_HEADERS ${localinput_FORMS})
add_library(inputlocal SHARED
${localinput_SOURCES}
${localinput_HEADERS_MOC}
${localinput_FORMS_HEADERS}
)
if (BUILD_DEBIAN)
target_include_directories(inputlocal PUBLIC
.
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${LIBCM256CCSRC}/..
)
else (BUILD_DEBIAN)
target_include_directories(inputlocal PUBLIC
.
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${CM256CC_INCLUDE_DIR}
)
endif (BUILD_DEBIAN)
if (BUILD_DEBIAN)
target_link_libraries(inputlocal
${QT_LIBRARIES}
cm256cc
sdrbase
sdrgui
swagger
)
else (BUILD_DEBIAN)
target_link_libraries(inputlocal
${QT_LIBRARIES}
${CM256CC_LIBRARIES}
sdrbase
sdrgui
swagger
)
endif (BUILD_DEBIAN)
target_link_libraries(inputlocal Qt5::Core Qt5::Widgets)
install(TARGETS inputlocal DESTINATION lib/plugins/samplesource)
@@ -0,0 +1,476 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 <QBuffer>
#include "SWGDeviceSettings.h"
#include "SWGDeviceState.h"
#include "SWGDeviceReport.h"
#include "SWGLocalInputReport.h"
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include "device/devicesourceapi.h"
#include "dsp/filerecord.h"
#include "localinput.h"
MESSAGE_CLASS_DEFINITION(LocalInput::MsgConfigureLocalInput, Message)
MESSAGE_CLASS_DEFINITION(LocalInput::MsgFileRecord, Message)
MESSAGE_CLASS_DEFINITION(LocalInput::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(LocalInput::MsgReportSampleRateAndFrequency, Message)
LocalInput::LocalInput(DeviceSourceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_settings(),
m_deviceDescription("LocalInput"),
m_startingTimeStamp(0)
{
m_sampleFifo.setSize(96000 * 4);
m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID()));
m_deviceAPI->addSink(m_fileSink);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
LocalInput::~LocalInput()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
stop();
m_deviceAPI->removeSink(m_fileSink);
delete m_fileSink;
}
void LocalInput::destroy()
{
delete this;
}
void LocalInput::init()
{
applySettings(m_settings, true);
}
bool LocalInput::start()
{
qDebug() << "LocalInput::start";
return true;
}
void LocalInput::stop()
{
qDebug() << "LocalInput::stop";
}
QByteArray LocalInput::serialize() const
{
return m_settings.serialize();
}
bool LocalInput::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureLocalInput* message = MsgConfigureLocalInput::create(m_settings, true);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureLocalInput* messageToGUI = MsgConfigureLocalInput::create(m_settings, true);
m_guiMessageQueue->push(messageToGUI);
}
return success;
}
void LocalInput::setMessageQueueToGUI(MessageQueue *queue)
{
m_guiMessageQueue = queue;
}
const QString& LocalInput::getDeviceDescription() const
{
return m_deviceDescription;
}
int LocalInput::getSampleRate() const
{
return m_sampleRate;
}
void LocalInput::setSampleRate(int sampleRate)
{
m_sampleRate = sampleRate;
if (getMessageQueueToGUI())
{
MsgReportSampleRateAndFrequency *msg = MsgReportSampleRateAndFrequency::create(m_sampleRate, m_centerFrequency);
getMessageQueueToGUI()->push(msg);
}
}
quint64 LocalInput::getCenterFrequency() const
{
return m_centerFrequency;
}
void LocalInput::setCenterFrequency(qint64 centerFrequency)
{
m_centerFrequency = centerFrequency;
if (getMessageQueueToGUI())
{
MsgReportSampleRateAndFrequency *msg = MsgReportSampleRateAndFrequency::create(m_sampleRate, m_centerFrequency);
getMessageQueueToGUI()->push(msg);
}
}
std::time_t LocalInput::getStartingTimeStamp() const
{
return m_startingTimeStamp;
}
bool LocalInput::handleMessage(const Message& message)
{
if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
return m_fileSink->handleMessage(notif); // forward to file sink
}
else if (MsgFileRecord::match(message))
{
MsgFileRecord& conf = (MsgFileRecord&) message;
qDebug() << "LocalInput::handleMessage: MsgFileRecord: " << conf.getStartStop();
if (conf.getStartStop())
{
if (m_settings.m_fileRecordName.size() != 0) {
m_fileSink->setFileName(m_settings.m_fileRecordName);
} else {
m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID());
}
m_fileSink->startRecording();
}
else
{
m_fileSink->stopRecording();
}
return true;
}
else if (MsgStartStop::match(message))
{
MsgStartStop& cmd = (MsgStartStop&) message;
qDebug() << "LocalInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop");
if (cmd.getStartStop())
{
if (m_deviceAPI->initAcquisition())
{
m_deviceAPI->startAcquisition();
}
}
else
{
m_deviceAPI->stopAcquisition();
}
if (m_settings.m_useReverseAPI) {
webapiReverseSendStartStop(cmd.getStartStop());
}
return true;
}
else if (MsgConfigureLocalInput::match(message))
{
qDebug() << "LocalInput::handleMessage:" << message.getIdentifier();
MsgConfigureLocalInput& conf = (MsgConfigureLocalInput&) message;
applySettings(conf.getSettings(), conf.getForce());
return true;
}
else
{
return false;
}
}
void LocalInput::applySettings(const LocalInputSettings& settings, bool force)
{
QMutexLocker mutexLocker(&m_mutex);
std::ostringstream os;
QString remoteAddress;
QList<QString> reverseAPIKeys;
if ((m_settings.m_dcBlock != settings.m_dcBlock) || force) {
reverseAPIKeys.append("dcBlock");
}
if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) {
reverseAPIKeys.append("iqCorrection");
}
if ((m_settings.m_fileRecordName != settings.m_fileRecordName) || force) {
reverseAPIKeys.append("fileRecordName");
}
if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force)
{
m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection);
qDebug("LocalInput::applySettings: corrections: DC block: %s IQ imbalance: %s",
settings.m_dcBlock ? "true" : "false",
settings.m_iqCorrection ? "true" : "false");
}
mutexLocker.unlock();
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
m_remoteAddress = remoteAddress;
qDebug() << "LocalInput::applySettings: "
<< " m_dcBlock: " << m_settings.m_dcBlock
<< " m_iqCorrection: " << m_settings.m_iqCorrection
<< " m_fileRecordName: " << m_settings.m_fileRecordName
<< " m_remoteAddress: " << m_remoteAddress;
}
int LocalInput::webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
return 200;
}
int LocalInput::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 LocalInput::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setLocalInputSettings(new SWGSDRangel::SWGLocalInputSettings());
response.getLocalInputSettings()->init();
webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int LocalInput::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) errorMessage;
LocalInputSettings settings = m_settings;
if (deviceSettingsKeys.contains("dcBlock")) {
settings.m_dcBlock = response.getLocalInputSettings()->getDcBlock() != 0;
}
if (deviceSettingsKeys.contains("iqCorrection")) {
settings.m_iqCorrection = response.getLocalInputSettings()->getIqCorrection() != 0;
}
if (deviceSettingsKeys.contains("fileRecordName")) {
settings.m_fileRecordName = *response.getLocalInputSettings()->getFileRecordName();
}
if (deviceSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getLocalInputSettings()->getUseReverseApi() != 0;
}
if (deviceSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getLocalInputSettings()->getReverseApiAddress();
}
if (deviceSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getLocalInputSettings()->getReverseApiPort();
}
if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getLocalInputSettings()->getReverseApiDeviceIndex();
}
MsgConfigureLocalInput *msg = MsgConfigureLocalInput::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureLocalInput *msgToGUI = MsgConfigureLocalInput::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatDeviceSettings(response, settings);
return 200;
}
void LocalInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LocalInputSettings& settings)
{
response.getLocalInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0);
response.getLocalInputSettings()->setIqCorrection(settings.m_iqCorrection);
if (response.getLocalInputSettings()->getFileRecordName()) {
*response.getLocalInputSettings()->getFileRecordName() = settings.m_fileRecordName;
} else {
response.getLocalInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName));
}
response.getLocalInputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getLocalInputSettings()->getReverseApiAddress()) {
*response.getLocalInputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getLocalInputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getLocalInputSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getLocalInputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
}
int LocalInput::webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setLocalInputReport(new SWGSDRangel::SWGLocalInputReport());
response.getLocalInputReport()->init();
webapiFormatDeviceReport(response);
return 200;
}
void LocalInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
{
(void) response;
}
void LocalInput::webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const LocalInputSettings& settings, bool force)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setTx(0);
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("LocalInput"));
swgDeviceSettings->setLocalInputSettings(new SWGSDRangel::SWGLocalInputSettings());
SWGSDRangel::SWGLocalInputSettings *swgLocalInputSettings = swgDeviceSettings->getLocalInputSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (deviceSettingsKeys.contains("dcBlock") || force) {
swgLocalInputSettings->setDcBlock(settings.m_dcBlock ? 1 : 0);
}
if (deviceSettingsKeys.contains("iqCorrection") || force) {
swgLocalInputSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0);
}
if (deviceSettingsKeys.contains("fileRecordName") || force) {
swgLocalInputSettings->setFileRecordName(new QString(settings.m_fileRecordName));
}
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
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
delete swgDeviceSettings;
}
void LocalInput::webapiReverseSendStartStop(bool start)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setTx(0);
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("LocalInput"));
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);
if (start) {
m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer);
} else {
m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer);
}
}
void LocalInput::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "LocalInput::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
return;
}
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("LocalInput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
@@ -0,0 +1,192 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 INCLUDE_REMOTEINPUT_H
#define INCLUDE_REMOTEINPUT_H
#include <ctime>
#include <iostream>
#include <stdint.h>
#include <QString>
#include <QByteArray>
#include <QTimer>
#include <QNetworkRequest>
#include "dsp/devicesamplesource.h"
#include "localinputsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class DeviceSourceAPI;
class FileRecord;
class LocalInput : public DeviceSampleSource {
Q_OBJECT
public:
class MsgConfigureLocalInput : public Message {
MESSAGE_CLASS_DECLARATION
public:
const LocalInputSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureLocalInput* create(const LocalInputSettings& settings, bool force = false)
{
return new MsgConfigureLocalInput(settings, force);
}
private:
LocalInputSettings m_settings;
bool m_force;
MsgConfigureLocalInput(const LocalInputSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgFileRecord : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getStartStop() const { return m_startStop; }
static MsgFileRecord* create(bool startStop) {
return new MsgFileRecord(startStop);
}
protected:
bool m_startStop;
MsgFileRecord(bool startStop) :
Message(),
m_startStop(startStop)
{ }
};
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 MsgReportSampleRateAndFrequency : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getCenterFrequency() const { return m_centerFrequency; }
static MsgReportSampleRateAndFrequency* create(int sampleRate, qint64 centerFrequency) {
return new MsgReportSampleRateAndFrequency(sampleRate, centerFrequency);
}
protected:
int m_sampleRate;
qint64 m_centerFrequency;
MsgReportSampleRateAndFrequency(int sampleRate, qint64 centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
};
LocalInput(DeviceSourceAPI *deviceAPI);
virtual ~LocalInput();
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);
virtual const QString& getDeviceDescription() const;
virtual int getSampleRate() const;
virtual void setSampleRate(int sampleRate);
virtual quint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
std::time_t getStartingTimeStamp() const;
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 webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage);
virtual int webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiRun(
bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
private:
DeviceSourceAPI *m_deviceAPI;
QMutex m_mutex;
LocalInputSettings m_settings;
qint64 m_centerFrequency;
int m_sampleRate;
QString m_remoteAddress;
QString m_deviceDescription;
std::time_t m_startingTimeStamp;
FileRecord *m_fileSink; //!< File sink to record device I/Q output
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const LocalInputSettings& settings, bool force = false);
void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LocalInputSettings& settings);
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
void webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const LocalInputSettings& settings, bool force);
void webapiReverseSendStartStop(bool start);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_REMOTEINPUT_H
@@ -0,0 +1,58 @@
#--------------------------------------------------------
#
# Pro file for Android and Windows builds with Qt Creator
#
#--------------------------------------------------------
TEMPLATE = lib
CONFIG += plugin
QT += core gui widgets multimedia network opengl
TARGET = inputlocal
INCLUDEPATH += $$PWD
INCLUDEPATH += ../../../exports
INCLUDEPATH += ../../../sdrbase
INCLUDEPATH += ../../../sdrgui
INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client
macx:INCLUDEPATH += /opt/local/include
DEFINES += USE_SSE2=1
QMAKE_CXXFLAGS += -msse2
DEFINES += USE_SSSE3=1
QMAKE_CXXFLAGS += -mssse3
DEFINES += USE_SSE4_1=1
QMAKE_CXXFLAGS += -msse4.1
QMAKE_CXXFLAGS += -std=c++11
CONFIG(Release):build_subdir = release
CONFIG(Debug):build_subdir = debug
CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0"
CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0"
CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0"
SOURCES += localinputgui.cpp\
localinput.cpp\
localinputsettings.cpp\
localinputplugin.cpp
HEADERS += localinputgui.h\
localinput.h\
localinputsettings.h\
localinputplugin.h
FORMS += localinputgui.ui
LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui
LIBS += -L../../../swagger/$${build_subdir} -lswagger
macx {
QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/
}
RESOURCES = ../../../sdrgui/resources/res.qrc
CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1
@@ -0,0 +1,371 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 <stdint.h>
#include <sstream>
#include <iostream>
#include <cassert>
#include <QDebug>
#include <QMessageBox>
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonParseError>
#include <QJsonObject>
#include "ui_localinputgui.h"
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "gui/crightclickenabler.h"
#include "gui/basicdevicesettingsdialog.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "mainwindow.h"
#include "util/simpleserializer.h"
#include "device/devicesourceapi.h"
#include "device/deviceuiset.h"
#include "localinputgui.h"
LocalInputGui::LocalInputGui(DeviceUISet *deviceUISet, QWidget* parent) :
QWidget(parent),
ui(new Ui::LocalInputGui),
m_deviceUISet(deviceUISet),
m_settings(),
m_sampleSource(0),
m_acquisition(false),
m_streamSampleRate(0),
m_streamCenterFrequency(0),
m_lastEngineState(DSPDeviceSourceEngine::StNotStarted),
m_framesDecodingStatus(0),
m_bufferLengthInSecs(0.0),
m_bufferGauge(-50),
m_nbOriginalBlocks(128),
m_nbFECBlocks(0),
m_sampleBits(16), // assume 16 bits to start with
m_sampleBytes(2),
m_samplesCount(0),
m_tickCount(0),
m_addressEdited(false),
m_dataPortEdited(false),
m_countUnrecoverable(0),
m_countRecovered(0),
m_doApplySettings(true),
m_forceSettings(true),
m_txDelay(0.0)
{
m_paletteGreenText.setColor(QPalette::WindowText, Qt::green);
m_paletteWhiteText.setColor(QPalette::WindowText, Qt::white);
m_startingTimeStampms = 0;
ui->setupUi(this);
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->centerFrequency->setValueRange(7, 0, 9999999U);
ui->centerFrequencyHz->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->centerFrequencyHz->setValueRange(3, 0, 999U);
CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop);
connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
displaySettings();
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
m_sampleSource = (LocalInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource();
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
m_eventsTime.start();
m_forceSettings = true;
sendSettings();
}
LocalInputGui::~LocalInputGui()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
delete ui;
}
void LocalInputGui::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void LocalInputGui::destroy()
{
delete this;
}
void LocalInputGui::setName(const QString& name)
{
setObjectName(name);
}
QString LocalInputGui::getName() const
{
return objectName();
}
void LocalInputGui::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
m_forceSettings = true;
sendSettings();
}
QByteArray LocalInputGui::serialize() const
{
return m_settings.serialize();
}
bool LocalInputGui::deserialize(const QByteArray& data)
{
qDebug("LocalInputGui::deserialize");
if (m_settings.deserialize(data))
{
displaySettings();
m_forceSettings = true;
sendSettings();
return true;
}
else
{
return false;
}
}
qint64 LocalInputGui::getCenterFrequency() const
{
return m_streamCenterFrequency;
}
void LocalInputGui::setCenterFrequency(qint64 centerFrequency)
{
(void) centerFrequency;
}
bool LocalInputGui::handleMessage(const Message& message)
{
if (LocalInput::MsgConfigureLocalInput::match(message))
{
const LocalInput::MsgConfigureLocalInput& cfg = (LocalInput::MsgConfigureLocalInput&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (LocalInput::MsgStartStop::match(message))
{
LocalInput::MsgStartStop& notif = (LocalInput::MsgStartStop&) message;
blockApplySettings(true);
ui->startStop->setChecked(notif.getStartStop());
blockApplySettings(false);
return true;
}
else if (LocalInput::MsgReportSampleRateAndFrequency::match(message))
{
LocalInput::MsgReportSampleRateAndFrequency& notif = (LocalInput::MsgReportSampleRateAndFrequency&) message;
m_streamSampleRate = notif.getSampleRate();
m_streamCenterFrequency = notif.getCenterFrequency();
updateSampleRateAndFrequency();
return true;
}
else
{
return false;
}
}
void LocalInputGui::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
//qDebug("LocalInputGui::handleInputMessages: message: %s", message->getIdentifier());
if (DSPSignalNotification::match(*message))
{
DSPSignalNotification* notif = (DSPSignalNotification*) message;
if (notif->getSampleRate() != m_streamSampleRate) {
m_streamSampleRate = notif->getSampleRate();
}
m_streamCenterFrequency = notif->getCenterFrequency();
qDebug("LocalInputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency());
updateSampleRateAndFrequency();
DSPSignalNotification *fwd = new DSPSignalNotification(*notif);
m_sampleSource->getInputMessageQueue()->push(fwd);
delete message;
}
else
{
if (handleMessage(*message))
{
delete message;
}
}
}
}
void LocalInputGui::updateSampleRateAndFrequency()
{
m_deviceUISet->getSpectrum()->setSampleRate(m_streamSampleRate);
m_deviceUISet->getSpectrum()->setCenterFrequency(m_streamCenterFrequency);
ui->deviceRateText->setText(tr("%1k").arg((float)m_streamSampleRate / 1000));
blockApplySettings(true);
ui->centerFrequency->setValue(m_streamCenterFrequency / 1000);
ui->centerFrequencyHz->setValue(m_streamCenterFrequency % 1000);
blockApplySettings(false);
}
void LocalInputGui::displaySettings()
{
blockApplySettings(true);
ui->centerFrequency->setValue(m_streamCenterFrequency / 1000);
ui->centerFrequencyHz->setValue(m_streamCenterFrequency % 1000);
ui->deviceRateText->setText(tr("%1k").arg(m_streamSampleRate / 1000.0));
ui->dcOffset->setChecked(m_settings.m_dcBlock);
ui->iqImbalance->setChecked(m_settings.m_iqCorrection);
blockApplySettings(false);
}
void LocalInputGui::sendSettings()
{
if(!m_updateTimer.isActive())
m_updateTimer.start(100);
}
void LocalInputGui::on_dcOffset_toggled(bool checked)
{
m_settings.m_dcBlock = checked;
sendSettings();
}
void LocalInputGui::on_iqImbalance_toggled(bool checked)
{
m_settings.m_iqCorrection = checked;
sendSettings();
}
void LocalInputGui::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
LocalInput::MsgStartStop *message = LocalInput::MsgStartStop::create(checked);
m_sampleSource->getInputMessageQueue()->push(message);
}
}
void LocalInputGui::on_record_toggled(bool checked)
{
if (checked) {
ui->record->setStyleSheet("QToolButton { background-color : red; }");
} else {
ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
LocalInput::MsgFileRecord* message = LocalInput::MsgFileRecord::create(checked);
m_sampleSource->getInputMessageQueue()->push(message);
}
void LocalInputGui::updateHardware()
{
if (m_doApplySettings)
{
qDebug() << "LocalInputGui::updateHardware";
LocalInput::MsgConfigureLocalInput* message =
LocalInput::MsgConfigureLocalInput::create(m_settings, m_forceSettings);
m_sampleSource->getInputMessageQueue()->push(message);
m_forceSettings = false;
m_updateTimer.stop();
}
}
void LocalInputGui::updateStatus()
{
int state = m_deviceUISet->m_deviceSourceAPI->state();
if(m_lastEngineState != state)
{
switch(state)
{
case DSPDeviceSourceEngine::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case DSPDeviceSourceEngine::StIdle:
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case DSPDeviceSourceEngine::StRunning:
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case DSPDeviceSourceEngine::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage());
break;
default:
break;
}
m_lastEngineState = state;
}
}
void LocalInputGui::openDeviceSettingsDialog(const QPoint& p)
{
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);
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();
sendSettings();
}
@@ -0,0 +1,127 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 INCLUDE_REMOTEINPUTGUI_H
#define INCLUDE_REMOTEINPUTGUI_H
#include <QTimer>
#include <QWidget>
#include <QNetworkRequest>
#include "plugin/plugininstancegui.h"
#include "util/messagequeue.h"
#include "localinput.h"
class DeviceUISet;
class QNetworkAccessManager;
class QNetworkReply;
class QJsonObject;
namespace Ui {
class LocalInputGui;
}
class LocalInputGui : public QWidget, public PluginInstanceGUI {
Q_OBJECT
public:
explicit LocalInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0);
virtual ~LocalInputGui();
virtual void destroy();
void setName(const QString& name);
QString getName() const;
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual qint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool handleMessage(const Message& message);
private:
Ui::LocalInputGui* ui;
DeviceUISet* m_deviceUISet;
LocalInputSettings m_settings; //!< current settings
LocalInput* m_sampleSource;
bool m_acquisition;
int m_streamSampleRate; //!< Sample rate of received stream
quint64 m_streamCenterFrequency; //!< Center frequency of received stream
QTimer m_updateTimer;
QTimer m_statusTimer;
int m_lastEngineState;
MessageQueue m_inputMessageQueue;
// int m_sampleRate;
// quint64 m_centerFrequency;
uint64_t m_startingTimeStampms;
int m_framesDecodingStatus;
bool m_allBlocksReceived;
float m_bufferLengthInSecs;
int32_t m_bufferGauge;
int m_minNbBlocks;
int m_minNbOriginalBlocks;
int m_maxNbRecovery;
float m_avgNbBlocks;
float m_avgNbOriginalBlocks;
float m_avgNbRecovery;
int m_nbOriginalBlocks;
int m_nbFECBlocks;
int m_sampleBits;
int m_sampleBytes;
int m_samplesCount;
std::size_t m_tickCount;
bool m_addressEdited;
bool m_dataPortEdited;
uint32_t m_countUnrecoverable;
uint32_t m_countRecovered;
QTime m_eventsTime;
bool m_doApplySettings;
bool m_forceSettings;
double m_txDelay;
QPalette m_paletteGreenText;
QPalette m_paletteWhiteText;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void blockApplySettings(bool block);
void displaySettings();
void displayTime();
void sendSettings();
void updateSampleRateAndFrequency();
private slots:
void handleInputMessages();
void on_dcOffset_toggled(bool checked);
void on_iqImbalance_toggled(bool checked);
void on_startStop_toggled(bool checked);
void on_record_toggled(bool checked);
void updateHardware();
void updateStatus();
void openDeviceSettingsDialog(const QPoint& p);
};
#endif // INCLUDE_REMOTEINPUTGUI_H
@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LocalInputGui</class>
<widget class="QWidget" name="LocalInputGui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>100</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Local Input</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">
<property name="topMargin">
<number>4</number>
</property>
<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>
<item>
<widget class="ButtonSwitch" name="record">
<property name="toolTip">
<string>Toggle record I/Q samples from device</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/record_off.png</normaloff>:/record_off.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="deviceRateLayout">
<item>
<widget class="QLabel" name="deviceRateText">
<property name="toolTip">
<string>I/Q sample rate kS/s</string>
</property>
<property name="text">
<string>00000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="freqLeftSpacer">
<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="enabled">
<bool>false</bool>
</property>
<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>20</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>ForbiddenCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Remote center frequency kHz</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="hertzLayout">
<item>
<widget class="ValueDial" name="centerFrequencyHz" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>ForbiddenCursor</cursorShape>
</property>
<property name="toolTip">
<string>Remote center frequency sub kHz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="freqUnits">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string> Hz</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_address">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="gridLayout_corr">
<item>
<widget class="ButtonSwitch" name="dcOffset">
<property name="toolTip">
<string>DC Offset auto correction</string>
</property>
<property name="text">
<string>DC</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="iqImbalance">
<property name="toolTip">
<string>IQ Imbalance auto correction</string>
</property>
<property name="text">
<string>IQ</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="padLayout">
<item>
<spacer name="verticalPadSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</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,114 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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"
#include "device/devicesourceapi.h"
#ifdef SERVER_MODE
#include "localinput.h"
#else
#include "localinputgui.h"
#endif
#include "localinputplugin.h"
const PluginDescriptor LocalInputPlugin::m_pluginDescriptor = {
QString("Local device input"),
QString("4.6.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
const QString LocalInputPlugin::m_hardwareID = "LocalInput";
const QString LocalInputPlugin::m_deviceTypeID = LOCALINPUT_DEVICE_TYPE_ID;
LocalInputPlugin::LocalInputPlugin(QObject* parent) :
QObject(parent)
{
}
const PluginDescriptor& LocalInputPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void LocalInputPlugin::initPlugin(PluginAPI* pluginAPI)
{
pluginAPI->registerSampleSource(m_deviceTypeID, this);
}
PluginInterface::SamplingDevices LocalInputPlugin::enumSampleSources()
{
SamplingDevices result;
result.append(SamplingDevice(
"LocalInput",
m_hardwareID,
m_deviceTypeID,
QString::null,
0,
PluginInterface::SamplingDevice::BuiltInDevice,
true,
1,
0));
return result;
}
#ifdef SERVER_MODE
PluginInstanceGUI* LocalInputPlugin::createSampleSourcePluginInstanceGUI(
const QString& sourceId __attribute((unused)),
QWidget **widget __attribute((unused)),
DeviceUISet *deviceUISet __attribute((unused)))
{
return 0;
}
#else
PluginInstanceGUI* LocalInputPlugin::createSampleSourcePluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
if(sourceId == m_deviceTypeID)
{
LocalInputGui* gui = new LocalInputGui(deviceUISet);
*widget = gui;
return gui;
}
else
{
return 0;
}
}
#endif
DeviceSampleSource *LocalInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI)
{
if (sourceId == m_deviceTypeID)
{
LocalInput* input = new LocalInput(deviceAPI);
return input;
}
else
{
return 0;
}
}
@@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 INCLUDE_LOCALINPUTPLUGIN_H
#define INCLUDE_LOCALINPUTPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
#define LOCALINPUT_DEVICE_TYPE_ID "sdrangel.samplesource.localinput"
class PluginAPI;
class LocalInputPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID LOCALINPUT_DEVICE_TYPE_ID)
public:
explicit LocalInputPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual SamplingDevices enumSampleSources();
virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet);
virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI);
static const QString m_hardwareID;
static const QString m_deviceTypeID;
private:
static const PluginDescriptor m_pluginDescriptor;
};
#endif // INCLUDE_LOCALINPUTPLUGIN_H
@@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 "localinputsettings.h"
LocalInputSettings::LocalInputSettings()
{
resetToDefaults();
}
void LocalInputSettings::resetToDefaults()
{
m_dcBlock = false;
m_iqCorrection = false;
m_fileRecordName = "";
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
}
QByteArray LocalInputSettings::serialize() const
{
SimpleSerializer s(1);
s.writeBool(1, m_dcBlock);
s.writeBool(2, m_iqCorrection);
s.writeBool(3, m_useReverseAPI);
s.writeString(4, m_reverseAPIAddress);
s.writeU32(5, m_reverseAPIPort);
s.writeU32(6, m_reverseAPIDeviceIndex);
return s.final();
}
bool LocalInputSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
quint32 uintval;
d.readBool(1, &m_dcBlock, false);
d.readBool(2, &m_iqCorrection, false);
d.readBool(3, &m_useReverseAPI, false);
d.readString(4, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(5, &uintval, 0);
if ((uintval > 1023) && (uintval < 65535)) {
m_reverseAPIPort = uintval;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(6, &uintval, 0);
m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval;
return true;
}
else
{
resetToDefaults();
return false;
}
}
@@ -0,0 +1,39 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 PLUGINS_SAMPLESOURCE_LOCALINPUT_LOCALINPUTSETTINGS_H_
#define PLUGINS_SAMPLESOURCE_LOCALINPUT_LOCALINPUTSETTINGS_H_
#include <QByteArray>
#include <QString>
struct LocalInputSettings {
bool m_dcBlock;
bool m_iqCorrection;
QString m_fileRecordName;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
LocalInputSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_SAMPLESOURCE_LOCALINPUT_LOCALINPUTSETTINGS_H_ */
+173
View File
@@ -0,0 +1,173 @@
<h1>Remote input plugin</h1>
<h2>Introduction</h2>
This input sample source plugin gets its samples over tbe network from a SDRangel instance's Remote channel sink using UDP connection.
Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links.
Please note that there is no provision for handling out of sync UDP blocks. It is assumed that frames and block numbers always increase with possible blocks missing. Such out of sync situation has never been encountered in practice.
The distant SDRangel instance that sends the data stream is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli)
A sample size conversion takes place if the stream sample size sent by the distant instance and the Rx sample size of the local instance do not match (i.e. 16 to 24 bits or 24 to 16 bits). Best performace is obtained when both instances use the same sample size.
It is present only in Linux binary releases.
<h2>Build</h2>
The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_DIR=/opt/install/cm256cc` to the cmake commands.
<h2>Interface</h2>
![SDR Remote input plugin GUI](../../../doc/img/RemoteInput_plugin.png)
<h3>1: Start/Stop</h3>
Device start / stop button.
- Blue triangle icon: device is ready and can be started
- Green square icon: device is running and can be stopped
<h3>2: Record</h3>
Record I/Q stream toggle button
<h3>3: Frequency</h3>
This is the center frequency in Hz sent in the meta data from the distant SDRangel instance and corresponds to the center frequency of reception. The sub kHz value (000 to 999 Hz) is represented in smaller digits on the right.
<h3>4: Stream sample rate</h3>
Stream I/Q sample rate in kS/s
<h3>5: Auto correction options and stream status</h3>
![SDR Remote input stream GUI](../../../doc/img/RemoteInput_plugin_02.png)
<h4>5.1: Auto correction options</h4>
These buttons control the local DSP auto correction options:
- **DC**: auto remove DC component
- **IQ**: auto make I/Q balance
<h4>5.2: Receive buffer length</h4>
This is the main buffer (writes from UDP / reads from DSP engine) length in units of time (seconds). As read and write pointers are normally about half the buffer apart the nominal delay introduced by the buffer is the half of this value.
<h4>5.3: Main buffer R/W pointers positions</h4>
Read and write pointers should always be a half buffer distance buffer apart. This is the difference in percent of the main buffer size from this ideal position.
- When positive it means that the read pointer is leading
- When negative it means that the write pointer is leading (read is lagging)
This corresponds to the value shown in the gauges above (9)
<h4>5.4: Date/time</h4>
This is the current timestamp of the block of data sent from the receiver. It is refreshed about every second. The plugin tries to take into account the buffer that is used between the data received from the network and the data effectively used by the system however this may not be extremely accurate. It is based on the timestamps sent from the Remote sink channel at the other hand that does not take into account its own buffers.
<h3>6: Main buffer R/W pointers gauge</h3>
There are two gauges separated by a dot in the center. Ideally these gauges should not display any value thus read and write pointers are always half a buffer apart. However due to the fact that a whole frame is reconstructed at once up to ~10% variation is normal and should appear on the left gauge (write leads).
- The left gauge is the negative gauge. It is the value in percent of buffer size from the write pointer position to the read pointer position when this difference is less than half of a buffer distance. It means that the writes are leading or reads are lagging.
- The right gauge is the positive gauge. It is the value in percent of buffer size of the difference from the read pointer position to the write pointer position when this difference is less than half of a buffer distance. It menas that the writes are lagging or reads are leading.
The system tries to compensate read / write unbalance however at start or when a large stream disruption has occurred a delay of a few tens of seconds is necessary before read / write reaches equilibrium.
<h3>7: Data stream status</h3>
![SDR Remote input stream GUI](../../../doc/img/RemoteInput_plugin_04.png)
<h4>7.1: Sample size</h4>
This is the size in bits of a I or Q sample sent in the stream by the distant server.
<h4>7.2: Total number of frames and number of FEC blocks</h4>
This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server.
A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction.
Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered.
<h4>7.3: Stream status</h4>
The color of the icon indicates stream status:
- Green: all original blocks have been received for all frames during the last polling timeframe (ex: 136)
- No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 135)
- Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128)
<h4>7.4: Minimum total number of blocks per frame</h4>
This is the minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+8 = 136.
If this number falls below 128 then some blocks are definitely lost and the lock lights in red.
<h4>7.5: Maximum number of FEC blocks used by frame</h4>
Maximum number of FEC blocks used for original blocks recovery during the last polling timeframe. Ideally this should be 0 when no blocks are lost but the system is able to correct lost blocks up to the nominal number of FEC blocks (Neutral lock icon).
<h4>7.6: Reset events counters</h4>
This push button can be used to reset the events counters (4.7 and 4.8) and reset the event counts timer (4.9)
<h4>7.7: Unrecoverable error events counter</h4>
This counter counts the unrecoverable error conditions found (i.e. 4.4 lower than 128) since the last counters reset.
<h4>7.8: Recoverable error events counter</h4>
This counter counts the unrecoverable error conditions found (i.e. 4.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset.
<h4>7.9: events counters timer</h4>
This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed.
<h3>8: Distant server API address and port</h3>
![SDR Remote input stream GUI](../../../doc/img/RemoteInput_plugin_05.png)
<h4>8.1: API connection indicator</h4>
The "API" label is lit in green when the connection is successful
<h4>8.2: API IP address</h4>
IP address of the distant SDRangel instance REST API
<h4>8.3: API port</h4>
Port of the distant SDRangel instance REST API
<h4>8.4: Validation button</h4>
When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8)
<h3>9: Local data address and port</h3>
![SDR Remote source input stream GUI](../../../doc/img/RemoteInput_plugin_06.png)
<h4>9.1: Data IP address</h4>
IP address of the local network interface the distant SDRangel instance sends the data to
<h4>9.2: Data port</h4>
Local port the distant SDRangel instance sends the data to
<h4>9.3: Validation button</h4>
When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values.
<h3>10: Status message</h3>
The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK
<h3>11: API information</h3>
This is the information returned by the API and is the distance SDRangel instance information if transaction is successful