diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 04f2916f9..5232822f1 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -39,3 +39,5 @@ endif() if(ENABLE_USRP AND UHD_FOUND) add_subdirectory(usrp) endif() + +add_subdirectory(metis) diff --git a/devices/metis/CMakeLists.txt b/devices/metis/CMakeLists.txt new file mode 100644 index 000000000..1a5f7009e --- /dev/null +++ b/devices/metis/CMakeLists.txt @@ -0,0 +1,24 @@ +project(metisdevice) + +set(metisdevice_SOURCES + devicemetis.cpp + devicemetisscan.cpp +) + +set(metisdevice_HEADERS + devicemetis.h + devicemetisscan.h +) + +add_library(metisdevice SHARED + ${metisdevice_SOURCES} +) + +set_target_properties(metisdevice + PROPERTIES DEFINE_SYMBOL "devices_EXPORTS") + +target_link_libraries(metisdevice + sdrbase +) + +install(TARGETS metisdevice DESTINATION ${INSTALL_LIB_DIR}) diff --git a/devices/metis/devicemetis.cpp b/devices/metis/devicemetis.cpp new file mode 100644 index 000000000..89fb50cdd --- /dev/null +++ b/devices/metis/devicemetis.cpp @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicemetis.h" + +DeviceMetis::DeviceMetis() +{ +} + +DeviceMetis::~DeviceMetis() +{ +} + +DeviceMetis& DeviceMetis::instance() +{ + static DeviceMetis inst; + return inst; +} \ No newline at end of file diff --git a/devices/metis/devicemetis.h b/devices/metis/devicemetis.h new file mode 100644 index 000000000..edb356538 --- /dev/null +++ b/devices/metis/devicemetis.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_METIS_DEVICEMETIS_H_ +#define DEVICES_METIS_DEVICEMETIS_H_ + +#include "export.h" +#include "devicemetisscan.h" + +class DEVICES_API DeviceMetis +{ +public: + static DeviceMetis& instance(); + void scan() { m_scan.scan(); } + void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices) { + m_scan.enumOriginDevices(hardwareId, originDevices); + } + const DeviceMetisScan::DeviceScan* getDeviceScanAt(unsigned int index) const { return m_scan.getDeviceAt(index); } + +protected: + DeviceMetis(); + ~DeviceMetis(); + +private: + DeviceMetisScan m_scan; +}; + +#endif // DEVICES_METIS_DEVICEMETIS_H_ \ No newline at end of file diff --git a/devices/metis/devicemetisscan.cpp b/devices/metis/devicemetisscan.cpp new file mode 100644 index 000000000..5772206d8 --- /dev/null +++ b/devices/metis/devicemetisscan.cpp @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "devicemetisscan.h" + +void DeviceMetisScan::scan() +{ + m_scans.clear(); + + if (m_udpSocket.bind(QHostAddress::AnyIPv4, 10001, QUdpSocket::ShareAddress)) + { + connect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + } + else + { + qDebug() << "DeviceMetisScan::scan: cannot bind socket"; + return; + } + + unsigned char buffer[63]; + buffer[0] = (unsigned char) 0xEF; + buffer[1] = (unsigned char) 0XFE; + buffer[2] = (unsigned char) 0x02; + std::fill(&buffer[3], &buffer[63], 0); + + if (m_udpSocket.writeDatagram((const char*) buffer, sizeof(buffer), QHostAddress::Broadcast, 1024) < 0) + { + qDebug() << "DeviceMetisScan::scan: discovery writeDatagram failed " << m_udpSocket.errorString(); + return; + } + else + { + qDebug() << "DeviceMetisScan::scan: discovery writeDatagram sent"; + } + + // wait for 1 second before returning + QEventLoop loop; + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer->start(500); + + qDebug() << "DeviceMetisScan::scan: start 0.5 second timeout loop"; + // Execute the event loop here and wait for the timeout to trigger + // which in turn will trigger event loop quit. + loop.exec(); + + disconnect(&m_udpSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + m_udpSocket.close(); +} + +void DeviceMetisScan::enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices) +{ + scan(); + + for (int i = 0; i < m_scans.size(); i++) + { + const DeviceScan& deviceInfo = m_scans.at(i); + QString serial = QString("%1:%2_%3").arg(deviceInfo.m_address.toString()).arg(deviceInfo.m_port).arg(deviceInfo.m_serial); + QString displayableName(QString("Metis[%1] %2").arg(i).arg(serial)); + originDevices.append(PluginInterface::OriginDevice( + displayableName, + hardwareId, + serial, + i, // sequence + 8, // Nb Rx + 1 // Nb Tx + )); + } +} + +const DeviceMetisScan::DeviceScan* DeviceMetisScan::getDeviceAt(unsigned int index) const +{ + if (index < m_scans.size()) { + return &m_scans.at(index); + } else { + return nullptr; + } +} + +void DeviceMetisScan::getSerials(QList& serials) const +{ + for (int i = 0; i < m_scans.size(); i++) { + serials.append(m_scans.at(i).m_serial); + } +} + +void DeviceMetisScan::readyRead() +{ + QHostAddress metisAddress; + quint16 metisPort; + unsigned char buffer[1024]; + + if (m_udpSocket.readDatagram((char*) &buffer, (qint64) sizeof(buffer), &metisAddress, &metisPort) < 0) + { + qDebug() << "DeviceMetisScan::readyRead: readDatagram failed " << m_udpSocket.errorString(); + return; + } + + QString metisIP = QString("%1:%2").arg(metisAddress.toString()).arg(metisPort); + + if (buffer[0] == 0xEF && buffer[1] == 0xFE) + { + switch(buffer[2]) + { + case 3: // reply + // should not happen on this port + break; + case 2: // response to a discovery packet + { + QByteArray array((char *) &buffer[3], 6); + QString serial = QString(array.toHex()); + m_scans.append(DeviceScan( + serial, + metisAddress, + metisPort + )); + m_serialMap.insert(serial, &m_scans.back()); + qDebug() << "DeviceMetisScan::readyRead: found Metis at:" << metisIP << "MAC:" << serial; + } + break; + case 1: // a data packet + break; + } + } + else + { + qDebug() << "DeviceMetisScan::readyRead: received invalid response to discovery"; + } +} \ No newline at end of file diff --git a/devices/metis/devicemetisscan.h b/devices/metis/devicemetisscan.h new file mode 100644 index 000000000..5d3b7366f --- /dev/null +++ b/devices/metis/devicemetisscan.h @@ -0,0 +1,71 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_METIS_DEVICEMETISSCAN_H_ +#define DEVICES_METIS_DEVICEMETISSCAN_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "plugin/plugininterface.h" +#include "export.h" + +class DEVICES_API DeviceMetisScan : public QObject +{ + Q_OBJECT +public: + struct DeviceScan + { + QString m_serial; + QHostAddress m_address; + quint16 m_port; + + DeviceScan( + const QString& serial, + const QHostAddress& address, + quint16 port + ) : + m_serial(serial), + m_address(address), + m_port(port) + {} + }; + + void scan(); + int getNbDevices() const { return m_scans.size(); } + const DeviceScan* getDeviceAt(unsigned int index) const; + void getSerials(QList& serials) const; + void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices); + +public slots: + void readyRead(); + +private: + QUdpSocket m_udpSocket; + QList m_scans; + QMap m_serialMap; + +}; + +#endif /* DEVICES_METIS_DEVICEMETISSCAN_H_ */ diff --git a/plugins/samplemimo/CMakeLists.txt b/plugins/samplemimo/CMakeLists.txt index 872f83c9d..676a2b69b 100644 --- a/plugins/samplemimo/CMakeLists.txt +++ b/plugins/samplemimo/CMakeLists.txt @@ -12,5 +12,6 @@ if(ENABLE_XTRX AND LIBXTRX_FOUND) add_subdirectory(xtrxmimo) endif() +add_subdirectory(metismiso) add_subdirectory(testmi) add_subdirectory(testmosync) diff --git a/plugins/samplemimo/metismiso/CMakeLists.txt b/plugins/samplemimo/metismiso/CMakeLists.txt new file mode 100644 index 000000000..512d19fe3 --- /dev/null +++ b/plugins/samplemimo/metismiso/CMakeLists.txt @@ -0,0 +1,60 @@ +project(metismiso) + +set(metismiso_SOURCES + metismiso.cpp + metismisoplugin.cpp + metismisoudphandler.cpp + metismisosettings.cpp + metismisowebapiadapter.cpp + metismisodecimators.cpp +) + +set(metismiso_HEADERS + metismiso.h + metismisoplugin.h + metismisoudphandler.h + metismisosettings.h + metismisowebapiadapter.h + metismisodecimators.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices +) + +if (NOT SERVER_MODE) + set (metismiso_SOURCES + ${metismiso_SOURCES} + metismisogui.cpp + metismisogui.ui + ) + set(metismiso_HEADERS + ${metismiso_HEADERS} + metismisogui.h + ) + set(TARGET_NAME mimometismiso) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME mimometismisosrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${metismiso_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger + metisdevice +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/samplemimo/metismiso/metismiso.cpp b/plugins/samplemimo/metismiso/metismiso.cpp new file mode 100644 index 000000000..6c1632c86 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismiso.cpp @@ -0,0 +1,898 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include +#include + +#include "SWGDeviceSettings.h" +#include "SWGDeviceState.h" +#include "SWGMetisMISOSettings.h" + +#include "device/deviceapi.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "dsp/dspdevicemimoengine.h" +#include "dsp/devicesamplesource.h" +#include "metis/devicemetis.h" + +#include "metismisoudphandler.h" +#include "metismiso.h" + +MESSAGE_CLASS_DEFINITION(MetisMISO::MsgConfigureMetisMISO, Message) +MESSAGE_CLASS_DEFINITION(MetisMISO::MsgStartStop, Message) + + +MetisMISO::MetisMISO(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_udpHandler(&m_sampleMIFifo, &m_sampleMOFifo, deviceAPI), + m_deviceDescription("MetisMISO"), + m_running(false), + m_masterTimer(deviceAPI->getMasterTimer()) +{ + m_mimoType = MIMOHalfSynchronous; + m_sampleMIFifo.init(MetisMISOSettings::m_maxReceivers, 96000 * 4); + m_sampleMOFifo.init(1, 96000 * 4); + m_deviceAPI->setNbSourceStreams(MetisMISOSettings::m_maxReceivers); + m_deviceAPI->setNbSinkStreams(1); + int deviceSequence = m_deviceAPI->getSamplingDeviceSequence(); + const DeviceMetisScan::DeviceScan *deviceScan = DeviceMetis::instance().getDeviceScanAt(deviceSequence); + m_udpHandler.setMetisAddress(deviceScan->m_address, deviceScan->m_port); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +MetisMISO::~MetisMISO() +{ + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + + if (m_running) { + stopRx(); + } +} + +void MetisMISO::destroy() +{ + delete this; +} + +void MetisMISO::init() +{ + applySettings(m_settings, true); +} + +bool MetisMISO::startRx() +{ + qDebug("MetisMISO::startRx"); + QMutexLocker mutexLocker(&m_mutex); + + if (!m_running) { + startMetis(); + } + + mutexLocker.unlock(); + + applySettings(m_settings, true); + m_running = true; + + return true; +} + +bool MetisMISO::startTx() +{ + qDebug("MetisMISO::startTx"); + QMutexLocker mutexLocker(&m_mutex); + + if (!m_running) { + startMetis(); + } + + mutexLocker.unlock(); + + applySettings(m_settings, true); + m_running = true; + + return true; +} + +void MetisMISO::stopRx() +{ + qDebug("MetisMISO::stopRx"); + QMutexLocker mutexLocker(&m_mutex); + + if (m_running) { + stopMetis(); + } + + m_running = false; +} + +void MetisMISO::stopTx() +{ + qDebug("MetisMISO::stopTx"); + QMutexLocker mutexLocker(&m_mutex); + + if (m_running) { + stopMetis(); + } + + m_running = false; +} + +void MetisMISO::startMetis() +{ + MetisMISOUDPHandler::MsgStartStop *message = MetisMISOUDPHandler::MsgStartStop::create(true); + m_udpHandler.getInputMessageQueue()->push(message); +} + +void MetisMISO::stopMetis() +{ + MetisMISOUDPHandler::MsgStartStop *message = MetisMISOUDPHandler::MsgStartStop::create(false); + m_udpHandler.getInputMessageQueue()->push(message); +} + +QByteArray MetisMISO::serialize() const +{ + return m_settings.serialize(); +} + +bool MetisMISO::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureMetisMISO* message = MsgConfigureMetisMISO::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureMetisMISO* messageToGUI = MsgConfigureMetisMISO::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& MetisMISO::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int MetisMISO::getSourceSampleRate(int index) const +{ + if (index < 3) { + return MetisMISOSettings::getSampleRateFromIndex(m_settings.m_sampleRateIndex); + } else { + return 0; + } +} + +quint64 MetisMISO::getSourceCenterFrequency(int index) const +{ + if (index < MetisMISOSettings::m_maxReceivers) { + return m_settings.m_rxCenterFrequencies[index]; + } else { + return 0; + } +} + +void MetisMISO::setSourceCenterFrequency(qint64 centerFrequency, int index) +{ + MetisMISOSettings settings = m_settings; // note: calls copy constructor + + if (index < MetisMISOSettings::m_maxReceivers) + { + settings.m_rxCenterFrequencies[index]; + + MsgConfigureMetisMISO* message = MsgConfigureMetisMISO::create(settings, false); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureMetisMISO* messageToGUI = MsgConfigureMetisMISO::create(settings, false); + m_guiMessageQueue->push(messageToGUI); + } + } +} + +bool MetisMISO::handleMessage(const Message& message) +{ + if (MsgConfigureMetisMISO::match(message)) + { + MsgConfigureMetisMISO& conf = (MsgConfigureMetisMISO&) message; + qDebug() << "MetisMISO::handleMessage: MsgConfigureMetisMISO"; + + bool success = applySettings(conf.getSettings(), conf.getForce()); + + if (!success) + { + qDebug("MetisMISO::handleMessage: config error"); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "MetisMISO::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + // Start Rx engine + if (m_deviceAPI->initDeviceEngine(0)) { + m_deviceAPI->startDeviceEngine(0); + } + + // Start Tx engine + if (m_deviceAPI->initDeviceEngine(1)) { + m_deviceAPI->startDeviceEngine(1); + } + } + else + { + m_deviceAPI->stopDeviceEngine(0); // Stop Rx engine + m_deviceAPI->stopDeviceEngine(1); // Stop Tx engine + } + + if (m_settings.m_useReverseAPI) { + webapiReverseSendStartStop(cmd.getStartStop()); + } + + return true; + } + else + { + return false; + } +} + +bool MetisMISO::applySettings(const MetisMISOSettings& settings, bool force) +{ + QList reverseAPIKeys; + + qDebug() << "MetisMISO::applySettings: " + << " m_nbReceivers:" << settings.m_nbReceivers + << " m_txEnable:" << settings.m_txEnable + << " m_rx1CenterFrequencies: [" + << " 1:" << settings.m_rxCenterFrequencies[0] + << " 2:" << settings.m_rxCenterFrequencies[1] + << " 3:" << settings.m_rxCenterFrequencies[2] + << " 4:" << settings.m_rxCenterFrequencies[3] + << " 5:" << settings.m_rxCenterFrequencies[4] + << " 6:" << settings.m_rxCenterFrequencies[5] + << " 7:" << settings.m_rxCenterFrequencies[6] + << " 8:" << settings.m_rxCenterFrequencies[7] + << " ] m_txCenterFrequency:" << settings.m_txCenterFrequency + << " m_rxSubsamplingIndexes: [" + << " 1:" << settings.m_rxSubsamplingIndexes[0] + << " 2:" << settings.m_rxSubsamplingIndexes[1] + << " 3:" << settings.m_rxSubsamplingIndexes[2] + << " 4:" << settings.m_rxSubsamplingIndexes[3] + << " 5:" << settings.m_rxSubsamplingIndexes[4] + << " 6:" << settings.m_rxSubsamplingIndexes[5] + << " 7:" << settings.m_rxSubsamplingIndexes[6] + << " 8:" << settings.m_rxSubsamplingIndexes[7] + << " ] m_rxTransverterMode:" << settings.m_rxTransverterMode + << " m_rxTransverterDeltaFrequency:" << settings.m_rxTransverterDeltaFrequency + << " m_txTransverterMode:" << settings.m_txTransverterMode + << " m_txTransverterDeltaFrequency:" << settings.m_txTransverterDeltaFrequency + << " m_iqOrder:" << settings.m_iqOrder + << " m_sampleRateIndex:" << settings.m_sampleRateIndex + << " m_log2Decim:" << settings.m_log2Decim + << " m_preamp:" << settings.m_preamp + << " m_random:" << settings.m_random + << " m_dither:" << settings.m_dither + << " m_duplex:" << settings.m_duplex + << " m_dcBlock:" << settings.m_dcBlock + << " m_iqCorrection:" << settings.m_iqCorrection + << " m_txDrive:" << settings.m_txDrive + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex; + + bool propagateSettings = false; + + if ((m_settings.m_nbReceivers != settings.m_nbReceivers) || force) + { + reverseAPIKeys.append("nbReceivers"); + propagateSettings = true; + } + + if ((m_settings.m_txEnable != settings.m_txEnable) || force) + { + reverseAPIKeys.append("txEnable"); + propagateSettings = true; + } + + for (int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) + { + if ((m_settings.m_rxCenterFrequencies[i] != settings.m_rxCenterFrequencies[i]) || force) + { + reverseAPIKeys.append(QString("rx%1CenterFrequency").arg(i+1)); + propagateSettings = true; + } + + if ((m_settings.m_rxSubsamplingIndexes[i] != settings.m_rxSubsamplingIndexes[i]) || force) + { + reverseAPIKeys.append(QString("rx%1SubsamplingIndex").arg(i+1)); + propagateSettings = true; + } + } + + if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) || force) + { + reverseAPIKeys.append("txCenterFrequency"); + propagateSettings = true; + } + + if ((m_settings.m_rxTransverterMode != settings.m_rxTransverterMode) || force) + { + reverseAPIKeys.append("rxTransverterMode"); + propagateSettings = true; + } + + if ((m_settings.m_rxTransverterDeltaFrequency != settings.m_rxTransverterDeltaFrequency) || force) + { + reverseAPIKeys.append("rxTransverterDeltaFrequency"); + propagateSettings = true; + } + + if ((m_settings.m_txTransverterMode != settings.m_txTransverterMode) || force) + { + reverseAPIKeys.append("txTransverterMode"); + propagateSettings = true; + } + + if ((m_settings.m_txTransverterDeltaFrequency != settings.m_txTransverterDeltaFrequency) || force) + { + reverseAPIKeys.append("txTransverterDeltaFrequency"); + propagateSettings = true; + } + + if ((m_settings.m_iqOrder != settings.m_iqOrder) || force) + { + reverseAPIKeys.append("iqOrder"); + propagateSettings = true; + } + + if ((m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) || force) + { + reverseAPIKeys.append("sampleRateIndex"); + propagateSettings = true; + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + reverseAPIKeys.append("log2Decim"); + propagateSettings = true; + } + + if ((m_settings.m_LOppmTenths != settings.m_LOppmTenths) || force) + { + reverseAPIKeys.append("LOppmTenths"); + propagateSettings = true; + } + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || force) { + reverseAPIKeys.append("dcBlock"); + } + + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) { + reverseAPIKeys.append("iqCorrection"); + } + + if ((m_settings.m_txDrive != settings.m_txDrive) || force) + { + reverseAPIKeys.append("txDrive"); + propagateSettings = true; + } + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || + (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 0); + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection, 1); + } + + for (int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) + { + if ((m_settings.m_rxCenterFrequencies[i] != settings.m_rxCenterFrequencies[i]) || + (m_settings.m_sampleRateIndex != settings.m_sampleRateIndex) || + (m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + int devSampleRate = (1<getDeviceEngineInputMessageQueue()->push(engineRxNotif); + } + } + + if ((m_settings.m_txCenterFrequency != settings.m_txCenterFrequency) || + (m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + DSPMIMOSignalNotification *engineTxNotif = new DSPMIMOSignalNotification( + 48000, settings.m_txCenterFrequency, false, 0); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(engineTxNotif); + } + + if (propagateSettings) { + m_udpHandler.applySettings(settings); + } + + if (settings.m_useReverseAPI) + { + qDebug("MetisMISO::applySettings: call webapiReverseSendSettings"); + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; + return true; +} + +int MetisMISO::webapiRunGet( + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + if ((subsystemIndex == 0) || (subsystemIndex == 1)) // Rx and Tx always started together + { + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; + } + else + { + errorMessage = QString("Subsystem index invalid: expect 0 (Rx) only"); + return 404; + } +} + +int MetisMISO::webapiRun( + bool run, + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + // Rx and Tx are started or stopped together + if ((subsystemIndex == 0) || (subsystemIndex == 1)) + { + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); // Rx driven + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; + } + else + { + errorMessage = QString("Subsystem index invalid: expect 0 (Rx) only"); + return 404; + } + +} + +int MetisMISO::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings()); + response.getMetisMisoSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int MetisMISO::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + MetisMISOSettings settings = m_settings; + webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response); + + MsgConfigureMetisMISO *msg = MsgConfigureMetisMISO::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureMetisMISO *msgToGUI = MsgConfigureMetisMISO::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void MetisMISO::webapiUpdateDeviceSettings( + MetisMISOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response) +{ + if (deviceSettingsKeys.contains("nbReceivers")) { + settings.m_nbReceivers = response.getMetisMisoSettings()->getNbReceivers(); + } + if (deviceSettingsKeys.contains("txEnable")) { + settings.m_txEnable = response.getMetisMisoSettings()->getTxEnable() != 0; + } + if (deviceSettingsKeys.contains("rx1CenterFrequency")) { + settings.m_rxCenterFrequencies[0] = response.getMetisMisoSettings()->getRx1CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx2CenterFrequency")) { + settings.m_rxCenterFrequencies[1] = response.getMetisMisoSettings()->getRx2CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx3CenterFrequency")) { + settings.m_rxCenterFrequencies[2] = response.getMetisMisoSettings()->getRx3CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx4CenterFrequency")) { + settings.m_rxCenterFrequencies[3] = response.getMetisMisoSettings()->getRx4CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx5CenterFrequency")) { + settings.m_rxCenterFrequencies[4] = response.getMetisMisoSettings()->getRx5CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx6CenterFrequency")) { + settings.m_rxCenterFrequencies[5] = response.getMetisMisoSettings()->getRx6CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx7CenterFrequency")) { + settings.m_rxCenterFrequencies[6] = response.getMetisMisoSettings()->getRx7CenterFrequency(); + } + if (deviceSettingsKeys.contains("rx8CenterFrequency")) { + settings.m_rxCenterFrequencies[7] = response.getMetisMisoSettings()->getRx8CenterFrequency(); + } + if (deviceSettingsKeys.contains("txCenterFrequency")) { + settings.m_txCenterFrequency = response.getMetisMisoSettings()->getTxCenterFrequency(); + } + if (deviceSettingsKeys.contains("rxTransverterMode")) { + settings.m_rxTransverterMode = response.getMetisMisoSettings()->getRxTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency")) { + settings.m_rxTransverterDeltaFrequency = response.getMetisMisoSettings()->getRxTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("txTransverterMode")) { + settings.m_txTransverterMode = response.getMetisMisoSettings()->getTxTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("txTransverterDeltaFrequency")) { + settings.m_txTransverterDeltaFrequency = response.getMetisMisoSettings()->getTxTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("iqOrder")) { + settings.m_iqOrder = response.getMetisMisoSettings()->getIqOrder() != 0; + } + if (deviceSettingsKeys.contains("rx1SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[0] = response.getMetisMisoSettings()->getRx1SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx2SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[1] = response.getMetisMisoSettings()->getRx2SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx3SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[2] = response.getMetisMisoSettings()->getRx3SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx4SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[3] = response.getMetisMisoSettings()->getRx4SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx5SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[4] = response.getMetisMisoSettings()->getRx5SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx6SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[5] = response.getMetisMisoSettings()->getRx6SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx7SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[6] = response.getMetisMisoSettings()->getRx7SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("rx8SubsamplingIndex")) { + settings.m_rxSubsamplingIndexes[7] = response.getMetisMisoSettings()->getRx8SubsamplingIndex(); + } + if (deviceSettingsKeys.contains("sampleRateIndex")) { + settings.m_sampleRateIndex = response.getMetisMisoSettings()->getSampleRateIndex(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getMetisMisoSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getMetisMisoSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("preamp")) { + settings.m_preamp = response.getMetisMisoSettings()->getPreamp() != 0; + } + if (deviceSettingsKeys.contains("random")) { + settings.m_random = response.getMetisMisoSettings()->getRandom() != 0; + } + if (deviceSettingsKeys.contains("dither")) { + settings.m_dither = response.getMetisMisoSettings()->getDither() != 0; + } + if (deviceSettingsKeys.contains("duplex")) { + settings.m_duplex = response.getMetisMisoSettings()->getDuplex() != 0; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getMetisMisoSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getMetisMisoSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("txDrive")) { + settings.m_txDrive = response.getMetisMisoSettings()->getTxDrive(); + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getMetisMisoSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getMetisMisoSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getMetisMisoSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getMetisMisoSettings()->getReverseApiDeviceIndex(); + } +} + +void MetisMISO::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const MetisMISOSettings& settings) +{ + response.getMetisMisoSettings()->setNbReceivers(settings.m_nbReceivers); + response.getMetisMisoSettings()->setTxEnable(settings.m_txEnable ? 1 : 0); + + response.getMetisMisoSettings()->setRx1CenterFrequency(settings.m_rxCenterFrequencies[0]); + response.getMetisMisoSettings()->setRx2CenterFrequency(settings.m_rxCenterFrequencies[1]); + response.getMetisMisoSettings()->setRx3CenterFrequency(settings.m_rxCenterFrequencies[2]); + response.getMetisMisoSettings()->setRx4CenterFrequency(settings.m_rxCenterFrequencies[3]); + response.getMetisMisoSettings()->setRx5CenterFrequency(settings.m_rxCenterFrequencies[4]); + response.getMetisMisoSettings()->setRx6CenterFrequency(settings.m_rxCenterFrequencies[5]); + response.getMetisMisoSettings()->setRx7CenterFrequency(settings.m_rxCenterFrequencies[6]); + response.getMetisMisoSettings()->setRx8CenterFrequency(settings.m_rxCenterFrequencies[7]); + + response.getMetisMisoSettings()->setRx1SubsamplingIndex(settings.m_rxSubsamplingIndexes[0]); + response.getMetisMisoSettings()->setRx2SubsamplingIndex(settings.m_rxSubsamplingIndexes[1]); + response.getMetisMisoSettings()->setRx3SubsamplingIndex(settings.m_rxSubsamplingIndexes[2]); + response.getMetisMisoSettings()->setRx4SubsamplingIndex(settings.m_rxSubsamplingIndexes[3]); + response.getMetisMisoSettings()->setRx5SubsamplingIndex(settings.m_rxSubsamplingIndexes[4]); + response.getMetisMisoSettings()->setRx6SubsamplingIndex(settings.m_rxSubsamplingIndexes[5]); + response.getMetisMisoSettings()->setRx7SubsamplingIndex(settings.m_rxSubsamplingIndexes[6]); + response.getMetisMisoSettings()->setRx8SubsamplingIndex(settings.m_rxSubsamplingIndexes[7]); + + response.getMetisMisoSettings()->setTxCenterFrequency(settings.m_txCenterFrequency); + response.getMetisMisoSettings()->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0); + response.getMetisMisoSettings()->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency); + response.getMetisMisoSettings()->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0); + response.getMetisMisoSettings()->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency); + response.getMetisMisoSettings()->setIqOrder(settings.m_iqOrder ? 1 : 0); + response.getMetisMisoSettings()->setSampleRateIndex(settings.m_sampleRateIndex); + response.getMetisMisoSettings()->setLog2Decim(settings.m_log2Decim); + response.getMetisMisoSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getMetisMisoSettings()->setPreamp(settings.m_preamp ? 1 : 0); + response.getMetisMisoSettings()->setRandom(settings.m_random ? 1 : 0); + response.getMetisMisoSettings()->setDither(settings.m_dither ? 1 : 0); + response.getMetisMisoSettings()->setDuplex(settings.m_duplex ? 1 : 0); + response.getMetisMisoSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getMetisMisoSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getMetisMisoSettings()->setTxDrive(settings.m_txDrive); + response.getMetisMisoSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getMetisMisoSettings()->getReverseApiAddress()) { + *response.getMetisMisoSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getMetisMisoSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getMetisMisoSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getMetisMisoSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +void MetisMISO::webapiReverseSendSettings(const QList& deviceSettingsKeys, const MetisMISOSettings& settings, bool force) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(2); // MIMO + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("MetisMISO")); + swgDeviceSettings->setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings()); + SWGSDRangel::SWGMetisMISOSettings *swgMetisMISOSettings = swgDeviceSettings->getMetisMisoSettings(); + + if (deviceSettingsKeys.contains("nbReceivers") || force) { + swgMetisMISOSettings->setNbReceivers(settings.m_nbReceivers); + } + if (deviceSettingsKeys.contains("txEnable") || force) { + swgMetisMISOSettings->setTxEnable(settings.m_txEnable ? 1 : 0); + } + + if (deviceSettingsKeys.contains("rx1CenterFrequency") || force) { + swgMetisMISOSettings->setRx1CenterFrequency(settings.m_rxCenterFrequencies[0]); + } + if (deviceSettingsKeys.contains("rx2CenterFrequency") || force) { + swgMetisMISOSettings->setRx2CenterFrequency(settings.m_rxCenterFrequencies[1]); + } + if (deviceSettingsKeys.contains("rx3CenterFrequency") || force) { + swgMetisMISOSettings->setRx3CenterFrequency(settings.m_rxCenterFrequencies[2]); + } + if (deviceSettingsKeys.contains("rx4CenterFrequency") || force) { + swgMetisMISOSettings->setRx4CenterFrequency(settings.m_rxCenterFrequencies[3]); + } + if (deviceSettingsKeys.contains("rx5CenterFrequency") || force) { + swgMetisMISOSettings->setRx5CenterFrequency(settings.m_rxCenterFrequencies[4]); + } + if (deviceSettingsKeys.contains("rx6CenterFrequency") || force) { + swgMetisMISOSettings->setRx6CenterFrequency(settings.m_rxCenterFrequencies[5]); + } + if (deviceSettingsKeys.contains("rx7CenterFrequency") || force) { + swgMetisMISOSettings->setRx7CenterFrequency(settings.m_rxCenterFrequencies[6]); + } + if (deviceSettingsKeys.contains("rx8CenterFrequency") || force) { + swgMetisMISOSettings->setRx8CenterFrequency(settings.m_rxCenterFrequencies[7]); + } + + if (deviceSettingsKeys.contains("rx1SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx1SubsamplingIndex(settings.m_rxSubsamplingIndexes[0]); + } + if (deviceSettingsKeys.contains("rx2SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx2SubsamplingIndex(settings.m_rxSubsamplingIndexes[1]); + } + if (deviceSettingsKeys.contains("rx3SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx3SubsamplingIndex(settings.m_rxSubsamplingIndexes[2]); + } + if (deviceSettingsKeys.contains("rx4SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx4SubsamplingIndex(settings.m_rxSubsamplingIndexes[3]); + } + if (deviceSettingsKeys.contains("rx5SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx5SubsamplingIndex(settings.m_rxSubsamplingIndexes[4]); + } + if (deviceSettingsKeys.contains("rx6SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx6SubsamplingIndex(settings.m_rxSubsamplingIndexes[5]); + } + if (deviceSettingsKeys.contains("rx7SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx7SubsamplingIndex(settings.m_rxSubsamplingIndexes[6]); + } + if (deviceSettingsKeys.contains("rx8SubsamplingIndex") || force) { + swgMetisMISOSettings->setRx8SubsamplingIndex(settings.m_rxSubsamplingIndexes[7]); + } + + if (deviceSettingsKeys.contains("txCenterFrequency") || force) { + swgMetisMISOSettings->setTxCenterFrequency(settings.m_txCenterFrequency); + } + if (deviceSettingsKeys.contains("rxTransverterMode") || force) { + swgMetisMISOSettings->setRxTransverterMode(settings.m_rxTransverterMode ? 1 : 0); + } + if (deviceSettingsKeys.contains("rxTransverterDeltaFrequency") || force) { + swgMetisMISOSettings->setRxTransverterDeltaFrequency(settings.m_rxTransverterDeltaFrequency); + } + if (deviceSettingsKeys.contains("txTransverterMode") || force) { + swgMetisMISOSettings->setTxTransverterMode(settings.m_txTransverterMode ? 1 : 0); + } + if (deviceSettingsKeys.contains("txTransverterDeltaFrequency") || force) { + swgMetisMISOSettings->setTxTransverterDeltaFrequency(settings.m_txTransverterDeltaFrequency); + } + if (deviceSettingsKeys.contains("iqOrder") || force) { + swgMetisMISOSettings->setIqOrder(settings.m_iqOrder ? 1 : 0); + } + if (deviceSettingsKeys.contains("sampleRateIndex") || force) { + swgMetisMISOSettings->setSampleRateIndex(settings.m_sampleRateIndex); + } + if (deviceSettingsKeys.contains("log2Decim") || force) { + swgMetisMISOSettings->setLog2Decim(settings.m_log2Decim); + } + if (deviceSettingsKeys.contains("LOppmTenths") || force) { + swgMetisMISOSettings->setLOppmTenths(settings.m_LOppmTenths); + } + if (deviceSettingsKeys.contains("preamp") || force) { + swgMetisMISOSettings->setPreamp(settings.m_preamp ? 1 : 0); + } + if (deviceSettingsKeys.contains("random") || force) { + swgMetisMISOSettings->setRandom(settings.m_random ? 1 : 0); + } + if (deviceSettingsKeys.contains("dither") || force) { + swgMetisMISOSettings->setDither(settings.m_dither ? 1 : 0); + } + if (deviceSettingsKeys.contains("duplex") || force) { + swgMetisMISOSettings->setDuplex(settings.m_duplex ? 1 : 0); + } + if (deviceSettingsKeys.contains("dcBlock") || force) { + swgMetisMISOSettings->setDcBlock(settings.m_dcBlock ? 1 : 0); + } + if (deviceSettingsKeys.contains("iqCorrection") || force) { + swgMetisMISOSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + } + if (deviceSettingsKeys.contains("txDrive") || force) { + swgMetisMISOSettings->setTxDrive(settings.m_txDrive); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgDeviceSettings; +} + +void MetisMISO::webapiReverseSendStartStop(bool start) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(2); // MIMO + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("MetisMISO")); + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + QNetworkReply *reply; + + if (start) { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer); + } else { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer); + } + + buffer->setParent(reply); + delete swgDeviceSettings; +} + +void MetisMISO::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "MetisMISO::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("MetisMISO::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/samplemimo/metismiso/metismiso.h b/plugins/samplemimo/metismiso/metismiso.h new file mode 100644 index 000000000..958d9e522 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismiso.h @@ -0,0 +1,169 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISO_H_ +#define _METISMISO_METISMISO_H_ + +#include +#include +#include +#include +#include + +#include "dsp/devicesamplemimo.h" +#include "metismisoudphandler.h" +#include "metismisosettings.h" + +class DeviceAPI; +class MetisMISOWorker; +class QNetworkAccessManager; +class QNetworkReply; + +class MetisMISO : public DeviceSampleMIMO { + Q_OBJECT +public: + class MsgConfigureMetisMISO : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const MetisMISOSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureMetisMISO* create(const MetisMISOSettings& settings, bool force) + { + return new MsgConfigureMetisMISO(settings, force); + } + + private: + MetisMISOSettings m_settings; + bool m_force; + + MsgConfigureMetisMISO(const MetisMISOSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + MetisMISO(DeviceAPI *deviceAPI); + virtual ~MetisMISO(); + virtual void destroy(); + + virtual void init(); + virtual bool startRx(); + virtual void stopRx(); + virtual bool startTx(); + virtual void stopTx(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + + virtual int getSourceSampleRate(int index) const; + virtual void setSourceSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; } + virtual quint64 getSourceCenterFrequency(int index) const; + virtual void setSourceCenterFrequency(qint64 centerFrequency, int index); + + virtual int getSinkSampleRate(int index) const { return 0; (void) index; } + virtual void setSinkSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; } + virtual quint64 getSinkCenterFrequency(int index) const { return 0; (void) index; } + virtual void setSinkCenterFrequency(qint64 centerFrequency, int index) { (void) centerFrequency; (void) index; } + + virtual quint64 getMIMOCenterFrequency() const { return getSourceCenterFrequency(0); } + virtual unsigned int getMIMOSampleRate() const { return getSourceSampleRate(0); } + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiRunGet( + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + static void webapiFormatDeviceSettings( + SWGSDRangel::SWGDeviceSettings& response, + const MetisMISOSettings& settings); + + static void webapiUpdateDeviceSettings( + MetisMISOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response); + +private: + struct DeviceSettingsKeys + { + QList m_commonSettingsKeys; + QList> m_streamsSettingsKeys; + }; + + DeviceAPI *m_deviceAPI; + QMutex m_mutex; + MetisMISOSettings m_settings; + MetisMISOUDPHandler m_udpHandler; + QString m_deviceDescription; + bool m_running; + const QTimer& m_masterTimer; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void startMetis(); + void stopMetis(); + bool applySettings(const MetisMISOSettings& settings, bool force); + void webapiReverseSendSettings(const QList& deviceSettingsKeys, const MetisMISOSettings& settings, bool force); + void webapiReverseSendStartStop(bool start); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // _METISMISO_METISMISO_H_ diff --git a/plugins/samplemimo/metismiso/metismisodecimators.cpp b/plugins/samplemimo/metismiso/metismisodecimators.cpp new file mode 100644 index 000000000..24d093b7f --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisodecimators.cpp @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "metismisodecimators.h" + +MetisMISODecimators::MetisMISODecimators() +{ + resetCounters(); +} + +void MetisMISODecimators::resetCounters() +{ + std::fill(m_counters, m_counters+MetisMISOSettings::m_maxReceivers, 0); +} + +int MetisMISODecimators::decimate2(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex) +{ + if (streamIndex < MetisMISOSettings::m_maxReceivers) + { + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI; + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ; + + if (m_counters[streamIndex] >= 8) + { + SampleVector::iterator it = result.begin(); + m_decimatorsIQ[streamIndex].decimate2_cen(&it, m_accumulators[streamIndex], 8); + m_counters[streamIndex] = 0; + return 2; // 2 samples available + } + } + + return 0; +} + +int MetisMISODecimators::decimate4(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex) +{ + if (streamIndex < MetisMISOSettings::m_maxReceivers) + { + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI; + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ; + + if (m_counters[streamIndex] >= 16) + { + SampleVector::iterator it = result.begin(); + m_decimatorsIQ[streamIndex].decimate4_cen(&it, m_accumulators[streamIndex], 16); + m_counters[streamIndex] = 0; + return 2; // 2 samples available + } + } + + return 0; +} + +int MetisMISODecimators::decimate8(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex) +{ + if (streamIndex < MetisMISOSettings::m_maxReceivers) + { + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleI; + m_accumulators[streamIndex][m_counters[streamIndex]++] = sampleQ; + + if (m_counters[streamIndex] >= 16) + { + SampleVector::iterator it = result.begin(); + m_decimatorsIQ[streamIndex].decimate8_cen(&it, m_accumulators[streamIndex], 16); + m_counters[streamIndex] = 0; + return 1; // 1 sample available + } + } + + return 0; +} diff --git a/plugins/samplemimo/metismiso/metismisodecimators.h b/plugins/samplemimo/metismiso/metismisodecimators.h new file mode 100644 index 000000000..ef76bd99f --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisodecimators.h @@ -0,0 +1,45 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Decimators adapters specific to Metis // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISODECIMATORS_H_ +#define _METISMISO_METISMISODECIMATORS_H_ + +#include "dsp/decimators.h" + +#include "metismisosettings.h" + +class MetisMISODecimators +{ +public: + MetisMISODecimators(); + + int decimate2(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex); + int decimate4(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex); + int decimate8(qint32 sampleI, qint32 sampleQ, SampleVector& result, unsigned int streamIndex); + void resetCounters(); + +private: + + qint32 m_accumulators[MetisMISOSettings::m_maxReceivers][256*2]; + int m_counters[MetisMISOSettings::m_maxReceivers]; + Decimators m_decimatorsIQ[MetisMISOSettings::m_maxReceivers]; +}; + + +#endif // _METISMISO_METISMISODECIMATORS_H_ diff --git a/plugins/samplemimo/metismiso/metismisogui.cpp b/plugins/samplemimo/metismiso/metismisogui.cpp new file mode 100644 index 000000000..dab5f5844 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisogui.cpp @@ -0,0 +1,568 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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 "util/db.h" + +#include "mainwindow.h" + +#include "ui_metismisogui.h" +#include "metismisogui.h" + +MetisMISOGui::MetisMISOGui(DeviceUISet *deviceUISet, QWidget* parent) : + DeviceGUI(parent), + ui(new Ui::MetisMISOGui), + m_deviceUISet(deviceUISet), + m_settings(), + m_doApplySettings(true), + m_forceSettings(true), + m_sampleMIMO(nullptr), + m_tickCount(0), + m_lastEngineState(DeviceAPI::StNotStarted) +{ + qDebug("MetisMISOGui::MetisMISOGui"); + m_sampleMIMO = m_deviceUISet->m_deviceAPI->getSampleMIMO(); + m_streamIndex = 0; + m_spectrumStreamIndex = 0; + m_rxSampleRate = 48000; + m_txSampleRate = 48000; + + ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, 0, 61440000); + + displaySettings(); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleMIMO->setMessageQueueToGUI(&m_inputMessageQueue); + + CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop); + connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &))); +} + +MetisMISOGui::~MetisMISOGui() +{ + delete ui; +} + +void MetisMISOGui::destroy() +{ + delete this; +} + +void MetisMISOGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +void MetisMISOGui::setCenterFrequency(qint64 centerFrequency) +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) { + m_settings.m_rxCenterFrequencies[m_streamIndex] = centerFrequency; + } else if (m_streamIndex == MetisMISOSettings::m_maxReceivers) { + m_settings.m_txCenterFrequency = centerFrequency; + } + + displaySettings(); + sendSettings(); +} + +QByteArray MetisMISOGui::serialize() const +{ + return m_settings.serialize(); +} + +bool MetisMISOGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void MetisMISOGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + MetisMISO::MsgStartStop *message = MetisMISO::MsgStartStop::create(checked); + m_sampleMIMO->getInputMessageQueue()->push(message); + } +} + +void MetisMISOGui::on_streamIndex_currentIndexChanged(int index) +{ + if (ui->streamLock->isChecked()) + { + m_spectrumStreamIndex = index; + + if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) + { + m_deviceUISet->m_spectrum->setDisplayedStream(true, index); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, m_spectrumStreamIndex); + m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF); + } + else + { + m_deviceUISet->m_spectrum->setDisplayedStream(false, 0); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF); + } + + updateSpectrum(); + + ui->spectrumSource->blockSignals(true); + ui->spectrumSource->setCurrentIndex(index); + ui->spectrumSource->blockSignals(false); + } + + m_streamIndex = index; + + updateSubsamplingIndex(); + displayFrequency(); + displaySampleRate(); +} + +void MetisMISOGui::on_spectrumSource_currentIndexChanged(int index) +{ + m_spectrumStreamIndex = index; + + if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) + { + m_deviceUISet->m_spectrum->setDisplayedStream(true, index); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, m_spectrumStreamIndex); + m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF); + } + else + { + m_deviceUISet->m_spectrum->setDisplayedStream(false, 0); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF); + } + + updateSpectrum(); + + if (ui->streamLock->isChecked()) + { + ui->streamIndex->blockSignals(true); + ui->streamIndex->setCurrentIndex(index); + ui->streamIndex->blockSignals(false); + m_streamIndex = index; + updateSubsamplingIndex(); + displayFrequency(); + displaySampleRate(); + } +} + +void MetisMISOGui::on_streamLock_toggled(bool checked) +{ + if (checked && (ui->streamIndex->currentIndex() != ui->spectrumSource->currentIndex())) { + ui->spectrumSource->setCurrentIndex(ui->streamIndex->currentIndex()); + } +} + +void MetisMISOGui::on_LOppm_valueChanged(int value) +{ + m_settings.m_LOppmTenths = value; + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + sendSettings(); +} + +void MetisMISOGui::on_centerFrequency_changed(quint64 value) +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) { + m_settings.m_rxCenterFrequencies[m_streamIndex] = value * 1000; + } else if (m_streamIndex == MetisMISOSettings::m_maxReceivers) { + m_settings.m_txCenterFrequency = value * 1000; + } + + sendSettings(); +} + +void MetisMISOGui::on_samplerateIndex_currentIndexChanged(int index) +{ + m_settings.m_sampleRateIndex = index < 0 ? 0 : index > 3 ? 3 : index; + sendSettings(); +} + +void MetisMISOGui::on_log2Decim_currentIndexChanged(int index) +{ + m_settings.m_log2Decim = index < 0 ? 0 : index > 3 ? 3 : index; + // displaySampleRate(); + sendSettings(); +} + +void MetisMISOGui::on_subsamplingIndex_currentIndexChanged(int index) +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) // valid for Rx only + { + m_settings.m_rxSubsamplingIndexes[m_streamIndex] = index; + ui->subsamplingIndex->setToolTip(tr("Subsampling band index [%1 - %2 MHz]") + .arg(index*61.44).arg((index+1)*61.44)); + displayFrequency(); + setCenterFrequency(ui->centerFrequency->getValueNew() * 1000); + sendSettings(); + } +} + +void MetisMISOGui::on_dcBlock_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + sendSettings(); +} + +void MetisMISOGui::on_iqCorrection_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + sendSettings(); +} + +void MetisMISOGui::on_transverter_clicked() +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) + { + m_settings.m_rxTransverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_rxTransverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + m_settings.m_iqOrder = ui->transverter->getIQOrder(); + qDebug("MetisMISOGui::on_transverter_clicked: Rx: %lld Hz %s", m_settings.m_rxTransverterDeltaFrequency, m_settings.m_rxTransverterMode ? "on" : "off"); + } + else + { + m_settings.m_txTransverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_txTransverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("MetisMISOGui::on_transverter_clicked: Tx: %lld Hz %s", m_settings.m_txTransverterDeltaFrequency, m_settings.m_txTransverterMode ? "on" : "off"); + } + + displayFrequency(); + setCenterFrequency(ui->centerFrequency->getValueNew() * 1000); + sendSettings(); +} + +void MetisMISOGui::on_preamp_toggled(bool checked) +{ + m_settings.m_preamp = checked; + sendSettings(); +} + +void MetisMISOGui::on_random_toggled(bool checked) +{ + m_settings.m_random = checked; + sendSettings(); +} + +void MetisMISOGui::on_dither_toggled(bool checked) +{ + m_settings.m_dither = checked; + sendSettings(); +} + +void MetisMISOGui::on_duplex_toggled(bool checked) +{ + m_settings.m_duplex = checked; + sendSettings(); +} + +void MetisMISOGui::on_nbRxIndex_currentIndexChanged(int index) +{ + m_settings.m_nbReceivers = index + 1; + sendSettings(); +} + +void MetisMISOGui::on_txEnable_toggled(bool checked) +{ + m_settings.m_txEnable = checked; + sendSettings(); +} + +void MetisMISOGui::on_txDrive_valueChanged(int value) +{ + m_settings.m_txDrive = value; + ui->txDriveText->setText(tr("%1").arg(m_settings.m_txDrive)); + sendSettings(); +} + +void MetisMISOGui::displaySettings() +{ + blockApplySettings(true); + + ui->streamIndex->setCurrentIndex(m_streamIndex); + ui->spectrumSource->setCurrentIndex(m_spectrumStreamIndex); + ui->nbRxIndex->setCurrentIndex(m_settings.m_nbReceivers - 1); + ui->samplerateIndex->setCurrentIndex(m_settings.m_sampleRateIndex); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + ui->log2Decim->setCurrentIndex(m_settings.m_log2Decim); + ui->dcBlock->setChecked(m_settings.m_dcBlock); + ui->iqCorrection->setChecked(m_settings.m_iqCorrection); + ui->preamp->setChecked(m_settings.m_preamp); + ui->random->setChecked(m_settings.m_random); + ui->dither->setChecked(m_settings.m_dither); + ui->duplex->setChecked(m_settings.m_duplex); + ui->nbRxIndex->setCurrentIndex(m_settings.m_nbReceivers - 1); + ui->txEnable->setChecked(m_settings.m_txEnable); + ui->txDrive->setValue(m_settings.m_txDrive); + ui->txDriveText->setText(tr("%1").arg(m_settings.m_txDrive)); + updateSubsamplingIndex(); + displayFrequency(); + displaySampleRate(); + updateSpectrum(); + + blockApplySettings(false); +} + +void MetisMISOGui::sendSettings() +{ + if(!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void MetisMISOGui::updateHardware() +{ + if (m_doApplySettings) + { + MetisMISO::MsgConfigureMetisMISO* message = MetisMISO::MsgConfigureMetisMISO::create(m_settings, m_forceSettings); + m_sampleMIMO->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void MetisMISOGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DeviceAPI::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DeviceAPI::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DeviceAPI::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DeviceAPI::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + +bool MetisMISOGui::handleMessage(const Message& message) +{ + if (MetisMISO::MsgConfigureMetisMISO::match(message)) + { + qDebug("MetisMISOGui::handleMessage: MsgConfigureMetisMISO"); + const MetisMISO::MsgConfigureMetisMISO& cfg = (MetisMISO::MsgConfigureMetisMISO&) message; + m_settings = cfg.getSettings(); + displaySettings(); + return true; + } + else if (MetisMISO::MsgStartStop::match(message)) + { + qDebug("MetisMISOGui::handleMessage: MsgStartStop"); + MetisMISO::MsgStartStop& notif = (MetisMISO::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void MetisMISOGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (DSPMIMOSignalNotification::match(*message)) + { + DSPMIMOSignalNotification* notif = (DSPMIMOSignalNotification*) message; + int istream = notif->getIndex(); + bool sourceOrSink = notif->getSourceOrSink(); + qint64 frequency = notif->getCenterFrequency(); + + if (sourceOrSink) + { + m_rxSampleRate = notif->getSampleRate(); + + if (istream < MetisMISOSettings::m_maxReceivers) { + m_settings.m_rxCenterFrequencies[istream] = frequency; + } + } + else + { + m_txSampleRate = notif->getSampleRate(); + m_settings.m_txCenterFrequency = frequency; + } + + qDebug() << "MetisMISOGui::handleInputMessages: DSPMIMOSignalNotification: " + << "sourceOrSink:" << sourceOrSink + << "istream:" << istream + << "m_rxSampleRate:" << m_rxSampleRate + << "m_txSampleRate:" << m_txSampleRate + << "frequency:" << frequency; + + displayFrequency(); + displaySampleRate(); + updateSpectrum(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void MetisMISOGui::displayFrequency() +{ + qint64 centerFrequency; + qint64 fBaseLow, fBaseHigh; + + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) + { + int subsamplingIndex = m_settings.m_rxSubsamplingIndexes[m_streamIndex]; + centerFrequency = m_settings.m_rxCenterFrequencies[m_streamIndex]; + fBaseLow = subsamplingIndex*61440; + fBaseHigh = (subsamplingIndex+1)*61440; + } + else if (m_streamIndex == MetisMISOSettings::m_maxReceivers) + { + centerFrequency = m_settings.m_txCenterFrequency; + fBaseLow = 0; + fBaseHigh = 61440; + } + else + { + fBaseLow = 0; + fBaseHigh = 61440; + centerFrequency = 0; + } + + ui->centerFrequency->setValueRange(7, fBaseLow, fBaseHigh); + ui->centerFrequency->setValue(centerFrequency / 1000); +} + +void MetisMISOGui::displaySampleRate() +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) { + ui->deviceRateText->setText(tr("%1k").arg((float) m_rxSampleRate / 1000)); + } else { + ui->deviceRateText->setText(tr("%1k").arg((float) m_txSampleRate / 1000)); + } +} + +void MetisMISOGui::updateSpectrum() +{ + qint64 centerFrequency; + + if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) { + centerFrequency = m_settings.m_rxCenterFrequencies[m_spectrumStreamIndex]; + } else if (m_spectrumStreamIndex == MetisMISOSettings::m_maxReceivers) { + centerFrequency = m_settings.m_txCenterFrequency; + } else { + centerFrequency = 0; + } + + m_deviceUISet->getSpectrum()->setCenterFrequency(centerFrequency); + + if (m_spectrumStreamIndex < MetisMISOSettings::m_maxReceivers) { + m_deviceUISet->getSpectrum()->setSampleRate(m_rxSampleRate); + } else { + m_deviceUISet->getSpectrum()->setSampleRate(m_txSampleRate); + } +} + +void MetisMISOGui::updateSubsamplingIndex() +{ + if (m_streamIndex < MetisMISOSettings::m_maxReceivers) + { + ui->subsamplingIndex->setEnabled(true); + ui->subsamplingIndex->setCurrentIndex(m_settings.m_rxSubsamplingIndexes[m_streamIndex]); + } + else + { + ui->subsamplingIndex->setEnabled(false); + ui->subsamplingIndex->setToolTip("No subsampling for Tx"); + } + +} + +void MetisMISOGui::openDeviceSettingsDialog(const QPoint& p) +{ + BasicDeviceSettingsDialog dialog(this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + + sendSettings(); +} diff --git a/plugins/samplemimo/metismiso/metismisogui.h b/plugins/samplemimo/metismiso/metismisogui.h new file mode 100644 index 000000000..96a33bfcb --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisogui.h @@ -0,0 +1,105 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISOGUI_H_ +#define _METISMISO_METISMISOGUI_H_ + +#include +#include +#include + +#include "util/messagequeue.h" + +#include "metismisosettings.h" +#include "metismiso.h" + +class DeviceUISet; + +namespace Ui { + class MetisMISOGui; +} + +class MetisMISOGui : public DeviceGUI { + Q_OBJECT + +public: + explicit MetisMISOGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~MetisMISOGui(); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +private: + Ui::MetisMISOGui* ui; + + DeviceUISet* m_deviceUISet; + MetisMISOSettings m_settings; + int m_streamIndex; //!< Current stream index being dealt with + int m_spectrumStreamIndex; //!< Index of the stream displayed on main spectrum + int m_rxSampleRate; + int m_txSampleRate; + QTimer m_updateTimer; + QTimer m_statusTimer; + bool m_doApplySettings; + bool m_forceSettings; + DeviceSampleMIMO* m_sampleMIMO; + std::size_t m_tickCount; + std::vector m_deviceSampleRates; + std::vector m_deviceCenterFrequencies; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displayFrequency(); + void displaySampleRate(); + void updateSubsamplingIndex(); + void updateSpectrum(); + void sendSettings(); + void setCenterFrequency(qint64 centerFrequency); + bool handleMessage(const Message& message); + +private slots: + void handleInputMessages(); + void on_streamIndex_currentIndexChanged(int index); + void on_spectrumSource_currentIndexChanged(int index); + void on_streamLock_toggled(bool checked); + void on_LOppm_valueChanged(int value); + void on_startStop_toggled(bool checked); + void on_centerFrequency_changed(quint64 value); + void on_samplerateIndex_currentIndexChanged(int index); + void on_log2Decim_currentIndexChanged(int index); + void on_subsamplingIndex_currentIndexChanged(int index); + void on_dcBlock_toggled(bool checked); + void on_iqCorrection_toggled(bool checked); + void on_transverter_clicked(); + void on_preamp_toggled(bool checked); + void on_random_toggled(bool checked); + void on_dither_toggled(bool checked); + void on_duplex_toggled(bool checked); + void on_nbRxIndex_currentIndexChanged(int index); + void on_txEnable_toggled(bool checked); + void on_txDrive_valueChanged(int value); + void openDeviceSettingsDialog(const QPoint& p); + void updateStatus(); + void updateHardware(); +}; + +#endif // _METISMISO_METISMISOGUI_H_ diff --git a/plugins/samplemimo/metismiso/metismisogui.ui b/plugins/samplemimo/metismiso/metismisogui.ui new file mode 100644 index 000000000..bed1e68ef --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisogui.ui @@ -0,0 +1,841 @@ + + + MetisMISOGui + + + + 0 + 0 + 483 + 228 + + + + + 0 + 0 + + + + + 360 + 200 + + + + + Liberation Sans + 9 + 50 + false + false + + + + Metis MISO + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Stream + + + + + + + + 60 + 0 + + + + Select stream to which settings apply (frequency only) + + + + Rx0 + + + + + Rx1 + + + + + Rx2 + + + + + Rx3 + + + + + Rx4 + + + + + Rx5 + + + + + Rx6 + + + + + Rx7 + + + + + Tx + + + + + + + + Spectrum + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Select stream for main spectrum source + + + + Rx0 + + + + + Rx1 + + + + + Rx2 + + + + + Rx3 + + + + + Rx4 + + + + + Rx5 + + + + + Rx6 + + + + + Rx7 + + + + + Tx + + + + + + + + Lock spectrum display to stream selection + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + + 58 + 0 + + + + I/Q sample rate kS/s + + + 0000.00k + + + 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 + + + + + + + + + + 3 + + + + + LO correction ppm + + + LO ppm + + + + + + + Local Oscillator frequency correction (ppm) + + + -500 + + + 500 + + + 1 + + + Qt::Horizontal + + + + + + + 0.0 + + + + + + + + + + + + 0 + 0 + + + + + 16 + 0 + + + + SR + + + + + + + + 70 + 16777215 + + + + Modulation + + + + 48k + + + + + 96k + + + + + 192k + + + + + 384k + + + + + + + + Dec + + + + + + + + 50 + 16777215 + + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + + + + Sub + + + + + + + + 40 + 16777215 + + + + Subsampling band index + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Corr + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + DC + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + IQ + + + + + + + + + + + Toggle preamplifier + + + PRE + + + + + + + Toggle LT2208 Random + + + RAN + + + + + + + Toggle LT2208 Dither + + + DITH + + + + + + + Toggle duplex + + + DUP + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 0 + + + + #Rx + + + + + + + + 40 + 16777215 + + + + Number of active receivers + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + + + + Enable/disable Tx + + + Tx + + + + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + + + + + + + + + Tx Drv + + + + + + + + 24 + 24 + + + + Tx drive level (0: off) + + + 15 + + + 1 + + + 15 + + + + + + + 00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 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/metismiso/metismisoplugin.cpp b/plugins/samplemimo/metismiso/metismisoplugin.cpp new file mode 100644 index 000000000..3eaf3bcfe --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisoplugin.cpp @@ -0,0 +1,139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "plugin/pluginapi.h" +#include "metis/devicemetis.h" +#include "util/simpleserializer.h" + +#ifdef SERVER_MODE +#include "metismiso.h" +#else +#include "metismisogui.h" +#endif +#include "metismisoplugin.h" +#include "metismisowebapiadapter.h" + +const PluginDescriptor MetisMISOPlugin::m_pluginDescriptor = { + QString("MetisMISO"), + QString("Metis MISO"), + QString("6.0.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString MetisMISOPlugin::m_hardwareID = "MetisMISO"; +const QString MetisMISOPlugin::m_deviceTypeID = METISMISO_DEVICE_TYPE_ID; + +MetisMISOPlugin::MetisMISOPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& MetisMISOPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void MetisMISOPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleMIMO(m_deviceTypeID, this); +} + +void MetisMISOPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + DeviceMetis::instance().enumOriginDevices(m_hardwareID, originDevices); + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices MetisMISOPlugin::enumSampleMIMO(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + result.append(SamplingDevice( + it->displayableName, + it->hardwareId, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::PhysicalDevice, + PluginInterface::SamplingDevice::StreamMIMO, + 1, // MIMO is always considered as a single device + 0) + ); + qDebug("MetisMISOPlugin::enumSampleMIMO: enumerated Metis device #%d", it->sequence); + } + } + + return result; +} + +#ifdef SERVER_MODE +DeviceGUI* MetisMISOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sourceId; + (void) widget; + (void) deviceUISet; + return 0; +} +#else +DeviceGUI* MetisMISOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if (sourceId == m_deviceTypeID) { + MetisMISOGui* gui = new MetisMISOGui(deviceUISet); + *widget = gui; + return gui; + } else { + return nullptr; + } +} +#endif + +DeviceSampleMIMO *MetisMISOPlugin::createSampleMIMOPluginInstance(const QString& mimoId, DeviceAPI *deviceAPI) +{ + if (mimoId == m_deviceTypeID) + { + MetisMISO* input = new MetisMISO(deviceAPI); + return input; + } + else + { + return nullptr; + } +} + +DeviceWebAPIAdapter *MetisMISOPlugin::createDeviceWebAPIAdapter() const +{ + return new MetisMISOWebAPIAdapter(); +} diff --git a/plugins/samplemimo/metismiso/metismisoplugin.h b/plugins/samplemimo/metismiso/metismisoplugin.h new file mode 100644 index 000000000..81d9655bd --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisoplugin.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMIMOPLUGIN_H +#define _METISMISO_METISMIMOPLUGIN_H + +#include + +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define METISMISO_DEVICE_TYPE_ID "sdrangel.samplemimo.metismiso" + +class MetisMISOPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID METISMISO_DEVICE_TYPE_ID) + +public: + explicit MetisMISOPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleMIMO(const OriginDevices& originDevices); + virtual DeviceGUI* createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleMIMO* createSampleMIMOPluginInstance(const QString& sourceId, DeviceAPI *deviceAPI); + virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const; + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // _METISMISO_METISMIMOPLUGIN_H diff --git a/plugins/samplemimo/metismiso/metismisosettings.cpp b/plugins/samplemimo/metismiso/metismisosettings.cpp new file mode 100644 index 000000000..89db63eaf --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisosettings.cpp @@ -0,0 +1,187 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "util/simpleserializer.h" +#include "metismisosettings.h" + +MetisMISOSettings::MetisMISOSettings() +{ + resetToDefaults(); +} + +MetisMISOSettings::MetisMISOSettings(const MetisMISOSettings& other) +{ + m_nbReceivers = other.m_nbReceivers; + m_txEnable = other.m_txEnable; + std::copy(other.m_rxCenterFrequencies, other.m_rxCenterFrequencies + m_maxReceivers, m_rxCenterFrequencies); + std::copy(other.m_rxSubsamplingIndexes, other.m_rxSubsamplingIndexes + m_maxReceivers, m_rxSubsamplingIndexes); + m_txCenterFrequency = other.m_txCenterFrequency; + m_rxTransverterMode = other.m_rxTransverterMode; + m_rxTransverterDeltaFrequency = other.m_rxTransverterDeltaFrequency; + m_txTransverterMode = other.m_txTransverterMode; + m_txTransverterDeltaFrequency = other.m_txTransverterDeltaFrequency; + m_iqOrder = other.m_iqOrder; + m_sampleRateIndex = other.m_sampleRateIndex; + m_log2Decim = other.m_log2Decim; + m_LOppmTenths = other.m_LOppmTenths; + m_preamp = other.m_preamp; + m_random = other.m_random; + m_dither = other.m_dither; + m_duplex = other.m_duplex; + m_dcBlock = other.m_dcBlock; + m_iqCorrection = other.m_iqCorrection; + m_txDrive = other.m_txDrive; + m_useReverseAPI = other.m_useReverseAPI; + m_reverseAPIAddress = other.m_reverseAPIAddress; + m_reverseAPIPort = other.m_reverseAPIPort; + m_reverseAPIDeviceIndex = other.m_reverseAPIDeviceIndex; +} + +void MetisMISOSettings::resetToDefaults() +{ + m_nbReceivers = 1; + m_txEnable = false; + std::fill(m_rxCenterFrequencies, m_rxCenterFrequencies + m_maxReceivers, 7074000); + std::fill(m_rxSubsamplingIndexes, m_rxSubsamplingIndexes + m_maxReceivers, 0); + m_txCenterFrequency = 7074000; + m_rxTransverterMode = false; + m_rxTransverterDeltaFrequency = 0; + m_txTransverterMode = false; + m_txTransverterDeltaFrequency = 0; + m_iqOrder = true; + m_sampleRateIndex = 0; // 48000 kS/s + m_log2Decim = 0; + m_LOppmTenths = 0; + m_preamp = false; + m_random = false; + m_dither = false; + m_duplex = false; + m_dcBlock = false; + m_iqCorrection = false; + m_txDrive = 15; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; +} + +QByteArray MetisMISOSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeU32(1, m_nbReceivers); + s.writeBool(2, m_txEnable); + s.writeU64(3, m_txCenterFrequency); + s.writeBool(4, m_rxTransverterMode); + s.writeS64(5, m_rxTransverterDeltaFrequency); + s.writeBool(6, m_txTransverterMode); + s.writeS64(7, m_txTransverterDeltaFrequency); + s.writeBool(8, m_iqOrder); + s.writeU32(9, m_sampleRateIndex); + s.writeU32(10, m_log2Decim); + s.writeS32(11, m_LOppmTenths); + s.writeBool(12, m_preamp); + s.writeBool(13, m_random); + s.writeBool(14, m_dither); + s.writeBool(15, m_duplex); + s.writeBool(16, m_dcBlock); + s.writeBool(17, m_iqCorrection); + s.writeU32(18, m_txDrive); + s.writeBool(19, m_useReverseAPI); + s.writeString(20, m_reverseAPIAddress); + s.writeU32(21, m_reverseAPIPort); + s.writeU32(22, m_reverseAPIDeviceIndex); + + for (int i = 0; i < m_maxReceivers; i++) + { + s.writeU64(30+i, m_rxCenterFrequencies[i]); + s.writeU32(50+i, m_rxSubsamplingIndexes[i]); + } + + return s.final(); +} + +bool MetisMISOSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + uint32_t utmp; + + d.readU32(1, &m_nbReceivers, 1); + d.readBool(2, &m_txEnable, false); + d.readU64(3, &m_txCenterFrequency, 7074000); + d.readBool(4, &m_rxTransverterMode, false); + d.readS64(5, &m_rxTransverterDeltaFrequency, 0); + d.readBool(6, &m_txTransverterMode, false); + d.readS64(7, &m_txTransverterDeltaFrequency, 0); + d.readBool(8, &m_iqOrder, true); + d.readU32(9, &m_sampleRateIndex, 0); + d.readU32(10, &m_log2Decim, 0); + d.readS32(11, &m_LOppmTenths, 0); + d.readBool(12, &m_preamp, false); + d.readBool(13, &m_random, false); + d.readBool(14, &m_dither, false); + d.readBool(15, &m_duplex, false); + d.readBool(16, &m_dcBlock, false); + d.readBool(17, &m_iqCorrection, false); + d.readU32(18, &m_txDrive, 15); + d.readBool(19, &m_useReverseAPI, false); + d.readString(20, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(21, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(22, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < m_maxReceivers; i++) + { + d.readU64(30+i, &m_rxCenterFrequencies[i], 7074000); + d.readU32(50+i, &m_rxSubsamplingIndexes[i], 0); + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +int MetisMISOSettings::getSampleRateFromIndex(unsigned int index) +{ + if (index < 3) { + return (1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISOSETTINGS_H_ +#define _METISMISO_METISMISOSETTINGS_H_ + +#include + +struct MetisMISOSettings { + static const int m_maxReceivers = 8; + unsigned int m_nbReceivers; + bool m_txEnable; + quint64 m_rxCenterFrequencies[m_maxReceivers]; + unsigned int m_rxSubsamplingIndexes[m_maxReceivers]; + quint64 m_txCenterFrequency; + bool m_rxTransverterMode; + qint64 m_rxTransverterDeltaFrequency; + bool m_txTransverterMode; + qint64 m_txTransverterDeltaFrequency; + bool m_iqOrder; + unsigned int m_sampleRateIndex; + unsigned int m_log2Decim; + int m_LOppmTenths; + bool m_preamp; + bool m_random; + bool m_dither; + bool m_duplex; + bool m_dcBlock; + bool m_iqCorrection; + unsigned int m_txDrive; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + + MetisMISOSettings(); + MetisMISOSettings(const MetisMISOSettings& other); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + static int getSampleRateFromIndex(unsigned int index); +}; + + +#endif /* _METISMISO_METISMISOSETTINGS_H_ */ diff --git a/plugins/samplemimo/metismiso/metismisoudphandler.cpp b/plugins/samplemimo/metismiso/metismisoudphandler.cpp new file mode 100644 index 000000000..1a46af72f --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisoudphandler.cpp @@ -0,0 +1,659 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/samplemififo.h" +#include "dsp/samplemofifo.h" + +#include "metismisoudphandler.h" + +MESSAGE_CLASS_DEFINITION(MetisMISOUDPHandler::MsgStartStop, Message) + +MetisMISOUDPHandler::MetisMISOUDPHandler(SampleMIFifo *sampleMIFifo, SampleMOFifo *sampleMOFifo, DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_socket(nullptr), + m_metisAddress(QHostAddress::LocalHost), + m_metisPort(9090), + m_running(false), + m_dataConnected(false), + m_sampleMIFifo(sampleMIFifo), + m_sampleMOFifo(sampleMOFifo), + m_sampleCount(0), + m_sampleTxCount(0), + m_messageQueueToGUI(nullptr), + m_mutex(QMutex::Recursive), + m_commandBase(0), + m_receiveSequence(0), + m_receiveSequenceError(0) +{ + setNbReceivers(m_settings.m_nbReceivers); + + for (unsigned int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) { + m_convertBuffer[i].resize(1024, Sample{0,0}); + } + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages())); +} + +MetisMISOUDPHandler::~MetisMISOUDPHandler() +{ + stop(); +} + +void MetisMISOUDPHandler::start() +{ + qDebug("MetisMISOUDPHandler::start"); + + m_rxFrame = 0; + m_txFrame = 0; + m_sendSequence = -1; + m_offset = 8; + m_receiveSequence = 0; + + if (m_running) { + return; + } + + if (!m_dataConnected) + { + if (m_socket.bind(QHostAddress::AnyIPv4, 10001, QUdpSocket::ShareAddress)) + { + qDebug("MetisMISOUDPHandler::start: bind host socket OK"); + connect(&m_socket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); + m_dataConnected = true; + } + else + { + qWarning("MetisMISOUDPHandler::start: cannot bind host socket"); + m_dataConnected = false; + return; + } + } + + // Start Metis + unsigned char buffer[64]; + + buffer[0] = (unsigned char) 0xEF; + buffer[1] = (unsigned char) 0XFE; + buffer[2] = (unsigned char) 0x04; + buffer[3] = (unsigned char) 0x01; + std::fill(&buffer[4], &buffer[64], 0); + + if (m_socket.writeDatagram((const char*) buffer, sizeof(buffer), m_metisAddress, m_metisPort) < 0) + { + qDebug() << "MetisMISOUDPHandler::start: writeDatagram start command failed " << m_socket.errorString(); + return; + } + else + { + qDebug() << "MetisMISOUDPHandler::start: writeDatagram start command" << m_metisAddress.toString() << ":" << m_metisPort; + } + + m_socket.flush(); + + // send 2 frames with control data + + sendData(); + sendData(); // TODO: on the next send frequencies + + m_running = true; +} + +void MetisMISOUDPHandler::stop() +{ + qDebug("MetisMISOUDPHandler::stop"); + + if (!m_running) { + return; + } + + // stop Metis + unsigned char buffer[64]; + + buffer[0] = (unsigned char) 0xEF; + buffer[1] = (unsigned char) 0XFE; + buffer[2] = (unsigned char) 0x04; + buffer[3] = (unsigned char) 0x00; + std::fill(&buffer[4], &buffer[64], 0); + + if (m_dataConnected) + { + disconnect(&m_socket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); + m_dataConnected = false; + } + + + if (m_socket.writeDatagram((const char*)buffer, sizeof(buffer), m_metisAddress,m_metisPort) < 0) + { + qDebug() << "MetisMISOUDPHandler::stop: writeDatagram failed " << m_socket.errorString(); + return; + } + else + { + qDebug()<<"MetisMISOUDPHandler::stop: writeDatagram stop command" << m_metisAddress.toString() << ":" << m_metisPort; + } + + m_socket.flush(); + m_socket.close(); + m_running = false; +} + +void MetisMISOUDPHandler::setNbReceivers(unsigned int nbReceivers) +{ + m_nbReceivers = nbReceivers; + + switch(m_nbReceivers) + { + case 1: m_bMax = 512-0; break; + case 2: m_bMax = 512-0; break; + case 3: m_bMax = 512-4; break; + case 4: m_bMax = 512-10; break; + case 5: m_bMax = 512-24; break; + case 6: m_bMax = 512-10; break; + case 7: m_bMax = 512-20; break; + case 8: m_bMax = 512-4; break; + } + + for (unsigned int i = 0; i < MetisMISOSettings::m_maxReceivers; i++) { + m_convertBuffer[i].resize(1024, Sample{0,0}); + } +} + +void MetisMISOUDPHandler::applySettings(const MetisMISOSettings& settings) +{ + if (m_settings.m_nbReceivers != settings.m_nbReceivers) + { + QMutexLocker mutexLocker(&m_mutex); + int nbReceivers = settings.m_nbReceivers < 1 ? + 1 : settings.m_nbReceivers > MetisMISOSettings::m_maxReceivers ? + MetisMISOSettings::m_maxReceivers : settings.m_nbReceivers; + setNbReceivers(nbReceivers); + } + + if (m_settings.m_log2Decim != settings.m_log2Decim) + { + QMutexLocker mutexLocker(&m_mutex); + m_decimators.resetCounters(); + } + + m_settings = settings; +} + +void MetisMISOUDPHandler::handleMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool MetisMISOUDPHandler::handleMessage(const Message& message) +{ + if (MsgStartStop::match(message)) + { + const MsgStartStop& cmd = (const MsgStartStop&) message; + + if (cmd.getStartStop()) { + start(); + } else { + stop(); + } + + return true; + } + else + { + return false; + } + +} + +void MetisMISOUDPHandler::sendMetisBuffer(int ep, unsigned char* buffer) +{ + if (m_offset == 8) // header and first HPSDR frame + { + m_sendSequence++; + m_outputBuffer[0] = 0xEF; + m_outputBuffer[1] = 0xFE; + m_outputBuffer[2] = 0x01; + m_outputBuffer[3] = ep; + m_outputBuffer[4] = (m_sendSequence>>24) & 0xFF; + m_outputBuffer[5] = (m_sendSequence>>16) & 0xFF; + m_outputBuffer[6] = (m_sendSequence>>8) & 0xFF; + m_outputBuffer[7] = (m_sendSequence) & 0xFF; + std::copy(buffer, buffer+512, &m_outputBuffer[m_offset]); // copy the buffer over + m_offset = 520; + } + else // second HPSDR frame and send + { + std::copy(buffer, buffer+512, &m_outputBuffer[m_offset]); // copy the buffer over + m_offset = 8; + + // send the buffer + if (m_socket.writeDatagram((const char*) m_outputBuffer, sizeof(m_outputBuffer), m_metisAddress, m_metisPort) < 0) + { + qDebug() << "MetisMISOUDPHandler::sendMetisBuffer: writeDatagram failed " << m_socket.errorString(); + return; + } + + m_socket.flush(); + } +} + +void MetisMISOUDPHandler::dataReadyRead() +{ + QHostAddress metisAddress; + quint16 metisPort; + unsigned char receiveBuffer[1032]; + qint64 length; + + long sequence; + + if ((length = m_socket.readDatagram((char*) &receiveBuffer, (qint64) sizeof(receiveBuffer), &metisAddress, &metisPort)) != 1032) + { + qDebug() << "MetisMISOUDPHandler::dataReadyRead: readDatagram failed " << m_socket.errorString(); + return; + } + + if (receiveBuffer[0] == 0xEF && receiveBuffer[1] == 0xFE) + { + // valid frame + switch(receiveBuffer[2]) + { + case 1: // IQ data + switch (receiveBuffer[3]) + { + case 4: // EP4 data + break; + case 6: // EP6 data + sequence = ((receiveBuffer[4] & 0xFF)<<24) + ((receiveBuffer[5] & 0xFF)<<16) + ((receiveBuffer[6] & 0xFF)<<8) +(receiveBuffer[7] & 0xFF); + if (m_receiveSequence == 0) + { + m_receiveSequence = sequence; + } + else + { + m_receiveSequence++; + + if (m_receiveSequence != sequence) + { + //qDebug()<<"Sequence error: expected "<>24; // C1 + buffer[5]= (commandValue>>16) & 0xFF; // C2 + buffer[6]= (commandValue>>8) & 0xFF; // C3 + buffer[7]= commandValue & 0xFF; // C4 + + if (m_commandBase < 18) { // base count 0 to 18 + m_commandBase++; + } else { + m_commandBase = 0; + } + + if (nullPayload) + { + std::fill(&buffer[8], &buffer[512], 0); + } + else + { + unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End; + int bufferIndex = 8; + + m_sampleMOFifo->readSync(63, iPart1Begin, iPart1End, iPart2Begin, iPart2End); + + if (iPart1Begin != iPart1End) { + fillBuffer(buffer, bufferIndex, iPart1Begin, iPart1End); + } + + if (iPart2Begin != iPart2End) { + fillBuffer(buffer, bufferIndex, iPart2Begin, iPart2End); + } + } + + sendMetisBuffer(2, buffer); + } + + m_txFrame++; +} + +void MetisMISOUDPHandler::fillBuffer(unsigned char *buffer, int& bufferIndex, int iBegin, int iEnd) +{ + SampleVector::iterator it = m_sampleMOFifo->getData(0).begin() + iBegin; + const SampleVector::iterator itEnd = m_sampleMOFifo->getData(0).begin() + iEnd; + + for (; it != itEnd; ++it) + { + std::fill(&buffer[bufferIndex], &buffer[bufferIndex+4], 0); // Fill with zeros + bufferIndex += 4; + buffer[bufferIndex++] = it->imag() >> 8; + buffer[bufferIndex++] = it->imag() & 0xFF; + buffer[bufferIndex++] = it->real() >> 8; + buffer[bufferIndex++] = it->real() & 0xFF; + } +} + +int MetisMISOUDPHandler::getCommandValue(int commandIndex) +{ + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + + if (commandIndex == 0) + { + c1 = m_settings.m_sampleRateIndex & 0x03; + c3 = m_settings.m_preamp ? 0x04 : 0; + c3 += m_settings.m_dither ? 0x08 : 0; + c3 += m_settings.m_random ? 0x10 : 0; + c4 = m_settings.m_duplex ? 0x04 : 0; + c4 += (((m_nbReceivers-1) & 0x07)<<3); + return (c1<<24) + (c3<<8) + c4; + } + else if (commandIndex == 2) + { + return getTxCenterFrequency(); + } + else if (commandIndex == 4) + { + return getRxCenterFrequency(0); + } + else if (commandIndex == 6) + { + return getRxCenterFrequency(1); + } + else if (commandIndex == 8) + { + return getRxCenterFrequency(2); + } + else if (commandIndex == 10) + { + return getRxCenterFrequency(3); + } + else if (commandIndex == 12) + { + return getRxCenterFrequency(4); + } + else if (commandIndex == 14) + { + return getRxCenterFrequency(5); + } + else if (commandIndex == 16) + { + return getRxCenterFrequency(6); + } + else if (commandIndex == 18) + { + c1 = (m_settings.m_txDrive & 0x0F) << 4; + return (c1<<24) + (c3<<8) + c4; + } + else if (commandIndex == 36) + { + return getRxCenterFrequency(7); + } + else + { + return 0; + } +} + +quint64 MetisMISOUDPHandler::getRxCenterFrequency(int index) +{ + qint64 deviceCenterFrequency; + qint64 loHalfFrequency = 61440000LL - ((m_settings.m_LOppmTenths * 122880000LL) / 20000000LL); + qint64 requiredRxFrequency = m_settings.m_rxCenterFrequencies[index] + - (m_settings.m_rxTransverterMode ? m_settings.m_rxTransverterDeltaFrequency : 0); + requiredRxFrequency = requiredRxFrequency < 0 ? 0 : requiredRxFrequency; + + if (m_settings.m_rxSubsamplingIndexes[index] % 2 == 0) { + deviceCenterFrequency = requiredRxFrequency - m_settings.m_rxSubsamplingIndexes[index]*loHalfFrequency; + } else { + deviceCenterFrequency = (m_settings.m_rxSubsamplingIndexes[index] + 1)*loHalfFrequency - requiredRxFrequency; + } + + qint64 df = ((qint64)deviceCenterFrequency * m_settings.m_LOppmTenths) / 10000000LL; + deviceCenterFrequency += df; + + return deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency > 61440000 ? 61440000 : deviceCenterFrequency; +} + +quint64 MetisMISOUDPHandler::getTxCenterFrequency() +{ + qint64 requiredTxFrequency = m_settings.m_txCenterFrequency; + - (m_settings.m_txTransverterMode ? m_settings.m_txTransverterDeltaFrequency : 0); + requiredTxFrequency = requiredTxFrequency < 0 ? 0 : requiredTxFrequency; + return requiredTxFrequency; +} + +bool MetisMISOUDPHandler::getRxIQInversion(int index) +{ + return ((m_settings.m_rxSubsamplingIndexes[index] % 2) == 1) ^ !m_settings.m_iqOrder; +} + +void MetisMISOUDPHandler::processIQBuffer(unsigned char* buffer) +{ + int b = 0; + int b_max; + int r; + int sampleI, sampleQ, sampleMic; + + + if (buffer[b++]==0x7F && buffer[b++]==0x7F && buffer[b++]==0x7F) + { + QMutexLocker mutexLocker(&m_mutex); + + // extract control bytes + m_controlIn[0] = buffer[b++]; + m_controlIn[1] = buffer[b++]; + m_controlIn[2] = buffer[b++]; + m_controlIn[3] = buffer[b++]; + m_controlIn[4] = buffer[b++]; + + // extract PTT, DOT and DASH + m_ptt = (m_controlIn[0] & 0x01) == 0x01; + m_dash = (m_controlIn[0] & 0x02) == 0x02; + m_dot = (m_controlIn[0] & 0x04) == 0x04; + + switch((m_controlIn[0]>>3) & 0x1F) + { + case 0: + m_lt2208ADCOverflow = m_controlIn[1] & 0x01; + m_IO1 = (m_controlIn[1] & 0x02) ? 0 : 1; + m_IO2 = (m_controlIn[1] & 0x04) ? 0 : 1; + m_IO3 = (m_controlIn[1] & 0x08) ? 0 : 1; + // { + // int nbRx = (m_controlIn[4]>>3) & 0x07; + // nbRx++; + // qDebug("MetisMISOUDPHandler::processIQBuffer: nbRx: %d", nbRx); + // } + if (m_mercurySoftwareVersion != m_controlIn[2]) + { + m_mercurySoftwareVersion = m_controlIn[2]; + qDebug("MetisMISOUDPHandler::processIQBuffer: Mercury Software version: %d (0x%0X)", + m_mercurySoftwareVersion, m_mercurySoftwareVersion); + } + + if (m_penelopeSoftwareVersion != m_controlIn[3]) + { + m_penelopeSoftwareVersion = m_controlIn[3]; + qDebug("MetisMISOUDPHandler::processIQBuffer: Penelope Software version: %d (0x%0X)", + m_penelopeSoftwareVersion, m_penelopeSoftwareVersion); + } + + if (m_ozySoftwareVersion != m_controlIn[4]) + { + m_ozySoftwareVersion = m_controlIn[4]; + qDebug("MetisMISOUDPHandler::processIQBuffer: Ozy Software version: %d (0x%0X)", + m_ozySoftwareVersion, m_ozySoftwareVersion); + } + break; + + case 1: + m_forwardPower = (m_controlIn[1]<<8) + m_controlIn[2]; // from Penelope or Hermes + m_alexForwardPower = (m_controlIn[3]<<8) + m_controlIn[4]; // from Alex or Apollo + break; + + case 2: + m_alexForwardPower = (m_controlIn[1]<<8) + m_controlIn[2]; // from Alex or Apollo + m_AIN3 = (m_controlIn[3]<<8) + m_controlIn[4]; // from Pennelope or Hermes + break; + + case 3: + m_AIN4 = (m_controlIn[1]<<8) + m_controlIn[2]; // from Pennelope or Hermes + m_AIN6 = (m_controlIn[3]<<8) + m_controlIn[4]; // from Pennelope or Hermes + break; + } + + // extract the samples + while (b < m_bMax) + { + int samplesAdded = 0; + + // extract each of the receivers + for (r = 0; r < m_nbReceivers; r++) + { + if (SDR_RX_SAMP_SZ == 16) + { + sampleQ = (int)((signed char)buffer[b++]) << 8; + sampleQ += (int)((unsigned char)buffer[b++]); + b++; + sampleI = (int)((signed char)buffer[b++]) << 8; + sampleI += (int)((unsigned char)buffer[b++]); + b++; + } + else + { + sampleQ = (int)((signed char)buffer[b++]) << 16; + sampleQ += (int)((unsigned char)buffer[b++]) << 8; + sampleQ += (int)((unsigned char)buffer[b++]); + sampleI = (int)((signed char)buffer[b++]) << 16; + sampleI += (int)((unsigned char)buffer[b++]) << 8; + sampleI += (int)((unsigned char)buffer[b++]); + } + + if (r < MetisMISOSettings::m_maxReceivers) + { + if (m_settings.m_log2Decim == 0) // no decimation - direct conversion + { + m_convertBuffer[r][m_sampleCount] = getRxIQInversion(r) ? Sample{sampleQ, sampleI} : Sample{sampleI, sampleQ}; + samplesAdded = 1; + } + else + { + SampleVector v(2, Sample{0, 0}); + + switch (m_settings.m_log2Decim) + { + case 1: + samplesAdded = getRxIQInversion(r) ? + m_decimators.decimate2(sampleQ, sampleI, v, r) : m_decimators.decimate2(sampleI, sampleQ, v, r); + break; + case 2: + samplesAdded = getRxIQInversion(r) ? + m_decimators.decimate4(sampleQ, sampleI, v, r) : m_decimators.decimate4(sampleI, sampleQ, v, r); + break; + case 3: + samplesAdded = getRxIQInversion(r) ? + m_decimators.decimate8(sampleQ, sampleI, v, r) : m_decimators.decimate8(sampleI, sampleQ, v, r); + break; + default: + break; + } + + if (samplesAdded != 0) + { + std::copy( + v.begin(), + v.begin() + samplesAdded, + &m_convertBuffer[r][m_sampleCount] + ); + } + } + } + } + + sampleMic = (int)((signed char) buffer[b++]) << 8; + sampleMic += (int)((unsigned char)buffer[b++]); + m_sampleTxCount++; + + if (m_sampleTxCount >= 63) // 63 samples per 512 byte Tx block + { + sendData(!m_settings.m_txEnable); // synchronous sending of a Tx frame + m_sampleTxCount = 0; + } + + if (samplesAdded != 0) + { + m_sampleCount += samplesAdded; + + if (m_sampleCount >= 1024) + { + std::vector vbegin; + + for (unsigned int channel = 0; channel < MetisMISOSettings::m_maxReceivers; channel++) { + vbegin.push_back(m_convertBuffer[channel].begin()); + } + + m_sampleMIFifo->writeSync(vbegin, 1024); + m_sampleCount = 0; + } + } + } + } + else + { + qDebug()<<"MetisMISOUDPHandler::processIQBuffer: SYNC Error"; + } + + m_rxFrame++; +} \ No newline at end of file diff --git a/plugins/samplemimo/metismiso/metismisoudphandler.h b/plugins/samplemimo/metismiso/metismisoudphandler.h new file mode 100644 index 000000000..24e0986b0 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisoudphandler.h @@ -0,0 +1,137 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISOUDPHANDLER_H_ +#define _METISMISO_METISMISOUDPHANDLER_H_ + +#include +#include +#include +#include + +#include "dsp/decimators.h" +#include "util/messagequeue.h" +#include "util/message.h" + +#include "metismisosettings.h" +#include "metismisodecimators.h" + +class SampleMIFifo; +class SampleMOFifo; +class DeviceAPI; + +class MetisMISOUDPHandler : public QObject +{ + Q_OBJECT +public: + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + MetisMISOUDPHandler(SampleMIFifo* sampleMIFifo, SampleMOFifo *sampleMOFifo, DeviceAPI *deviceAPI); + ~MetisMISOUDPHandler(); + void setMessageQueueToGUI(MessageQueue *queue) { m_messageQueueToGUI = queue; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + void start(); + void stop(); + void setMetisAddress(const QHostAddress& address, quint16 port) { + m_metisAddress = address; + m_metisPort = port; + } + void setNbReceivers(unsigned int nbReceivers); + void applySettings(const MetisMISOSettings& settings); + +public slots: + void dataReadyRead(); + +private: + DeviceAPI *m_deviceAPI; + QUdpSocket m_socket; + QHostAddress m_metisAddress; + quint16 m_metisPort; + bool m_running; + bool m_dataConnected; + SampleMIFifo *m_sampleMIFifo; + SampleMOFifo *m_sampleMOFifo; + SampleVector m_convertBuffer[MetisMISOSettings::m_maxReceivers]; + int m_sampleCount; + int m_sampleTxCount; + MessageQueue *m_messageQueueToGUI; + MessageQueue m_inputMessageQueue; + MetisMISOSettings m_settings; + QMutex m_mutex; + MetisMISODecimators m_decimators; + + unsigned long m_sendSequence; + int m_offset; + int m_commandBase; + unsigned long m_rxFrame; + unsigned long m_txFrame; + unsigned char m_outputBuffer[1032]; //!< buffer to send + unsigned char m_metisBuffer[512]; //!< current HPSDR frame + int metisBufferIndex; + unsigned long m_receiveSequence; + int m_receiveSequenceError; + unsigned char m_controlIn[5]; + unsigned int m_nbReceivers; + int m_bMax; + bool m_ptt; + bool m_dash; + bool m_dot; + bool m_lt2208ADCOverflow; + int m_IO1; + int m_IO2; + int m_IO3; + int m_mercurySoftwareVersion; + int m_penelopeSoftwareVersion; + int m_ozySoftwareVersion; + int m_forwardPower; + int m_alexForwardPower; + int m_AIN3; + int m_AIN4; + int m_AIN6; + + void sendMetisBuffer(int ep, unsigned char* buffer); + bool handleMessage(const Message& message); + void sendData(bool nullPayload = true); + void fillBuffer(unsigned char *buffer, int& bufferIndex, int iBegin, int iEnd); + int getCommandValue(int commandIndex); + void processIQBuffer(unsigned char* buffer); + quint64 getRxCenterFrequency(int index); + quint64 getTxCenterFrequency(); + bool getRxIQInversion(int index); + +private slots: + void handleMessages(); +}; + +#endif // _METISMISO_METISMISOUDPHANDLER_H_ \ No newline at end of file diff --git a/plugins/samplemimo/metismiso/metismisowebapiadapter.cpp b/plugins/samplemimo/metismiso/metismisowebapiadapter.cpp new file mode 100644 index 000000000..49db41806 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisowebapiadapter.cpp @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGDeviceSettings.h" +#include "metismiso.h" +#include "metismisowebapiadapter.h" + +MetisMISOWebAPIAdapter::MetisMISOWebAPIAdapter() +{} + +MetisMISOWebAPIAdapter::~MetisMISOWebAPIAdapter() +{} + +int MetisMISOWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setMetisMisoSettings(new SWGSDRangel::SWGMetisMISOSettings()); + response.getMetisMisoSettings()->init(); + MetisMISO::webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int MetisMISOWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + MetisMISO::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response); + return 200; +} diff --git a/plugins/samplemimo/metismiso/metismisowebapiadapter.h b/plugins/samplemimo/metismiso/metismisowebapiadapter.h new file mode 100644 index 000000000..9ce44b393 --- /dev/null +++ b/plugins/samplemimo/metismiso/metismisowebapiadapter.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef _METISMISO_METISMISOWEBAPIADAPTER_H_ +#define _METISMISO_METISMISOWEBAPIADAPTER_H_ + +#include "device/devicewebapiadapter.h" +#include "metismisosettings.h" + +class MetisMISOWebAPIAdapter : public DeviceWebAPIAdapter +{ +public: + MetisMISOWebAPIAdapter(); + virtual ~MetisMISOWebAPIAdapter(); + virtual QByteArray serialize() { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + +private: + MetisMISOSettings m_settings; +}; + +#endif // _METISMISO_METISMISOWEBAPIADAPTER_H_ diff --git a/plugins/samplemimo/metismiso/readme.md b/plugins/samplemimo/metismiso/readme.md new file mode 100644 index 000000000..713206f71 --- /dev/null +++ b/plugins/samplemimo/metismiso/readme.md @@ -0,0 +1,142 @@ +

Metis Multiple Input Single Output plugin

+ +

Introduction

+ +This is a v5 only plugin. + +This plugin is mainly intended to be used to process samples to/from a [Red Pitaya board](https://www.redpitaya.com/) with [Pavel's HPSDR compatible applications](http://pavel-demin.github.io/red-pitaya-notes/). More precisely: + + - [SDR transceiver compatible with HPSDR](http://pavel-demin.github.io/red-pitaya-notes/sdr-transceiver-hpsdr/) + - [SDR receiver compatible with HPSDR](http://pavel-demin.github.io/red-pitaya-notes/sdr-receiver-hpsdr-122-88/) + +The plugin has 8 receiving (sink) streams and one transmitting (source) stream. Depending on the design of the Metis compatible hardware you may or may not have all of them available. Streams with no connection will just be filled with zero samples. You may choose the number of active streams with the control (9.5) - see next. + +While compatible with [Metis HPSDR-2 protocol](https://github.com/softerhardware/Hermes-Lite2/wiki/Protocol) implementation is minimal in order to be able to control and work with the Red Pitaya. It has not been tested in another context nor has provision to control the Red Pitaya or HPSDR peripherals. These controls may be added in the future as needs arise. + +

Build

+ +The plugin is present in the core of the software and thus is always present in the list of MIMO devices. + +

Interface

+ +![Metis MISO plugin GUI](../../../doc/img/MetisMISO_plugin.png) + +

1: Active stream selection

+ +Select for which streams the controls are active. Controls specific to each stream are: + + - Center frequency + - Subsampling index + +

2: Spectrum source selection

+ +Select which stream is routed to the main spectrum display + +

3: Active stream / spectrum source lock

+ +This ties together the stream selection and spectrum source stream selections. + +

4: Start/Stop

+ +Device start / stop button. + + - Blue triangle icon: device is ready and can be started + - Green square icon: device is running and can be stopped + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + +Starting the device means that the network stream from the Metis compatible device is started. It will be stopped by the stop button. This effectively starts all available streams that can be controlled with the Rx number select (9.5) or Tx enable (9.6) + +

5: Stream sample rate

+ +Baseband I/Q sample rate in kS/s. This is the device to host sample rate (8.1) divided by the software decimation factor (8.2). + +

6: Center frequency

+ +Tunes the center frequency of the active stream + +

7: Local Oscillator frequency correction in ppm

+ +This lets you compensate for the main oscillator frequency inaccuracy. Value is in ppm (parts per million) + +

8: Sample rate - Decimation - Subsampling - DC and IQ corrections

+ +![Metis Miso GUI 1](../../../doc/img/MetisMISO_plugin_1.png) + +

8.1: Sample rate

+ +This combo box lets you control the four pssible values for the device to host sample rate (Rx). Host to device (Tx) sample rate is fixed by design of the Metis interface at 48 kS/s: + + - **48k**: 48000 samples per second + - **96k**: 96000 samples per second + - **192k**: 192000 samples per second + - **384k**: 384000 samples per second + +

8.2: Decimation factor

+ +The I/Q stream from the Metis stream is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8. + +

8.3: Subsampling index

+ +The Red Pitaya has a LTC2185 ADC specified for a bandwidth up to 550 MHz. This lets you use the Red Pitaya receivers in subsampling mode with appropriate filtering and LNA chain as a front end. In this mode the received frequency may extend above 61.44 MHz in successive 61.44 MHz wide bands. This index corresponds to the frequency band index from 0 to 7 and let you input the frequency directly corresponding the the subsampling scheme. The band limits appear in the tooltip and are the following: + + - **0**: 0 to 61.44 MHz - fundamental no subsampling + - **1**: 61.44 to 122.88 MHz + - **2**: 122.88 to 184.32 MHz + - **3**: 184.32 to 245.76 MHz + - **4**: 245.76 to 307.2 MHz + - **5**: 307.2 to 368.64 MHz + - **6**: 368.64 to 430.08 MHz + - **7**: 430.08 to 491.52 MHz + +Of course the more the higher the frequency above the fundamental range the worse the performance is. In practice it is still OK at VHF frequencies but not much above. + +

8.4: DC correction

+ +This corrects residual DC present at the center of the passband. By construction this is useless for the Red Pitaya. + +

8.5: IQ imbalance correction

+ +This corrects I/Q imbalance. By construction this is useless for the Red Pitaya. + +

9: Preamp - Random - Dither - Duplex - Number of receivers - Tx enable - Transverter

+ +![Metis Miso GUI 1](../../../doc/img/MetisMISO_plugin_2.png) + +

9.1: Preamp

+ +Toggle Rx preamplifier - not found to be effective + +

9.2: Random

+ +Toggle LTC2185 randomization - not found to be effective + +

9.3: Dither

+ +Toggle LTC2185 dithering - not found to be effective + +

9.4: Duplex

+ +Toggle duplex - not found to be effective + +

9.5: Number or active receivers

+ +Controls the number of active receivers. Each receiver allocates a slot in the data stream from the Metis interface. + + - For the SDR receiver compatible with HPSDR choose a maximum of 4 receivers + - For the SDR receiver compatible with HPSDR choose a maximum of 8 receivers + +It is a waste to have more active receivers than you actually need because it will increase network traffic for nothing + +

9.6: Toggle Tx activation

+ +Use this button to toggle the generation and sending of Tx samples in the Metis stream from host to device. When inactivated null samples are sent in the return payload from host to device. + +

9.7: Transverter mode

+ +This button opens a dialog to set the transverter mode frequency translation options. The details about this dialog can be found [here](../../../sdrgui/gui/transverterdialog.md) + +Transverter mixing is the same for all receivers and may be different for the transmitter. + +

10: Tx drive level

+ +Choose a level from 0 (deactivated) to 15 (full power)