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

Metis MISO: initial commit with 4 receivers

This commit is contained in:
f4exb
2020-09-05 10:41:14 +02:00
parent 4254d1fa5d
commit a180c552f8
37 changed files with 5569 additions and 2 deletions
+1
View File
@@ -12,5 +12,6 @@ if(ENABLE_XTRX AND LIBXTRX_FOUND)
add_subdirectory(xtrxmimo)
endif()
add_subdirectory(metismiso)
add_subdirectory(testmi)
add_subdirectory(testmosync)
@@ -0,0 +1,60 @@
project(metismiso)
set(metismiso_SOURCES
metismiso.cpp
metismisoplugin.cpp
metismisoudphandler.cpp
metismisosettings.cpp
metismisowebapiadapter.cpp
metismisodecimators.cpp
)
set(metismiso_HEADERS
metismiso.h
metismisoplugin.h
metismisoudphandler.h
metismisosettings.h
metismisowebapiadapter.h
metismisodecimators.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${CMAKE_SOURCE_DIR}/devices
)
if (NOT SERVER_MODE)
set (metismiso_SOURCES
${metismiso_SOURCES}
metismisogui.cpp
metismisogui.ui
)
set(metismiso_HEADERS
${metismiso_HEADERS}
metismisogui.h
)
set(TARGET_NAME mimometismiso)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME mimometismisosrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${metismiso_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
metisdevice
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
+706
View File
@@ -0,0 +1,706 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "SWGDeviceSettings.h"
#include "SWGDeviceState.h"
#include "SWGMetisMISOSettings.h"
#include "device/deviceapi.h"
#include "dsp/dspcommands.h"
#include "dsp/dspengine.h"
#include "dsp/dspdevicemimoengine.h"
#include "dsp/devicesamplesource.h"
#include "metis/devicemetis.h"
#include "metismisoudphandler.h"
#include "metismiso.h"
MESSAGE_CLASS_DEFINITION(MetisMISO::MsgConfigureMetisMISO, Message)
MESSAGE_CLASS_DEFINITION(MetisMISO::MsgStartStop, Message)
MetisMISO::MetisMISO(DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_settings(),
m_udpHandler(&m_sampleMIFifo, deviceAPI),
m_deviceDescription("MetisMISO"),
m_running(false),
m_masterTimer(deviceAPI->getMasterTimer())
{
m_mimoType = MIMOHalfSynchronous;
m_sampleMIFifo.init(4, 96000 * 4);
m_deviceAPI->setNbSourceStreams(4);
m_deviceAPI->setNbSinkStreams(1);
int deviceSequence = m_deviceAPI->getSamplingDeviceSequence();
const DeviceMetisScan::DeviceScan *deviceScan = DeviceMetis::instance().getDeviceScanAt(deviceSequence);
m_udpHandler.setMetisAddress(deviceScan->m_address, deviceScan->m_port);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
MetisMISO::~MetisMISO()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
if (m_running) {
stopRx();
}
}
void MetisMISO::destroy()
{
delete this;
}
void MetisMISO::init()
{
applySettings(m_settings, true);
}
bool MetisMISO::startRx()
{
qDebug("MetisMISO::startRx");
QMutexLocker mutexLocker(&m_mutex);
if (!m_running) {
startMetis();
}
mutexLocker.unlock();
applySettings(m_settings, true);
m_running = true;
return true;
}
bool MetisMISO::startTx()
{
qDebug("MetisMISO::startTx");
QMutexLocker mutexLocker(&m_mutex);
if (!m_running) {
startMetis();
}
mutexLocker.unlock();
applySettings(m_settings, true);
m_running = true;
return true;
}
void MetisMISO::stopRx()
{
qDebug("MetisMISO::stopRx");
QMutexLocker mutexLocker(&m_mutex);
if (m_running) {
stopMetis();
}
m_running = false;
}
void MetisMISO::stopTx()
{
qDebug("MetisMISO::stopTx");
QMutexLocker mutexLocker(&m_mutex);
if (m_running) {
stopMetis();
}
m_running = false;
}
void MetisMISO::startMetis()
{
MetisMISOUDPHandler::MsgStartStop *message = MetisMISOUDPHandler::MsgStartStop::create(true);
m_udpHandler.getInputMessageQueue()->push(message);
}
void MetisMISO::stopMetis()
{
MetisMISOUDPHandler::MsgStartStop *message = MetisMISOUDPHandler::MsgStartStop::create(false);
m_udpHandler.getInputMessageQueue()->push(message);
}
QByteArray MetisMISO::serialize() const
{
return m_settings.serialize();
}
bool MetisMISO::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureMetisMISO* message = MsgConfigureMetisMISO::create(m_settings, true);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureMetisMISO* messageToGUI = MsgConfigureMetisMISO::create(m_settings, true);
m_guiMessageQueue->push(messageToGUI);
}
return success;
}
const QString& MetisMISO::getDeviceDescription() const
{
return m_deviceDescription;
}
int MetisMISO::getSourceSampleRate(int index) const
{
if (index < 3) {
return MetisMISOSettings::getSampleRateFromIndex(m_settings.m_sampleRateIndex);
} else {
return 0;
}
}
quint64 MetisMISO::getSourceCenterFrequency(int index) const
{
switch(index)
{
case 0:
return m_settings.m_rx1CenterFrequency;
break;
case 1:
return m_settings.m_rx2CenterFrequency;
break;
default:
return 0;
}
}
void MetisMISO::setSourceCenterFrequency(qint64 centerFrequency, int index)
{
MetisMISOSettings settings = m_settings; // note: calls copy constructor
if (index < 2)
{
if (index == 0) {
settings.m_rx1CenterFrequency = centerFrequency;
} else {
settings.m_rx2CenterFrequency = centerFrequency;
}
MsgConfigureMetisMISO* message = MsgConfigureMetisMISO::create(settings, false);
m_inputMessageQueue.push(message);
if (m_guiMessageQueue)
{
MsgConfigureMetisMISO* messageToGUI = MsgConfigureMetisMISO::create(settings, false);
m_guiMessageQueue->push(messageToGUI);
}
}
}
bool MetisMISO::handleMessage(const Message& message)
{
if (MsgConfigureMetisMISO::match(message))
{
MsgConfigureMetisMISO& conf = (MsgConfigureMetisMISO&) message;
qDebug() << "MetisMISO::handleMessage: MsgConfigureMetisMISO";
bool success = applySettings(conf.getSettings(), conf.getForce());
if (!success)
{
qDebug("MetisMISO::handleMessage: config error");
}
return true;
}
else if (MsgStartStop::match(message))
{
MsgStartStop& cmd = (MsgStartStop&) message;
qDebug() << "MetisMISO::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;
}
}
bool MetisMISO::applySettings(const MetisMISOSettings& settings, bool force)
{
QList<QString> reverseAPIKeys;
qDebug() << "MetisMISO::applySettings: "
<< " m_nbReceivers:" << settings.m_nbReceivers
<< " m_rx1CenterFrequency:" << settings.m_rx1CenterFrequency
<< " m_rx2CenterFrequency:" << settings.m_rx2CenterFrequency
<< " m_rx3CenterFrequency:" << settings.m_rx3CenterFrequency
<< " m_rx3CenterFrequency:" << settings.m_rx4CenterFrequency
<< " m_txCenterFrequency:" << settings.m_txCenterFrequency
<< " m_sampleRateIndex:" << settings.m_sampleRateIndex
<< " m_log2Decim:" << settings.m_log2Decim
<< " m_preamp:" << settings.m_preamp
<< " m_random:" << settings.m_random
<< " m_dither:" << settings.m_dither
<< " m_duplex:" << settings.m_duplex
<< " m_dcBlock:" << settings.m_dcBlock
<< " m_iqCorrection:" << settings.m_iqCorrection
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex;
bool propagateSettings = false;
if ((m_settings.m_nbReceivers != settings.m_nbReceivers) || force)
{
reverseAPIKeys.append("nbReceivers");
propagateSettings = true;
}
if ((m_settings.m_rx1CenterFrequency != settings.m_rx1CenterFrequency) || force)
{
reverseAPIKeys.append("rx1CenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_rx2CenterFrequency != settings.m_rx2CenterFrequency) || force)
{
reverseAPIKeys.append("rx2CenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_rx3CenterFrequency != settings.m_rx3CenterFrequency) || force)
{
reverseAPIKeys.append("rx3CenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_rx4CenterFrequency != settings.m_rx4CenterFrequency) || force)
{
reverseAPIKeys.append("rx4CenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) || force)
{
reverseAPIKeys.append("txCenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) || force)
{
reverseAPIKeys.append("sampleRateIndex");
propagateSettings = true;
}
if ((m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
reverseAPIKeys.append("log2Decim");
propagateSettings = true;
}
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_dcBlock != settings.m_dcBlock) ||
(m_settings.m_iqCorrection != settings.m_iqCorrection) || force)
{
m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 0);
m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 1);
}
if ((m_settings.m_rx1CenterFrequency != settings.m_rx1CenterFrequency) ||
(m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
int devSampleRate = (1<<settings.m_sampleRateIndex) * 48000;
int sampleRate = devSampleRate / (1<<settings.m_log2Decim);
DSPMIMOSignalNotification *engineRx1Notif = new DSPMIMOSignalNotification(
sampleRate, settings.m_rx1CenterFrequency, true, 0);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineRx1Notif);
}
if ((m_settings.m_rx2CenterFrequency != settings.m_rx2CenterFrequency) ||
(m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
int devSampleRate = (1<<settings.m_sampleRateIndex) * 48000;
int sampleRate = devSampleRate / (1<<settings.m_log2Decim);
DSPMIMOSignalNotification *engineRx2Notif = new DSPMIMOSignalNotification(
sampleRate, settings.m_rx2CenterFrequency, true, 1);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineRx2Notif);
}
if ((m_settings.m_rx3CenterFrequency != settings.m_rx3CenterFrequency) ||
(m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
int devSampleRate = (1<<settings.m_sampleRateIndex) * 48000;
int sampleRate = devSampleRate / (1<<settings.m_log2Decim);
DSPMIMOSignalNotification *engineRx3Notif = new DSPMIMOSignalNotification(
sampleRate, settings.m_rx3CenterFrequency, true, 2);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineRx3Notif);
}
if ((m_settings.m_rx4CenterFrequency != settings.m_rx4CenterFrequency) ||
(m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
int devSampleRate = (1<<settings.m_sampleRateIndex) * 48000;
int sampleRate = devSampleRate / (1<<settings.m_log2Decim);
DSPMIMOSignalNotification *engineRx4Notif = new DSPMIMOSignalNotification(
sampleRate, settings.m_rx4CenterFrequency, true, 3);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineRx4Notif);
}
if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) ||
(m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
int devSampleRate = (1<<settings.m_sampleRateIndex) * 48000;
int sampleRate = devSampleRate / (1<<settings.m_log2Decim);
DSPMIMOSignalNotification *engineTxNotif = new DSPMIMOSignalNotification(
sampleRate, settings.m_txCenterFrequency, false, 0);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineTxNotif);
}
if (propagateSettings) {
m_udpHandler.applySettings(settings);
}
if (settings.m_useReverseAPI)
{
qDebug("MetisMISO::applySettings: call webapiReverseSendSettings");
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;
return true;
}
int MetisMISO::webapiRunGet(
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
if (subsystemIndex == 0)
{
m_deviceAPI->getDeviceEngineStateStr(*response.getState()); // Rx only
return 200;
}
else
{
errorMessage = QString("Subsystem index invalid: expect 0 (Rx) only");
return 404;
}
}
int MetisMISO::webapiRun(
bool run,
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
if (subsystemIndex == 0)
{
m_deviceAPI->getDeviceEngineStateStr(*response.getState()); // Rx only
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;
}
else
{
errorMessage = QString("Subsystem index invalid: expect 0 (Rx) only");
return 404;
}
}
int MetisMISO::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings());
response.getMetisMisoSettings()->init();
webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int MetisMISO::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) errorMessage;
MetisMISOSettings settings = m_settings;
webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response);
MsgConfigureMetisMISO *msg = MsgConfigureMetisMISO::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureMetisMISO *msgToGUI = MsgConfigureMetisMISO::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatDeviceSettings(response, settings);
return 200;
}
void MetisMISO::webapiUpdateDeviceSettings(
MetisMISOSettings& settings,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response)
{
if (deviceSettingsKeys.contains("rx1CenterFrequency")) {
settings.m_rx1CenterFrequency = response.getMetisMisoSettings()->getRx1CenterFrequency();
}
if (deviceSettingsKeys.contains("rx2CenterFrequency")) {
settings.m_rx2CenterFrequency = response.getMetisMisoSettings()->getRx2CenterFrequency();
}
if (deviceSettingsKeys.contains("rx3CenterFrequency")) {
settings.m_rx3CenterFrequency = response.getMetisMisoSettings()->getRx3CenterFrequency();
}
if (deviceSettingsKeys.contains("txCenterFrequency")) {
settings.m_txCenterFrequency = response.getMetisMisoSettings()->getTxCenterFrequency();
}
if (deviceSettingsKeys.contains("sampleRateIndex")) {
settings.m_sampleRateIndex = response.getMetisMisoSettings()->getSampleRateIndex();
}
if (deviceSettingsKeys.contains("preamp")) {
settings.m_preamp = response.getMetisMisoSettings()->getPreamp() != 0;
}
if (deviceSettingsKeys.contains("random")) {
settings.m_random = response.getMetisMisoSettings()->getRandom() != 0;
}
if (deviceSettingsKeys.contains("dither")) {
settings.m_dither = response.getMetisMisoSettings()->getDither() != 0;
}
if (deviceSettingsKeys.contains("duplex")) {
settings.m_duplex = response.getMetisMisoSettings()->getDuplex() != 0;
}
if (deviceSettingsKeys.contains("dcBlock")) {
settings.m_dcBlock = response.getMetisMisoSettings()->getDcBlock() != 0;
}
if (deviceSettingsKeys.contains("iqCorrection")) {
settings.m_iqCorrection = response.getMetisMisoSettings()->getIqCorrection() != 0;
}
if (deviceSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getMetisMisoSettings()->getUseReverseApi() != 0;
}
if (deviceSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getMetisMisoSettings()->getReverseApiAddress();
}
if (deviceSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getMetisMisoSettings()->getReverseApiPort();
}
if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getMetisMisoSettings()->getReverseApiDeviceIndex();
}
}
void MetisMISO::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const MetisMISOSettings& settings)
{
response.getMetisMisoSettings()->setRx1CenterFrequency(settings.m_rx1CenterFrequency);
response.getMetisMisoSettings()->setRx2CenterFrequency(settings.m_rx2CenterFrequency);
response.getMetisMisoSettings()->setRx3CenterFrequency(settings.m_rx3CenterFrequency);
response.getMetisMisoSettings()->setTxCenterFrequency(settings.m_txCenterFrequency);
response.getMetisMisoSettings()->setSampleRateIndex(settings.m_sampleRateIndex);
response.getMetisMisoSettings()->setPreamp(settings.m_preamp ? 1 : 0);
response.getMetisMisoSettings()->setRandom(settings.m_random ? 1 : 0);
response.getMetisMisoSettings()->setDither(settings.m_dither ? 1 : 0);
response.getMetisMisoSettings()->setDuplex(settings.m_duplex ? 1 : 0);
response.getMetisMisoSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0);
response.getMetisMisoSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0);
response.getMetisMisoSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getMetisMisoSettings()->getReverseApiAddress()) {
*response.getMetisMisoSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getMetisMisoSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getMetisMisoSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getMetisMisoSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
}
void MetisMISO::webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const MetisMISOSettings& settings, bool force)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(2); // MIMO
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("MetisMISO"));
swgDeviceSettings->setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings());
SWGSDRangel::SWGMetisMISOSettings *swgMetisMISOSettings = swgDeviceSettings->getMetisMisoSettings();
if (deviceSettingsKeys.contains("rx1CenterFrequency") || force) {
swgMetisMISOSettings->setRx1CenterFrequency(settings.m_rx1CenterFrequency);
}
if (deviceSettingsKeys.contains("rx2CenterFrequency") || force) {
swgMetisMISOSettings->setRx2CenterFrequency(settings.m_rx2CenterFrequency);
}
if (deviceSettingsKeys.contains("rx3CenterFrequency") || force) {
swgMetisMISOSettings->setRx3CenterFrequency(settings.m_rx3CenterFrequency);
}
if (deviceSettingsKeys.contains("txCenterFrequency") || force) {
swgMetisMISOSettings->setTxCenterFrequency(settings.m_txCenterFrequency);
}
if (deviceSettingsKeys.contains("sampleRateIndex") || force) {
swgMetisMISOSettings->setSampleRateIndex(settings.m_sampleRateIndex);
}
if (deviceSettingsKeys.contains("preamp") || force) {
swgMetisMISOSettings->setPreamp(settings.m_preamp ? 1 : 0);
}
if (deviceSettingsKeys.contains("random") || force) {
swgMetisMISOSettings->setRandom(settings.m_random ? 1 : 0);
}
if (deviceSettingsKeys.contains("dither") || force) {
swgMetisMISOSettings->setDither(settings.m_dither ? 1 : 0);
}
if (deviceSettingsKeys.contains("duplex") || force) {
swgMetisMISOSettings->setDuplex(settings.m_duplex ? 1 : 0);
}
if (deviceSettingsKeys.contains("dcBlock") || force) {
swgMetisMISOSettings->setDcBlock(settings.m_dcBlock ? 1 : 0);
}
if (deviceSettingsKeys.contains("iqCorrection") || force) {
swgMetisMISOSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0);
}
QString channelSettingsURL = 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(channelSettingsURL));
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 MetisMISO::webapiReverseSendStartStop(bool start)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(2); // MIMO
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("MetisMISO"));
QString channelSettingsURL = 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(channelSettingsURL));
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 MetisMISO::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "MetisMISO::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("MetisMISO::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
+169
View File
@@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 _METISMISO_METISMISO_H_
#define _METISMISO_METISMISO_H_
#include <QString>
#include <QByteArray>
#include <QTimer>
#include <QNetworkRequest>
#include <QThread>
#include "dsp/devicesamplemimo.h"
#include "metismisoudphandler.h"
#include "metismisosettings.h"
class DeviceAPI;
class MetisMISOWorker;
class QNetworkAccessManager;
class QNetworkReply;
class MetisMISO : public DeviceSampleMIMO {
Q_OBJECT
public:
class MsgConfigureMetisMISO : public Message {
MESSAGE_CLASS_DECLARATION
public:
const MetisMISOSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureMetisMISO* create(const MetisMISOSettings& settings, bool force)
{
return new MsgConfigureMetisMISO(settings, force);
}
private:
MetisMISOSettings m_settings;
bool m_force;
MsgConfigureMetisMISO(const MetisMISOSettings& settings, bool force) :
Message(),
m_settings(settings),
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)
{ }
};
MetisMISO(DeviceAPI *deviceAPI);
virtual ~MetisMISO();
virtual void destroy();
virtual void init();
virtual bool startRx();
virtual void stopRx();
virtual bool startTx();
virtual void stopTx();
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 getSourceSampleRate(int index) const;
virtual void setSourceSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; }
virtual quint64 getSourceCenterFrequency(int index) const;
virtual void setSourceCenterFrequency(qint64 centerFrequency, int index);
virtual int getSinkSampleRate(int index) const { return 0; (void) index; }
virtual void setSinkSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; }
virtual quint64 getSinkCenterFrequency(int index) const { return 0; (void) index; }
virtual void setSinkCenterFrequency(qint64 centerFrequency, int index) { (void) centerFrequency; (void) index; }
virtual quint64 getMIMOCenterFrequency() const { return getSourceCenterFrequency(0); }
virtual unsigned int getMIMOSampleRate() const { return getSourceSampleRate(0); }
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(
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiRun(
bool run,
int subsystemIndex,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
static void webapiFormatDeviceSettings(
SWGSDRangel::SWGDeviceSettings& response,
const MetisMISOSettings& settings);
static void webapiUpdateDeviceSettings(
MetisMISOSettings& settings,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response);
private:
struct DeviceSettingsKeys
{
QList<QString> m_commonSettingsKeys;
QList<QList<QString>> m_streamsSettingsKeys;
};
DeviceAPI *m_deviceAPI;
QMutex m_mutex;
MetisMISOSettings m_settings;
MetisMISOUDPHandler m_udpHandler;
QString m_deviceDescription;
bool m_running;
const QTimer& m_masterTimer;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void startMetis();
void stopMetis();
bool applySettings(const MetisMISOSettings& settings, bool force);
void webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const MetisMISOSettings& settings, bool force);
void webapiReverseSendStartStop(bool start);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // _METISMISO_METISMISO_H_
@@ -0,0 +1,85 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "metismisodecimators.h"
MetisMISODecimators::MetisMISODecimators()
{
resetCounters();
m_wSampleVector.resize(1);
}
void MetisMISODecimators::resetCounters()
{
std::fill(m_counters, m_counters+MetisMISOSettings::m_maxReceivers, 0);
}
int MetisMISODecimators::decimate2(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex)
{
if (streamIndex < MetisMISOSettings::m_maxReceivers)
{
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI;
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ;
if (m_counters[streamIndex] >= 8)
{
SampleVector::iterator it = result.begin();
m_decimatorsIQ[streamIndex].decimate2_cen(&it, m_accumulators[streamIndex], 8);
m_counters[streamIndex] = 0;
return 2; // 2 samples available
}
}
return 0;
}
int MetisMISODecimators::decimate4(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex)
{
if (streamIndex < MetisMISOSettings::m_maxReceivers)
{
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI;
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ;
if (m_counters[streamIndex] >= 16)
{
SampleVector::iterator it = result.begin();
m_decimatorsIQ[streamIndex].decimate4_cen(&it, m_accumulators[streamIndex], 16);
m_counters[streamIndex] = 0;
return 2; // 2 samples available
}
}
return 0;
}
int MetisMISODecimators::decimate8(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex)
{
if (streamIndex < MetisMISOSettings::m_maxReceivers)
{
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI;
m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ;
if (m_counters[streamIndex] >= 16)
{
SampleVector::iterator it = result.begin();
m_decimatorsIQ[streamIndex].decimate8_cen(&it, m_accumulators[streamIndex], 16);
m_counters[streamIndex] = 0;
return 1; // 1 sample available
}
}
return 0;
}
@@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// Decimators adapters specific to Metis //
// //
// 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 _METISMISO_METISMISODECIMATORS_H_
#define _METISMISO_METISMISODECIMATORS_H_
#include "dsp/decimators.h"
#include "metismisosettings.h"
class MetisMISODecimators
{
public:
MetisMISODecimators();
int decimate2(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex);
int decimate4(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex);
int decimate8(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex);
void resetCounters();
private:
qint32 m_accumulators[MetisMISOSettings::m_maxReceivers][256*2];
int m_counters[MetisMISOSettings::m_maxReceivers];
Decimators<qint32, qint32, SDR_RX_SAMP_SZ, 24, true> m_decimatorsIQ[MetisMISOSettings::m_maxReceivers];
Decimators<qint32, qint32, SDR_RX_SAMP_SZ, 24, false> m_decimatorsQI[MetisMISOSettings::m_maxReceivers];
SampleVector m_wSampleVector;
};
#endif // _METISMISO_METISMISODECIMATORS_H_
@@ -0,0 +1,517 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "plugin/pluginapi.h"
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "gui/crightclickenabler.h"
#include "gui/basicdevicesettingsdialog.h"
#include "dsp/dspengine.h"
#include "dsp/dspdevicemimoengine.h"
#include "dsp/dspcommands.h"
#include "util/db.h"
#include "mainwindow.h"
#include "ui_metismisogui.h"
#include "metismisogui.h"
MetisMISOGui::MetisMISOGui(DeviceUISet *deviceUISet, QWidget* parent) :
QWidget(parent),
ui(new Ui::MetisMISOGui),
m_deviceUISet(deviceUISet),
m_settings(),
m_doApplySettings(true),
m_forceSettings(true),
m_sampleMIMO(nullptr),
m_tickCount(0),
m_lastEngineState(DeviceAPI::StNotStarted)
{
qDebug("MetisMISOGui::MetisMISOGui");
m_sampleMIMO = m_deviceUISet->m_deviceAPI->getSampleMIMO();
m_streamIndex = 0;
m_spectrumStreamIndex = 0;
m_rxSampleRate = 48000;
ui->setupUi(this);
ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->centerFrequency->setValueRange(7, 0, 61440000);
displaySettings();
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_sampleMIMO->setMessageQueueToGUI(&m_inputMessageQueue);
CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop);
connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &)));
}
MetisMISOGui::~MetisMISOGui()
{
delete ui;
}
void MetisMISOGui::destroy()
{
delete this;
}
void MetisMISOGui::setName(const QString& name)
{
setObjectName(name);
}
QString MetisMISOGui::getName() const
{
return objectName();
}
void MetisMISOGui::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
sendSettings();
}
qint64 MetisMISOGui::getCenterFrequency() const
{
if (m_streamIndex == 0) {
return m_settings.m_rx1CenterFrequency;
} else if (m_streamIndex == 1) {
return m_settings.m_rx2CenterFrequency;
} else if (m_streamIndex == 2) {
return m_settings.m_rx3CenterFrequency;
} else if (m_streamIndex == 3) {
return m_settings.m_rx4CenterFrequency;
} else if (m_streamIndex == 4) {
return m_settings.m_txCenterFrequency;
} else {
return 0;
}
}
void MetisMISOGui::setCenterFrequency(qint64 centerFrequency)
{
if (m_streamIndex == 0) {
m_settings.m_rx1CenterFrequency = centerFrequency;
} else if (m_streamIndex == 1) {
m_settings.m_rx2CenterFrequency = centerFrequency;
} else if (m_streamIndex == 2) {
m_settings.m_rx3CenterFrequency = centerFrequency;
} else if (m_streamIndex == 3) {
m_settings.m_rx4CenterFrequency = centerFrequency;
} else if (m_streamIndex == 4) {
m_settings.m_txCenterFrequency = centerFrequency;
} else {
m_settings.m_txCenterFrequency = 0;
}
displaySettings();
sendSettings();
}
QByteArray MetisMISOGui::serialize() const
{
return m_settings.serialize();
}
bool MetisMISOGui::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
m_forceSettings = true;
sendSettings();
return true;
} else {
resetToDefaults();
return false;
}
}
void MetisMISOGui::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
MetisMISO::MsgStartStop *message = MetisMISO::MsgStartStop::create(checked);
m_sampleMIMO->getInputMessageQueue()->push(message);
}
}
void MetisMISOGui::on_streamIndex_currentIndexChanged(int index)
{
if (ui->streamLock->isChecked())
{
m_spectrumStreamIndex = index;
if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers)
{
m_deviceUISet->m_spectrum->setDisplayedStream(true, index);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, m_spectrumStreamIndex);
}
else
{
m_deviceUISet->m_spectrum->setDisplayedStream(false, 0);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0);
}
ui->spectrumSource->blockSignals(true);
ui->spectrumSource->setCurrentIndex(index);
ui->spectrumSource->blockSignals(false);
}
m_streamIndex = index;
displayFrequency();
}
void MetisMISOGui::on_spectrumSource_currentIndexChanged(int index)
{
m_spectrumStreamIndex = index;
if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers)
{
m_deviceUISet->m_spectrum->setDisplayedStream(true, index);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, m_spectrumStreamIndex);
}
else
{
m_deviceUISet->m_spectrum->setDisplayedStream(false, 0);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0);
}
updateSpectrum();
if (ui->streamLock->isChecked())
{
ui->streamIndex->blockSignals(true);
ui->streamIndex->setCurrentIndex(index);
ui->streamIndex->blockSignals(false);
m_streamIndex = index;
displayFrequency();
}
}
void MetisMISOGui::on_streamLock_toggled(bool checked)
{
if (checked && (ui->streamIndex->currentIndex() != ui->spectrumSource->currentIndex())) {
ui->spectrumSource->setCurrentIndex(ui->streamIndex->currentIndex());
}
}
void MetisMISOGui::on_centerFrequency_changed(quint64 value)
{
if (m_streamIndex == 0) {
m_settings.m_rx1CenterFrequency = value * 1000;
} else if (m_streamIndex == 1) {
m_settings.m_rx2CenterFrequency = value * 1000;
} else if (m_streamIndex == 2) {
m_settings.m_rx3CenterFrequency = value * 1000;
} else if (m_streamIndex == 3) {
m_settings.m_rx4CenterFrequency = value * 1000;
} else if (m_streamIndex == 4) {
m_settings.m_txCenterFrequency = value * 1000;
} else {
m_settings.m_txCenterFrequency = 0;
}
sendSettings();
}
void MetisMISOGui::on_samplerateIndex_currentIndexChanged(int index)
{
m_settings.m_sampleRateIndex = index < 0 ? 0 : index > 3 ? 3 : index;
sendSettings();
}
void MetisMISOGui::on_log2Decim_currentIndexChanged(int index)
{
m_settings.m_log2Decim = index < 0 ? 0 : index > 3 ? 3 : index;
displaySampleRate();
sendSettings();
}
void MetisMISOGui::on_dcBlock_toggled(bool checked)
{
m_settings.m_dcBlock = checked;
sendSettings();
}
void MetisMISOGui::on_iqCorrection_toggled(bool checked)
{
m_settings.m_iqCorrection = checked;
sendSettings();
}
void MetisMISOGui::on_preamp_toggled(bool checked)
{
m_settings.m_preamp = checked;
sendSettings();
}
void MetisMISOGui::on_random_toggled(bool checked)
{
m_settings.m_random = checked;
sendSettings();
}
void MetisMISOGui::on_dither_toggled(bool checked)
{
m_settings.m_dither = checked;
sendSettings();
}
void MetisMISOGui::on_duplex_toggled(bool checked)
{
m_settings.m_duplex = checked;
sendSettings();
}
void MetisMISOGui::on_nbRxIndex_currentIndexChanged(int index)
{
m_settings.m_nbReceivers = index + 1;
sendSettings();
}
void MetisMISOGui::displaySettings()
{
blockApplySettings(true);
ui->streamIndex->setCurrentIndex(m_streamIndex);
ui->spectrumSource->setCurrentIndex(m_spectrumStreamIndex);
ui->nbRxIndex->setCurrentIndex(m_settings.m_nbReceivers - 1);
ui->samplerateIndex->setCurrentIndex(m_settings.m_sampleRateIndex);
ui->dcBlock->setChecked(m_settings.m_dcBlock);
ui->iqCorrection->setChecked(m_settings.m_iqCorrection);
ui->preamp->setChecked(m_settings.m_preamp);
ui->random->setChecked(m_settings.m_random);
ui->dither->setChecked(m_settings.m_dither);
ui->duplex->setChecked(m_settings.m_duplex);
displayFrequency();
displaySampleRate();
updateSpectrum();
blockApplySettings(false);
}
void MetisMISOGui::sendSettings()
{
if(!m_updateTimer.isActive()) {
m_updateTimer.start(100);
}
}
void MetisMISOGui::updateHardware()
{
if (m_doApplySettings)
{
MetisMISO::MsgConfigureMetisMISO* message = MetisMISO::MsgConfigureMetisMISO::create(m_settings, m_forceSettings);
m_sampleMIMO->getInputMessageQueue()->push(message);
m_forceSettings = false;
m_updateTimer.stop();
}
}
void MetisMISOGui::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 MetisMISOGui::handleMessage(const Message& message)
{
if (MetisMISO::MsgConfigureMetisMISO::match(message))
{
qDebug("MetisMISOGui::handleMessage: MsgConfigureMetisMISO");
const MetisMISO::MsgConfigureMetisMISO& cfg = (MetisMISO::MsgConfigureMetisMISO&) message;
m_settings = cfg.getSettings();
displaySettings();
return true;
}
else if (MetisMISO::MsgStartStop::match(message))
{
qDebug("MetisMISOGui::handleMessage: MsgStartStop");
MetisMISO::MsgStartStop& notif = (MetisMISO::MsgStartStop&) message;
blockApplySettings(true);
ui->startStop->setChecked(notif.getStartStop());
blockApplySettings(false);
return true;
}
else
{
return false;
}
}
void MetisMISOGui::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (DSPMIMOSignalNotification::match(*message))
{
DSPMIMOSignalNotification* notif = (DSPMIMOSignalNotification*) message;
int istream = notif->getIndex();
bool sourceOrSink = notif->getSourceOrSink();
qint64 frequency = notif->getCenterFrequency();
if (sourceOrSink)
{
m_rxSampleRate = notif->getSampleRate();
if (istream == 0) {
m_settings.m_rx1CenterFrequency = frequency;
} else if (istream == 1) {
m_settings.m_rx2CenterFrequency = frequency;
} else if (istream == 2) {
m_settings.m_rx3CenterFrequency = frequency;
} else if (istream == 3) {
m_settings.m_rx4CenterFrequency = frequency;
}
}
else
{
m_settings.m_txCenterFrequency = frequency;
}
qDebug("MetisMISOGui::handleInputMessages: DSPMIMOSignalNotification: %s stream: %d m_rxSampleRate:%d, CenterFrequency:%llu",
sourceOrSink ? "source" : "sink",
istream,
m_rxSampleRate,
frequency);
displayFrequency();
updateSpectrum();
delete message;
}
else
{
if (handleMessage(*message))
{
delete message;
}
}
}
}
void MetisMISOGui::displayFrequency()
{
qint64 centerFrequency;
if (m_streamIndex == 0) {
centerFrequency = m_settings.m_rx1CenterFrequency;
} else if (m_streamIndex == 1) {
centerFrequency = m_settings.m_rx2CenterFrequency;
} else if (m_streamIndex == 2) {
centerFrequency = m_settings.m_rx3CenterFrequency;
} else if (m_streamIndex == 3) {
centerFrequency = m_settings.m_rx4CenterFrequency;
} else if (m_streamIndex == 4) {
centerFrequency = m_settings.m_txCenterFrequency;
} else {
centerFrequency = 0;
}
ui->centerFrequency->setValue(centerFrequency / 1000);
}
void MetisMISOGui::displaySampleRate()
{
int deviceSampleRate = 48000 * (1<<m_settings.m_sampleRateIndex);
int sampleRate = deviceSampleRate / (1<<m_settings.m_log2Decim);
ui->deviceRateText->setText(tr("%1k").arg((float) sampleRate / 1000));
}
void MetisMISOGui::updateSpectrum()
{
qint64 centerFrequency;
if (m_spectrumStreamIndex == 0) {
centerFrequency = m_settings.m_rx1CenterFrequency;
} else if (m_spectrumStreamIndex == 1) {
centerFrequency = m_settings.m_rx2CenterFrequency;
} else if (m_spectrumStreamIndex == 2) {
centerFrequency = m_settings.m_rx3CenterFrequency;
} else if (m_spectrumStreamIndex == 3) {
centerFrequency = m_settings.m_rx4CenterFrequency;
} else if (m_spectrumStreamIndex == 4) {
centerFrequency = m_settings.m_txCenterFrequency;
} else {
centerFrequency = 0;
}
m_deviceUISet->getSpectrum()->setSampleRate(m_rxSampleRate);
m_deviceUISet->getSpectrum()->setCenterFrequency(centerFrequency);
}
void MetisMISOGui::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();
}
+102
View File
@@ -0,0 +1,102 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 _METISMISO_METISMISOGUI_H_
#define _METISMISO_METISMISOGUI_H_
#include <plugin/plugininstancegui.h>
#include <QTimer>
#include <QWidget>
#include "util/messagequeue.h"
#include "metismisosettings.h"
#include "metismiso.h"
class DeviceUISet;
namespace Ui {
class MetisMISOGui;
}
class MetisMISOGui : public QWidget, public PluginInstanceGUI {
Q_OBJECT
public:
explicit MetisMISOGui(DeviceUISet *deviceUISet, QWidget* parent = 0);
virtual ~MetisMISOGui();
virtual void destroy();
void setName(const QString& name);
QString getName() const;
void resetToDefaults();
virtual qint64 getCenterFrequency() const;
virtual void setCenterFrequency(qint64 centerFrequency);
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool handleMessage(const Message& message);
private:
Ui::MetisMISOGui* ui;
DeviceUISet* m_deviceUISet;
MetisMISOSettings m_settings;
int m_streamIndex; //!< Current stream index being dealt with
int m_spectrumStreamIndex; //!< Index of the stream displayed on main spectrum
int m_rxSampleRate;
QTimer m_updateTimer;
QTimer m_statusTimer;
bool m_doApplySettings;
bool m_forceSettings;
DeviceSampleMIMO* m_sampleMIMO;
std::size_t m_tickCount;
std::vector<int> m_deviceSampleRates;
std::vector<quint64> m_deviceCenterFrequencies; //!< Center frequency in device
int m_lastEngineState;
MessageQueue m_inputMessageQueue;
void blockApplySettings(bool block) { m_doApplySettings = !block; }
void displaySettings();
void displayFrequency();
void displaySampleRate();
void updateSpectrum();
void sendSettings();
private slots:
void handleInputMessages();
void on_streamIndex_currentIndexChanged(int index);
void on_spectrumSource_currentIndexChanged(int index);
void on_streamLock_toggled(bool checked);
void on_startStop_toggled(bool checked);
void on_centerFrequency_changed(quint64 value);
void on_samplerateIndex_currentIndexChanged(int index);
void on_log2Decim_currentIndexChanged(int index);
void on_dcBlock_toggled(bool checked);
void on_iqCorrection_toggled(bool checked);
void on_preamp_toggled(bool checked);
void on_random_toggled(bool checked);
void on_dither_toggled(bool checked);
void on_duplex_toggled(bool checked);
void on_nbRxIndex_currentIndexChanged(int index);
void openDeviceSettingsDialog(const QPoint& p);
void updateStatus();
void updateHardware();
};
#endif // _METISMISO_METISMISOGUI_H_
@@ -0,0 +1,606 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetisMISOGui</class>
<widget class="QWidget" name="MetisMISOGui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>200</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>200</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>Metis MISO</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="streamLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Stream</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="streamIndex">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Select stream to which settings apply (frequency only)</string>
</property>
<item>
<property name="text">
<string>Rx0</string>
</property>
</item>
<item>
<property name="text">
<string>Rx1</string>
</property>
</item>
<item>
<property name="text">
<string>Rx2</string>
</property>
</item>
<item>
<property name="text">
<string>Rx3</string>
</property>
</item>
<item>
<property name="text">
<string>Tx</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="spectrumSourceLabel">
<property name="text">
<string>Spectrum</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="spectrumSource">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Select stream for main spectrum source</string>
</property>
<item>
<property name="text">
<string>Rx0</string>
</property>
</item>
<item>
<property name="text">
<string>Rx1</string>
</property>
</item>
<item>
<property name="text">
<string>Rx2</string>
</property>
</item>
<item>
<property name="text">
<string>Rx3</string>
</property>
</item>
<item>
<property name="text">
<string>Tx</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QToolButton" name="streamLock">
<property name="toolTip">
<string>Lock spectrum display to stream selection</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/unlocked.png</normaloff>
<normalon>:/locked.png</normalon>:/unlocked.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<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_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<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>20</pointsize>
</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>
<layout class="QHBoxLayout" name="autoCorrectionsLayout">
<item>
<widget class="QLabel" name="samplerateLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>SR</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="samplerateIndex">
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Modulation</string>
</property>
<item>
<property name="text">
<string>48k</string>
</property>
</item>
<item>
<property name="text">
<string>96k</string>
</property>
</item>
<item>
<property name="text">
<string>192k</string>
</property>
</item>
<item>
<property name="text">
<string>384k</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="decimationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="log2Decim">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</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>
<item>
<widget class="QLabel" name="autoCorrLabel">
<property name="text">
<string>Corr</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="dcBlock">
<property name="minimumSize">
<size>
<width>45</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>DC</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="iqCorrection">
<property name="minimumSize">
<size>
<width>45</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>45</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>IQ</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="switchesLayout">
<item>
<widget class="ButtonSwitch" name="preamp">
<property name="toolTip">
<string>Toggle preamplifier</string>
</property>
<property name="text">
<string>PRE</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="random">
<property name="toolTip">
<string>Toggle LT2208 Random</string>
</property>
<property name="text">
<string>RAN</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="dither">
<property name="toolTip">
<string>Toggle LT2208 Dither</string>
</property>
<property name="text">
<string>DITH</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="duplex">
<property name="toolTip">
<string>Toggle duplex</string>
</property>
<property name="text">
<string>DUP</string>
</property>
</widget>
</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>
<item>
<widget class="QLabel" name="nbRxLabel">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>#Rx</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="nbRxIndex">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Number of active receivers</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<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>
</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,139 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "metis/devicemetis.h"
#include "util/simpleserializer.h"
#ifdef SERVER_MODE
#include "metismiso.h"
#else
#include "metismisogui.h"
#endif
#include "metismisoplugin.h"
#include "metismisowebapiadapter.h"
const PluginDescriptor MetisMISOPlugin::m_pluginDescriptor = {
QString("MetisMISO"),
QString("Metis MISO"),
QString("5.10.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
const QString MetisMISOPlugin::m_hardwareID = "MetisMISO";
const QString MetisMISOPlugin::m_deviceTypeID = METISMISO_DEVICE_TYPE_ID;
MetisMISOPlugin::MetisMISOPlugin(QObject* parent) :
QObject(parent)
{
}
const PluginDescriptor& MetisMISOPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void MetisMISOPlugin::initPlugin(PluginAPI* pluginAPI)
{
pluginAPI->registerSampleMIMO(m_deviceTypeID, this);
}
void MetisMISOPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices)
{
if (listedHwIds.contains(m_hardwareID)) { // check if it was done
return;
}
DeviceMetis::instance().enumOriginDevices(m_hardwareID, originDevices);
listedHwIds.append(m_hardwareID);
}
PluginInterface::SamplingDevices MetisMISOPlugin::enumSampleMIMO(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,
it->hardwareId,
m_deviceTypeID,
it->serial,
it->sequence,
PluginInterface::SamplingDevice::PhysicalDevice,
PluginInterface::SamplingDevice::StreamMIMO,
1, // MIMO is always considered as a single device
0)
);
qDebug("MetisMISOPlugin::enumSampleMIMO: enumerated Metis device #%d", it->sequence);
}
}
return result;
}
#ifdef SERVER_MODE
PluginInstanceGUI* MetisMISOPlugin::createSampleMIMOPluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
(void) sourceId;
(void) widget;
(void) deviceUISet;
return 0;
}
#else
PluginInstanceGUI* MetisMISOPlugin::createSampleMIMOPluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
if (sourceId == m_deviceTypeID) {
MetisMISOGui* gui = new MetisMISOGui(deviceUISet);
*widget = gui;
return gui;
} else {
return nullptr;
}
}
#endif
DeviceSampleMIMO *MetisMISOPlugin::createSampleMIMOPluginInstance(const QString& mimoId, DeviceAPI *deviceAPI)
{
if (mimoId == m_deviceTypeID)
{
MetisMISO* input = new MetisMISO(deviceAPI);
return input;
}
else
{
return nullptr;
}
}
DeviceWebAPIAdapter *MetisMISOPlugin::createDeviceWebAPIAdapter() const
{
return new MetisMISOWebAPIAdapter();
}
@@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 _METISMISO_METISMIMOPLUGIN_H
#define _METISMISO_METISMIMOPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class PluginAPI;
#define METISMISO_DEVICE_TYPE_ID "sdrangel.samplemimo.metismiso"
class MetisMISOPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID METISMISO_DEVICE_TYPE_ID)
public:
explicit MetisMISOPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices);
virtual SamplingDevices enumSampleMIMO(const OriginDevices& originDevices);
virtual PluginInstanceGUI* createSampleMIMOPluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet);
virtual DeviceSampleMIMO* createSampleMIMOPluginInstance(const QString& sourceId, DeviceAPI *deviceAPI);
virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const;
static const QString m_hardwareID;
static const QString m_deviceTypeID;
private:
static const PluginDescriptor m_pluginDescriptor;
};
#endif // _METISMISO_METISMIMOPLUGIN_H
@@ -0,0 +1,155 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <QtGlobal>
#include "util/simpleserializer.h"
#include "metismisosettings.h"
MetisMISOSettings::MetisMISOSettings()
{
resetToDefaults();
}
MetisMISOSettings::MetisMISOSettings(const MetisMISOSettings& other)
{
m_nbReceivers = other.m_nbReceivers;
m_rx1CenterFrequency = other.m_rx1CenterFrequency;
m_rx2CenterFrequency = other.m_rx2CenterFrequency;
m_rx3CenterFrequency = other.m_rx3CenterFrequency;
m_rx4CenterFrequency = other.m_rx4CenterFrequency;
m_txCenterFrequency = other.m_txCenterFrequency;
m_sampleRateIndex = other.m_sampleRateIndex;
m_log2Decim = other.m_log2Decim;
m_preamp = other.m_preamp;
m_random = other.m_random;
m_dither = other.m_dither;
m_duplex = other.m_duplex;
m_dcBlock = other.m_dcBlock;
m_iqCorrection = other.m_iqCorrection;
m_useReverseAPI = other.m_useReverseAPI;
m_reverseAPIAddress = other.m_reverseAPIAddress;
m_reverseAPIPort = other.m_reverseAPIPort;
m_reverseAPIDeviceIndex = other.m_reverseAPIDeviceIndex;
}
void MetisMISOSettings::resetToDefaults()
{
m_nbReceivers = 1;
m_rx1CenterFrequency = 7074000;
m_rx2CenterFrequency = 7074000;
m_rx3CenterFrequency = 7074000;
m_rx4CenterFrequency = 7074000;
m_txCenterFrequency = 7074000;
m_sampleRateIndex = 0; // 48000 kS/s
m_log2Decim = 0;
m_preamp = false;
m_random = false;
m_dither = false;
m_duplex = false;
m_dcBlock = false;
m_iqCorrection = false;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
}
QByteArray MetisMISOSettings::serialize() const
{
SimpleSerializer s(1);
s.writeU32(1, m_nbReceivers);
s.writeU64(2, m_rx1CenterFrequency);
s.writeU64(3, m_rx2CenterFrequency);
s.writeU64(4, m_rx3CenterFrequency);
s.writeU64(5, m_rx4CenterFrequency);
s.writeU64(6, m_txCenterFrequency);
s.writeU32(7, m_sampleRateIndex);
s.writeU32(8, m_log2Decim);
s.writeBool(9, m_preamp);
s.writeBool(10, m_random);
s.writeBool(11, m_dither);
s.writeBool(12, m_duplex);
s.writeBool(13, m_dcBlock);
s.writeBool(14, m_iqCorrection);
s.writeBool(15, m_useReverseAPI);
s.writeString(16, m_reverseAPIAddress);
s.writeU32(17, m_reverseAPIPort);
s.writeU32(18, m_reverseAPIDeviceIndex);
return s.final();
}
bool MetisMISOSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
int intval;
uint32_t utmp;
d.readU32(1, &m_nbReceivers, 1);
d.readU64(2, &m_rx1CenterFrequency, 7074000);
d.readU64(3, &m_rx2CenterFrequency, 7074000);
d.readU64(4, &m_rx3CenterFrequency, 7074000);
d.readU64(5, &m_rx4CenterFrequency, 7074000);
d.readU64(6, &m_txCenterFrequency, 7074000);
d.readU32(7, &m_sampleRateIndex, 0);
d.readU32(8, &m_log2Decim, 0);
d.readBool(8, &m_preamp, false);
d.readBool(9, &m_random, false);
d.readBool(10, &m_dither, false);
d.readBool(11, &m_duplex, false);
d.readBool(12, &m_dcBlock, false);
d.readBool(13, &m_iqCorrection, false);
d.readBool(14, &m_useReverseAPI, false);
d.readString(15, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(16, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(17, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}
int MetisMISOSettings::getSampleRateFromIndex(unsigned int index)
{
if (index < 3) {
return (1<<index) * 48000;
} else {
return 48000;
}
}
@@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 _METISMISO_METISMISOSETTINGS_H_
#define _METISMISO_METISMISOSETTINGS_H_
#include <QString>
struct MetisMISOSettings {
unsigned int m_nbReceivers;
quint64 m_rx1CenterFrequency;
quint64 m_rx2CenterFrequency;
quint64 m_rx3CenterFrequency;
quint64 m_rx4CenterFrequency;
quint64 m_txCenterFrequency;
unsigned int m_sampleRateIndex;
unsigned int m_log2Decim;
bool m_preamp;
bool m_random;
bool m_dither;
bool m_duplex;
bool m_dcBlock;
bool m_iqCorrection;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
static const int m_maxReceivers = 4;
MetisMISOSettings();
MetisMISOSettings(const MetisMISOSettings& other);
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static int getSampleRateFromIndex(unsigned int index);
};
#endif /* _METISMISO_METISMISOSETTINGS_H_ */
@@ -0,0 +1,557 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "dsp/samplemififo.h"
#include "metismisoudphandler.h"
MESSAGE_CLASS_DEFINITION(MetisMISOUDPHandler::MsgStartStop, Message)
MetisMISOUDPHandler::MetisMISOUDPHandler(SampleMIFifo *sampleFifo, DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_socket(nullptr),
m_metisAddress(QHostAddress::LocalHost),
m_metisPort(9090),
m_running(false),
m_dataConnected(false),
m_sampleFifo(sampleFifo),
m_sampleCount(0),
m_messageQueueToGUI(nullptr),
m_mutex(QMutex::Recursive),
m_commandBase(0),
m_receiveSequence(0),
m_receiveSequenceError(0)
{
setNbReceivers(m_settings.m_nbReceivers);
for (unsigned int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) {
m_convertBuffer[i].resize(1024, Sample{0,0});
}
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()));
}
MetisMISOUDPHandler::~MetisMISOUDPHandler()
{
stop();
}
void MetisMISOUDPHandler::start()
{
qDebug("MetisMISOUDPHandler::start");
m_rxFrame = 0;
m_txFrame = 0;
m_sendSequence = -1;
m_offset = 8;
m_receiveSequence = 0;
if (m_running) {
return;
}
if (!m_dataConnected)
{
if (m_socket.bind(QHostAddress::AnyIPv4, 10001, QUdpSocket::ShareAddress))
{
qDebug("MetisMISOUDPHandler::start: bind host socket OK");
connect(&m_socket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
m_dataConnected = true;
}
else
{
qWarning("MetisMISOUDPHandler::start: cannot bind host socket");
m_dataConnected = false;
return;
}
}
// Start Metis
unsigned char buffer[64];
buffer[0] = (unsigned char) 0xEF;
buffer[1] = (unsigned char) 0XFE;
buffer[2] = (unsigned char) 0x04;
buffer[3] = (unsigned char) 0x01;
std::fill(&buffer[4], &buffer[64], 0);
if (m_socket.writeDatagram((const char*) buffer, sizeof(buffer), m_metisAddress, m_metisPort) < 0)
{
qDebug() << "MetisMISOUDPHandler::start: writeDatagram start command failed " << m_socket.errorString();
return;
}
else
{
qDebug() << "MetisMISOUDPHandler::start: writeDatagram start command" << m_metisAddress.toString() << ":" << m_metisPort;
}
m_socket.flush();
// send 2 frames with control data
sendNullBuffer();
sendNullBuffer(); // TODO: on the next send frequencies
m_running = true;
}
void MetisMISOUDPHandler::stop()
{
qDebug("MetisMISOUDPHandler::stop");
if (!m_running) {
return;
}
// stop Metis
unsigned char buffer[64];
buffer[0] = (unsigned char) 0xEF;
buffer[1] = (unsigned char) 0XFE;
buffer[2] = (unsigned char) 0x04;
buffer[3] = (unsigned char) 0x00;
std::fill(&buffer[4], &buffer[64], 0);
if (m_dataConnected)
{
disconnect(&m_socket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()));
m_dataConnected = false;
}
if (m_socket.writeDatagram((const char*)buffer, sizeof(buffer), m_metisAddress,m_metisPort) < 0)
{
qDebug() << "MetisMISOUDPHandler::stop: writeDatagram failed " << m_socket.errorString();
return;
}
else
{
qDebug()<<"MetisMISOUDPHandler::stop: writeDatagram stop command" << m_metisAddress.toString() << ":" << m_metisPort;
}
m_socket.flush();
m_socket.close();
m_running = false;
}
void MetisMISOUDPHandler::setNbReceivers(unsigned int nbReceivers)
{
m_nbReceivers = nbReceivers;
switch(m_nbReceivers)
{
case 1: m_bMax = 512-0; break;
case 2: m_bMax = 512-0; break;
case 3: m_bMax = 512-4; break;
case 4: m_bMax = 512-10; break;
case 5: m_bMax = 512-24; break;
case 6: m_bMax = 512-10; break;
case 7: m_bMax = 512-20; break;
case 8: m_bMax = 512-4; break;
}
for (unsigned int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) {
m_convertBuffer[i].resize(1024, Sample{0,0});
}
}
void MetisMISOUDPHandler::applySettings(const MetisMISOSettings& settings)
{
if (m_settings.m_nbReceivers != settings.m_nbReceivers)
{
QMutexLocker mutexLocker(&m_mutex);
int nbReceivers = settings.m_nbReceivers < 1 ?
1 : settings.m_nbReceivers > MetisMISOSettings::m_maxReceivers ?
MetisMISOSettings::m_maxReceivers : settings.m_nbReceivers;
setNbReceivers(nbReceivers);
}
if (m_settings.m_log2Decim != settings.m_log2Decim)
{
QMutexLocker mutexLocker(&m_mutex);
m_decimators.resetCounters();
}
m_settings = settings;
}
void MetisMISOUDPHandler::handleMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool MetisMISOUDPHandler::handleMessage(const Message& message)
{
if (MsgStartStop::match(message))
{
const MsgStartStop& cmd = (const MsgStartStop&) message;
if (cmd.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else
{
return false;
}
}
void MetisMISOUDPHandler::sendMetisBuffer(int ep, unsigned char* buffer)
{
if (m_offset == 8) // header and first HPSDR frame
{
m_sendSequence++;
m_outputBuffer[0] = 0xEF;
m_outputBuffer[1] = 0xFE;
m_outputBuffer[2] = 0x01;
m_outputBuffer[3] = ep;
m_outputBuffer[4] = (m_sendSequence>>24) & 0xFF;
m_outputBuffer[5] = (m_sendSequence>>16) & 0xFF;
m_outputBuffer[6] = (m_sendSequence>>8) & 0xFF;
m_outputBuffer[7] = (m_sendSequence) & 0xFF;
std::copy(buffer, buffer+512, &m_outputBuffer[m_offset]); // copy the buffer over
m_offset = 520;
}
else // second HPSDR frame and send
{
std::copy(buffer, buffer+512, &m_outputBuffer[m_offset]); // copy the buffer over
m_offset = 8;
// send the buffer
if (m_socket.writeDatagram((const char*) m_outputBuffer, sizeof(m_outputBuffer), m_metisAddress, m_metisPort) < 0)
{
qDebug() << "MetisMISOUDPHandler::sendMetisBuffer: writeDatagram failed " << m_socket.errorString();
return;
}
m_socket.flush();
}
}
void MetisMISOUDPHandler::dataReadyRead()
{
QHostAddress metisAddress;
quint16 metisPort;
unsigned char receiveBuffer[1032];
qint64 length;
long sequence;
if ((length = m_socket.readDatagram((char*) &receiveBuffer, (qint64) sizeof(receiveBuffer), &metisAddress, &metisPort)) != 1032)
{
qDebug() << "MetisMISOUDPHandler::dataReadyRead: readDatagram failed " << m_socket.errorString();
return;
}
sendNullBuffer(); // TODO: for now a null payload is sent
sendNullBuffer();
if (receiveBuffer[0] == 0xEF && receiveBuffer[1] == 0xFE)
{
// valid frame
switch(receiveBuffer[2])
{
case 1: // IQ data
switch (receiveBuffer[3])
{
case 4: // EP4 data
break;
case 6: // EP6 data
sequence = ((receiveBuffer[4] & 0xFF)<<24) + ((receiveBuffer[5] & 0xFF)<<16) + ((receiveBuffer[6] & 0xFF)<<8) +(receiveBuffer[7] & 0xFF);
if (m_receiveSequence == 0)
{
m_receiveSequence = sequence;
}
else
{
m_receiveSequence++;
if (m_receiveSequence != sequence)
{
//qDebug()<<"Sequence error: expected "<<receive_sequence<<" got "<<sequence;
m_receiveSequence = sequence;
m_receiveSequenceError++;
}
}
processIQBuffer(&receiveBuffer[8]);
processIQBuffer(&receiveBuffer[520]);
break;
default:
qDebug() << "MetisMISOUDPHandler::dataReadyRead: invalid EP" << receiveBuffer[3];
break;
}
break;
default:
qDebug() << "MetisMISOUDPHandler::dataReadyRead: expected data packet (1) got " << receiveBuffer[2];
break;
}
} else {
qDebug()<<"expected EFFE";
}
}
void MetisMISOUDPHandler::sendNullBuffer()
{
unsigned char buffer[512];
if (m_txFrame % (m_settings.m_sampleRateIndex+1) == 0)
{
int commandIndex = 2*m_commandBase; // command rotation
buffer[0] = (unsigned char) 0x7F;
buffer[1] = (unsigned char) 0x7F;
buffer[2] = (unsigned char) 0x7F;
buffer[3] = commandIndex; // C0
int commandValue = getCommandValue(commandIndex);
buffer[4]= commandValue>>24; // C1
buffer[5]= (commandValue>>16) & 0xFF; // C2
buffer[6]= (commandValue>>8) & 0xFF; // C3
buffer[7]= commandValue & 0xFF; // C4
if (m_commandBase < 15) { // base count 0 to 16
m_commandBase++;
} else {
m_commandBase = 0;
}
std::fill(&buffer[8], &buffer[512], 0);
sendMetisBuffer(2, buffer);
}
m_txFrame++;
}
int MetisMISOUDPHandler::getCommandValue(int commandIndex)
{
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
if (commandIndex == 0)
{
c1 = m_settings.m_sampleRateIndex & 0x03;
c3 = m_settings.m_preamp ? 0x04 : 0;
c3 += m_settings.m_dither ? 0x08 : 0;
c3 += m_settings.m_random ? 0x10 : 0;
c4 = m_settings.m_duplex ? 0x04 : 0;
c4 += (((m_nbReceivers-1) & 0x07)<<3);
return (c1<<24) + (c3<<8) + c4;
}
else if (commandIndex == 2)
{
return m_settings.m_txCenterFrequency;
}
else if (commandIndex == 4)
{
return m_settings.m_rx1CenterFrequency;
}
else if (commandIndex == 6)
{
return m_settings.m_rx2CenterFrequency;
}
else if (commandIndex == 8)
{
return m_settings.m_rx3CenterFrequency;
}
else if (commandIndex == 10)
{
return m_settings.m_rx4CenterFrequency;
}
else
{
return 0;
}
}
void MetisMISOUDPHandler::processIQBuffer(unsigned char* buffer)
{
int b = 0;
int b_max;
int r;
int sampleI, sampleQ, sampleMic;
if (buffer[b++]==0x7F && buffer[b++]==0x7F && buffer[b++]==0x7F)
{
QMutexLocker mutexLocker(&m_mutex);
// extract control bytes
m_controlIn[0] = buffer[b++];
m_controlIn[1] = buffer[b++];
m_controlIn[2] = buffer[b++];
m_controlIn[3] = buffer[b++];
m_controlIn[4] = buffer[b++];
// extract PTT, DOT and DASH
m_ptt = (m_controlIn[0] & 0x01) == 0x01;
m_dash = (m_controlIn[0] & 0x02) == 0x02;
m_dot = (m_controlIn[0] & 0x04) == 0x04;
switch((m_controlIn[0]>>3) & 0x1F)
{
case 0:
m_lt2208ADCOverflow = m_controlIn[1] & 0x01;
m_IO1 = (m_controlIn[1] & 0x02) ? 0 : 1;
m_IO2 = (m_controlIn[1] & 0x04) ? 0 : 1;
m_IO3 = (m_controlIn[1] & 0x08) ? 0 : 1;
// {
// int nbRx = (m_controlIn[4]>>3) & 0x07;
// nbRx++;
// qDebug("MetisMISOUDPHandler::processIQBuffer: nbRx: %d", nbRx);
// }
if (m_mercurySoftwareVersion != m_controlIn[2])
{
m_mercurySoftwareVersion = m_controlIn[2];
qDebug("MetisMISOUDPHandler::processIQBuffer: Mercury Software version: %d (0x%0X)",
m_mercurySoftwareVersion, m_mercurySoftwareVersion);
}
if (m_penelopeSoftwareVersion != m_controlIn[3])
{
m_penelopeSoftwareVersion = m_controlIn[3];
qDebug("MetisMISOUDPHandler::processIQBuffer: Penelope Software version: %d (0x%0X)",
m_penelopeSoftwareVersion, m_penelopeSoftwareVersion);
}
if (m_ozySoftwareVersion != m_controlIn[4])
{
m_ozySoftwareVersion = m_controlIn[4];
qDebug("MetisMISOUDPHandler::processIQBuffer: Ozy Software version: %d (0x%0X)",
m_ozySoftwareVersion, m_ozySoftwareVersion);
}
break;
case 1:
m_forwardPower = (m_controlIn[1]<<8) + m_controlIn[2]; // from Penelope or Hermes
m_alexForwardPower = (m_controlIn[3]<<8) + m_controlIn[4]; // from Alex or Apollo
break;
case 2:
m_alexForwardPower = (m_controlIn[1]<<8) + m_controlIn[2]; // from Alex or Apollo
m_AIN3 = (m_controlIn[3]<<8) + m_controlIn[4]; // from Pennelope or Hermes
break;
case 3:
m_AIN4 = (m_controlIn[1]<<8) + m_controlIn[2]; // from Pennelope or Hermes
m_AIN6 = (m_controlIn[3]<<8) + m_controlIn[4]; // from Pennelope or Hermes
break;
}
// extract the samples
while (b < m_bMax)
{
int samplesAdded = 0;
// extract each of the receivers
for (r = 0; r < m_nbReceivers; r++)
{
if (SDR_RX_SAMP_SZ == 16)
{
sampleQ = (int)((signed char)buffer[b++]) << 8;
sampleQ += (int)((unsigned char)buffer[b++]);
b++;
sampleI = (int)((signed char)buffer[b++]) << 8;
sampleI += (int)((unsigned char)buffer[b++]);
b++;
}
else
{
sampleQ = (int)((signed char)buffer[b++]) << 16;
sampleQ += (int)((unsigned char)buffer[b++]) << 8;
sampleQ += (int)((unsigned char)buffer[b++]);
sampleI = (int)((signed char)buffer[b++]) << 16;
sampleI += (int)((unsigned char)buffer[b++]) << 8;
sampleI += (int)((unsigned char)buffer[b++]);
}
if (r < MetisMISOSettings::m_maxReceivers)
{
if (m_settings.m_log2Decim == 0) // no decimation - direct conversion
{
m_convertBuffer[r][m_sampleCount] = Sample{sampleI, sampleQ};
samplesAdded = 1;
}
else
{
SampleVector v(2, Sample{0, 0});
switch (m_settings.m_log2Decim)
{
case 1:
samplesAdded = m_decimators.decimate2(sampleI, sampleQ, v, r);
break;
case 2:
samplesAdded = m_decimators.decimate4(sampleI, sampleQ, v, r);
break;
case 3:
samplesAdded = m_decimators.decimate8(sampleI, sampleQ, v, r);
break;
default:
break;
}
if (samplesAdded != 0)
{
std::copy(
v.begin(),
v.begin() + samplesAdded,
&m_convertBuffer[r][m_sampleCount]
);
}
}
}
}
sampleMic = (int)((signed char) buffer[b++]) << 8;
sampleMic += (int)((unsigned char)buffer[b++]);
if (samplesAdded != 0)
{
m_sampleCount += samplesAdded;
if (m_sampleCount >= 1024)
{
std::vector<SampleVector::const_iterator> vbegin;
for (unsigned int channel = 0; channel < MetisMISOSettings::m_maxReceivers; channel++) {
vbegin.push_back(m_convertBuffer[channel].begin());
}
m_sampleFifo->writeSync(vbegin, 1024);
m_sampleCount = 0;
}
}
}
}
else
{
qDebug()<<"MetisMISOUDPHandler::processIQBuffer: SYNC Error";
}
m_rxFrame++;
}
@@ -0,0 +1,130 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 _METISMISO_METISMISOUDPHANDLER_H_
#define _METISMISO_METISMISOUDPHANDLER_H_
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QMutex>
#include "dsp/decimators.h"
#include "util/messagequeue.h"
#include "util/message.h"
#include "metismisosettings.h"
#include "metismisodecimators.h"
class SampleMIFifo;
class DeviceAPI;
class MetisMISOUDPHandler : public QObject
{
Q_OBJECT
public:
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)
{ }
};
MetisMISOUDPHandler(SampleMIFifo* sampleFifo, DeviceAPI *deviceAPI);
~MetisMISOUDPHandler();
void setMessageQueueToGUI(MessageQueue *queue) { m_messageQueueToGUI = queue; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void start();
void stop();
void setMetisAddress(const QHostAddress& address, quint16 port) {
m_metisAddress = address;
m_metisPort = port;
}
void setNbReceivers(unsigned int nbReceivers);
void applySettings(const MetisMISOSettings& settings);
public slots:
void dataReadyRead();
private:
DeviceAPI *m_deviceAPI;
QUdpSocket m_socket;
QHostAddress m_metisAddress;
quint16 m_metisPort;
bool m_running;
bool m_dataConnected;
SampleMIFifo *m_sampleFifo;
SampleVector m_convertBuffer[MetisMISOSettings::m_maxReceivers];
int m_sampleCount;
MessageQueue *m_messageQueueToGUI;
MessageQueue m_inputMessageQueue;
MetisMISOSettings m_settings;
QMutex m_mutex;
MetisMISODecimators m_decimators;
unsigned long m_sendSequence;
int m_offset;
int m_commandBase;
unsigned long m_rxFrame;
unsigned long m_txFrame;
unsigned char m_outputBuffer[1032]; //!< buffer to send
unsigned char m_metisBuffer[512]; //!< current HPSDR frame
int metisBufferIndex;
unsigned long m_receiveSequence;
int m_receiveSequenceError;
unsigned char m_controlIn[5];
unsigned int m_nbReceivers;
int m_bMax;
bool m_ptt;
bool m_dash;
bool m_dot;
bool m_lt2208ADCOverflow;
int m_IO1;
int m_IO2;
int m_IO3;
int m_mercurySoftwareVersion;
int m_penelopeSoftwareVersion;
int m_ozySoftwareVersion;
int m_forwardPower;
int m_alexForwardPower;
int m_AIN3;
int m_AIN4;
int m_AIN6;
void sendMetisBuffer(int ep, unsigned char* buffer);
bool handleMessage(const Message& message);
void sendNullBuffer();
int getCommandValue(int commandIndex);
void processIQBuffer(unsigned char* buffer);
private slots:
void handleMessages();
};
#endif // _METISMISO_METISMISOUDPHANDLER_H_
@@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "metismiso.h"
#include "metismisowebapiadapter.h"
MetisMISOWebAPIAdapter::MetisMISOWebAPIAdapter()
{}
MetisMISOWebAPIAdapter::~MetisMISOWebAPIAdapter()
{}
int MetisMISOWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings());
response.getMetisMisoSettings()->init();
MetisMISO::webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int MetisMISOWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) errorMessage;
MetisMISO::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response);
return 200;
}
@@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef _METISMISO_METISMISOWEBAPIADAPTER_H_
#define _METISMISO_METISMISOWEBAPIADAPTER_H_
#include "device/devicewebapiadapter.h"
#include "metismisosettings.h"
class MetisMISOWebAPIAdapter : public DeviceWebAPIAdapter
{
public:
MetisMISOWebAPIAdapter();
virtual ~MetisMISOWebAPIAdapter();
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:
MetisMISOSettings m_settings;
};
#endif // _METISMISO_METISMISOWEBAPIADAPTER_H_
+137
View File
@@ -0,0 +1,137 @@
<h1>Test source input plugin</h1>
<h2>Introduction</h2>
This is a v5 only plugin.
This input sample source plugin is an internal continuous wave generator that can be used to carry out test of software internals.
<h2>Build</h2>
The plugin is present in the core of the software and thus is always present in the list of sources.
<h2>Interface</h2>
![Test source input plugin GUI](../../../doc/img/TestSourceInput_plugin.png)
<h3>1: Common stream parameters</h3>
![Remote source input stream GUI](../../../doc/img/RemoteInput_plugin_01.png)
<h4>1.1: Frequency</h4>
This is the center frequency of reception in kHz.
<h4>1.2: Start/Stop</h4>
Device start / stop button.
- Blue triangle icon: device is ready and can be started
- Green square icon: device is running and can be stopped
- Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.
<h4>1.3: Record</h4>
- Left click: record baseband I/Q stream toggle button
- Right click: choose record file
<h4>1.4: Stream sample rate</h4>
Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4).
<h3>2: Various options</h3>
![Test source input plugin GUI 2](../../../doc/img/TestSourceInput_plugin_2.png)
<h4>2.1: Auto corrections</h4>
This combo box control the local DSP auto correction options:
- **None**: no correction
- **DC**: auto remove DC component
- **DC+IQ**: auto remove DC component and correct I/Q balance.
<h4>2.2: Decimation factor</h4>
The I/Q stream from the generator is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. This exercises the decimation chain.
This exercises the decimation chain.
<h4>2.3: Baseband center frequency position relative the center frequency</h4>
- **Cen**: the decimation operation takes place around the center frequency Fs
- **Inf**: the decimation operation takes place around Fs - Fc.
- **Sup**: the decimation operation takes place around Fs + Fc.
With SR as the sample rate before decimation Fc is calculated as:
- if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband.
- if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.
<h3>2.4: Sample size</h3>
This is the sample size in number of bits. It corresponds to the actual sample size used by the devices supported:
- **8**: RTL-SDR, HackRF
- **12**: Airspy, BladeRF, LimeSDR, PlutoSDR, SDRplay
- **16**: Airspy HF+, FCD Pro, FCD Pro+
<h3>3: Sample rate</h3>
This controls the generator sample rate in samples per second.
<h3>4: Modulation</h4>
- **No**: No modulation
- **AM**: Amplitude modulation (AM)
- **FM**: Frequency modulation (FM)
- **P0**: Pattern 0 is a binary pattern
- Pulse width: 150 samples
- Sync pattern: 010 at full amplitude
- Binary pattern LSB first on 3 bits from 0 to 7 at 0.3 amplitude
- **P1**: Pattern 1 is a sawtooth pattern
- Pulse width: 1000 samples
- Starts at full amplitude then amplitude decreases linearly down to zero
- **P2**: Pattern 2 is a 50% duty cycle square pattern
- Pulse width: 1000 samples
- Starts with a full amplitude pulse then down to zero for the duration of one pulse
<h3>5: Modulating tone frequency</h3>
This controls the modulating tone frequency in kHz in 10 Hz steps.
<h3>6: Carrier shift from center frequency</h3>
Use this control to set the offset of the carrier from the center frequency of reception.
<h3>7: AM modulation factor</h3>
This controls the AM modulation factor from 0 to 99%
<h3>8: FM deviation</h3>
This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate.
<h3>9: Amplitude coarse control</h3>
This slider controls the number of amplitude bits by steps of 100 bits. The total number of amplitude bits appear on the right.
<h3>10: Amplitude fine control</h3>
This slider controls the number of amplitude bits by steps of 1 bit. The signal power in dB relative to the maximum power (full bit range) appear on the right.
<h3>11: DC bias</h3>
Use this slider to give a DC component in percentage of maximum amplitude.
<h3>12: I bias</h3>
Use this slider to give an in-phase (I) bias in percentage of maximum amplitude.
<h3>13: Q bias</h3>
Use this slider to give an quadrature-phase (Q) bias in percentage of maximum amplitude.
<h3>14: Phase imbalance</h3>
Use this slider to introduce a phase imbalance in percentage of full period (continuous wave) or percentage of I signal injected in Q (AM, FM).