From 26bc4d8f8e175e44c66d1e8fac52ed9474cec856 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 31 Aug 2018 18:30:52 +0200 Subject: [PATCH] SDRDaemon channel source: added plugin --- plugins/channeltx/CMakeLists.txt | 12 +- plugins/channeltx/modam/ammodgui.cpp | 6 +- .../sdrdaemonchannelsource/CMakeLists.txt | 57 +++ .../sdrdaemonchannelsource.cpp | 413 ++++++++++++++++++ .../sdrdaemonchannelsource.h | 135 ++++++ .../sdrdaemonchannelsourcegui.cpp | 275 ++++++++++++ .../sdrdaemonchannelsourcegui.h | 92 ++++ .../sdrdaemonchannelsourcegui.ui | 179 ++++++++ .../sdrdaemonchannelsourceplugin.cpp | 77 ++++ .../sdrdaemonchannelsourceplugin.h | 47 ++ .../sdrdaemonchannelsourcesettings.cpp | 95 ++++ .../sdrdaemonchannelsourcesettings.h | 48 ++ .../sdrdaemonchannelsourcethread.cpp | 193 ++++++++ .../sdrdaemonchannelsourcethread.h | 115 +++++ 14 files changed, 1738 insertions(+), 6 deletions(-) create mode 100644 plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp create mode 100644 plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 30118e09e..b7186967c 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -1,13 +1,19 @@ project(mod) -find_package(OpenCV) - add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) add_subdirectory(udpsink) +find_package(CM256cc) + +if(CM256CC_FOUND) + add_subdirectory(sdrdaemonchannelsource) +endif(CM256CC_FOUND) + +find_package(OpenCV) + if (OpenCV_FOUND) -add_subdirectory(modatv) + add_subdirectory(modatv) endif() diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index bf27a61ca..25270da64 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -20,13 +20,10 @@ #include #include -#include "ammodgui.h" - #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "dsp/upchannelizer.h" -#include "ui_ammodgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" @@ -36,6 +33,9 @@ #include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" +#include "ui_ammodgui.h" +#include "ammodgui.h" + AMModGUI* AMModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { AMModGUI* gui = new AMModGUI(pluginAPI, deviceUISet, channelTx); diff --git a/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt b/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt new file mode 100644 index 000000000..dc9629b31 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/CMakeLists.txt @@ -0,0 +1,57 @@ +project(sdrdaemonchannelsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(sdrdaemonchannelsource_SOURCES + sdrdaemonchannelsource.cpp + sdrdaemonchannelsourcethread.cpp + sdrdaemonchannelsourcegui.cpp + sdrdaemonchannelsourceplugin.cpp + sdrdaemonchannelsourcesettings.cpp +) + +set(sdrdaemonchannelsource_HEADERS + sdrdaemonchannelsource.h + sdrdaemonchannelsourcethread.h + sdrdaemonchannelsourcegui.h + sdrdaemonchannelsourceplugin.h + sdrdaemonchannelsourcesettings.h +) + +set(sdrdaemonchannelsource_FORMS + sdrdaemonchannelsourcegui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/sdrdaemon + ${CM256CC_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(sdrdaemonchannelsource_FORMS_HEADERS ${sdrdaemonchannelsource_FORMS}) + +add_library(sdrdaemonchannelsource SHARED + ${sdrdaemonchannelsource_SOURCES} + ${sdrdaemonchannelsource_HEADERS_MOC} + ${sdrdaemonchannelsource_FORMS_HEADERS} +) + +target_link_libraries(sdrdaemonchannelsource + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrdaemon + sdrgui + swagger +) + +target_link_libraries(sdrdaemonchannelsource Qt5::Core Qt5::Widgets Qt5::Network) + +install(TARGETS sdrdaemonchannelsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp new file mode 100644 index 000000000..580d2ecba --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.cpp @@ -0,0 +1,413 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSDRDaemonChannelSourceReport.h" + +#include "util/simpleserializer.h" +#include "dsp/threadedbasebandsamplesource.h" +#include "dsp/upchannelizer.h" +#include "dsp/devicesamplesink.h" +#include "device/devicesinkapi.h" +#include "sdrdaemonchannelsource.h" +#include "channel/sdrdaemonchannelsourcethread.h" +#include "channel/sdrdaemondatablock.h" + +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource, Message) + +const QString SDRDaemonChannelSource::m_channelIdURI = "sdrangel.channel.sdrdaemonsource"; +const QString SDRDaemonChannelSource::m_channelId = "SDRDaemonChannelSource"; + +SDRDaemonChannelSource::SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_sourceThread(0), + m_running(false), + m_nbCorrectableErrors(0), + m_nbUncorrectableErrors(0) +{ + setObjectName(m_channelId); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); + + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); +} + +SDRDaemonChannelSource::~SDRDaemonChannelSource() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void SDRDaemonChannelSource::pull(Sample& sample) +{ + m_dataReadQueue.readSample(sample); +} + +void SDRDaemonChannelSource::start() +{ + qDebug("SDRDaemonChannelSink::start"); + + if (m_running) { + stop(); + } + + m_sourceThread = new SDRDaemonChannelSourceThread(&m_dataQueue); + m_sourceThread->startStop(true); + m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); + m_running = true; +} + +void SDRDaemonChannelSource::stop() +{ + qDebug("SDRDaemonChannelSink::stop"); + + if (m_sourceThread != 0) + { + m_sourceThread->startStop(false); + m_sourceThread->deleteLater(); + m_sourceThread = 0; + } + + m_running = false; +} + +void SDRDaemonChannelSource::setDataLink(const QString& dataAddress, uint16_t dataPort) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + settings.m_dataAddress = dataAddress; + settings.m_dataPort = dataPort; + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, false); + m_inputMessageQueue.push(msg); +} + +bool SDRDaemonChannelSource::handleMessage(const Message& cmd __attribute__((unused))) +{ + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: UpChannelizer::MsgChannelizerNotification:" + << " basebandSampleRate: " << notif.getBasebandSampleRate() + << " outputSampleRate: " << notif.getSampleRate() + << " inputFrequencyOffset: " << notif.getFrequencyOffset(); + + //applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); + + return true; + } + else if (MsgConfigureSDRDaemonChannelSource::match(cmd)) + { + MsgConfigureSDRDaemonChannelSource& cfg = (MsgConfigureSDRDaemonChannelSource&) cmd; + qDebug() << "SDRDaemonChannelSource::handleMessage: MsgConfigureSDRDaemonChannelSource"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + return false; + } +} + +QByteArray SDRDaemonChannelSource::serialize() const +{ + return m_settings.serialize(); +} + +bool SDRDaemonChannelSource::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void SDRDaemonChannelSource::applySettings(const SDRDaemonChannelSourceSettings& settings, bool force) +{ + qDebug() << "SDRDaemonChannelSource::applySettings:" + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + bool change = false; + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + change = true; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + change = true; + } + + if (change && m_sourceThread) { + m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); + } + + m_settings = settings; +} + +void SDRDaemonChannelSource::handleDataBlock(SDRDaemonDataBlock* dataBlock) +{ + if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + { + qWarning("SDRDaemonChannelSource::handleDataBlock: incomplete data block: not processing"); + } + else + { + int blockCount = 0; + + for (int blockIndex = 0; blockIndex < 256; blockIndex++) + { + if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) + { + m_cm256DescriptorBlocks[blockCount].Index = 0; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + blockCount++; + } + else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + { + m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); + blockCount++; + } + } + + //qDebug("SDRDaemonChannelSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + + // Need to use the CM256 recovery + if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + { + qDebug("SDRDaemonChannelSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + CM256::cm256_encoder_params paramsCM256; + paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes + + if (m_currentMeta.m_tv_sec == 0) { + paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; + } else { + paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } + + // update counters + if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; + } else { + m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; + } + + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: decode CM256 error:" + << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; + } + else + { + for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; + int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; + SDRDaemonProtectedBlock *recoveredBlock = + (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; + memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { + dataBlock->m_rxControlBlock.m_metaRetrieved = true; + } + } + } + } + + // Validate block zero and retrieve its data + if (dataBlock->m_rxControlBlock.m_metaRetrieved) + { + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + if (!(m_currentMeta == *metaData)) + { + printMeta("SDRDaemonChannelSource::handleDataBlock", metaData); + + if (m_currentMeta.m_centerFrequency != metaData->m_centerFrequency) { + m_deviceAPI->getSampleSink()->setCenterFrequency(metaData->m_centerFrequency*1000); // frequency is in kHz + } + + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) + { + m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); + } + } + + m_currentMeta = *metaData; + } + else + { + qWarning() << "SDRDaemonChannelSource::handleDataBlock: recovered meta: invalid CRC32"; + } + } + + m_dataReadQueue.push(dataBlock); // Push into R/W buffer + } +} + +void SDRDaemonChannelSource::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { + handleDataBlock(dataBlock); + } +} + +void SDRDaemonChannelSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +{ + qDebug().noquote() << header << ": " + << "|" << metaData->m_centerFrequency + << ":" << metaData->m_sampleRate + << ":" << (int) (metaData->m_sampleBytes & 0xF) + << ":" << (int) metaData->m_sampleBits + << ":" << (int) metaData->m_nbOriginalBlocks + << ":" << (int) metaData->m_nbFECBlocks + << "|" << metaData->m_tv_sec + << ":" << metaData->m_tv_usec + << "|"; +} + +uint32_t SDRDaemonChannelSource::calculateDataReadQueueSize(int sampleRate) +{ + // scale for 20 blocks at 48 kS/s. Take next even number. + uint32_t maxSize = sampleRate / 2400; + maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; + qDebug("SDRDaemonChannelSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + return maxSize; +} + +int SDRDaemonChannelSource::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceSettings(new SWGSDRangel::SWGSDRDaemonChannelSourceSettings()); + response.getSdrDaemonChannelSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SDRDaemonChannelSource::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SDRDaemonChannelSourceSettings settings = m_settings; + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonChannelSourceSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getSdrDaemonChannelSourceSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + MsgConfigureSDRDaemonChannelSource *msg = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SDRDaemonChannelSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRDaemonChannelSource *msgToGUI = MsgConfigureSDRDaemonChannelSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SDRDaemonChannelSource::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonChannelSourceReport(new SWGSDRangel::SWGSDRDaemonChannelSourceReport()); + response.getSdrDaemonChannelSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SDRDaemonChannelSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings) +{ + if (response.getSdrDaemonChannelSourceSettings()->getDataAddress()) { + *response.getSdrDaemonChannelSourceSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getSdrDaemonChannelSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getSdrDaemonChannelSourceSettings()->setDataPort(settings.m_dataPort); +} + +void SDRDaemonChannelSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + response.getSdrDaemonChannelSourceReport()->setTvSec(tv.tv_sec); + response.getSdrDaemonChannelSourceReport()->setTvUSec(tv.tv_usec); + response.getSdrDaemonChannelSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getSdrDaemonChannelSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getSdrDaemonChannelSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getSdrDaemonChannelSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getSdrDaemonChannelSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); +} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h new file mode 100644 index 000000000..cf51177c2 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsource.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ + +#include "cm256.h" + +#include "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" +#include "util/message.h" +#include "sdrdaemonchannelsourcesettings.h" +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" + +class ThreadedBasebandSampleSource; +class UpChannelizer; +class DeviceSinkAPI; +class SDRDaemonChannelSourceThread; +class SDRDaemonDataBlock; + +class SDRDaemonChannelSource : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT +public: + class MsgConfigureSDRDaemonChannelSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SDRDaemonChannelSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSDRDaemonChannelSource* create(const SDRDaemonChannelSourceSettings& settings, bool force) + { + return new MsgConfigureSDRDaemonChannelSource(settings, force); + } + + private: + SDRDaemonChannelSourceSettings m_settings; + bool m_force; + + MsgConfigureSDRDaemonChannelSource(const SDRDaemonChannelSourceSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + SDRDaemonChannelSource(DeviceSinkAPI *deviceAPI); + ~SDRDaemonChannelSource(); + virtual void destroy() { delete this; } + + virtual void pull(Sample& sample); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = "SDRDaemon Source"; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + void setDataLink(const QString& dataAddress, uint16_t dataPort); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSinkAPI *m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + SDRDaemonDataQueue m_dataQueue; + SDRDaemonChannelSourceThread *m_sourceThread; + CM256 m_cm256; + CM256 *m_cm256p; + bool m_running; + + SDRDaemonChannelSourceSettings m_settings; + + CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + SDRDaemonMetaDataFEC m_currentMeta; + + SDRDaemonDataReadQueue m_dataReadQueue; + + uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks + uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks + + void applySettings(const SDRDaemonChannelSourceSettings& settings, bool force = false); + void handleDataBlock(SDRDaemonDataBlock *dataBlock); + void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); + uint32_t calculateDataReadQueueSize(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SDRDaemonChannelSourceSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + +private slots: + void handleData(); +}; + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCE_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp new file mode 100644 index 000000000..ff46c1914 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.cpp @@ -0,0 +1,275 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) GUI // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +#include "ui_sdrdaemonchannelsourcegui.h" +#include "sdrdaemonchannelsource.h" +#include "sdrdaemonchannelsourcegui.h" + + +SDRDaemonChannelSourceGUI* SDRDaemonChannelSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +{ + SDRDaemonChannelSourceGUI* gui = new SDRDaemonChannelSourceGUI(pluginAPI, deviceUISet, channelTx); + return gui; +} + +void SDRDaemonChannelSourceGUI::destroy() +{ + delete this; +} + +void SDRDaemonChannelSourceGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString SDRDaemonChannelSourceGUI::getName() const +{ + return objectName(); +} + +qint64 SDRDaemonChannelSourceGUI::getCenterFrequency() const { + return m_channelMarker.getCenterFrequency(); +} + +void SDRDaemonChannelSourceGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ + // you can't do that center frquency is fixed to zero + // m_channelMarker.setCenterFrequency(centerFrequency); + // applySettings(); +} + +void SDRDaemonChannelSourceGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray SDRDaemonChannelSourceGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool SDRDaemonChannelSourceGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool SDRDaemonChannelSourceGUI::handleMessage(const Message& message) +{ + if (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::match(message)) + { + const SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource& cfg = (SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void SDRDaemonChannelSourceGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void SDRDaemonChannelSourceGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void SDRDaemonChannelSourceGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +SDRDaemonChannelSourceGUI::SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::SDRDaemonChannelSourceGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_tickCount(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_sdrDaemonChannelSource = (SDRDaemonChannelSource*) channelTx; + m_sdrDaemonChannelSource->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::cyan); + m_channelMarker.setBandwidth(5000); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("SDRDaemon sink"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerTxChannelInstance(SDRDaemonChannelSource::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + + displaySettings(); + applySettings(true); +} + +SDRDaemonChannelSourceGUI::~SDRDaemonChannelSourceGUI() +{ + m_deviceUISet->removeTxChannelInstance(this); + delete m_sdrDaemonChannelSource; // TODO: check this: when the GUI closes it has to delete the channel + delete ui; +} + +void SDRDaemonChannelSourceGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void SDRDaemonChannelSourceGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource* message = SDRDaemonChannelSource::MsgConfigureSDRDaemonChannelSource::create(m_settings, force); + m_sdrDaemonChannelSource->getInputMessageQueue()->push(message); + } +} + +void SDRDaemonChannelSourceGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + // TODO + + blockApplySettings(false); +} + +void SDRDaemonChannelSourceGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void SDRDaemonChannelSourceGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void SDRDaemonChannelSourceGUI::tick() +{ + // TODO if anything useful +} + +void SDRDaemonChannelSourceGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void SDRDaemonChannelSourceGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void SDRDaemonChannelSourceGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} \ No newline at end of file diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h new file mode 100644 index 000000000..08ecf3423 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) GUI // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ + +#include "plugin/plugininstancegui.h" +#include "gui/rollupwidget.h" +#include "dsp/channelmarker.h" +#include "util/messagequeue.h" + +#include "sdrdaemonchannelsourcesettings.h" + +class PluginAPI; +class DeviceUISet; +class SDRDaemonChannelSource; +class BasebandSampleSource; + +namespace Ui { + class SDRDaemonChannelSourceGUI; +} + +class SDRDaemonChannelSourceGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static SDRDaemonChannelSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceAPI, BasebandSampleSource *channelTx); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::SDRDaemonChannelSourceGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + SDRDaemonChannelSourceSettings m_settings; + bool m_doApplySettings; + int m_tickCount; + + SDRDaemonChannelSource* m_sdrDaemonChannelSource; + MessageQueue m_inputMessageQueue; + + explicit SDRDaemonChannelSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~SDRDaemonChannelSourceGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void tick(); +}; + +#endif // CHANNEL_TX_SDRDAEMONCHANNELSOURCEGUI_H_ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui new file mode 100644 index 000000000..67e75a9ea --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcegui.ui @@ -0,0 +1,179 @@ + + + SDRDaemonChannelSourceGUI + + + + 0 + 0 + 320 + 90 + + + + + 0 + 0 + + + + + 320 + 90 + + + + + Liberation Sans + 9 + + + + LoRa Demodulator + + + + + 10 + 10 + 301 + 61 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + 30 + 0 + + + + Data + + + + + + + + 120 + 0 + + + + Local data listener address + + + 000.000.000.000 + + + 0... + + + + + + + : + + + + + + + + 50 + 16777215 + + + + Local data listener port + + + 00000 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set local data listener address and port + + + Set + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+
+ + +
diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp new file mode 100644 index 000000000..7415d6215 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.cpp @@ -0,0 +1,77 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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" + +#ifndef SERVER_MODE +#include "sdrdaemonchannelsourcegui.h" +#endif +#include "sdrdaemonchannelsource.h" +#include "sdrdaemonchannelsourceplugin.h" + +const PluginDescriptor SDRDaemonChannelSourcePlugin::m_pluginDescriptor = { + QString("SDRDaemon source"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +SDRDaemonChannelSourcePlugin::SDRDaemonChannelSourcePlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& SDRDaemonChannelSourcePlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SDRDaemonChannelSourcePlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register + m_pluginAPI->registerTxChannel(SDRDaemonChannelSource::m_channelIdURI, SDRDaemonChannelSource::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SDRDaemonChannelSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +{ + return SDRDaemonChannelSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); +} +#endif + +BasebandSampleSource* SDRDaemonChannelSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +{ + return new SDRDaemonChannelSource(deviceAPI); +} + +ChannelSourceAPI* SDRDaemonChannelSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new SDRDaemonChannelSource(deviceAPI); +} + diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h new file mode 100644 index 000000000..fb0a07036 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourceplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 // +// // +// 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 INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H +#define INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSource; + +class SDRDaemonChannelSourcePlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.sdrdaemonsource") + +public: + explicit SDRDaemonChannelSourcePlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); + virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); + virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_SDRDAEMONCHANNELSOURCEPLUGIN_H diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp new file mode 100644 index 000000000..85892e7fb --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.cpp @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "settings/serializable.h" +#include "sdrdaemonchannelsourcesettings.h" + +SDRDaemonChannelSourceSettings::SDRDaemonChannelSourceSettings() +{ + resetToDefaults(); +} + +void SDRDaemonChannelSourceSettings::resetToDefaults() +{ + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_rgbColor = QColor(0, 255, 255).rgb(); + m_title = "SDRDaemon sink"; + +} + +QByteArray SDRDaemonChannelSourceSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_dataAddress); + s.writeU32(2, m_dataPort); + s.writeU32(3, m_rgbColor); + s.writeString(4, m_title); + + return s.final(); +} + +bool SDRDaemonChannelSourceSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readString(1, &m_dataAddress, "127.0.0.1"); + d.readU32(2, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(4, &m_title, "AM Modulator"); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + + + + diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h new file mode 100644 index 000000000..ea3f5311c --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcesettings.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ + +#include +#include + +class Serializable; + +struct SDRDaemonChannelSourceSettings +{ + QString m_dataAddress; //!< Listening (local) data address + uint16_t m_dataPort; //!< Listening data port + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; + + SDRDaemonChannelSourceSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCESETTINGS_H_ */ diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp new file mode 100644 index 000000000..3b2d55998 --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.cpp @@ -0,0 +1,193 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemonchannelsourcethread.h" + +#include "cm256.h" + +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SDRDaemonChannelSourceThread::MsgDataBind, Message) + +SDRDaemonChannelSourceThread::SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread() +{ + qDebug("SDRDaemonChannelSourceThread::~SDRDaemonChannelSourceThread"); +} + +void SDRDaemonChannelSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::dataBind(const QString& address, uint16_t port) +{ + MsgDataBind *msg = MsgDataBind::create(address, port); + m_inputMessageQueue.push(msg); +} + +void SDRDaemonChannelSourceThread::startWork() +{ + qDebug("SDRDaemonChannelSourceThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void SDRDaemonChannelSourceThread::stopWork() +{ + qDebug("SDRDaemonChannelSourceThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void SDRDaemonChannelSourceThread::run() +{ + qDebug("SDRDaemonChannelSourceThread::run: begin"); + m_running = true; + m_startWaiter.wakeAll(); + + while (m_running) + { + sleep(1); // Do nothing as everything is in the data handler (dequeuer) + } + + m_running = false; + qDebug("SDRDaemonChannelSourceThread::run: end"); +} + + +void SDRDaemonChannelSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + else if (MsgDataBind::match(*message)) + { + MsgDataBind* notif = (MsgDataBind*) message; + qDebug("SDRDaemonChannelSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + + if (m_socket) + { + disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + m_socket->bind(notif->getAddress(), notif->getPort()); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + } + } +} + +void SDRDaemonChannelSourceThread::readPendingDatagrams() +{ + SDRDaemonSuperBlock superBlock; + qint64 size; + + while (m_socket->hasPendingDatagrams()) + { + QHostAddress sender; + quint16 senderPort = 0; + //qint64 pendingDataSize = m_socket->pendingDatagramSize(); + size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); + + if (size == sizeof(SDRDaemonSuperBlock)) + { + unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; + + // create the first block for this index + if (m_dataBlocks[dataBlockIndex] == 0) { + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + } + + if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) + { + // initialize virgin block with the frame index + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + else + { + // if the frame index is not the same for the same slot it means we are starting a new frame + uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; + + if (superBlock.m_header.m_frameIndex != frameIndex) + { + //qDebug("SDRDaemonChannelSourceThread::readPendingDatagrams: push frame %u", frameIndex); + m_dataQueue->push(m_dataBlocks[dataBlockIndex]); + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + } + + m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; + + if (superBlock.m_header.m_blockIndex == 0) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; + } + + if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; + } else { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; + } + + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; + } + else + { + qWarning("SDRDaemonChannelSourceThread::readPendingDatagrams: wrong super block size not processing"); + } + } +} diff --git a/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h new file mode 100644 index 000000000..fed573f2e --- /dev/null +++ b/plugins/channeltx/sdrdaemonchannelsource/sdrdaemonchannelsourcethread.h @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon source channel (Tx) UDP receiver thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// 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 // +// // +// 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 CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ +#define CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ + +#include +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class QUdpSocket; + +class SDRDaemonChannelSourceThread : public QThread { + 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) + { } + }; + + class MsgDataBind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QHostAddress getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgDataBind* create(const QString& address, uint16_t port) { + return new MsgDataBind(address, port); + } + + protected: + QHostAddress m_address; + uint16_t m_port; + + MsgDataBind(const QString& address, uint16_t port) : + Message(), + m_port(port) + { + m_address.setAddress(address); + } + }; + + SDRDaemonChannelSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~SDRDaemonChannelSourceThread(); + + void startStop(bool start); + void dataBind(const QString& address, uint16_t port); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + MessageQueue m_inputMessageQueue; + SDRDaemonDataQueue *m_dataQueue; + + QHostAddress m_address; + QUdpSocket *m_socket; + + static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer + SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity + + void startWork(); + void stopWork(); + + void run(); + +private slots: + void handleInputMessages(); + void readPendingDatagrams(); +}; + + + +#endif /* CHANNEL_TX_SDRDAEMONCHANNELSOURCETHREAD_H_ */