diff --git a/plugins/samplemimo/CMakeLists.txt b/plugins/samplemimo/CMakeLists.txt index acc069928..07c626536 100644 --- a/plugins/samplemimo/CMakeLists.txt +++ b/plugins/samplemimo/CMakeLists.txt @@ -1,3 +1,4 @@ project(samplemimo) +add_subdirectory(bladerf2mimo) add_subdirectory(testmi) diff --git a/plugins/samplemimo/bladerf2mimo/CMakeLists.txt b/plugins/samplemimo/bladerf2mimo/CMakeLists.txt new file mode 100644 index 000000000..1f658b28d --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/CMakeLists.txt @@ -0,0 +1,62 @@ +project(bladerf2mimo) + +set(bladerf2mimo_SOURCES + bladerf2mimo.cpp + bladerf2mimoplugin.cpp + bladerf2mithread.cpp + bladerf2mothread.cpp + bladerf2mimosettings.cpp + bladerf2mimowebapiadapter.cpp +) + +set(bladerf2mimo_HEADERS + bladerf2mimo.h + bladerf2mimoplugin.h + bladerf2mithread.h + bladerf2mothread.h + bladerf2mimosettings.h + bladerf2mimowebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIRS} +) + +if (NOT SERVER_MODE) + set (bladerf2mimo_SOURCES + ${bladerf2mimo_SOURCES} + bladerf2mimogui.cpp + bladerf2mimogui.ui + ) + set(bladerf2mimo_HEADERS + ${bladerf2mimo_HEADERS} + bladerf2mimogui.h + ) + set(TARGET_NAME mimobladerf2) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME mimobladerf2srv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${bladerf2mimo_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger + ${LIBBLADERF_LIBRARIES} + bladerf2device +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimo.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mimo.cpp new file mode 100644 index 000000000..cb71106b8 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimo.cpp @@ -0,0 +1,1308 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include +#include + +#include "SWGDeviceSettings.h" +#include "SWGDeviceState.h" +#include "SWGTestMISettings.h" + +#include "device/deviceapi.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "dsp/dspdevicemimoengine.h" +#include "dsp/devicesamplesource.h" +#include "dsp/devicesamplesink.h" +#include "dsp/filerecord.h" +#include "bladerf2/devicebladerf2.h" + +#include "bladerf2mithread.h" +#include "bladerf2mothread.h" +#include "bladerf2mimo.h" + +MESSAGE_CLASS_DEFINITION(BladeRF2MIMO::MsgConfigureBladeRF2MIMO, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2MIMO::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2MIMO::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2MIMO::MsgReportGainRange, Message) + +BladeRF2MIMO::BladeRF2MIMO(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_sourceThread(nullptr), + m_sinkThread(nullptr), + m_deviceDescription("BladeRF2MIMO"), + m_rxElseTx(true), + m_runningRx(false), + m_runningTx(false), + m_dev(nullptr), + m_open(false) +{ + m_open = openDevice(); + + if (m_dev) + { + const bladerf_gain_modes *modes = 0; + int nbModes = m_dev->getGainModesRx(&modes); + + if (modes) + { + for (int i = 0; i < nbModes; i++) { + m_rxGainModes.push_back(GainMode{QString(modes[i].name), modes[i].mode}); + } + } + } + + m_mimoType = MIMOHalfSynchronous; + m_sampleSinkFifos.push_back(SampleSinkFifo(96000 * 4)); + m_sampleSinkFifos.push_back(SampleSinkFifo(96000 * 4)); + m_deviceAPI->setNbSourceStreams(2); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +BladeRF2MIMO::~BladeRF2MIMO() +{ + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + + if (m_runningRx) { + stop(); + } + + std::vector::iterator it = m_fileSinks.begin(); + int istream = 0; + + for (; it != m_fileSinks.end(); ++it, istream++) + { + m_deviceAPI->removeAncillarySink(*it, istream); + delete *it; + } + + m_deviceAPI->removeLastSourceStream(); // Remove the last source stream data set in the engine + m_deviceAPI->removeLastSourceStream(); // Remove the last source stream data set in the engine +} + +void BladeRF2MIMO::destroy() +{ + delete this; +} + +bool BladeRF2MIMO::openDevice() +{ + m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSamplingDeviceSerial())); + + if (!m_dev->open(serial)) + { + qCritical("BladeRF2MIMO::openDevice: cannot open BladeRF2 device"); + return false; + } + else + { + qDebug("BladeRF2MIMO::openDevice: device opened"); + return true; + } +} + +void BladeRF2MIMO::closeDevice() +{ + if (m_dev == nullptr) { // was never open + return; + } + + if (m_runningRx) { + stop(); + } + + m_dev->close(); + delete m_dev; + m_dev = nullptr; +} + +void BladeRF2MIMO::init() +{ + m_fileSinks.push_back(new FileRecord(QString("test_0_%1.sdriq").arg(m_deviceAPI->getDeviceUID()))); + m_fileSinks.push_back(new FileRecord(QString("test_1_%1.sdriq").arg(m_deviceAPI->getDeviceUID()))); + m_deviceAPI->addAncillarySink(m_fileSinks[0], 0); + m_deviceAPI->addAncillarySink(m_fileSinks[1], 1); + + applySettings(m_settings, true); +} + +bool BladeRF2MIMO::start() +{ + if (!m_open) + { + qCritical("BladeRF2MIMO::start: device could not be opened"); + return false; + } + + if (m_rxElseTx) { + startRx(); + } else { + startTx(); + } + + return true; +} + +void BladeRF2MIMO::startRx() +{ + qDebug("BladeRF2MIMO::start"); + QMutexLocker mutexLocker(&m_mutex); + + if (m_runningRx) { + stop(); + } + + m_sourceThread = new BladeRF2MIThread(m_dev->getDev()); + m_sourceThread->setFifo(0, &m_sampleSinkFifos[0]); + m_sourceThread->setFifo(1, &m_sampleSinkFifos[1]); + m_sourceThread->setFcPos(m_settings.m_fcPos); + m_sourceThread->setLog2Decimation(m_settings.m_log2Decim); + + for (int i = 0; i < 2; i++) + { + if (!m_dev->openRx(i)) { + qCritical("BladeRF2MIMO::start: Rx channel %u cannot be enabled", i); + } + } + + m_sourceThread->startWork(); + + mutexLocker.unlock(); + + applySettings(m_settings, true); + m_runningRx = true; +} + +void BladeRF2MIMO::startTx() +{ + // TODO +} + +void BladeRF2MIMO::stop() +{ + if (m_rxElseTx) { + stopRx(); + } else { + stopTx(); + } +} + +void BladeRF2MIMO::stopRx() +{ + qDebug("BladeRF2MIMO::stop"); + + if (!m_sourceThread) { + return; + } + + QMutexLocker mutexLocker(&m_mutex); + + m_sourceThread->stopWork(); + delete m_sourceThread; + m_sourceThread = nullptr; + m_runningRx = false; + + for (int i = 0; i < 2; i++) { + m_dev->closeRx(i); + } +} + +void BladeRF2MIMO::stopTx() +{ + // TODO +} + +QByteArray BladeRF2MIMO::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2MIMO::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureBladeRF2MIMO* message = MsgConfigureBladeRF2MIMO::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2MIMO* messageToGUI = MsgConfigureBladeRF2MIMO::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& BladeRF2MIMO::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int BladeRF2MIMO::getSourceSampleRate(int index) const +{ + (void) index; + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +quint64 BladeRF2MIMO::getSinkCenterFrequency(int index) const +{ + (void) index; + return m_settings.m_txCenterFrequency; +} + +void BladeRF2MIMO::setSinkCenterFrequency(qint64 centerFrequency, int index) +{ + (void) index; + BladeRF2MIMOSettings settings = m_settings; + settings.m_txCenterFrequency = centerFrequency; + + MsgConfigureBladeRF2MIMO* message = MsgConfigureBladeRF2MIMO::create(settings, false); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2MIMO* messageToGUI = MsgConfigureBladeRF2MIMO::create(settings, false); + m_guiMessageQueue->push(messageToGUI); + } +} + +bool BladeRF2MIMO::handleMessage(const Message& message) +{ + if (MsgConfigureBladeRF2MIMO::match(message)) + { + MsgConfigureBladeRF2MIMO& conf = (MsgConfigureBladeRF2MIMO&) message; + qDebug() << "BladeRF2MIMO::handleMessage: MsgConfigureBladeRF2MIMO"; + + bool success = applySettings(conf.getSettings(), conf.getForce()); + + if (!success) { + qDebug("BladeRF2MIMO::handleMessage: config error"); + } + + return true; + } + else if (MsgFileRecord::match(message)) + { + MsgFileRecord& conf = (MsgFileRecord&) message; + qDebug() << "BladeRF2MIMO::handleMessage: MsgFileRecord: " << conf.getStartStop(); + int istream = conf.getStreamIndex(); + + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSinks[istream]->setFileName(m_settings.m_fileRecordName + "_0.sdriq"); + } else { + m_fileSinks[istream]->genUniqueFileName(m_deviceAPI->getDeviceUID(), istream); + } + + m_fileSinks[istream]->startRecording(); + } + else + { + m_fileSinks[istream]->stopRecording(); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "BladeRF2MIMO::handleMessage: " + << " " << (cmd.getRxElseTx() ? "Rx" : "Tx") + << " MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + m_rxElseTx = cmd.getRxElseTx(); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initDeviceEngine()) { + m_deviceAPI->startDeviceEngine(); + } + } + else + { + m_deviceAPI->stopDeviceEngine(); + } + + if (m_settings.m_useReverseAPI) { + webapiReverseSendStartStop(cmd.getStartStop()); + } + + return true; + } + else + { + return false; + } +} + +bool BladeRF2MIMO::applySettings(const BladeRF2MIMOSettings& settings, bool force) +{ + QList reverseAPIKeys; + bool forwardChangeRxDSP = false; + bool forwardChangeTxDSP = false; + + qDebug() << "BladeRF2MIMO::applySettings: common: " + << " m_devSampleRate: " << settings.m_devSampleRate + << " m_LOppmTenths: " << settings.m_LOppmTenths + << " m_rxCenterFrequency: " << settings.m_rxCenterFrequency + << " m_log2Decim: " << settings.m_log2Decim + << " m_fcPos: " << settings.m_fcPos + << " m_rxBandwidth: " << settings.m_rxBandwidth + << " m_rx0GainMode: " << settings.m_rx0GainMode + << " m_rx0GlobalGain: " << settings.m_rx0GlobalGain + << " m_rx1GainMode: " << settings.m_rx1GainMode + << " m_rx1GlobalGain: " << settings.m_rx1GlobalGain + << " m_rxBiasTee: " << settings.m_rxBiasTee + << " m_dcBlock: " << settings.m_dcBlock + << " m_iqCorrection: " << settings.m_iqCorrection + << " m_rxTransverterMode: " << settings.m_rxTransverterMode + << " m_rxTransverterDeltaFrequency: " << settings.m_rxTransverterDeltaFrequency + << " m_txCenterFrequency: " << settings.m_txCenterFrequency + << " m_log2Interp: " << settings.m_log2Interp + << " m_txBandwidth: " << settings.m_txBandwidth + << " m_tx0GlobalGain: " << settings.m_tx0GlobalGain + << " m_tx1GlobalGain: " << settings.m_tx1GlobalGain + << " m_txBiasTee: " << settings.m_txBiasTee + << " m_txTransverterMode: " << settings.m_txTransverterMode + << " m_txTransverterDeltaFrequency: " << settings.m_txTransverterDeltaFrequency + << " m_fileRecordName: " << settings.m_fileRecordName + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex; + + struct bladerf *dev = m_dev ? m_dev->getDev() : nullptr; + + qint64 rxXlatedDeviceCenterFrequency = settings.m_rxCenterFrequency; + rxXlatedDeviceCenterFrequency -= settings.m_rxTransverterMode ? settings.m_rxTransverterDeltaFrequency : 0; + rxXlatedDeviceCenterFrequency = rxXlatedDeviceCenterFrequency < 0 ? 0 : rxXlatedDeviceCenterFrequency; + + + qint64 txXlatedDeviceCenterFrequency = settings.m_txCenterFrequency; + txXlatedDeviceCenterFrequency -= settings.m_txTransverterMode ? settings.m_txTransverterDeltaFrequency : 0; + txXlatedDeviceCenterFrequency = txXlatedDeviceCenterFrequency < 0 ? 0 : txXlatedDeviceCenterFrequency; + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + reverseAPIKeys.append("devSampleRate"); + + if (dev) + { + unsigned int actualSamplerate; + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_RX(0), settings.m_devSampleRate, &actualSamplerate); + + if (status < 0) + { + qCritical("BladeRF2MIMO::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2MIMO::applySettings: bladerf_set_sample_rate: actual sample rate is " << actualSamplerate; + } + + } + } + + // Rx settings + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || force) { + reverseAPIKeys.append("dcBlock"); + } + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) { + reverseAPIKeys.append("iqCorrection"); + } + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || + (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 0); + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 1); + } + + if ((m_settings.m_rxBandwidth != settings.m_rxBandwidth) || force) + { + reverseAPIKeys.append("rxBandwidth"); + + if (dev) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), settings.m_rxBandwidth, &actualBandwidth); + + if(status < 0) { + qCritical("BladeRF2MIMO::applySettings: could not set RX0 bandwidth: %d: %s", settings.m_rxBandwidth, bladerf_strerror(status)); + } else { + qDebug() << "BladeRF2MIMO::applySettings: RX0: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + + status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(1), settings.m_rxBandwidth, &actualBandwidth); + + if(status < 0) { + qCritical("BladeRF2MIMO::applySettings: could not set RX1 bandwidth: %d: %s", settings.m_rxBandwidth, bladerf_strerror(status)); + } else { + qDebug() << "BladeRF2MIMO::applySettings: RX1: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_fcPos != settings.m_fcPos) || force) + { + reverseAPIKeys.append("fcPos"); + + if (m_sourceThread) + { + m_sourceThread->setFcPos((int) settings.m_fcPos); + qDebug() << "BladeRF2MIMO::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + reverseAPIKeys.append("log2Decim"); + + if (m_sourceThread) + { + m_sourceThread->setLog2Decimation(settings.m_log2Decim); + qDebug() << "BladeRF2MIMO::applySettings: set decimation to " << (1<setLog2Interpolation(settings.m_log2Interp); + qDebug() << "BladeRF2Input::applySettings: set interpolation to " << (1<push(msg); + } + + } + } + + forwardChangeRxDSP = true; + } + + if ((m_settings.m_rxBiasTee != settings.m_rxBiasTee) || force) + { + reverseAPIKeys.append("rxBiasTee"); + + if (m_dev) { + m_dev->setBiasTeeRx(settings.m_rxBiasTee); + } + } + + if ((m_settings.m_rx0GainMode != settings.m_rx0GainMode) || force) + { + reverseAPIKeys.append("rx0GainMode"); + + if (dev) + { + int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(0), (bladerf_gain_mode) settings.m_rx0GainMode); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: RX0: bladerf_set_gain_mode(%d) failed: %s", + settings.m_rx0GainMode, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: RX0: bladerf_set_gain_mode(%d)", settings.m_rx0GainMode); + } + } + } + + if ((m_settings.m_rx1GainMode != settings.m_rx1GainMode) || force) + { + reverseAPIKeys.append("rx1GainMode"); + + if (dev) + { + int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(1), (bladerf_gain_mode) settings.m_rx1GainMode); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: RX1: bladerf_set_gain_mode(%d) failed: %s", + settings.m_rx1GainMode, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: RX1: bladerf_set_gain_mode(%d)", settings.m_rx1GainMode); + } + } + } + + if ((m_settings.m_rx0GlobalGain != settings.m_rx0GlobalGain) || force) { + reverseAPIKeys.append("rx0GlobalGain"); + } + if ((m_settings.m_rx1GlobalGain != settings.m_rx1GlobalGain) || force) { + reverseAPIKeys.append("rx1GlobalGain"); + } + + if ((m_settings.m_rx0GlobalGain != settings.m_rx0GlobalGain) + || ((m_settings.m_rx0GlobalGain != settings.m_rx0GlobalGain) && (settings.m_rx0GlobalGain == BLADERF_GAIN_MANUAL)) || force) + { + if (dev) + { + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(0), settings.m_rx0GlobalGain); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: RX0: bladerf_set_gain(%d) failed: %s", + settings.m_rx0GlobalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: RX0: bladerf_set_gain(%d)", settings.m_rx0GlobalGain); + } + } + } + + if ((m_settings.m_rx1GlobalGain != settings.m_rx1GlobalGain) + || ((m_settings.m_rx1GlobalGain != settings.m_rx1GlobalGain) && (settings.m_rx1GlobalGain == BLADERF_GAIN_MANUAL)) || force) + { + if (dev) + { + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(1), settings.m_rx1GlobalGain); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: RX1: bladerf_set_gain(%d) failed: %s", + settings.m_rx1GlobalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: RX1: bladerf_set_gain(%d)", settings.m_rx1GlobalGain); + } + } + } + + // Tx settings + + if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) || force) { + reverseAPIKeys.append("txCenterFrequency"); + } + if ((m_settings.m_txTransverterMode != settings.m_txTransverterMode) || force) { + reverseAPIKeys.append("txTransverterMode"); + } + if ((m_settings.m_txTransverterDeltaFrequency != settings.m_txTransverterDeltaFrequency) || force) { + reverseAPIKeys.append("txTransverterDeltaFrequency"); + } + + if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) + || (m_settings.m_txTransverterMode != settings.m_txTransverterMode) + || (m_settings.m_txTransverterDeltaFrequency != settings.m_txTransverterDeltaFrequency) + || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) + || (m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + if (dev) + { + qint64 deviceCenterFrequency = DeviceSampleSink::calculateDeviceCenterFrequency( + settings.m_txCenterFrequency, + settings.m_txTransverterDeltaFrequency, + settings.m_log2Interp, + (DeviceSampleSink::fcPos_t) DeviceSampleSink::FC_POS_CENTER, + settings.m_devSampleRate, + settings.m_txTransverterMode); + + if (setTxDeviceCenterFrequency(dev, deviceCenterFrequency, settings.m_LOppmTenths)) + { + if (getMessageQueueToGUI()) + { + int min, max, step; + getTxGlobalGainRange(min, max, step); + MsgReportGainRange *msg = MsgReportGainRange::create(min, max, step, false); + getMessageQueueToGUI()->push(msg); + } + } + } + + forwardChangeTxDSP = true; + } + + if ((m_settings.m_txBandwidth != settings.m_txBandwidth) || force) + { + reverseAPIKeys.append("txBandwidth"); + + if (dev) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), settings.m_txBandwidth, &actualBandwidth); + + if (status < 0) + { + qCritical("BladeRF2MIMO::applySettings: TX0: could not set bandwidth: %d: %s", + settings.m_txBandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2MIMO::applySettings: TX0: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + + status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), settings.m_txBandwidth, &actualBandwidth); + + if (status < 0) + { + qCritical("BladeRF2MIMO::applySettings: TX1: could not set bandwidth: %d: %s", + settings.m_txBandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2MIMO::applySettings: TX1: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + reverseAPIKeys.append("log2Interp"); + + if (m_sinkThread) + { + m_sinkThread->setLog2Interpolation(settings.m_log2Interp); + qDebug() << "BladeRF2MIMO::applySettings: set interpolation to " << (1<setBiasTeeTx(settings.m_txBiasTee); + } + } + + if ((m_settings.m_tx0GlobalGain != settings.m_tx0GlobalGain) || force) + { + reverseAPIKeys.append("tx0GlobalGain"); + + if (dev) + { + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(0), settings.m_tx0GlobalGain); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: TX0: bladerf_set_gain(%d) failed: %s", + settings.m_tx0GlobalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: TX0: bladerf_set_gain(%d)", settings.m_tx0GlobalGain); + } + } + } + + if ((m_settings.m_tx1GlobalGain != settings.m_tx1GlobalGain) || force) + { + reverseAPIKeys.append("tx1GlobalGain"); + + if (dev) + { + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(1), settings.m_tx1GlobalGain); + + if (status < 0) { + qWarning("BladeRF2MIMO::applySettings: TX1: bladerf_set_gain(%d) failed: %s", + settings.m_tx1GlobalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2MIMO::applySettings: TX1: bladerf_set_gain(%d)", settings.m_tx1GlobalGain); + } + } + } + + if (forwardChangeRxDSP) + { + int sampleRate = settings.m_devSampleRate/(1<handleMessage(*notifFileSink0); // forward to file sinks + DSPSignalNotification *notifFileSink1 = new DSPSignalNotification(sampleRate, settings.m_rxCenterFrequency); + m_fileSinks[1]->handleMessage(*notifFileSink0); // forward to file sinks + DSPMIMOSignalNotification *notif0 = new DSPMIMOSignalNotification(sampleRate, settings.m_rxCenterFrequency, true, 0); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif0); + DSPMIMOSignalNotification *notif1 = new DSPMIMOSignalNotification(sampleRate, settings.m_rxCenterFrequency, true, 1); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif1); + } + + if (forwardChangeTxDSP) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif0); + DSPMIMOSignalNotification *notif1 = new DSPMIMOSignalNotification(sampleRate, settings.m_rxCenterFrequency, false, 1); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif1); + } + + // Reverse API settings + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; + return true; +} + +bool BladeRF2MIMO::setRxDeviceCenterFrequency(struct bladerf *dev, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(0), freq_hz); + + if (status < 0) + { + qWarning("BladeRF2MIMO::setDeviceCenterFrequency: RX0: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2MIMO::setDeviceCenterFrequency: RX0: bladerf_set_frequency(%lld)", freq_hz); + } + + status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(1), freq_hz); + + if (status < 0) + { + qWarning("BladeRF2MIMO::setDeviceCenterFrequency: RX1: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2MIMO::setDeviceCenterFrequency: RX1: bladerf_set_frequency(%lld)", freq_hz); + } + + return true; +} + +bool BladeRF2MIMO::setTxDeviceCenterFrequency(struct bladerf *dev, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(0), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Output::setDeviceCenterFrequency: TX0: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Output::setDeviceCenterFrequency: TX0: bladerf_set_frequency(%lld)", freq_hz); + return true; + } + + status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(1), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Output::setDeviceCenterFrequency: TX1: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Output::setDeviceCenterFrequency: TX1: bladerf_set_frequency(%lld)", freq_hz); + return true; + } +} + +void BladeRF2MIMO::getRxFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_dev) { + m_dev->getFrequencyRangeRx(min, max, step); + } +} + +void BladeRF2MIMO::getRxSampleRateRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getSampleRateRangeRx(min, max, step); + } +} + +void BladeRF2MIMO::getRxBandwidthRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getBandwidthRangeRx(min, max, step); + } +} + +void BladeRF2MIMO::getRxGlobalGainRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getGlobalGainRangeRx(min, max, step); + } +} + +void BladeRF2MIMO::getTxFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_dev) { + m_dev->getFrequencyRangeTx(min, max, step); + } +} + +void BladeRF2MIMO::getTxSampleRateRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getSampleRateRangeTx(min, max, step); + } +} + +void BladeRF2MIMO::getTxBandwidthRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getBandwidthRangeTx(min, max, step); + } +} + +void BladeRF2MIMO::getTxGlobalGainRange(int& min, int& max, int& step) +{ + if (m_dev) { + m_dev->getGlobalGainRangeTx(min, max, step); + } +} + +int BladeRF2MIMO::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setBladeRf2MimoSettings(new SWGSDRangel::SWGBladeRF2MIMOSettings()); + response.getBladeRf2MimoSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2MIMO::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + BladeRF2MIMOSettings settings = m_settings; + webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response); + + MsgConfigureBladeRF2MIMO *msg = MsgConfigureBladeRF2MIMO::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladeRF2MIMO *msgToGUI = MsgConfigureBladeRF2MIMO::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void BladeRF2MIMO::webapiUpdateDeviceSettings( + BladeRF2MIMOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response) +{ + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf2MimoSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getBladeRf2MimoSettings()->getLOppmTenths(); + } + + if (deviceSettingsKeys.contains("rxCenterFrequency")) { + settings.m_rxCenterFrequency = response.getBladeRf2MimoSettings()->getRxCenterFrequency(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getBladeRf2MimoSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + settings.m_fcPos = static_cast(response.getBladeRf2MimoSettings()->getFcPos()); + } + if (deviceSettingsKeys.contains("rxBandwidth")) { + settings.m_rxBandwidth = response.getBladeRf2MimoSettings()->getRxBandwidth(); + } + if (deviceSettingsKeys.contains("rx0GainMode")) { + settings.m_rx0GainMode = response.getBladeRf2MimoSettings()->getRx0GainMode(); + } + if (deviceSettingsKeys.contains("rx0GlobalGain")) { + settings.m_rx0GlobalGain = response.getBladeRf2MimoSettings()->getRx0GlobalGain(); + } + if (deviceSettingsKeys.contains("rx1GainMode")) { + settings.m_rx1GainMode = response.getBladeRf2MimoSettings()->getRx1GainMode(); + } + if (deviceSettingsKeys.contains("rx1GlobalGain")) { + settings.m_rx1GlobalGain = response.getBladeRf2MimoSettings()->getRx1GlobalGain(); + } + if (deviceSettingsKeys.contains("rxBiasTee")) { + settings.m_rxBiasTee = response.getBladeRf2MimoSettings()->getRxBiasTee() != 0; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getBladeRf2MimoSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getBladeRf2MimoSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency")) { + settings.m_rxTransverterDeltaFrequency = response.getBladeRf2MimoSettings()->getRxTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("rxTransverterMode")) { + settings.m_rxTransverterMode = response.getBladeRf2MimoSettings()->getRxTransverterMode() != 0; + } + + if (deviceSettingsKeys.contains("txCenterFrequency")) { + settings.m_txCenterFrequency = response.getBladeRf2MimoSettings()->getTxCenterFrequency(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getBladeRf2MimoSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("txBandwidth")) { + settings.m_txBandwidth = response.getBladeRf2MimoSettings()->getTxBandwidth(); + } + if (deviceSettingsKeys.contains("tx0GlobalGain")) { + settings.m_tx0GlobalGain = response.getBladeRf2MimoSettings()->getTx0GlobalGain(); + } + if (deviceSettingsKeys.contains("tx1GlobalGain")) { + settings.m_tx1GlobalGain = response.getBladeRf2MimoSettings()->getTx1GlobalGain(); + } + if (deviceSettingsKeys.contains("txBiasTee")) { + settings.m_txBiasTee = response.getBladeRf2MimoSettings()->getTxBiasTee() != 0; + } + if (deviceSettingsKeys.contains("txTransverterMode")) { + settings.m_txTransverterMode = response.getBladeRf2MimoSettings()->getTxTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("txTransverterDeltaFrequency")) { + settings.m_txTransverterDeltaFrequency = response.getBladeRf2MimoSettings()->getTxTransverterDeltaFrequency(); + } + + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getBladeRf2MimoSettings()->getFileRecordName(); + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getBladeRf2OutputSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getBladeRf2OutputSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getBladeRf2OutputSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getBladeRf2OutputSettings()->getReverseApiDeviceIndex(); + } +} + +void BladeRF2MIMO::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2MIMOSettings& settings) +{ + response.getBladeRf2MimoSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf2MimoSettings()->setLOppmTenths(settings.m_LOppmTenths); + + response.getBladeRf2MimoSettings()->setRxCenterFrequency(settings.m_rxCenterFrequency); + response.getBladeRf2MimoSettings()->setLog2Decim(settings.m_log2Decim); + response.getBladeRf2MimoSettings()->setFcPos((int) settings.m_fcPos); + response.getBladeRf2MimoSettings()->setRxBandwidth(settings.m_rxBandwidth); + response.getBladeRf2MimoSettings()->setRx0GainMode(settings.m_rx0GainMode); + response.getBladeRf2MimoSettings()->setRx0GlobalGain(settings.m_rx0GlobalGain); + response.getBladeRf2MimoSettings()->setRx1GainMode(settings.m_rx1GainMode); + response.getBladeRf2MimoSettings()->setRx1GlobalGain(settings.m_rx1GlobalGain); + response.getBladeRf2MimoSettings()->setRxBiasTee(settings.m_rxBiasTee ? 1 : 0); + response.getBladeRf2MimoSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getBladeRf2MimoSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getBladeRf2MimoSettings()->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency); + response.getBladeRf2MimoSettings()->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0); + + response.getBladeRf2MimoSettings()->setTxCenterFrequency(settings.m_txCenterFrequency); + response.getBladeRf2MimoSettings()->setLog2Interp(settings.m_log2Interp); + response.getBladeRf2MimoSettings()->setTxBandwidth(settings.m_txBandwidth); + response.getBladeRf2MimoSettings()->setTx0GlobalGain(settings.m_tx0GlobalGain); + response.getBladeRf2MimoSettings()->setTx1GlobalGain(settings.m_tx1GlobalGain); + response.getBladeRf2MimoSettings()->setTxBiasTee(settings.m_txBiasTee ? 1 : 0); + response.getBladeRf2MimoSettings()->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency); + response.getBladeRf2MimoSettings()->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0); + + if (response.getBladeRf2MimoSettings()->getFileRecordName()) { + *response.getBladeRf2MimoSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getBladeRf2MimoSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } + + response.getBladeRf2OutputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getBladeRf2OutputSettings()->getReverseApiAddress()) { + *response.getBladeRf2OutputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getBladeRf2OutputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getBladeRf2OutputSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getBladeRf2OutputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +int BladeRF2MIMO::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int BladeRF2MIMO::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run, true); // TODO: Tx support + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run, true); // TODO: Tx support + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} + +void BladeRF2MIMO::webapiReverseSendSettings(QList& deviceSettingsKeys, const BladeRF2MIMOSettings& settings, bool force) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(2); // MIMO + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("BladeRF2")); + swgDeviceSettings->setBladeRf2MimoSettings(new SWGSDRangel::SWGBladeRF2MIMOSettings()); + SWGSDRangel::SWGBladeRF2MIMOSettings *swgBladeRF2MIMOSettings = swgDeviceSettings->getBladeRf2MimoSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (deviceSettingsKeys.contains("devSampleRate") || force) { + swgBladeRF2MIMOSettings->setDevSampleRate(settings.m_devSampleRate); + } + if (deviceSettingsKeys.contains("LOppmTenths") || force) { + swgBladeRF2MIMOSettings->setLOppmTenths(settings.m_LOppmTenths); + } + + if (deviceSettingsKeys.contains("rxCenterFrequency") || force) { + swgBladeRF2MIMOSettings->setRxCenterFrequency(settings.m_rxCenterFrequency); + } + if (deviceSettingsKeys.contains("log2Decim") || force) { + swgBladeRF2MIMOSettings->setLog2Decim(settings.m_log2Decim); + } + if (deviceSettingsKeys.contains("fcPos") || force) { + swgBladeRF2MIMOSettings->setFcPos((int) settings.m_fcPos); + } + if (deviceSettingsKeys.contains("rxBandwidth") || force) { + swgBladeRF2MIMOSettings->setRxBandwidth(settings.m_rxBandwidth); + } + if (deviceSettingsKeys.contains("rx0GainMode")) { + swgBladeRF2MIMOSettings->setRx0GainMode(settings.m_rx0GainMode); + } + if (deviceSettingsKeys.contains("rx0GlobalGain")) { + swgBladeRF2MIMOSettings->setRx0GlobalGain(settings.m_rx0GlobalGain); + } + if (deviceSettingsKeys.contains("rx1GainMode")) { + swgBladeRF2MIMOSettings->setRx1GainMode(settings.m_rx1GainMode); + } + if (deviceSettingsKeys.contains("rx1GlobalGain")) { + swgBladeRF2MIMOSettings->setRx1GlobalGain(settings.m_rx1GlobalGain); + } + if (deviceSettingsKeys.contains("rxBiasTee") || force) { + swgBladeRF2MIMOSettings->setRxBiasTee(settings.m_rxBiasTee ? 1 : 0); + } + if (deviceSettingsKeys.contains("dcBlock") || force) { + swgBladeRF2MIMOSettings->setDcBlock(settings.m_dcBlock ? 1 : 0); + } + if (deviceSettingsKeys.contains("iqCorrection") || force) { + swgBladeRF2MIMOSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + } + if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency") || force) { + swgBladeRF2MIMOSettings->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency); + } + if (deviceSettingsKeys.contains("rxTransverterMode") || force) { + swgBladeRF2MIMOSettings->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0); + } + + if (deviceSettingsKeys.contains("txCenterFrequency") || force) { + swgBladeRF2MIMOSettings->setTxCenterFrequency(settings.m_txCenterFrequency); + } + if (deviceSettingsKeys.contains("log2Interp") || force) { + swgBladeRF2MIMOSettings->setLog2Interp(settings.m_log2Interp); + } + if (deviceSettingsKeys.contains("txBandwidth") || force) { + swgBladeRF2MIMOSettings->setTxBandwidth(settings.m_txBandwidth); + } + if (deviceSettingsKeys.contains("tx0GlobalGain") || force) { + swgBladeRF2MIMOSettings->setTx0GlobalGain(settings.m_tx0GlobalGain); + } + if (deviceSettingsKeys.contains("tx1GlobalGain") || force) { + swgBladeRF2MIMOSettings->setTx1GlobalGain(settings.m_tx1GlobalGain); + } + if (deviceSettingsKeys.contains("txBiasTee") || force) { + swgBladeRF2MIMOSettings->setTxBiasTee(settings.m_txBiasTee ? 1 : 0); + } + if (deviceSettingsKeys.contains("txTransverterDeltaFrequency") || force) { + swgBladeRF2MIMOSettings->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency); + } + if (deviceSettingsKeys.contains("txTransverterMode") || force) { + swgBladeRF2MIMOSettings->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0); + } + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer=new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgDeviceSettings; +} + +void BladeRF2MIMO::webapiReverseSendStartStop(bool start) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(2); // MIMO + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("BladeRF2")); + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer=new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + if (start) { + m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer); + } else { + m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer); + } + + delete swgDeviceSettings; +} + +void BladeRF2MIMO::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "BladeRF2MIMO::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + return; + } + + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("BladeRF2MIMO::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); +} diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimo.h b/plugins/samplemimo/bladerf2mimo/bladerf2mimo.h new file mode 100644 index 000000000..212831012 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimo.h @@ -0,0 +1,250 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMO_H_ +#define PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMO_H_ + +#include + +#include +#include +#include + +#include "dsp/devicesamplemimo.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2mimosettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class DeviceAPI; +class BladeRF2MIThread; +class BladeRF2MOThread; +class FileRecord; +class DeviceBladeRF2; +struct bladerf_gain_modes; +struct bladerf; + +class BladeRF2MIMO : public DeviceSampleMIMO { + Q_OBJECT + +public: + class MsgConfigureBladeRF2MIMO : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2MIMOSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2MIMO* create(const BladeRF2MIMOSettings& settings, bool force) + { + return new MsgConfigureBladeRF2MIMO(settings, force); + } + + private: + BladeRF2MIMOSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2MIMO(const BladeRF2MIMOSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + int getStreamIndex() const { return m_streamIndex; } + + static MsgFileRecord* create(bool startStop, int streamIndex) { + return new MsgFileRecord(startStop, streamIndex); + } + + protected: + bool m_startStop; + int m_streamIndex; + + MsgFileRecord(bool startStop, int streamIndex) : + Message(), + m_startStop(startStop), + m_streamIndex(streamIndex) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgStartStop* create(bool startStop, bool rxElseTx) { + return new MsgStartStop(startStop, rxElseTx); + } + + protected: + bool m_startStop; + bool m_rxElseTx; + + MsgStartStop(bool startStop, bool rxElseTx) : + Message(), + m_startStop(startStop), + m_rxElseTx(rxElseTx) + { } + }; + + struct GainMode + { + QString m_name; + int m_value; + }; + + class MsgReportGainRange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getStep() const { return m_step; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgReportGainRange* create(int min, int max, int step, bool rxElseTx) { + return new MsgReportGainRange(min, max, step, rxElseTx); + } + + protected: + int m_min; + int m_max; + int m_step; + bool m_rxElseTx; + + MsgReportGainRange(int min, int max, int step, bool rxElseTx) : + Message(), + m_min(min), + m_max(max), + m_step(step), + m_rxElseTx(rxElseTx) + {} + }; + + BladeRF2MIMO(DeviceAPI *deviceAPI); + virtual ~BladeRF2MIMO(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + + virtual int 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; + virtual void setSinkSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; } + virtual quint64 getSinkCenterFrequency(int index) const; + virtual void setSinkCenterFrequency(qint64 centerFrequency, int index); + + virtual quint64 getMIMOCenterFrequency() const { return getSourceCenterFrequency(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( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + static void webapiFormatDeviceSettings( + SWGSDRangel::SWGDeviceSettings& response, + const BladeRF2MIMOSettings& settings); + + static void webapiUpdateDeviceSettings( + BladeRF2MIMOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response); + + bool isRecording(unsigned int istream) const; + + void getRxFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getRxSampleRateRange(int& min, int& max, int& step); + void getRxBandwidthRange(int& min, int& max, int& step); + void getRxGlobalGainRange(int& min, int& max, int& step); + const std::vector& getRxGainModes() { return m_rxGainModes; } + + void getTxFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getTxSampleRateRange(int& min, int& max, int& step); + void getTxBandwidthRange(int& min, int& max, int& step); + void getTxGlobalGainRange(int& min, int& max, int& step); + +private: + DeviceAPI *m_deviceAPI; + std::vector m_fileSinks; //!< File sinks to record device I/Q output + QMutex m_mutex; + BladeRF2MIMOSettings m_settings; + BladeRF2MIThread* m_sourceThread; + BladeRF2MOThread* m_sinkThread; + QString m_deviceDescription; + bool m_rxElseTx; + bool m_runningRx; + bool m_runningTx; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + DeviceBladeRF2 *m_dev; + bool m_open; + std::vector m_rxGainModes; + + bool openDevice(); + void closeDevice(); + void startRx(); + void stopRx(); + void startTx(); + void stopTx(); + + bool applySettings(const BladeRF2MIMOSettings& settings, bool force); + bool setRxDeviceCenterFrequency(struct bladerf *dev, quint64 freq_hz, int loPpmTenths); + bool setTxDeviceCenterFrequency(struct bladerf *dev, quint64 freq_hz, int loPpmTenths); + void webapiReverseSendSettings(QList& deviceSettingsKeys, const BladeRF2MIMOSettings& settings, bool force); + void webapiReverseSendStartStop(bool start); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMO_H_ diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.cpp new file mode 100644 index 000000000..103ff5f48 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.cpp @@ -0,0 +1,706 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include + +#include +#include +#include +#include + +#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 "dsp/devicesamplestatic.h" +#include "util/db.h" + +#include "mainwindow.h" + +#include "bladerf2mimo.h" +#include "ui_bladerf2mimogui.h" +#include "bladerf2mimogui.h" + +BladeRF2MIMOGui::BladeRF2MIMOGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::BladeRF2MIMOGui), + m_deviceUISet(deviceUISet), + m_settings(), + m_rxElseTx(true), + m_streamIndex(0), + m_spectrumRxElseTx(true), + m_spectrumStreamIndex(0), + m_doApplySettings(true), + m_forceSettings(true), + m_sampleMIMO(nullptr), + m_tickCount(0), + m_deviceSampleRate(3072000), + m_rxDeviceCenterFrequency(435000*1000), + m_txDeviceCenterFrequency(435000*1000), + m_lastEngineState(DeviceAPI::StNotStarted), + m_sampleRateMode(true) +{ + qDebug("BladeRF2MIMOGui::BladeRF2MIMOGui"); + ui->setupUi(this); + m_sampleMIMO = (BladeRF2MIMO*) m_deviceUISet->m_deviceAPI->getSampleMIMO(); + + m_sampleMIMO->getRxFrequencyRange(m_fMinRx, m_fMaxRx, m_fStepRx); + m_sampleMIMO->getTxFrequencyRange(m_fMinTx, m_fMaxTx, m_fStepTx); + m_sampleMIMO->getRxBandwidthRange(m_bwMinRx, m_bwMaxRx, m_bwStepRx); + m_sampleMIMO->getTxBandwidthRange(m_bwMinTx, m_bwMaxTx, m_bwStepTx); + + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + + int minRx, maxRx, stepRx, minTx, maxTx, stepTx; + m_sampleMIMO->getRxSampleRateRange(minRx, maxRx, stepRx); + m_sampleMIMO->getTxSampleRateRange(minTx, maxTx, stepTx); + m_srMin = std::max(minRx, minTx); + m_srMax = std::min(maxRx, maxTx); + + displayGainModes(); + 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 &))); +} + +BladeRF2MIMOGui::~BladeRF2MIMOGui() +{ + delete ui; +} + +void BladeRF2MIMOGui::destroy() +{ + delete this; +} + +void BladeRF2MIMOGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString BladeRF2MIMOGui::getName() const +{ + return objectName(); +} + +void BladeRF2MIMOGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 BladeRF2MIMOGui::getCenterFrequency() const +{ + return m_settings.m_rxCenterFrequency; +} + +void BladeRF2MIMOGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_rxCenterFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray BladeRF2MIMOGui::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2MIMOGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void BladeRF2MIMOGui::displaySettings() +{ + if (m_rxElseTx) + { + ui->transverter->setDeltaFrequency(m_settings.m_rxTransverterDeltaFrequency); + ui->transverter->setDeltaFrequencyActive(m_settings.m_rxTransverterMode); + ui->centerFrequency->setValueRange(7, m_fMinRx / 1000, m_fMaxRx / 1000); + ui->centerFrequency->setValue(m_settings.m_rxCenterFrequency / 1000); + ui->bandwidth->setValueRange(5, m_bwMinRx / 1000, m_bwMaxRx / 1000); + ui->bandwidth->setValue(m_settings.m_rxBandwidth / 1000); + ui->record->setEnabled(true); + uint32_t basebandSampleRate = m_settings.m_devSampleRate/(1<deviceRateText->setText(tr("%1k").arg(QString::number(basebandSampleRate / 1000.0f, 'g', 5))); + ui->dcOffset->setEnabled(true); + ui->dcOffset->setChecked(m_settings.m_dcBlock); + ui->iqImbalance->setEnabled(true); + ui->iqImbalance->setChecked(m_settings.m_iqCorrection); + ui->biasTee->setChecked(m_settings.m_rxBiasTee); + ui->decim->setCurrentIndex(m_settings.m_log2Decim); + ui->label_decim->setText(QString("Dec")); + ui->decim->setToolTip(QString("Decimation factor")); + ui->gainMode->setEnabled(true); + + if (m_streamIndex == 0) + { + ui->gainMode->setCurrentIndex(m_settings.m_rx0GainMode); + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_rx0GlobalGain)); + ui->gain->setValue(m_settings.m_rx0GlobalGain); + } + else if (m_streamIndex == 1) + { + ui->gainMode->setCurrentIndex(m_settings.m_rx1GainMode); + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_rx1GlobalGain)); + ui->gain->setValue(m_settings.m_rx1GlobalGain); + } + } + else + { + ui->transverter->setDeltaFrequency(m_settings.m_txTransverterDeltaFrequency); + ui->transverter->setDeltaFrequencyActive(m_settings.m_txTransverterMode); + ui->centerFrequency->setValueRange(7, m_fMinTx / 1000, m_fMaxTx / 1000); + ui->centerFrequency->setValue(m_settings.m_txCenterFrequency / 1000); + ui->bandwidth->setValueRange(5, m_bwMinTx / 1000, m_bwMaxTx / 1000); + ui->bandwidth->setValue(m_settings.m_txBandwidth / 1000); + ui->record->setEnabled(false); + uint32_t basebandSampleRate = m_settings.m_devSampleRate/(1<deviceRateText->setText(tr("%1k").arg(QString::number(basebandSampleRate / 1000.0f, 'g', 5))); + ui->dcOffset->setEnabled(false); + ui->iqImbalance->setEnabled(false); + ui->biasTee->setChecked(m_settings.m_txBiasTee); + ui->decim->setCurrentIndex(m_settings.m_log2Interp); + ui->label_decim->setText(QString("Int")); + ui->decim->setToolTip(QString("Interpolation factor")); + ui->gainMode->setEnabled(false); + + if (m_streamIndex == 0) + { + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_tx0GlobalGain)); + ui->gain->setValue(m_settings.m_tx0GlobalGain); + } + else if (m_streamIndex == 1) + { + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_tx1GlobalGain)); + ui->gain->setValue(m_settings.m_tx1GlobalGain); + } + } + + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + displaySampleRate(); +} + +void BladeRF2MIMOGui::displaySampleRate() +{ + ui->sampleRate->blockSignals(true); + displayFcTooltip(); + quint32 log2Factor = m_rxElseTx ? m_settings.m_log2Decim : m_settings.m_log2Interp; + + if (m_sampleRateMode) + { + ui->sampleRateMode->setStyleSheet("QToolButton { background:rgb(60,60,60); }"); + ui->sampleRateMode->setText("SR"); + // BladeRF can go as low as 80 kS/s but because of buffering in practice experience is not good below 330 kS/s + ui->sampleRate->setValueRange(8, m_srMin, m_srMax); + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->sampleRate->setToolTip("Device to host sample rate (S/s)"); + ui->deviceRateText->setToolTip("Baseband sample rate (S/s)"); + uint32_t basebandSampleRate = m_settings.m_devSampleRate/(1<deviceRateText->setText(tr("%1k").arg(QString::number(basebandSampleRate / 1000.0f, 'g', 5))); + } + else + { + ui->sampleRateMode->setStyleSheet("QToolButton { background:rgb(50,50,50); }"); + ui->sampleRateMode->setText("BB"); + // BladeRF can go as low as 80 kS/s but because of buffering in practice experience is not good below 330 kS/s + ui->sampleRate->setValueRange(8, m_srMin/(1<sampleRate->setValue(m_settings.m_devSampleRate/(1<sampleRate->setToolTip("Baseband sample rate (S/s)"); + ui->deviceRateText->setToolTip("Device to host sample rate (S/s)"); + ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_settings.m_devSampleRate / 1000.0f, 'g', 5))); + } + + ui->sampleRate->blockSignals(false); +} + +void BladeRF2MIMOGui::displayFcTooltip() +{ + int32_t fShift; + + if (m_rxElseTx) + { + fShift = DeviceSampleStatic::calculateSourceFrequencyShift( + m_settings.m_log2Decim, + (DeviceSampleStatic::fcPos_t) m_settings.m_fcPos, + m_settings.m_devSampleRate, + DeviceSampleStatic::FrequencyShiftScheme::FSHIFT_STD + ); + } + else + { + fShift = DeviceSampleStatic::calculateSinkFrequencyShift( + m_settings.m_log2Decim, + (DeviceSampleStatic::fcPos_t) m_settings.m_fcPos, + m_settings.m_devSampleRate + ); + } + + ui->fcPos->setToolTip(tr("Relative position of device center frequency: %1 kHz").arg(QString::number(fShift / 1000.0f, 'g', 5))); +} + +void BladeRF2MIMOGui::displayGainModes() +{ + ui->gainMode->blockSignals(true); + + if (m_rxElseTx) + { + const std::vector& modes = m_sampleMIMO->getRxGainModes(); + std::vector::const_iterator it = modes.begin(); + + for (; it != modes.end(); ++it) { + ui->gainMode->addItem(it->m_name); + } + } + else + { + ui->gainMode->clear(); + } + + ui->gainMode->blockSignals(false); +} + +bool BladeRF2MIMOGui::handleMessage(const Message& message) +{ + if (DSPMIMOSignalNotification::match(message)) + { + const DSPMIMOSignalNotification& notif = (const DSPMIMOSignalNotification&) message; + int istream = notif.getIndex(); + bool sourceOrSink = notif.getSourceOrSink(); + m_deviceSampleRate = notif.getSampleRate(); + + if (sourceOrSink) { + m_rxDeviceCenterFrequency = notif.getCenterFrequency(); + } else { + m_txDeviceCenterFrequency = notif.getCenterFrequency(); + } + + qDebug("BladeRF2MIMOGui::handleInputMessages: DSPMIMOSignalNotification: %s stream: %d SampleRate:%d, CenterFrequency:%llu", + sourceOrSink ? "source" : "sink", + istream, + notif.getSampleRate(), + notif.getCenterFrequency()); + + updateSampleRateAndFrequency(); + + return true; + } + + return false; +} + +void BladeRF2MIMOGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) { + delete message; + } else { + qDebug("BladeRF2MIMOGui::handleInputMessages: unhandled message: %s", message->getIdentifier()); + } + } +} + +void BladeRF2MIMOGui::sendSettings() +{ + if(!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void BladeRF2MIMOGui::updateHardware() +{ + if (m_doApplySettings) + { + BladeRF2MIMO::MsgConfigureBladeRF2MIMO* message = BladeRF2MIMO::MsgConfigureBladeRF2MIMO::create(m_settings, m_forceSettings); + m_sampleMIMO->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void BladeRF2MIMOGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_deviceSampleRate); + + if (m_rxElseTx) { + m_deviceUISet->getSpectrum()->setCenterFrequency(m_rxDeviceCenterFrequency); + } else { + m_deviceUISet->getSpectrum()->setCenterFrequency(m_txDeviceCenterFrequency); + } +} + +void BladeRF2MIMOGui::updateFileRecordStatus() +{ + if (m_sampleMIMO->isRecording(m_streamIndex)) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } +} + +void BladeRF2MIMOGui::on_streamSide_currentIndexChanged(int index) +{ + m_rxElseTx = index == 0; + displayGainModes(); + displaySettings(); +} + +void BladeRF2MIMOGui::on_streamIndex_currentIndexChanged(int index) +{ + m_streamIndex = index < 0 ? 0 : index > 1 ? 1 : index; + displaySettings(); +} + +void BladeRF2MIMOGui::on_spectrumSide_currentIndexChanged(int index) +{ + m_spectrumRxElseTx = index == 0; + // TODO +} + +void BladeRF2MIMOGui::on_spectrumIndex_currentIndexChanged(int index) +{ + m_spectrumStreamIndex = index < 0 ? 0 : index > 1 ? 1 : index; + m_deviceUISet->m_spectrum->setDisplayedStream(true, m_spectrumStreamIndex); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, m_spectrumStreamIndex); + updateSampleRateAndFrequency(); +} + +void BladeRF2MIMOGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + BladeRF2MIMO::MsgStartStop *message = BladeRF2MIMO::MsgStartStop::create(checked, m_rxElseTx); + m_sampleMIMO->getInputMessageQueue()->push(message); + } +} + +void BladeRF2MIMOGui::on_record_toggled(bool checked) +{ + if (checked) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + BladeRF2MIMO::MsgFileRecord* message = BladeRF2MIMO::MsgFileRecord::create(checked, m_streamIndex); + m_sampleMIMO->getInputMessageQueue()->push(message); +} + +void BladeRF2MIMOGui::on_centerFrequency_changed(quint64 value) +{ + if (m_rxElseTx) { + m_settings.m_rxCenterFrequency = value * 1000; + } else { + m_settings.m_txCenterFrequency = value * 1000; + } + + sendSettings(); +} + +void BladeRF2MIMOGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void BladeRF2MIMOGui::on_dcOffset_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + sendSettings(); +} + +void BladeRF2MIMOGui::on_iqImbalance_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + sendSettings(); +} + +void BladeRF2MIMOGui::on_bandwidth_changed(quint64 value) +{ + if (m_rxElseTx) { + m_settings.m_rxBandwidth = value * 1000; + } else { + m_settings.m_txBandwidth = value * 1000; + } + + sendSettings(); +} + +void BladeRF2MIMOGui::on_sampleRate_changed(quint64 value) +{ + if (m_sampleRateMode) + { + m_settings.m_devSampleRate = value; + } + else + { + if (m_rxElseTx) { + m_settings.m_devSampleRate = value * (1 << m_settings.m_log2Decim); + } else { + m_settings.m_devSampleRate = value * (1 << m_settings.m_log2Interp); + } + } + + displayFcTooltip(); + sendSettings(); +} + +void BladeRF2MIMOGui::on_fcPos_currentIndexChanged(int index) +{ + m_settings.m_fcPos = (BladeRF2MIMOSettings::fcPos_t) (index < 0 ? 0 : index > 2 ? 2 : index); + displayFcTooltip(); + sendSettings(); +} + +void BladeRF2MIMOGui::on_decim_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) { + return; + } + + if (m_rxElseTx) { + m_settings.m_log2Decim = index; + } else { + m_settings.m_log2Interp = index; + } + + displaySampleRate(); + + if (m_sampleRateMode) { + m_settings.m_devSampleRate = ui->sampleRate->getValueNew(); + } else { + m_settings.m_devSampleRate = ui->sampleRate->getValueNew() * (1 << (m_rxElseTx ? m_settings.m_log2Decim : m_settings.m_log2Interp)); + } + + sendSettings(); +} + +void BladeRF2MIMOGui::on_gainMode_currentIndexChanged(int index) +{ + if (!m_rxElseTx) { // not for Tx + return; + } + + const std::vector& modes = m_sampleMIMO->getRxGainModes(); + unsigned int uindex = index < 0 ? 0 : (unsigned int) index; + + if (uindex < modes.size()) + { + BladeRF2MIMO::GainMode mode = modes[index]; + + if (m_streamIndex == 0) + { + if (m_settings.m_rx0GainMode != mode.m_value) + { + if (mode.m_value == BLADERF_GAIN_MANUAL) + { + m_settings.m_rx0GlobalGain = ui->gain->value(); + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + } + + m_settings.m_rx0GainMode = mode.m_value; + } + else if (m_streamIndex == 1) + { + if (m_settings.m_rx1GainMode != mode.m_value) + { + if (mode.m_value == BLADERF_GAIN_MANUAL) + { + m_settings.m_rx1GlobalGain = ui->gain->value(); + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + } + + m_settings.m_rx1GainMode = mode.m_value; + } + + sendSettings(); + } +} + +void BladeRF2MIMOGui::on_gain_valueChanged(int value) +{ + ui->gainText->setText(tr("%1 dB").arg(value)); + + if (m_rxElseTx) + { + if (m_streamIndex == 0) { + m_settings.m_rx0GlobalGain = value; + } else { + m_settings.m_rx1GlobalGain = value; + } + } + else + { + if (m_streamIndex == 0) { + m_settings.m_tx0GlobalGain = value; + } else { + m_settings.m_tx1GlobalGain = value; + } + } + + sendSettings(); +} + +void BladeRF2MIMOGui::on_biasTee_toggled(bool checked) +{ + if (m_rxElseTx) { + m_settings.m_rxBiasTee = checked; + } else { + m_settings.m_txBiasTee = checked; + } + + sendSettings(); +} + +void BladeRF2MIMOGui::on_transverter_clicked() +{ + if (m_rxElseTx) + { + m_settings.m_rxTransverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_rxTransverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("BladeRF2InputGui::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("BladeRF2InputGui::on_transverter_clicked: Tx: %lld Hz %s", m_settings.m_txTransverterDeltaFrequency, m_settings.m_txTransverterMode ? "on" : "off"); + } + + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void BladeRF2MIMOGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + int step; + + if (m_rxElseTx) + { + qint64 deltaFrequency = m_settings.m_rxTransverterMode ? m_settings.m_rxTransverterDeltaFrequency/1000 : 0; + m_sampleMIMO->getRxFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2MIMOGui::updateFrequencyLimits: Rx: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); + } + else + { + qint64 deltaFrequency = m_settings.m_txTransverterMode ? m_settings.m_txTransverterDeltaFrequency/1000 : 0; + m_sampleMIMO->getRxFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2MIMOGui::updateFrequencyLimits: Rx: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); + } +} + +void BladeRF2MIMOGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + if (m_rxElseTx) { + m_settings.m_rxCenterFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + } else { + m_settings.m_txCenterFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + } + + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +void BladeRF2MIMOGui::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; + } +} diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.h b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.h new file mode 100644 index 000000000..d5465a895 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.h @@ -0,0 +1,119 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _BLADERF2MIMO_BLADERF2MIMOGUI_H_ +#define _BLADERF2MIMO_BLADERF2MIMOGUI_H_ + +#include +#include + +#include "util/messagequeue.h" +#include "plugin/plugininstancegui.h" + +#include "bladerf2mimosettings.h" + +class DeviceUISet; +class BladeRF2MIMO; + +namespace Ui { + class BladeRF2MIMOGui; +} + +class BladeRF2MIMOGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT +public: + explicit BladeRF2MIMOGui(DeviceUISet *deviceUISet, QWidget* parent = nullptr); + virtual ~BladeRF2MIMOGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::BladeRF2MIMOGui* ui; + + DeviceUISet* m_deviceUISet; + BladeRF2MIMOSettings m_settings; + bool m_rxElseTx; //!< Which side is being dealt with + int m_streamIndex; //!< Current stream index being dealt with + bool m_spectrumRxElseTx; + int m_spectrumStreamIndex; //!< Index of the stream displayed on main spectrum + QTimer m_updateTimer; + QTimer m_statusTimer; + bool m_doApplySettings; + bool m_forceSettings; + BladeRF2MIMO* m_sampleMIMO; + std::size_t m_tickCount; + int m_deviceSampleRate; + quint64 m_rxDeviceCenterFrequency; //!< Center frequency in Rx device + quint64 m_txDeviceCenterFrequency; //!< Center frequency in Tx device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + bool m_sampleRateMode; + int m_srMax, m_srMin, m_srStep; + int m_bwMaxRx, m_bwMinRx, m_bwStepRx; + int m_bwMaxTx, m_bwMinTx, m_bwStepTx; + uint64_t m_fMinRx, m_fMaxRx; + uint64_t m_fMinTx, m_fMaxTx; + int m_fStepRx, m_fStepTx; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displaySampleRate(); + void displayFcTooltip(); + void displayGainModes(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFileRecordStatus(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + +private slots: + void handleInputMessages(); + void updateHardware(); + void updateStatus(); + void openDeviceSettingsDialog(const QPoint& p); + void on_streamSide_currentIndexChanged(int index); + void on_streamIndex_currentIndexChanged(int index); + void on_spectrumSide_currentIndexChanged(int index); + void on_spectrumIndex_currentIndexChanged(int index); + void on_startStop_toggled(bool checked); + void on_record_toggled(bool checked); + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void on_dcOffset_toggled(bool checked); + void on_iqImbalance_toggled(bool checked); + void on_bandwidth_changed(quint64 value); + void on_sampleRate_changed(quint64 value); + void on_fcPos_currentIndexChanged(int index); + void on_decim_currentIndexChanged(int index); + void on_gainMode_currentIndexChanged(int index); + void on_gain_valueChanged(int value); + void on_biasTee_toggled(bool checked); + void on_transverter_clicked(); +}; + +#endif // _BLADERF2MIMO_BLADERF2MIMOGUI_H_ diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.ui b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.ui new file mode 100644 index 000000000..375271bb8 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimogui.ui @@ -0,0 +1,765 @@ + + + BladeRF2MIMOGui + + + + 0 + 0 + 350 + 220 + + + + + 0 + 0 + + + + + 350 + 220 + + + + + Liberation Sans + 9 + + + + BladeRF2 + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 6 + + + + + + 16777215 + 22 + + + + + + + :/antenna.png + + + + + + + + 45 + 16777215 + + + + Select Rx or Tx settings + + + + Rx + + + + + Tx + + + + + + + + + 35 + 16777215 + + + + Select stream index to which settings apply + + + + 0 + + + + + 1 + + + + + + + + + 16777215 + 22 + + + + + + + :/dsb.png + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + Select Rx or Tx spectrum + + + + Rx + + + + + Tx + + + + + + + + + 35 + 16777215 + + + + Select which stream index to display spectrum + + + + 0 + + + + + 1 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Toggle record I/Q samples from device + + + + + + + :/record_off.png + :/record_on.png:/record_off.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + LO ppm + + + + + + + Local Oscillator ppm correction + + + -20 + + + 20 + + + 1 + + + Qt::Horizontal + + + + + + + + 26 + 0 + + + + -0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Automatic IQ imbalance correction (Rx) + + + IQ + + + + + + + Automatic DC offset removal (Rx) + + + DC + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Auto + + + + + + + kHz + + + + + + + + 0 + 0 + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + RF bandwidth + + + + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + 2 + + + + + + 24 + 0 + + + + + 24 + 16777215 + + + + Toggle between device to host (SR) and base band (BB) sample rate input + + + SR + + + true + + + true + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Device sample rate + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Fp + + + + + + + Relative position of device center frequency (Rx) + + + + Inf + + + + + Sup + + + + + Cen + + + + + + + + Dec + + + + + + + + 50 + 16777215 + + + + Decimation factor + + + 0 + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + + 3 + + + + + Gain value + + + Qt::Horizontal + + + + + + + Gain mode + + + + + + + Gain + + + + + + + + 45 + 0 + + + + 000 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Bias Tee + + + BT + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
+
+ + + + +
diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.cpp new file mode 100644 index 000000000..d162e5ef5 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.cpp @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" + +#ifdef SERVER_MODE +#include "bladerf2mimo.h" +#else +#include "bladerf2mimogui.h" +#include "bladerf2mimo.h" // TODO +#endif +#include "bladerf2mimoplugin.h" +//#include "testmiwebapiadapter.h" + +const PluginDescriptor BladeRF2MIMOPlugin::m_pluginDescriptor = { + QString("BladeRF2 MIMO"), + QString("4.12.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString BladeRF2MIMOPlugin::m_hardwareID = "BladeRF2"; +const QString BladeRF2MIMOPlugin::m_deviceTypeID = BLADERF2MIMO_DEVICE_TYPE_ID; + +BladeRF2MIMOPlugin::BladeRF2MIMOPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& BladeRF2MIMOPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void BladeRF2MIMOPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleMIMO(m_deviceTypeID, this); +} + +void BladeRF2MIMOPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + DeviceBladeRF2::enumOriginDevices(m_hardwareID, originDevices); + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices BladeRF2MIMOPlugin::enumSampleMIMO(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + QString displayedName = it->displayableName; + displayedName.replace(QString(":$1]"), QString("]")); + result.append(SamplingDevice( + displayedName, + m_hardwareID, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::PhysicalDevice, + PluginInterface::SamplingDevice::StreamMIMO, + 1, + 0 + )); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* BladeRF2MIMOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sourceId; + (void) widget; + (void) deviceUISet; + return nullptr; +} +#else +PluginInstanceGUI* BladeRF2MIMOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if (sourceId == m_deviceTypeID) + { + BladeRF2MIMOGui* gui = new BladeRF2MIMOGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return nullptr; + } +} +#endif + +DeviceSampleMIMO *BladeRF2MIMOPlugin::createSampleMIMOPluginInstance(const QString& mimoId, DeviceAPI *deviceAPI) +{ + if (mimoId == m_deviceTypeID) + { + BladeRF2MIMO* input = new BladeRF2MIMO(deviceAPI); + return input; + } + else + { + return nullptr; + } +} + +DeviceWebAPIAdapter *BladeRF2MIMOPlugin::createDeviceWebAPIAdapter() const +{ + // TODO + //return new BladeRF2MIMOWebAPIAdapter(); + return nullptr; +} diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.h b/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.h new file mode 100644 index 000000000..9bd39154f --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimoplugin.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _BLADERF2MIMO_BLADERF2MIMOPLUGIN_H +#define _BLADERF2MIMO_BLADERF2MIMOPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define BLADERF2MIMO_DEVICE_TYPE_ID "sdrangel.samplemimo.bladerf2mimo" + +class BladeRF2MIMOPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID BLADERF2MIMO_DEVICE_TYPE_ID) + +public: + explicit BladeRF2MIMOPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleMIMO(const OriginDevices& originDevices); + + virtual PluginInstanceGUI* createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleMIMO* createSampleMIMOPluginInstance(const QString& sourceId, DeviceAPI *deviceAPI); + virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const; + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // _BLADERF2MIMO_BLADERF2MIMOPLUGIN_H diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.cpp new file mode 100644 index 000000000..59902a876 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.cpp @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2mimosettings.h" + +#include "util/simpleserializer.h" + +BladeRF2MIMOSettings::BladeRF2MIMOSettings() +{ + resetToDefaults(); +} + +void BladeRF2MIMOSettings::resetToDefaults() +{ + m_devSampleRate = 3072000; + m_LOppmTenths = 0; + + m_rxCenterFrequency = 435000*1000; + m_log2Decim = 0; + m_fcPos = FC_POS_INFRA; + m_rxBandwidth = 1500000; + m_rx0GainMode = 0; + m_rx0GlobalGain = 0; + m_rx1GainMode = 0; + m_rx1GlobalGain = 0; + m_rxBiasTee = false; + m_dcBlock = false; + m_iqCorrection = false; + m_rxTransverterMode = false; + m_rxTransverterDeltaFrequency = 0; + + m_txCenterFrequency = 435000*1000; + m_log2Interp = 0; + m_txBandwidth = 1500000; + m_tx0GlobalGain = -3; + m_tx1GlobalGain = -3; + m_txBiasTee = false; + m_txTransverterMode = false; + m_txTransverterDeltaFrequency = 0; + + m_fileRecordName = ""; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; +} + +QByteArray BladeRF2MIMOSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_LOppmTenths); + + s.writeU64(10, m_rxCenterFrequency); + s.writeU32(11, m_log2Decim); + s.writeS32(12, (int) m_fcPos); + s.writeS32(13, m_rxBandwidth); + s.writeS32(14, m_rx0GainMode); + s.writeS32(15, m_rx0GlobalGain); + s.writeS32(16, m_rx1GainMode); + s.writeS32(17, m_rx1GlobalGain); + s.writeBool(18, m_rxBiasTee); + s.writeBool(19, m_dcBlock); + s.writeBool(20, m_iqCorrection); + s.writeBool(21, m_rxTransverterMode); + s.writeS64(22, m_rxTransverterDeltaFrequency); + + s.writeU64(30, m_txCenterFrequency); + s.writeU32(31, m_log2Interp); + s.writeS32(32, m_txBandwidth); + s.writeS32(33, m_tx0GlobalGain); + s.writeS32(34, m_tx1GlobalGain); + s.writeBool(35, m_txBiasTee); + s.writeBool(36, m_txTransverterMode); + s.writeS64(37, m_txTransverterDeltaFrequency); + + s.writeString(50, m_fileRecordName); + s.writeBool(51, m_useReverseAPI); + s.writeString(52, m_reverseAPIAddress); + s.writeU32(53, m_reverseAPIPort); + s.writeU32(54, m_reverseAPIDeviceIndex); + + return s.final(); +} + +bool BladeRF2MIMOSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + uint32_t uintval; + + d.readS32(1, &m_devSampleRate, 3072000); + d.readS32(2, &m_LOppmTenths); + + d.readU64(10, &m_rxCenterFrequency, 435000*1000); + d.readU32(11, &m_log2Decim); + d.readS32(12, &intval); + m_fcPos = (fcPos_t) intval; + d.readS32(13, &m_rxBandwidth); + d.readS32(14, &m_rx0GainMode); + d.readS32(15, &m_rx0GlobalGain); + d.readS32(16, &m_rx1GainMode); + d.readS32(17, &m_rx1GlobalGain); + d.readBool(18, &m_rxBiasTee); + d.readBool(19, &m_dcBlock); + d.readBool(20, &m_iqCorrection); + d.readBool(21, &m_rxTransverterMode, false); + d.readS64(22, &m_rxTransverterDeltaFrequency, 0); + + d.readU64(30, &m_txCenterFrequency, 435000*1000); + d.readU32(31, &m_log2Interp); + d.readS32(32, &m_txBandwidth); + d.readS32(33, &m_tx0GlobalGain); + d.readS32(34, &m_tx1GlobalGain); + d.readBool(35, &m_txBiasTee); + d.readBool(36, &m_txTransverterMode, false); + d.readS64(37, &m_txTransverterDeltaFrequency, 0); + + d.readString(50, &m_fileRecordName, ""); + d.readBool(51, &m_useReverseAPI, false); + d.readString(52, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(53, &uintval, 0); + + if ((uintval > 1023) && (uintval < 65535)) { + m_reverseAPIPort = uintval; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(54, &uintval, 0); + m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.h b/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.h new file mode 100644 index 000000000..c89e76a50 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimosettings.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMOSETTINGS_H_ +#define PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMOSETTINGS_H_ + +#include +#include + +struct BladeRF2MIMOSettings { + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + qint32 m_devSampleRate; + qint32 m_LOppmTenths; + + quint64 m_rxCenterFrequency; + quint32 m_log2Decim; + fcPos_t m_fcPos; + qint32 m_rxBandwidth; + int m_rx0GainMode; + int m_rx0GlobalGain; + int m_rx1GainMode; + int m_rx1GlobalGain; + bool m_rxBiasTee; + bool m_dcBlock; + bool m_iqCorrection; + bool m_rxTransverterMode; + qint64 m_rxTransverterDeltaFrequency; + + quint64 m_txCenterFrequency; + quint32 m_log2Interp; + qint32 m_txBandwidth; + int m_tx0GlobalGain; + int m_tx1GlobalGain; + bool m_txBiasTee; + bool m_txTransverterMode; + qint64 m_txTransverterDeltaFrequency; + + QString m_fileRecordName; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + + BladeRF2MIMOSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + + + +#endif /* PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MIMOSETTINGS_H_ */ diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.cpp new file mode 100644 index 000000000..7fa0fe2d6 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.cpp @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGDeviceSettings.h" +#include "bladerf2mimo.h" +#include "bladerf2mimowebapiadapter.h" + +BladeRF2MIMOWebAPIAdapter::BladeRF2MIMOWebAPIAdapter() +{} + +BladeRF2MIMOWebAPIAdapter::~BladeRF2MIMOWebAPIAdapter() +{} + +int BladeRF2MIMOWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setBladeRf2MimoSettings(new SWGSDRangel::SWGBladeRF2MIMOSettings()); + response.getBladeRf2MimoSettings()->init(); + BladeRF2MIMO::webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2MIMOWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + BladeRF2MIMO::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response); + return 200; +} diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.h b/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.h new file mode 100644 index 000000000..5f40452f8 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mimowebapiadapter.h @@ -0,0 +1,44 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "device/devicewebapiadapter.h" +#include "bladerf2mimosettings.h" + +class BladeRF2MIMOWebAPIAdapter : public DeviceWebAPIAdapter +{ +public: + BladeRF2MIMOWebAPIAdapter(); + virtual ~BladeRF2MIMOWebAPIAdapter(); + 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: + BladeRF2MIMOSettings m_settings; +}; \ No newline at end of file diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mithread.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mithread.cpp new file mode 100644 index 000000000..82d542b3b --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mithread.cpp @@ -0,0 +1,245 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/samplesinkfifo.h" + +#include "bladerf2mithread.h" + +BladeRF2MIThread::BladeRF2MIThread(struct bladerf* dev, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev) +{ + qDebug("BladeRF2MIThread::BladeRF2MIThread"); + m_buf = new qint16[2*DeviceBladeRF2::blockSize*2]; + + for (unsigned int i = 0; i < 2; i++) { + m_convertBuffer[i].resize(DeviceBladeRF2::blockSize, Sample{0,0}); + } +} + +BladeRF2MIThread::~BladeRF2MIThread() +{ + qDebug("BladeRF2MIThread::~BladeRF2MIThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; +} + +void BladeRF2MIThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void BladeRF2MIThread::stopWork() +{ + m_running = false; + wait(); +} + +void BladeRF2MIThread::setLog2Decimation(unsigned int log2_decim) +{ + m_log2Decim = log2_decim; +} + +unsigned int BladeRF2MIThread::getLog2Decimation() const +{ + return m_log2Decim; +} + +void BladeRF2MIThread::setFcPos(int fcPos) +{ + m_fcPos = fcPos; +} + +int BladeRF2MIThread::getFcPos() const +{ + return m_fcPos; +} + +void BladeRF2MIThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if (channel < 2) { + m_sampleFifo[channel] = sampleFifo; + } +} + +SampleSinkFifo *BladeRF2MIThread::getFifo(unsigned int channel) +{ + if (channel < 2) { + return m_sampleFifo[channel]; + } else { + return nullptr; + } +} + +void BladeRF2MIThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + int status = bladerf_sync_config(m_dev, BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + + if (status < 0) + { + qCritical("BladeRF2MIThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + qDebug("BladeRF2MIThread::run: start running loop"); + + while (m_running) + { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize*2, nullptr, 1500); + + if (res < 0) + { + qCritical("BladeRF2MIThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + + callback(m_buf, DeviceBladeRF2::blockSize); + } + + qDebug("BladeRF2MIThread::run: stop running loop"); + m_running = false; + } +} + +void BladeRF2MIThread::callback(const qint16* buf, qint32 samplesPerChannel) +{ + int status = bladerf_deinterleave_stream_buffer(BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*2, (void *) buf); + + if (status < 0) + { + qCritical("BladeRF2MIThread::callback: cannot de-interleave buffer: %s", bladerf_strerror(status)); + return; + } + + for (unsigned int channel = 0; channel < 2; channel++) + { + if (m_sampleFifo[channel]) { + channelCallback(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + } + } +} + +void BladeRF2MIThread::channelCallback(const qint16* buf, qint32 len, int channel) +{ + SampleVector::iterator it = m_convertBuffer[channel].begin(); + + if (m_log2Decim == 0) + { + m_decimators[channel].decimate1(&it, buf, len); + } + else + { + if (m_fcPos == 0) // Infra + { + switch (m_log2Decim) + { + case 1: + m_decimators[channel].decimate2_inf(&it, buf, len); + break; + case 2: + m_decimators[channel].decimate4_inf(&it, buf, len); + break; + case 3: + m_decimators[channel].decimate8_inf(&it, buf, len); + break; + case 4: + m_decimators[channel].decimate16_inf(&it, buf, len); + break; + case 5: + m_decimators[channel].decimate32_inf(&it, buf, len); + break; + case 6: + m_decimators[channel].decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_fcPos == 1) // Supra + { + switch (m_log2Decim) + { + case 1: + m_decimators[channel].decimate2_sup(&it, buf, len); + break; + case 2: + m_decimators[channel].decimate4_sup(&it, buf, len); + break; + case 3: + m_decimators[channel].decimate8_sup(&it, buf, len); + break; + case 4: + m_decimators[channel].decimate16_sup(&it, buf, len); + break; + case 5: + m_decimators[channel].decimate32_sup(&it, buf, len); + break; + case 6: + m_decimators[channel].decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_fcPos == 2) // Center + { + switch (m_log2Decim) + { + case 1: + m_decimators[channel].decimate2_cen(&it, buf, len); + break; + case 2: + m_decimators[channel].decimate4_cen(&it, buf, len); + break; + case 3: + m_decimators[channel].decimate8_cen(&it, buf, len); + break; + case 4: + m_decimators[channel].decimate16_cen(&it, buf, len); + break; + case 5: + m_decimators[channel].decimate32_cen(&it, buf, len); + break; + case 6: + m_decimators[channel].decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_sampleFifo[channel]->write(m_convertBuffer[channel].begin(), it); +} \ No newline at end of file diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mithread.h b/plugins/samplemimo/bladerf2mimo/bladerf2mithread.h new file mode 100644 index 000000000..ff47eacd7 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mithread.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MITHREAD_H_ +#define PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MITHREAD_H_ + +// BladerRF2 is a SISO/MIMO device. It can support one or two Rx. Here ww will +// configure two Rx + +#include +#include +#include + +#include + +#include "dsp/decimators.h" + +class SampleSinkFifo; + +class BladeRF2MIThread : public QThread { + Q_OBJECT + +public: + BladeRF2MIThread(struct bladerf* dev, QObject* parent = nullptr); + ~BladeRF2MIThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + void setLog2Decimation(unsigned int log2_decim); + unsigned int getLog2Decimation() const; + void setFcPos(int fcPos); + int getFcPos() const; + void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + SampleSinkFifo *getFifo(unsigned int channel); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + qint16 *m_buf; + SampleVector m_convertBuffer[2]; + SampleSinkFifo* m_sampleFifo[2]; + Decimators m_decimators[2]; + unsigned int m_log2Decim; + int m_fcPos; + + void run(); + void callback(const qint16* buf, qint32 samplesPerChannel); + void channelCallback(const qint16* buf, qint32 len, int channel); +}; + +#endif // PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MITHREAD_H_ diff --git a/plugins/samplemimo/bladerf2mimo/bladerf2mothread.cpp b/plugins/samplemimo/bladerf2mimo/bladerf2mothread.cpp new file mode 100644 index 000000000..c2e735ba9 --- /dev/null +++ b/plugins/samplemimo/bladerf2mimo/bladerf2mothread.cpp @@ -0,0 +1,197 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/samplesourcefifo.h" + +#include "bladerf2mothread.h" + +BladeRF2MOThread::BladeRF2MOThread(struct bladerf* dev, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_log2Interp(0) +{ + qDebug("BladeRF2MOThread::BladeRF2MOThread"); + m_buf = new qint16[2*DeviceBladeRF2::blockSize*2]; +} + +BladeRF2MOThread::~BladeRF2MOThread() +{ + qDebug("BladeRF2MOThread::~BladeRF2MOThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; +} + +void BladeRF2MOThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void BladeRF2MOThread::stopWork() +{ + m_running = false; + wait(); +} + +void BladeRF2MOThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + int status; + + status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 128, 16384, 32, 1500); + + if (status < 0) + { + qCritical("BladeRF2MOThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + qDebug("BladeRF2MOThread::run: start running loop"); + + while (m_running) + { + callback(m_buf, DeviceBladeRF2::blockSize); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize*2, 0, 1500); + + if (res < 0) + { + qCritical("BladeRF2MOThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + } + + qDebug("BladeRF2MOThread::run: stop running loop"); + } + + m_running = false; +} + +void BladeRF2MOThread::setLog2Interpolation(unsigned int log2_interp) +{ + m_log2Interp = log2_interp; +} + +unsigned int BladeRF2MOThread::getLog2Interpolation() const +{ + return m_log2Interp; +} + +void BladeRF2MOThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) +{ + if (channel < 2) { + m_sampleFifo[channel] = sampleFifo; + } +} + +SampleSourceFifo *BladeRF2MOThread::getFifo(unsigned int channel) +{ + if (channel < 2) { + return m_sampleFifo[channel]; + } else { + return nullptr; + } +} + +void BladeRF2MOThread::callback(qint16* buf, qint32 samplesPerChannel) +{ + for (unsigned int channel = 0; channel < 2; channel++) + { + if (m_sampleFifo[channel]) { + channelCallback(&buf[2*samplesPerChannel*channel], samplesPerChannel, channel); + } else { + std::fill(&buf[2*samplesPerChannel*channel], &buf[2*samplesPerChannel*channel]+2*samplesPerChannel, 0); // fill with zero samples + } + } + + int status = bladerf_interleave_stream_buffer(BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*2, (void *) buf); + + if (status < 0) + { + qCritical("BladeRF2MOThread::callback: cannot interleave buffer: %s", bladerf_strerror(status)); + return; + } +} + +// Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) +void BladeRF2MOThread::channelCallback(qint16* buf, qint32 len, unsigned int channel) +{ + if (m_sampleFifo[channel]) + { + float bal = m_sampleFifo[channel]->getRWBalance(); + + if (bal < -0.25) { + qDebug("BladeRF2MOThread::channelCallback: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("BladeRF2MOThread::channelCallback: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_sampleFifo[channel]->readAdvance(beginRead, len/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MOTHREAD_H_ +#define PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MOTHREAD_H_ + +#include +#include +#include +#include + +#include "dsp/interpolators.h" + +class SampleSourceFifo; + +class BladeRF2MOThread : public QThread { + Q_OBJECT + +public: + BladeRF2MOThread(struct bladerf* dev, QObject* parent = nullptr); + ~BladeRF2MOThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + void setLog2Interpolation(unsigned int log2_interp); + unsigned int getLog2Interpolation() const; + void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); + SampleSourceFifo *getFifo(unsigned int channel); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + SampleSourceFifo* m_sampleFifo[2]; + Interpolators m_interpolators[2]; + unsigned int m_log2Interp; + + void run(); + unsigned int getNbFifos(); + void channelCallback(qint16* buf, qint32 len, unsigned int channel = 0); + void callback(qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLEMIMO_BLADERF2MIMO_BLADERF2MOTHREAD_H_ */