Metis MISO

This commit is contained in:
f4exb 2020-11-11 10:10:04 +01:00
parent 3e24e5af5d
commit 0a33a93273
24 changed files with 4569 additions and 0 deletions

View File

@ -39,3 +39,5 @@ endif()
if(ENABLE_USRP AND UHD_FOUND)
add_subdirectory(usrp)
endif()
add_subdirectory(metis)

View File

@ -0,0 +1,24 @@
project(metisdevice)
set(metisdevice_SOURCES
devicemetis.cpp
devicemetisscan.cpp
)
set(metisdevice_HEADERS
devicemetis.h
devicemetisscan.h
)
add_library(metisdevice SHARED
${metisdevice_SOURCES}
)
set_target_properties(metisdevice
PROPERTIES DEFINE_SYMBOL "devices_EXPORTS")
target_link_libraries(metisdevice
sdrbase
)
install(TARGETS metisdevice DESTINATION ${INSTALL_LIB_DIR})

View File

@ -0,0 +1,32 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "devicemetis.h"
DeviceMetis::DeviceMetis()
{
}
DeviceMetis::~DeviceMetis()
{
}
DeviceMetis& DeviceMetis::instance()
{
static DeviceMetis inst;
return inst;
}

View File

@ -0,0 +1,42 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 DEVICES_METIS_DEVICEMETIS_H_
#define DEVICES_METIS_DEVICEMETIS_H_
#include "export.h"
#include "devicemetisscan.h"
class DEVICES_API DeviceMetis
{
public:
static DeviceMetis& instance();
void scan() { m_scan.scan(); }
void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices) {
m_scan.enumOriginDevices(hardwareId, originDevices);
}
const DeviceMetisScan::DeviceScan* getDeviceScanAt(unsigned int index) const { return m_scan.getDeviceAt(index); }
protected:
DeviceMetis();
~DeviceMetis();
private:
DeviceMetisScan m_scan;
};
#endif // DEVICES_METIS_DEVICEMETIS_H_

View File

@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QEventLoop>
#include <QTimer>
#include "devicemetisscan.h"
void DeviceMetisScan::scan()
{
m_scans.clear();
if (m_udpSocket.bind(QHostAddress::AnyIPv4, 10001, QUdpSocket::ShareAddress))
{
connect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
}
else
{
qDebug() << "DeviceMetisScan::scan: cannot bind socket";
return;
}
unsigned char buffer[63];
buffer[0] = (unsigned char) 0xEF;
buffer[1] = (unsigned char) 0XFE;
buffer[2] = (unsigned char) 0x02;
std::fill(&buffer[3], &buffer[63], 0);
if (m_udpSocket.writeDatagram((const char*) buffer, sizeof(buffer), QHostAddress::Broadcast, 1024) < 0)
{
qDebug() << "DeviceMetisScan::scan: discovery writeDatagram failed " << m_udpSocket.errorString();
return;
}
else
{
qDebug() << "DeviceMetisScan::scan: discovery writeDatagram sent";
}
// wait for 1 second before returning
QEventLoop loop;
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer->start(500);
qDebug() << "DeviceMetisScan::scan: start 0.5 second timeout loop";
// Execute the event loop here and wait for the timeout to trigger
// which in turn will trigger event loop quit.
loop.exec();
disconnect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
m_udpSocket.close();
}
void DeviceMetisScan::enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices)
{
scan();
for (int i = 0; i < m_scans.size(); i++)
{
const DeviceScan& deviceInfo = m_scans.at(i);
QString serial = QString("%1:%2_%3").arg(deviceInfo.m_address.toString()).arg(deviceInfo.m_port).arg(deviceInfo.m_serial);
QString displayableName(QString("Metis[%1] %2").arg(i).arg(serial));
originDevices.append(PluginInterface::OriginDevice(
displayableName,
hardwareId,
serial,
i, // sequence
8, // Nb Rx
1 // Nb Tx
));
}
}
const DeviceMetisScan::DeviceScan* DeviceMetisScan::getDeviceAt(unsigned int index) const
{
if (index < m_scans.size()) {
return &m_scans.at(index);
} else {
return nullptr;
}
}
void DeviceMetisScan::getSerials(QList<QString>& serials) const
{
for (int i = 0; i < m_scans.size(); i++) {
serials.append(m_scans.at(i).m_serial);
}
}
void DeviceMetisScan::readyRead()
{
QHostAddress metisAddress;
quint16 metisPort;
unsigned char buffer[1024];
if (m_udpSocket.readDatagram((char*) &buffer, (qint64) sizeof(buffer), &metisAddress, &metisPort) < 0)
{
qDebug() << "DeviceMetisScan::readyRead: readDatagram failed " << m_udpSocket.errorString();
return;
}
QString metisIP = QString("%1:%2").arg(metisAddress.toString()).arg(metisPort);
if (buffer[0] == 0xEF && buffer[1] == 0xFE)
{
switch(buffer[2])
{
case 3: // reply
// should not happen on this port
break;
case 2: // response to a discovery packet
{
QByteArray array((char *) &buffer[3], 6);
QString serial = QString(array.toHex());
m_scans.append(DeviceScan(
serial,
metisAddress,
metisPort
));
m_serialMap.insert(serial, &m_scans.back());
qDebug() << "DeviceMetisScan::readyRead: found Metis at:" << metisIP << "MAC:" << serial;
}
break;
case 1: // a data packet
break;
}
}
else
{
qDebug() << "DeviceMetisScan::readyRead: received invalid response to discovery";
}
}

View File

@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 DEVICES_METIS_DEVICEMETISSCAN_H_
#define DEVICES_METIS_DEVICEMETISSCAN_H_
#include <QObject>
#include <QString>
#include <QUdpSocket>
#include <QList>
#include <QMap>
#include <string>
#include <vector>
#include <map>
#include "plugin/plugininterface.h"
#include "export.h"
class DEVICES_API DeviceMetisScan : public QObject
{
Q_OBJECT
public:
struct DeviceScan
{
QString m_serial;
QHostAddress m_address;
quint16 m_port;
DeviceScan(
const QString& serial,
const QHostAddress& address,
quint16 port
) :
m_serial(serial),
m_address(address),
m_port(port)
{}
};
void scan();
int getNbDevices() const { return m_scans.size(); }
const DeviceScan* getDeviceAt(unsigned int index) const;
void getSerials(QList<QString>& serials) const;
void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices);
public slots:
void readyRead();
private:
QUdpSocket m_udpSocket;
QList<DeviceScan> m_scans;
QMap<QString, DeviceScan*> m_serialMap;
};
#endif /* DEVICES_METIS_DEVICEMETISSCAN_H_ */

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)

View File

@ -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})

View File

@ -0,0 +1,898 @@
///////////////////////////////////////////////////////////////////////////////////
// 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, &m_sampleMOFifo, deviceAPI),
m_deviceDescription("MetisMISO"),
m_running(false),
m_masterTimer(deviceAPI->getMasterTimer())
{
m_mimoType = MIMOHalfSynchronous;
m_sampleMIFifo.init(MetisMISOSettings::m_maxReceivers, 96000 * 4);
m_sampleMOFifo.init(1, 96000 * 4);
m_deviceAPI->setNbSourceStreams(MetisMISOSettings::m_maxReceivers);
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
{
if (index < MetisMISOSettings::m_maxReceivers) {
return m_settings.m_rxCenterFrequencies[index];
} else {
return 0;
}
}
void MetisMISO::setSourceCenterFrequency(qint64 centerFrequency, int index)
{
MetisMISOSettings settings = m_settings; // note: calls copy constructor
if (index < MetisMISOSettings::m_maxReceivers)
{
settings.m_rxCenterFrequencies[index];
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())
{
// Start Rx engine
if (m_deviceAPI->initDeviceEngine(0)) {
m_deviceAPI->startDeviceEngine(0);
}
// Start Tx engine
if (m_deviceAPI->initDeviceEngine(1)) {
m_deviceAPI->startDeviceEngine(1);
}
}
else
{
m_deviceAPI->stopDeviceEngine(0); // Stop Rx engine
m_deviceAPI->stopDeviceEngine(1); // Stop Tx engine
}
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_txEnable:" << settings.m_txEnable
<< " m_rx1CenterFrequencies: ["
<< " 1:" << settings.m_rxCenterFrequencies[0]
<< " 2:" << settings.m_rxCenterFrequencies[1]
<< " 3:" << settings.m_rxCenterFrequencies[2]
<< " 4:" << settings.m_rxCenterFrequencies[3]
<< " 5:" << settings.m_rxCenterFrequencies[4]
<< " 6:" << settings.m_rxCenterFrequencies[5]
<< " 7:" << settings.m_rxCenterFrequencies[6]
<< " 8:" << settings.m_rxCenterFrequencies[7]
<< " ] m_txCenterFrequency:" << settings.m_txCenterFrequency
<< " m_rxSubsamplingIndexes: ["
<< " 1:" << settings.m_rxSubsamplingIndexes[0]
<< " 2:" << settings.m_rxSubsamplingIndexes[1]
<< " 3:" << settings.m_rxSubsamplingIndexes[2]
<< " 4:" << settings.m_rxSubsamplingIndexes[3]
<< " 5:" << settings.m_rxSubsamplingIndexes[4]
<< " 6:" << settings.m_rxSubsamplingIndexes[5]
<< " 7:" << settings.m_rxSubsamplingIndexes[6]
<< " 8:" << settings.m_rxSubsamplingIndexes[7]
<< " ] m_rxTransverterMode:" << settings.m_rxTransverterMode
<< " m_rxTransverterDeltaFrequency:" << settings.m_rxTransverterDeltaFrequency
<< " m_txTransverterMode:" << settings.m_txTransverterMode
<< " m_txTransverterDeltaFrequency:" << settings.m_txTransverterDeltaFrequency
<< " m_iqOrder:" << settings.m_iqOrder
<< " 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_txDrive:" << settings.m_txDrive
<< " 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_txEnable != settings.m_txEnable) || force)
{
reverseAPIKeys.append("txEnable");
propagateSettings = true;
}
for (int i = 0; i < MetisMISOSettings::m_maxReceivers; i++)
{
if ((m_settings.m_rxCenterFrequencies[i] != settings.m_rxCenterFrequencies[i]) || force)
{
reverseAPIKeys.append(QString("rx%1CenterFrequency").arg(i+1));
propagateSettings = true;
}
if ((m_settings.m_rxSubsamplingIndexes[i] != settings.m_rxSubsamplingIndexes[i]) || force)
{
reverseAPIKeys.append(QString("rx%1SubsamplingIndex").arg(i+1));
propagateSettings = true;
}
}
if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) || force)
{
reverseAPIKeys.append("txCenterFrequency");
propagateSettings = true;
}
if ((m_settings.m_rxTransverterMode != settings.m_rxTransverterMode) || force)
{
reverseAPIKeys.append("rxTransverterMode");
propagateSettings = true;
}
if ((m_settings.m_rxTransverterDeltaFrequency != settings.m_rxTransverterDeltaFrequency) || force)
{
reverseAPIKeys.append("rxTransverterDeltaFrequency");
propagateSettings = true;
}
if ((m_settings.m_txTransverterMode != settings.m_txTransverterMode) || force)
{
reverseAPIKeys.append("txTransverterMode");
propagateSettings = true;
}
if ((m_settings.m_txTransverterDeltaFrequency != settings.m_txTransverterDeltaFrequency) || force)
{
reverseAPIKeys.append("txTransverterDeltaFrequency");
propagateSettings = true;
}
if ((m_settings.m_iqOrder != settings.m_iqOrder) || force)
{
reverseAPIKeys.append("iqOrder");
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_LOppmTenths != settings.m_LOppmTenths) || force)
{
reverseAPIKeys.append("LOppmTenths");
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_txDrive != settings.m_txDrive) || force)
{
reverseAPIKeys.append("txDrive");
propagateSettings = true;
}
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);
}
for (int i = 0; i < MetisMISOSettings::m_maxReceivers; i++)
{
if ((m_settings.m_rxCenterFrequencies[i] != settings.m_rxCenterFrequencies[i]) ||
(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 *engineRxNotif = new DSPMIMOSignalNotification(
sampleRate, settings.m_rxCenterFrequencies[i], true, i);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineRxNotif);
}
}
if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) ||
(m_settings.m_log2Decim != settings.m_log2Decim) || force)
{
DSPMIMOSignalNotification *engineTxNotif = new DSPMIMOSignalNotification(
48000, 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) || (subsystemIndex == 1)) // Rx and Tx always started together
{
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
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)
{
// Rx and Tx are started or stopped together
if ((subsystemIndex == 0) || (subsystemIndex == 1))
{
m_deviceAPI->getDeviceEngineStateStr(*response.getState()); // Rx driven
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("nbReceivers")) {
settings.m_nbReceivers = response.getMetisMisoSettings()->getNbReceivers();
}
if (deviceSettingsKeys.contains("txEnable")) {
settings.m_txEnable = response.getMetisMisoSettings()->getTxEnable() != 0;
}
if (deviceSettingsKeys.contains("rx1CenterFrequency")) {
settings.m_rxCenterFrequencies[0] = response.getMetisMisoSettings()->getRx1CenterFrequency();
}
if (deviceSettingsKeys.contains("rx2CenterFrequency")) {
settings.m_rxCenterFrequencies[1] = response.getMetisMisoSettings()->getRx2CenterFrequency();
}
if (deviceSettingsKeys.contains("rx3CenterFrequency")) {
settings.m_rxCenterFrequencies[2] = response.getMetisMisoSettings()->getRx3CenterFrequency();
}
if (deviceSettingsKeys.contains("rx4CenterFrequency")) {
settings.m_rxCenterFrequencies[3] = response.getMetisMisoSettings()->getRx4CenterFrequency();
}
if (deviceSettingsKeys.contains("rx5CenterFrequency")) {
settings.m_rxCenterFrequencies[4] = response.getMetisMisoSettings()->getRx5CenterFrequency();
}
if (deviceSettingsKeys.contains("rx6CenterFrequency")) {
settings.m_rxCenterFrequencies[5] = response.getMetisMisoSettings()->getRx6CenterFrequency();
}
if (deviceSettingsKeys.contains("rx7CenterFrequency")) {
settings.m_rxCenterFrequencies[6] = response.getMetisMisoSettings()->getRx7CenterFrequency();
}
if (deviceSettingsKeys.contains("rx8CenterFrequency")) {
settings.m_rxCenterFrequencies[7] = response.getMetisMisoSettings()->getRx8CenterFrequency();
}
if (deviceSettingsKeys.contains("txCenterFrequency")) {
settings.m_txCenterFrequency = response.getMetisMisoSettings()->getTxCenterFrequency();
}
if (deviceSettingsKeys.contains("rxTransverterMode")) {
settings.m_rxTransverterMode = response.getMetisMisoSettings()->getRxTransverterMode() != 0;
}
if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency")) {
settings.m_rxTransverterDeltaFrequency = response.getMetisMisoSettings()->getRxTransverterDeltaFrequency();
}
if (deviceSettingsKeys.contains("txTransverterMode")) {
settings.m_txTransverterMode = response.getMetisMisoSettings()->getTxTransverterMode() != 0;
}
if (deviceSettingsKeys.contains("txTransverterDeltaFrequency")) {
settings.m_txTransverterDeltaFrequency = response.getMetisMisoSettings()->getTxTransverterDeltaFrequency();
}
if (deviceSettingsKeys.contains("iqOrder")) {
settings.m_iqOrder = response.getMetisMisoSettings()->getIqOrder() != 0;
}
if (deviceSettingsKeys.contains("rx1SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[0] = response.getMetisMisoSettings()->getRx1SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx2SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[1] = response.getMetisMisoSettings()->getRx2SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx3SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[2] = response.getMetisMisoSettings()->getRx3SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx4SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[3] = response.getMetisMisoSettings()->getRx4SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx5SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[4] = response.getMetisMisoSettings()->getRx5SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx6SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[5] = response.getMetisMisoSettings()->getRx6SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx7SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[6] = response.getMetisMisoSettings()->getRx7SubsamplingIndex();
}
if (deviceSettingsKeys.contains("rx8SubsamplingIndex")) {
settings.m_rxSubsamplingIndexes[7] = response.getMetisMisoSettings()->getRx8SubsamplingIndex();
}
if (deviceSettingsKeys.contains("sampleRateIndex")) {
settings.m_sampleRateIndex = response.getMetisMisoSettings()->getSampleRateIndex();
}
if (deviceSettingsKeys.contains("log2Decim")) {
settings.m_log2Decim = response.getMetisMisoSettings()->getLog2Decim();
}
if (deviceSettingsKeys.contains("LOppmTenths")) {
settings.m_LOppmTenths = response.getMetisMisoSettings()->getLOppmTenths();
}
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("txDrive")) {
settings.m_txDrive = response.getMetisMisoSettings()->getTxDrive();
}
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()->setNbReceivers(settings.m_nbReceivers);
response.getMetisMisoSettings()->setTxEnable(settings.m_txEnable ? 1 : 0);
response.getMetisMisoSettings()->setRx1CenterFrequency(settings.m_rxCenterFrequencies[0]);
response.getMetisMisoSettings()->setRx2CenterFrequency(settings.m_rxCenterFrequencies[1]);
response.getMetisMisoSettings()->setRx3CenterFrequency(settings.m_rxCenterFrequencies[2]);
response.getMetisMisoSettings()->setRx4CenterFrequency(settings.m_rxCenterFrequencies[3]);
response.getMetisMisoSettings()->setRx5CenterFrequency(settings.m_rxCenterFrequencies[4]);
response.getMetisMisoSettings()->setRx6CenterFrequency(settings.m_rxCenterFrequencies[5]);
response.getMetisMisoSettings()->setRx7CenterFrequency(settings.m_rxCenterFrequencies[6]);
response.getMetisMisoSettings()->setRx8CenterFrequency(settings.m_rxCenterFrequencies[7]);
response.getMetisMisoSettings()->setRx1SubsamplingIndex(settings.m_rxSubsamplingIndexes[0]);
response.getMetisMisoSettings()->setRx2SubsamplingIndex(settings.m_rxSubsamplingIndexes[1]);
response.getMetisMisoSettings()->setRx3SubsamplingIndex(settings.m_rxSubsamplingIndexes[2]);
response.getMetisMisoSettings()->setRx4SubsamplingIndex(settings.m_rxSubsamplingIndexes[3]);
response.getMetisMisoSettings()->setRx5SubsamplingIndex(settings.m_rxSubsamplingIndexes[4]);
response.getMetisMisoSettings()->setRx6SubsamplingIndex(settings.m_rxSubsamplingIndexes[5]);
response.getMetisMisoSettings()->setRx7SubsamplingIndex(settings.m_rxSubsamplingIndexes[6]);
response.getMetisMisoSettings()->setRx8SubsamplingIndex(settings.m_rxSubsamplingIndexes[7]);
response.getMetisMisoSettings()->setTxCenterFrequency(settings.m_txCenterFrequency);
response.getMetisMisoSettings()->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0);
response.getMetisMisoSettings()->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency);
response.getMetisMisoSettings()->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0);
response.getMetisMisoSettings()->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency);
response.getMetisMisoSettings()->setIqOrder(settings.m_iqOrder ? 1 : 0);
response.getMetisMisoSettings()->setSampleRateIndex(settings.m_sampleRateIndex);
response.getMetisMisoSettings()->setLog2Decim(settings.m_log2Decim);
response.getMetisMisoSettings()->setLOppmTenths(settings.m_LOppmTenths);
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()->setTxDrive(settings.m_txDrive);
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("nbReceivers") || force) {
swgMetisMISOSettings->setNbReceivers(settings.m_nbReceivers);
}
if (deviceSettingsKeys.contains("txEnable") || force) {
swgMetisMISOSettings->setTxEnable(settings.m_txEnable ? 1 : 0);
}
if (deviceSettingsKeys.contains("rx1CenterFrequency") || force) {
swgMetisMISOSettings->setRx1CenterFrequency(settings.m_rxCenterFrequencies[0]);
}
if (deviceSettingsKeys.contains("rx2CenterFrequency") || force) {
swgMetisMISOSettings->setRx2CenterFrequency(settings.m_rxCenterFrequencies[1]);
}
if (deviceSettingsKeys.contains("rx3CenterFrequency") || force) {
swgMetisMISOSettings->setRx3CenterFrequency(settings.m_rxCenterFrequencies[2]);
}
if (deviceSettingsKeys.contains("rx4CenterFrequency") || force) {
swgMetisMISOSettings->setRx4CenterFrequency(settings.m_rxCenterFrequencies[3]);
}
if (deviceSettingsKeys.contains("rx5CenterFrequency") || force) {
swgMetisMISOSettings->setRx5CenterFrequency(settings.m_rxCenterFrequencies[4]);
}
if (deviceSettingsKeys.contains("rx6CenterFrequency") || force) {
swgMetisMISOSettings->setRx6CenterFrequency(settings.m_rxCenterFrequencies[5]);
}
if (deviceSettingsKeys.contains("rx7CenterFrequency") || force) {
swgMetisMISOSettings->setRx7CenterFrequency(settings.m_rxCenterFrequencies[6]);
}
if (deviceSettingsKeys.contains("rx8CenterFrequency") || force) {
swgMetisMISOSettings->setRx8CenterFrequency(settings.m_rxCenterFrequencies[7]);
}
if (deviceSettingsKeys.contains("rx1SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx1SubsamplingIndex(settings.m_rxSubsamplingIndexes[0]);
}
if (deviceSettingsKeys.contains("rx2SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx2SubsamplingIndex(settings.m_rxSubsamplingIndexes[1]);
}
if (deviceSettingsKeys.contains("rx3SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx3SubsamplingIndex(settings.m_rxSubsamplingIndexes[2]);
}
if (deviceSettingsKeys.contains("rx4SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx4SubsamplingIndex(settings.m_rxSubsamplingIndexes[3]);
}
if (deviceSettingsKeys.contains("rx5SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx5SubsamplingIndex(settings.m_rxSubsamplingIndexes[4]);
}
if (deviceSettingsKeys.contains("rx6SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx6SubsamplingIndex(settings.m_rxSubsamplingIndexes[5]);
}
if (deviceSettingsKeys.contains("rx7SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx7SubsamplingIndex(settings.m_rxSubsamplingIndexes[6]);
}
if (deviceSettingsKeys.contains("rx8SubsamplingIndex") || force) {
swgMetisMISOSettings->setRx8SubsamplingIndex(settings.m_rxSubsamplingIndexes[7]);
}
if (deviceSettingsKeys.contains("txCenterFrequency") || force) {
swgMetisMISOSettings->setTxCenterFrequency(settings.m_txCenterFrequency);
}
if (deviceSettingsKeys.contains("rxTransverterMode") || force) {
swgMetisMISOSettings->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0);
}
if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency") || force) {
swgMetisMISOSettings->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency);
}
if (deviceSettingsKeys.contains("txTransverterMode") || force) {
swgMetisMISOSettings->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0);
}
if (deviceSettingsKeys.contains("txTransverterDeltaFrequency") || force) {
swgMetisMISOSettings->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency);
}
if (deviceSettingsKeys.contains("iqOrder") || force) {
swgMetisMISOSettings->setIqOrder(settings.m_iqOrder ? 1 : 0);
}
if (deviceSettingsKeys.contains("sampleRateIndex") || force) {
swgMetisMISOSettings->setSampleRateIndex(settings.m_sampleRateIndex);
}
if (deviceSettingsKeys.contains("log2Decim") || force) {
swgMetisMISOSettings->setLog2Decim(settings.m_log2Decim);
}
if (deviceSettingsKeys.contains("LOppmTenths") || force) {
swgMetisMISOSettings->setLOppmTenths(settings.m_LOppmTenths);
}
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);
}
if (deviceSettingsKeys.contains("txDrive") || force) {
swgMetisMISOSettings->setTxDrive(settings.m_txDrive);
}
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();
}

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_

View File

@ -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();
}
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;
}

View File

@ -0,0 +1,45 @@
///////////////////////////////////////////////////////////////////////////////////
// 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];
};
#endif // _METISMISO_METISMISODECIMATORS_H_

View File

@ -0,0 +1,568 @@
///////////////////////////////////////////////////////////////////////////////////
// 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) :
DeviceGUI(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;
m_txSampleRate = 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::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
sendSettings();
}
void MetisMISOGui::setCenterFrequency(qint64 centerFrequency)
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers) {
m_settings.m_rxCenterFrequencies[m_streamIndex] = centerFrequency;
} else if (m_streamIndex == MetisMISOSettings::m_maxReceivers) {
m_settings.m_txCenterFrequency = centerFrequency;
}
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);
m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF);
}
else
{
m_deviceUISet->m_spectrum->setDisplayedStream(false, 0);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0);
m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF);
}
updateSpectrum();
ui->spectrumSource->blockSignals(true);
ui->spectrumSource->setCurrentIndex(index);
ui->spectrumSource->blockSignals(false);
}
m_streamIndex = index;
updateSubsamplingIndex();
displayFrequency();
displaySampleRate();
}
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);
m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF);
}
else
{
m_deviceUISet->m_spectrum->setDisplayedStream(false, 0);
m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0);
m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF);
}
updateSpectrum();
if (ui->streamLock->isChecked())
{
ui->streamIndex->blockSignals(true);
ui->streamIndex->setCurrentIndex(index);
ui->streamIndex->blockSignals(false);
m_streamIndex = index;
updateSubsamplingIndex();
displayFrequency();
displaySampleRate();
}
}
void MetisMISOGui::on_streamLock_toggled(bool checked)
{
if (checked && (ui->streamIndex->currentIndex() != ui->spectrumSource->currentIndex())) {
ui->spectrumSource->setCurrentIndex(ui->streamIndex->currentIndex());
}
}
void MetisMISOGui::on_LOppm_valueChanged(int value)
{
m_settings.m_LOppmTenths = value;
ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1)));
sendSettings();
}
void MetisMISOGui::on_centerFrequency_changed(quint64 value)
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers) {
m_settings.m_rxCenterFrequencies[m_streamIndex] = value * 1000;
} else if (m_streamIndex == MetisMISOSettings::m_maxReceivers) {
m_settings.m_txCenterFrequency = value * 1000;
}
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_subsamplingIndex_currentIndexChanged(int index)
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers) // valid for Rx only
{
m_settings.m_rxSubsamplingIndexes[m_streamIndex] = index;
ui->subsamplingIndex->setToolTip(tr("Subsampling band index [%1 - %2 MHz]")
.arg(index*61.44).arg((index+1)*61.44));
displayFrequency();
setCenterFrequency(ui->centerFrequency->getValueNew() * 1000);
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_transverter_clicked()
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers)
{
m_settings.m_rxTransverterMode = ui->transverter->getDeltaFrequencyAcive();
m_settings.m_rxTransverterDeltaFrequency = ui->transverter->getDeltaFrequency();
m_settings.m_iqOrder = ui->transverter->getIQOrder();
qDebug("MetisMISOGui::on_transverter_clicked: Rx: %lld Hz %s", m_settings.m_rxTransverterDeltaFrequency, m_settings.m_rxTransverterMode ? "on" : "off");
}
else
{
m_settings.m_txTransverterMode = ui->transverter->getDeltaFrequencyAcive();
m_settings.m_txTransverterDeltaFrequency = ui->transverter->getDeltaFrequency();
qDebug("MetisMISOGui::on_transverter_clicked: Tx: %lld Hz %s", m_settings.m_txTransverterDeltaFrequency, m_settings.m_txTransverterMode ? "on" : "off");
}
displayFrequency();
setCenterFrequency(ui->centerFrequency->getValueNew() * 1000);
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::on_txEnable_toggled(bool checked)
{
m_settings.m_txEnable = checked;
sendSettings();
}
void MetisMISOGui::on_txDrive_valueChanged(int value)
{
m_settings.m_txDrive = value;
ui->txDriveText->setText(tr("%1").arg(m_settings.m_txDrive));
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->LOppm->setValue(m_settings.m_LOppmTenths);
ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1)));
ui->log2Decim->setCurrentIndex(m_settings.m_log2Decim);
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);
ui->nbRxIndex->setCurrentIndex(m_settings.m_nbReceivers - 1);
ui->txEnable->setChecked(m_settings.m_txEnable);
ui->txDrive->setValue(m_settings.m_txDrive);
ui->txDriveText->setText(tr("%1").arg(m_settings.m_txDrive));
updateSubsamplingIndex();
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 < MetisMISOSettings::m_maxReceivers) {
m_settings.m_rxCenterFrequencies[istream] = frequency;
}
}
else
{
m_txSampleRate = notif->getSampleRate();
m_settings.m_txCenterFrequency = frequency;
}
qDebug() << "MetisMISOGui::handleInputMessages: DSPMIMOSignalNotification: "
<< "sourceOrSink:" << sourceOrSink
<< "istream:" << istream
<< "m_rxSampleRate:" << m_rxSampleRate
<< "m_txSampleRate:" << m_txSampleRate
<< "frequency:" << frequency;
displayFrequency();
displaySampleRate();
updateSpectrum();
delete message;
}
else
{
if (handleMessage(*message))
{
delete message;
}
}
}
}
void MetisMISOGui::displayFrequency()
{
qint64 centerFrequency;
qint64 fBaseLow, fBaseHigh;
if (m_streamIndex < MetisMISOSettings::m_maxReceivers)
{
int subsamplingIndex = m_settings.m_rxSubsamplingIndexes[m_streamIndex];
centerFrequency = m_settings.m_rxCenterFrequencies[m_streamIndex];
fBaseLow = subsamplingIndex*61440;
fBaseHigh = (subsamplingIndex+1)*61440;
}
else if (m_streamIndex == MetisMISOSettings::m_maxReceivers)
{
centerFrequency = m_settings.m_txCenterFrequency;
fBaseLow = 0;
fBaseHigh = 61440;
}
else
{
fBaseLow = 0;
fBaseHigh = 61440;
centerFrequency = 0;
}
ui->centerFrequency->setValueRange(7, fBaseLow, fBaseHigh);
ui->centerFrequency->setValue(centerFrequency / 1000);
}
void MetisMISOGui::displaySampleRate()
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers) {
ui->deviceRateText->setText(tr("%1k").arg((float) m_rxSampleRate / 1000));
} else {
ui->deviceRateText->setText(tr("%1k").arg((float) m_txSampleRate / 1000));
}
}
void MetisMISOGui::updateSpectrum()
{
qint64 centerFrequency;
if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) {
centerFrequency = m_settings.m_rxCenterFrequencies[m_spectrumStreamIndex];
} else if (m_spectrumStreamIndex == MetisMISOSettings::m_maxReceivers) {
centerFrequency = m_settings.m_txCenterFrequency;
} else {
centerFrequency = 0;
}
m_deviceUISet->getSpectrum()->setCenterFrequency(centerFrequency);
if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) {
m_deviceUISet->getSpectrum()->setSampleRate(m_rxSampleRate);
} else {
m_deviceUISet->getSpectrum()->setSampleRate(m_txSampleRate);
}
}
void MetisMISOGui::updateSubsamplingIndex()
{
if (m_streamIndex < MetisMISOSettings::m_maxReceivers)
{
ui->subsamplingIndex->setEnabled(true);
ui->subsamplingIndex->setCurrentIndex(m_settings.m_rxSubsamplingIndexes[m_streamIndex]);
}
else
{
ui->subsamplingIndex->setEnabled(false);
ui->subsamplingIndex->setToolTip("No subsampling for Tx");
}
}
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();
}

View File

@ -0,0 +1,105 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <device/devicegui.h>
#include <QTimer>
#include <QWidget>
#include "util/messagequeue.h"
#include "metismisosettings.h"
#include "metismiso.h"
class DeviceUISet;
namespace Ui {
class MetisMISOGui;
}
class MetisMISOGui : public DeviceGUI {
Q_OBJECT
public:
explicit MetisMISOGui(DeviceUISet *deviceUISet, QWidget* parent = 0);
virtual ~MetisMISOGui();
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
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;
int m_txSampleRate;
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 updateSubsamplingIndex();
void updateSpectrum();
void sendSettings();
void setCenterFrequency(qint64 centerFrequency);
bool handleMessage(const Message& message);
private slots:
void handleInputMessages();
void on_streamIndex_currentIndexChanged(int index);
void on_spectrumSource_currentIndexChanged(int index);
void on_streamLock_toggled(bool checked);
void on_LOppm_valueChanged(int value);
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_subsamplingIndex_currentIndexChanged(int index);
void on_dcBlock_toggled(bool checked);
void on_iqCorrection_toggled(bool checked);
void on_transverter_clicked();
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 on_txEnable_toggled(bool checked);
void on_txDrive_valueChanged(int value);
void openDeviceSettingsDialog(const QPoint& p);
void updateStatus();
void updateHardware();
};
#endif // _METISMISO_METISMISOGUI_H_

View File

@ -0,0 +1,841 @@
<?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>483</width>
<height>228</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>Rx4</string>
</property>
</item>
<item>
<property name="text">
<string>Rx5</string>
</property>
</item>
<item>
<property name="text">
<string>Rx6</string>
</property>
</item>
<item>
<property name="text">
<string>Rx7</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>Rx4</string>
</property>
</item>
<item>
<property name="text">
<string>Rx5</string>
</property>
</item>
<item>
<property name="text">
<string>Rx6</string>
</property>
</item>
<item>
<property name="text">
<string>Rx7</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="QGridLayout" name="gridLayout_LOppm">
<property name="spacing">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="LOppmLabel">
<property name="toolTip">
<string>LO correction ppm</string>
</property>
<property name="text">
<string>LO ppm</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="LOppm">
<property name="toolTip">
<string>Local Oscillator frequency correction (ppm)</string>
</property>
<property name="minimum">
<number>-500</number>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="LOppmText">
<property name="text">
<string>0.0</string>
</property>
</widget>
</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>
<widget class="QLabel" name="subsamplingIndexLabel">
<property name="text">
<string>Sub</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="subsamplingIndex">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Subsampling band index </string>
</property>
<item>
<property name="text">
<string>0</string>
</property>
</item>
<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>
<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>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="txEnable">
<property name="toolTip">
<string>Enable/disable Tx</string>
</property>
<property name="text">
<string>Tx</string>
</property>
</widget>
</item>
<item>
<widget class="TransverterButton" name="transverter">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Transverter frequency translation dialog</string>
</property>
<property name="text">
<string>X</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="miscControlsLayout">
<item>
<widget class="QLabel" name="txDriveLabel">
<property name="text">
<string>Tx Drv</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="txDrive">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Tx drive level (0: off)</string>
</property>
<property name="maximum">
<number>15</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>15</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="txDriveText">
<property name="text">
<string>00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<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>
<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>
<customwidget>
<class>TransverterButton</class>
<extends>QPushButton</extends>
<header>gui/transverterbutton.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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("6.0.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
DeviceGUI* MetisMISOPlugin::createSampleMIMOPluginInstanceGUI(
const QString& sourceId,
QWidget **widget,
DeviceUISet *deviceUISet)
{
(void) sourceId;
(void) widget;
(void) deviceUISet;
return 0;
}
#else
DeviceGUI* 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();
}

View File

@ -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 DeviceGUI* 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

View File

@ -0,0 +1,187 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_txEnable = other.m_txEnable;
std::copy(other.m_rxCenterFrequencies, other.m_rxCenterFrequencies + m_maxReceivers, m_rxCenterFrequencies);
std::copy(other.m_rxSubsamplingIndexes, other.m_rxSubsamplingIndexes + m_maxReceivers, m_rxSubsamplingIndexes);
m_txCenterFrequency = other.m_txCenterFrequency;
m_rxTransverterMode = other.m_rxTransverterMode;
m_rxTransverterDeltaFrequency = other.m_rxTransverterDeltaFrequency;
m_txTransverterMode = other.m_txTransverterMode;
m_txTransverterDeltaFrequency = other.m_txTransverterDeltaFrequency;
m_iqOrder = other.m_iqOrder;
m_sampleRateIndex = other.m_sampleRateIndex;
m_log2Decim = other.m_log2Decim;
m_LOppmTenths = other.m_LOppmTenths;
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_txDrive = other.m_txDrive;
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_txEnable = false;
std::fill(m_rxCenterFrequencies, m_rxCenterFrequencies + m_maxReceivers, 7074000);
std::fill(m_rxSubsamplingIndexes, m_rxSubsamplingIndexes + m_maxReceivers, 0);
m_txCenterFrequency = 7074000;
m_rxTransverterMode = false;
m_rxTransverterDeltaFrequency = 0;
m_txTransverterMode = false;
m_txTransverterDeltaFrequency = 0;
m_iqOrder = true;
m_sampleRateIndex = 0; // 48000 kS/s
m_log2Decim = 0;
m_LOppmTenths = 0;
m_preamp = false;
m_random = false;
m_dither = false;
m_duplex = false;
m_dcBlock = false;
m_iqCorrection = false;
m_txDrive = 15;
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.writeBool(2, m_txEnable);
s.writeU64(3, m_txCenterFrequency);
s.writeBool(4, m_rxTransverterMode);
s.writeS64(5, m_rxTransverterDeltaFrequency);
s.writeBool(6, m_txTransverterMode);
s.writeS64(7, m_txTransverterDeltaFrequency);
s.writeBool(8, m_iqOrder);
s.writeU32(9, m_sampleRateIndex);
s.writeU32(10, m_log2Decim);
s.writeS32(11, m_LOppmTenths);
s.writeBool(12, m_preamp);
s.writeBool(13, m_random);
s.writeBool(14, m_dither);
s.writeBool(15, m_duplex);
s.writeBool(16, m_dcBlock);
s.writeBool(17, m_iqCorrection);
s.writeU32(18, m_txDrive);
s.writeBool(19, m_useReverseAPI);
s.writeString(20, m_reverseAPIAddress);
s.writeU32(21, m_reverseAPIPort);
s.writeU32(22, m_reverseAPIDeviceIndex);
for (int i = 0; i < m_maxReceivers; i++)
{
s.writeU64(30+i, m_rxCenterFrequencies[i]);
s.writeU32(50+i, m_rxSubsamplingIndexes[i]);
}
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.readBool(2, &m_txEnable, false);
d.readU64(3, &m_txCenterFrequency, 7074000);
d.readBool(4, &m_rxTransverterMode, false);
d.readS64(5, &m_rxTransverterDeltaFrequency, 0);
d.readBool(6, &m_txTransverterMode, false);
d.readS64(7, &m_txTransverterDeltaFrequency, 0);
d.readBool(8, &m_iqOrder, true);
d.readU32(9, &m_sampleRateIndex, 0);
d.readU32(10, &m_log2Decim, 0);
d.readS32(11, &m_LOppmTenths, 0);
d.readBool(12, &m_preamp, false);
d.readBool(13, &m_random, false);
d.readBool(14, &m_dither, false);
d.readBool(15, &m_duplex, false);
d.readBool(16, &m_dcBlock, false);
d.readBool(17, &m_iqCorrection, false);
d.readU32(18, &m_txDrive, 15);
d.readBool(19, &m_useReverseAPI, false);
d.readString(20, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(21, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(22, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < m_maxReceivers; i++)
{
d.readU64(30+i, &m_rxCenterFrequencies[i], 7074000);
d.readU32(50+i, &m_rxSubsamplingIndexes[i], 0);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}
int MetisMISOSettings::getSampleRateFromIndex(unsigned int index)
{
if (index < 3) {
return (1<<index) * 48000;
} else {
return 48000;
}
}

View File

@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 {
static const int m_maxReceivers = 8;
unsigned int m_nbReceivers;
bool m_txEnable;
quint64 m_rxCenterFrequencies[m_maxReceivers];
unsigned int m_rxSubsamplingIndexes[m_maxReceivers];
quint64 m_txCenterFrequency;
bool m_rxTransverterMode;
qint64 m_rxTransverterDeltaFrequency;
bool m_txTransverterMode;
qint64 m_txTransverterDeltaFrequency;
bool m_iqOrder;
unsigned int m_sampleRateIndex;
unsigned int m_log2Decim;
int m_LOppmTenths;
bool m_preamp;
bool m_random;
bool m_dither;
bool m_duplex;
bool m_dcBlock;
bool m_iqCorrection;
unsigned int m_txDrive;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
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_ */

View File

@ -0,0 +1,659 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "dsp/samplemofifo.h"
#include "metismisoudphandler.h"
MESSAGE_CLASS_DEFINITION(MetisMISOUDPHandler::MsgStartStop, Message)
MetisMISOUDPHandler::MetisMISOUDPHandler(SampleMIFifo *sampleMIFifo, SampleMOFifo *sampleMOFifo, DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_socket(nullptr),
m_metisAddress(QHostAddress::LocalHost),
m_metisPort(9090),
m_running(false),
m_dataConnected(false),
m_sampleMIFifo(sampleMIFifo),
m_sampleMOFifo(sampleMOFifo),
m_sampleCount(0),
m_sampleTxCount(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
sendData();
sendData(); // 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;
}
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() << "MetisMISOUDPHandler::dataReadyRead: expected EFFE";
}
}
void MetisMISOUDPHandler::sendData(bool nullPayload)
{
unsigned char buffer[512];
if ((m_settings.m_sampleRateIndex == 0) || (m_txFrame % (1<<m_settings.m_sampleRateIndex) == 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 + (m_settings.m_txEnable ? 1 : 0); // 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 < 18) { // base count 0 to 18
m_commandBase++;
} else {
m_commandBase = 0;
}
if (nullPayload)
{
std::fill(&buffer[8], &buffer[512], 0);
}
else
{
unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End;
int bufferIndex = 8;
m_sampleMOFifo->readSync(63, iPart1Begin, iPart1End, iPart2Begin, iPart2End);
if (iPart1Begin != iPart1End) {
fillBuffer(buffer, bufferIndex, iPart1Begin, iPart1End);
}
if (iPart2Begin != iPart2End) {
fillBuffer(buffer, bufferIndex, iPart2Begin, iPart2End);
}
}
sendMetisBuffer(2, buffer);
}
m_txFrame++;
}
void MetisMISOUDPHandler::fillBuffer(unsigned char *buffer, int& bufferIndex, int iBegin, int iEnd)
{
SampleVector::iterator it = m_sampleMOFifo->getData(0).begin() + iBegin;
const SampleVector::iterator itEnd = m_sampleMOFifo->getData(0).begin() + iEnd;
for (; it != itEnd; ++it)
{
std::fill(&buffer[bufferIndex], &buffer[bufferIndex+4], 0); // Fill <L1><L0><R1><R0> with zeros
bufferIndex += 4;
buffer[bufferIndex++] = it->imag() >> 8;
buffer[bufferIndex++] = it->imag() & 0xFF;
buffer[bufferIndex++] = it->real() >> 8;
buffer[bufferIndex++] = it->real() & 0xFF;
}
}
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 getTxCenterFrequency();
}
else if (commandIndex == 4)
{
return getRxCenterFrequency(0);
}
else if (commandIndex == 6)
{
return getRxCenterFrequency(1);
}
else if (commandIndex == 8)
{
return getRxCenterFrequency(2);
}
else if (commandIndex == 10)
{
return getRxCenterFrequency(3);
}
else if (commandIndex == 12)
{
return getRxCenterFrequency(4);
}
else if (commandIndex == 14)
{
return getRxCenterFrequency(5);
}
else if (commandIndex == 16)
{
return getRxCenterFrequency(6);
}
else if (commandIndex == 18)
{
c1 = (m_settings.m_txDrive & 0x0F) << 4;
return (c1<<24) + (c3<<8) + c4;
}
else if (commandIndex == 36)
{
return getRxCenterFrequency(7);
}
else
{
return 0;
}
}
quint64 MetisMISOUDPHandler::getRxCenterFrequency(int index)
{
qint64 deviceCenterFrequency;
qint64 loHalfFrequency = 61440000LL - ((m_settings.m_LOppmTenths * 122880000LL) / 20000000LL);
qint64 requiredRxFrequency = m_settings.m_rxCenterFrequencies[index]
- (m_settings.m_rxTransverterMode ? m_settings.m_rxTransverterDeltaFrequency : 0);
requiredRxFrequency = requiredRxFrequency < 0 ? 0 : requiredRxFrequency;
if (m_settings.m_rxSubsamplingIndexes[index] % 2 == 0) {
deviceCenterFrequency = requiredRxFrequency - m_settings.m_rxSubsamplingIndexes[index]*loHalfFrequency;
} else {
deviceCenterFrequency = (m_settings.m_rxSubsamplingIndexes[index] + 1)*loHalfFrequency - requiredRxFrequency;
}
qint64 df = ((qint64)deviceCenterFrequency * m_settings.m_LOppmTenths) / 10000000LL;
deviceCenterFrequency += df;
return deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency > 61440000 ? 61440000 : deviceCenterFrequency;
}
quint64 MetisMISOUDPHandler::getTxCenterFrequency()
{
qint64 requiredTxFrequency = m_settings.m_txCenterFrequency;
- (m_settings.m_txTransverterMode ? m_settings.m_txTransverterDeltaFrequency : 0);
requiredTxFrequency = requiredTxFrequency < 0 ? 0 : requiredTxFrequency;
return requiredTxFrequency;
}
bool MetisMISOUDPHandler::getRxIQInversion(int index)
{
return ((m_settings.m_rxSubsamplingIndexes[index] % 2) == 1) ^ !m_settings.m_iqOrder;
}
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] = getRxIQInversion(r) ? Sample{sampleQ, sampleI} : Sample{sampleI, sampleQ};
samplesAdded = 1;
}
else
{
SampleVector v(2, Sample{0, 0});
switch (m_settings.m_log2Decim)
{
case 1:
samplesAdded = getRxIQInversion(r) ?
m_decimators.decimate2(sampleQ, sampleI, v, r) : m_decimators.decimate2(sampleI, sampleQ, v, r);
break;
case 2:
samplesAdded = getRxIQInversion(r) ?
m_decimators.decimate4(sampleQ, sampleI, v, r) : m_decimators.decimate4(sampleI, sampleQ, v, r);
break;
case 3:
samplesAdded = getRxIQInversion(r) ?
m_decimators.decimate8(sampleQ, sampleI, v, r) : 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++]);
m_sampleTxCount++;
if (m_sampleTxCount >= 63) // 63 samples per 512 byte Tx block
{
sendData(!m_settings.m_txEnable); // synchronous sending of a Tx frame
m_sampleTxCount = 0;
}
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_sampleMIFifo->writeSync(vbegin, 1024);
m_sampleCount = 0;
}
}
}
}
else
{
qDebug()<<"MetisMISOUDPHandler::processIQBuffer: SYNC Error";
}
m_rxFrame++;
}

View File

@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 SampleMOFifo;
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* sampleMIFifo, SampleMOFifo *sampleMOFifo, 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_sampleMIFifo;
SampleMOFifo *m_sampleMOFifo;
SampleVector m_convertBuffer[MetisMISOSettings::m_maxReceivers];
int m_sampleCount;
int m_sampleTxCount;
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 sendData(bool nullPayload = true);
void fillBuffer(unsigned char *buffer, int& bufferIndex, int iBegin, int iEnd);
int getCommandValue(int commandIndex);
void processIQBuffer(unsigned char* buffer);
quint64 getRxCenterFrequency(int index);
quint64 getTxCenterFrequency();
bool getRxIQInversion(int index);
private slots:
void handleMessages();
};
#endif // _METISMISO_METISMISOUDPHANDLER_H_

View File

@ -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;
}

View File

@ -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_

View File

@ -0,0 +1,142 @@
<h1>Metis Multiple Input Single Output plugin</h1>
<h2>Introduction</h2>
This is a v5 only plugin.
This plugin is mainly intended to be used to process samples to/from a [Red Pitaya board](https://www.redpitaya.com/) with [Pavel's HPSDR compatible applications](http://pavel-demin.github.io/red-pitaya-notes/). More precisely:
- [SDR transceiver compatible with HPSDR](http://pavel-demin.github.io/red-pitaya-notes/sdr-transceiver-hpsdr/)
- [SDR receiver compatible with HPSDR](http://pavel-demin.github.io/red-pitaya-notes/sdr-receiver-hpsdr-122-88/)
The plugin has 8 receiving (sink) streams and one transmitting (source) stream. Depending on the design of the Metis compatible hardware you may or may not have all of them available. Streams with no connection will just be filled with zero samples. You may choose the number of active streams with the control (9.5) - see next.
While compatible with [Metis HPSDR-2 protocol](https://github.com/softerhardware/Hermes-Lite2/wiki/Protocol) implementation is minimal in order to be able to control and work with the Red Pitaya. It has not been tested in another context nor has provision to control the Red Pitaya or HPSDR peripherals. These controls may be added in the future as needs arise.
<h2>Build</h2>
The plugin is present in the core of the software and thus is always present in the list of MIMO devices.
<h2>Interface</h2>
![Metis MISO plugin GUI](../../../doc/img/MetisMISO_plugin.png)
<h3>1: Active stream selection</h3>
Select for which streams the controls are active. Controls specific to each stream are:
- Center frequency
- Subsampling index
<h3>2: Spectrum source selection</h3>
Select which stream is routed to the main spectrum display
<h3>3: Active stream / spectrum source lock</h3>
This ties together the stream selection and spectrum source stream selections.
<h3>4: Start/Stop</h3>
Device start / stop button.
- Blue triangle icon: device is ready and can be started
- Green square icon: device is running and can be stopped
- 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.
Starting the device means that the network stream from the Metis compatible device is started. It will be stopped by the stop button. This effectively starts all available streams that can be controlled with the Rx number select (9.5) or Tx enable (9.6)
<h3>5: Stream sample rate</h3>
Baseband I/Q sample rate in kS/s. This is the device to host sample rate (8.1) divided by the software decimation factor (8.2).
<h3>6: Center frequency</h3>
Tunes the center frequency of the active stream
<h3>7: Local Oscillator frequency correction in ppm</h3>
This lets you compensate for the main oscillator frequency inaccuracy. Value is in ppm (parts per million)
<h3>8: Sample rate - Decimation - Subsampling - DC and IQ corrections</h3>
![Metis Miso GUI 1](../../../doc/img/MetisMISO_plugin_1.png)
<h4>8.1: Sample rate</h4>
This combo box lets you control the four pssible values for the device to host sample rate (Rx). Host to device (Tx) sample rate is fixed by design of the Metis interface at 48 kS/s:
- **48k**: 48000 samples per second
- **96k**: 96000 samples per second
- **192k**: 192000 samples per second
- **384k**: 384000 samples per second
<h4>8.2: Decimation factor</h4>
The I/Q stream from the Metis stream 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.
<h4>8.3: Subsampling index</h4>
The Red Pitaya has a LTC2185 ADC specified for a bandwidth up to 550 MHz. This lets you use the Red Pitaya receivers in subsampling mode with appropriate filtering and LNA chain as a front end. In this mode the received frequency may extend above 61.44 MHz in successive 61.44 MHz wide bands. This index corresponds to the frequency band index from 0 to 7 and let you input the frequency directly corresponding the the subsampling scheme. The band limits appear in the tooltip and are the following:
- **0**: 0 to 61.44 MHz - fundamental no subsampling
- **1**: 61.44 to 122.88 MHz
- **2**: 122.88 to 184.32 MHz
- **3**: 184.32 to 245.76 MHz
- **4**: 245.76 to 307.2 MHz
- **5**: 307.2 to 368.64 MHz
- **6**: 368.64 to 430.08 MHz
- **7**: 430.08 to 491.52 MHz
Of course the more the higher the frequency above the fundamental range the worse the performance is. In practice it is still OK at VHF frequencies but not much above.
<h4>8.4: DC correction</h4>
This corrects residual DC present at the center of the passband. By construction this is useless for the Red Pitaya.
<h4>8.5: IQ imbalance correction</h4>
This corrects I/Q imbalance. By construction this is useless for the Red Pitaya.
<h3>9: Preamp - Random - Dither - Duplex - Number of receivers - Tx enable - Transverter</h3>
![Metis Miso GUI 1](../../../doc/img/MetisMISO_plugin_2.png)
<h4>9.1: Preamp</h4>
Toggle Rx preamplifier - not found to be effective
<h4>9.2: Random</h4>
Toggle LTC2185 randomization - not found to be effective
<h4>9.3: Dither</h4>
Toggle LTC2185 dithering - not found to be effective
<h4>9.4: Duplex</h4>
Toggle duplex - not found to be effective
<h4>9.5: Number or active receivers</h4>
Controls the number of active receivers. Each receiver allocates a slot in the data stream from the Metis interface.
- For the SDR receiver compatible with HPSDR choose a maximum of 4 receivers
- For the SDR receiver compatible with HPSDR choose a maximum of 8 receivers
It is a waste to have more active receivers than you actually need because it will increase network traffic for nothing
<h4>9.6: Toggle Tx activation</h4>
Use this button to toggle the generation and sending of Tx samples in the Metis stream from host to device. When inactivated null samples are sent in the return payload from host to device.
<h4>9.7: Transverter mode</h4>
This button opens a dialog to set the transverter mode frequency translation options. The details about this dialog can be found [here](../../../sdrgui/gui/transverterdialog.md)
Transverter mixing is the same for all receivers and may be different for the transmitter.
<h3>10: Tx drive level</h3>
Choose a level from 0 (deactivated) to 15 (full power)