diff options
author | Christian Daniel <cd@maintech.de> | 2013-03-22 11:18:30 +0100 |
---|---|---|
committer | Christian Daniel <cd@maintech.de> | 2013-03-22 11:18:30 +0100 |
commit | eca56e35be841396f6c57bab540a3e1503253d56 (patch) | |
tree | 4a0a29cd53a84339e56754c32bf67391c83494fe /plugins | |
parent | 2c8c930b39fe069b36d81caa00401d9ac182a9d8 (diff) |
monster rework
- pluginify whole project
- reorganize directory structure
- fix PortAudio detection script
- implement generic channelizer
- fix several OpenGL problems
- rework presets
- add audio mixing
- too many more
Diffstat (limited to 'plugins')
42 files changed, 5342 insertions, 0 deletions
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..fffde4b --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,4 @@ +project(plugins) + +add_subdirectory(demod) +add_subdirectory(samplesource) diff --git a/plugins/demod/CMakeLists.txt b/plugins/demod/CMakeLists.txt new file mode 100644 index 0000000..78804e6 --- /dev/null +++ b/plugins/demod/CMakeLists.txt @@ -0,0 +1,4 @@ +project(demod) + +add_subdirectory(nfm) +add_subdirectory(tetra) diff --git a/plugins/demod/nfm/CMakeLists.txt b/plugins/demod/nfm/CMakeLists.txt new file mode 100644 index 0000000..ee36a4d --- /dev/null +++ b/plugins/demod/nfm/CMakeLists.txt @@ -0,0 +1,44 @@ +project(nfm) + +set(nfm_SOURCES + nfmdemod.cpp + nfmdemodgui.cpp + nfmplugin.cpp +) + +set(nfm_HEADERS + nfmdemod.h + nfmdemodgui.h + nfmplugin.h +) + +set(nfm_FORMS + nfmdemodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${OPENGL_INCLUDE_DIR} +) + +include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt4_wrap_cpp(nfm_HEADERS_MOC ${nfm_HEADERS}) +qt4_wrap_ui(nfm_FORMS_HEADERS ${nfm_FORMS}) + +add_library(demodnfm SHARED + ${nfm_SOURCES} + ${nfm_HEADERS_MOC} + ${nfm_FORMS_HEADERS} +) + +target_link_libraries(demodnfm + ${QT_LIBRARIES} + ${OPENGL_LIBRARIES} +) diff --git a/plugins/demod/nfm/nfmdemod.cpp b/plugins/demod/nfm/nfmdemod.cpp new file mode 100644 index 0000000..7324852 --- /dev/null +++ b/plugins/demod/nfm/nfmdemod.cpp @@ -0,0 +1,140 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <QTime> +#include <stdio.h> +#include "nfmdemod.h" +#include "audio/audiooutput.h" +#include "dsp/dspcommands.h" + +MessageRegistrator NFMDemod::MsgConfigureNFMDemod::ID("MsgConfigureNFMDemod"); + +NFMDemod::NFMDemod(AudioFifo* audioFifo, SampleSink* sampleSink) : + m_sampleSink(sampleSink), + m_audioFifo(audioFifo) +{ + m_rfBandwidth = 12500; + m_volume = 2.0; + m_squelchLevel = pow(10.0, -40.0 / 20.0); + m_sampleRate = 500000; + m_frequency = 0; + m_squelchLevel *= m_squelchLevel; + + m_nco.setFreq(m_frequency, m_sampleRate); + m_interpolator.create(1, 32, 32 * m_sampleRate, 12500); + m_sampleDistanceRemain = (Real)m_sampleRate / 44100.0; + + m_lowpass.create(21, 44100, 3000); + + m_audioBuffer.resize(256); + m_audioBufferFill = 0; +} + +NFMDemod::~NFMDemod() +{ +} + +void NFMDemod::configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch) +{ + Message* cmd = MsgConfigureNFMDemod::create(rfBandwidth, afBandwidth, volume, squelch); + cmd->submit(messageQueue, this); +} + +void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst) +{ + size_t count = end - begin; + + Complex ci; + bool consumed; + + for(SampleVector::const_iterator it = begin; it < end; ++it) { + Complex c(it->real() / 32768.0, it->imag() / 32768.0); + c *= m_nco.nextIQ(); + + consumed = false; + if(m_interpolator.interpolate(&m_sampleDistanceRemain, c, &consumed, &ci)) { + m_sampleBuffer.push_back(Sample(ci.real() * 32768.0, ci.imag() * 32768.0)); + + if((ci.real() * ci.real() + ci.imag() * ci.imag()) >= m_squelchLevel) + m_squelchState = m_sampleRate / 100; + + if(m_squelchState > 0) { + m_squelchState--; + Complex d = ci * conj(m_lastSample); + m_lastSample = ci; + Real demod = atan2(d.imag(), d.real()) / M_PI; + demod = m_lowpass.filter(demod); + demod *= m_volume; + qint16 sample = demod * 32767; + + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + ++m_audioBufferFill; + if(m_audioBufferFill >= m_audioBuffer.size()) { + if(m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill) + ;//qDebug("lost samples"); + m_audioBufferFill = 0; + } + } + + m_sampleDistanceRemain += (Real)m_sampleRate / 44100.0; + } + } + if(m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill) + ;//qDebug("lost samples"); + m_audioBufferFill = 0; + + if(m_sampleSink != NULL) + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst); + m_sampleBuffer.clear(); +} + +void NFMDemod::start() +{ + m_squelchState = 0; +} + +void NFMDemod::stop() +{ +} + +bool NFMDemod::handleMessage(Message* cmd) +{ + if(cmd->id() == DSPSignalNotification::ID()) { + DSPSignalNotification* signal = (DSPSignalNotification*)cmd; + qDebug("%d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset()); + m_sampleRate = signal->getSampleRate(); + m_nco.setFreq(-signal->getFrequencyOffset(), m_sampleRate); + m_interpolator.create(25, 32, 32 * m_sampleRate, m_rfBandwidth / 2.0); + m_sampleDistanceRemain = m_sampleRate / 44100.0; + m_squelchState = 0; + cmd->completed(); + return true; + } else if(cmd->id() == MsgConfigureNFMDemod::ID()) { + MsgConfigureNFMDemod* cfg = (MsgConfigureNFMDemod*)cmd; + m_rfBandwidth = cfg->getRFBandwidth(); + m_interpolator.create(25, 32, 32 * m_sampleRate, m_rfBandwidth / 2.0); + m_lowpass.create(21, 44100, cfg->getAFBandwidth()); + m_squelchLevel = pow(10.0, cfg->getSquelch() / 20.0); + m_squelchLevel *= m_squelchLevel; + m_volume = cfg->getVolume(); + cmd->completed(); + return true; + } else { + return false; + } +} diff --git a/plugins/demod/nfm/nfmdemod.h b/plugins/demod/nfm/nfmdemod.h new file mode 100644 index 0000000..c3e1e6e --- /dev/null +++ b/plugins/demod/nfm/nfmdemod.h @@ -0,0 +1,102 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_NFMDEMOD_H +#define INCLUDE_NFMDEMOD_H + +#include <vector> +#include "dsp/samplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/lowpass.h" +#include "audio/audiofifo.h" +#include "util/message.h" + +class AudioFifo; + +class NFMDemod : public SampleSink { +public: + NFMDemod(AudioFifo* audioFifo, SampleSink* sampleSink); + ~NFMDemod(); + + void configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch); + + void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst); + void start(); + void stop(); + bool handleMessage(Message* cmd); + +private: + class MsgConfigureNFMDemod : public Message { + public: + static MessageRegistrator ID; + + Real getRFBandwidth() const { return m_rfBandwidth; } + Real getAFBandwidth() const { return m_afBandwidth; } + Real getVolume() const { return m_volume; } + Real getSquelch() const { return m_squelch; } + + static MsgConfigureNFMDemod* create(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch) + { + return new MsgConfigureNFMDemod(rfBandwidth, afBandwidth, volume, squelch); + } + + private: + Real m_rfBandwidth; + Real m_afBandwidth; + Real m_volume; + Real m_squelch; + + MsgConfigureNFMDemod(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch) : + Message(ID()), + m_rfBandwidth(rfBandwidth), + m_afBandwidth(afBandwidth), + m_volume(volume), + m_squelch(squelch) + { } + }; + + struct AudioSample { + qint16 l; + qint16 r; + }; + typedef std::vector<AudioSample> AudioVector; + + Real m_rfBandwidth; + Real m_volume; + Real m_squelchLevel; + int m_sampleRate; + int m_frequency; + + NCO m_nco; + Interpolator m_interpolator; + Real m_sampleDistanceRemain; + Lowpass<Real> m_lowpass; + + int m_squelchState; + + Complex m_lastSample; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + AudioFifo* m_audioFifo; + + SampleSink* m_sampleSink; + SampleVector m_sampleBuffer; +}; + +#endif // INCLUDE_NFMDEMOD_H diff --git a/plugins/demod/nfm/nfmdemodgui.cpp b/plugins/demod/nfm/nfmdemodgui.cpp new file mode 100644 index 0000000..a630dd4 --- /dev/null +++ b/plugins/demod/nfm/nfmdemodgui.cpp @@ -0,0 +1,179 @@ +#include <QDockWidget> +#include <QMainWindow> +#include "nfmdemodgui.h" +#include "ui_nfmdemodgui.h" +#include "nfmdemodgui.h" +#include "ui_nfmdemodgui.h" +#include "dsp/threadedsamplesink.h" +#include "dsp/channelizer.h" +#include "nfmdemod.h" +#include "dsp/spectrumvis.h" +#include "gui/glspectrum.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" + +const int NFMDemodGUI::m_rfBW[] = { + 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000 +}; + +NFMDemodGUI* NFMDemodGUI::create(PluginAPI* pluginAPI) +{ + QDockWidget* dock = pluginAPI->createMainWindowDock(Qt::RightDockWidgetArea, tr("NFM Demodulator")); + dock->setObjectName(QString::fromUtf8("NFM Demodulator")); + NFMDemodGUI* gui = new NFMDemodGUI(pluginAPI, dock); + dock->setWidget(gui); + return gui; +} + +void NFMDemodGUI::destroy() +{ + delete m_dockWidget; +} + +void NFMDemodGUI::setWidgetName(const QString& name) +{ + qDebug("NFM: %s", qPrintable(name)); + m_dockWidget->setObjectName(name); +} + +void NFMDemodGUI::resetToDefaults() +{ + ui->rfBW->setValue(4); + ui->afBW->setValue(3); + ui->volume->setValue(20); + ui->squelch->setValue(-40); + applySettings(); +} + +QByteArray NFMDemodGUI::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_channelMarker->getCenterFrequency()); + s.writeS32(2, ui->rfBW->value()); + s.writeS32(3, ui->afBW->value()); + s.writeS32(4, ui->volume->value()); + s.writeS32(5, ui->squelch->value()); + return s.final(); +} + +bool NFMDemodGUI::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) { + qint32 tmp; + d.readS32(1, &tmp, 0); + m_channelMarker->setCenterFrequency(tmp); + d.readS32(2, &tmp, 4); + ui->rfBW->setValue(tmp); + d.readS32(3, &tmp, 3); + ui->afBW->setValue(tmp); + d.readS32(4, &tmp, 20); + ui->volume->setValue(tmp); + d.readS32(5, &tmp, -40); + ui->squelch->setValue(tmp); + applySettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool NFMDemodGUI::handleMessage(Message* message) +{ + return false; +} + +void NFMDemodGUI::viewChanged() +{ + applySettings(); +} + +void NFMDemodGUI::on_rfBW_valueChanged(int value) +{ + ui->rfBWText->setText(QString("%1 kHz").arg(m_rfBW[value] / 1000.0)); + m_channelMarker->setBandwidth(m_rfBW[value]); + applySettings(); +} + +void NFMDemodGUI::on_afBW_valueChanged(int value) +{ + ui->afBWText->setText(QString("%1 kHz").arg(value)); + applySettings(); +} + +void NFMDemodGUI::on_volume_valueChanged(int value) +{ + ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + applySettings(); +} + +void NFMDemodGUI::on_squelch_valueChanged(int value) +{ + ui->squelchText->setText(QString("%1 dB").arg(value)); + applySettings(); +} + +NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent) : + PluginGUI(parent), + ui(new Ui::NFMDemodGUI), + m_pluginAPI(pluginAPI), + m_dockWidget(dockWidget) +{ + ui->setupUi(this); + + m_audioFifo = new AudioFifo(4, 44100 / 4); + m_spectrumVis = new SpectrumVis(ui->glSpectrum); + m_nfmDemod = new NFMDemod(m_audioFifo, m_spectrumVis); + m_channelizer = new Channelizer(m_nfmDemod); + m_threadedSampleSink = new ThreadedSampleSink(m_channelizer); + m_pluginAPI->addAudioSource(m_audioFifo); + m_pluginAPI->addSampleSink(m_threadedSampleSink); + + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(44100); + ui->glSpectrum->setDisplayWaterfall(true); + ui->glSpectrum->setDisplayLiveSpectrum(true); + m_spectrumVis->configure(m_threadedSampleSink->getMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + + m_channelMarker = new ChannelMarker(this); + m_channelMarker->setColor(Qt::red); + m_channelMarker->setBandwidth(12500); + m_channelMarker->setCenterFrequency(0); + m_channelMarker->setVisible(true); + connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged())); + m_pluginAPI->addChannelMarker(m_channelMarker); + + applySettings(); +} + +NFMDemodGUI::~NFMDemodGUI() +{ + m_pluginAPI->removeAudioSource(m_audioFifo); + m_pluginAPI->removeSampleSink(m_threadedSampleSink); + delete m_threadedSampleSink; + delete m_channelizer; + delete m_nfmDemod; + delete m_spectrumVis; + delete m_audioFifo; + delete m_channelMarker; + delete ui; +} + +void NFMDemodGUI::applySettings() +{ + m_channelizer->configure(m_threadedSampleSink->getMessageQueue(), + 44100, + m_channelMarker->getCenterFrequency()); + m_nfmDemod->configure(m_threadedSampleSink->getMessageQueue(), + m_rfBW[ui->rfBW->value()], + ui->afBW->value() * 1000.0, + ui->volume->value() / 10.0, + ui->squelch->value()); +} diff --git a/plugins/demod/nfm/nfmdemodgui.h b/plugins/demod/nfm/nfmdemodgui.h new file mode 100644 index 0000000..1381b5a --- /dev/null +++ b/plugins/demod/nfm/nfmdemodgui.h @@ -0,0 +1,64 @@ +#ifndef INCLUDE_NFMDEMODGUI_H +#define INCLUDE_NFMDEMODGUI_H + +#include "plugin/plugingui.h" + +class QDockWidget; + +class PluginAPI; +class ChannelMarker; + +class AudioFifo; +class ThreadedSampleSink; +class Channelizer; +class NFMDemod; +class SpectrumVis; + +namespace Ui { + class NFMDemodGUI; +} + +class NFMDemodGUI : public PluginGUI { + Q_OBJECT + +public: + static NFMDemodGUI* create(PluginAPI* pluginAPI); + void destroy(); + + void setWidgetName(const QString& name); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + bool handleMessage(Message* message); + +private slots: + void viewChanged(); + void on_rfBW_valueChanged(int value); + void on_afBW_valueChanged(int value); + void on_volume_valueChanged(int value); + void on_squelch_valueChanged(int value); + +private: + Ui::NFMDemodGUI* ui; + PluginAPI* m_pluginAPI; + QDockWidget* m_dockWidget; + ChannelMarker* m_channelMarker; + + AudioFifo* m_audioFifo; + ThreadedSampleSink* m_threadedSampleSink; + Channelizer* m_channelizer; + NFMDemod* m_nfmDemod; + SpectrumVis* m_spectrumVis; + + static const QString m_demodName; + static const int m_rfBW[]; + + explicit NFMDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent = NULL); + ~NFMDemodGUI(); + + void applySettings(); +}; + +#endif // INCLUDE_NFMDEMODGUI_H diff --git a/plugins/demod/nfm/nfmdemodgui.ui b/plugins/demod/nfm/nfmdemodgui.ui new file mode 100644 index 0000000..5087276 --- /dev/null +++ b/plugins/demod/nfm/nfmdemodgui.ui @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NFMDemodGUI</class> + <widget class="QWidget" name="NFMDemodGUI"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>208</width> + <height>226</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="GLSpectrum" name="glSpectrum" native="true"> + <property name="minimumSize"> + <size> + <width>200</width> + <height>150</height> + </size> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="margin"> + <number>2</number> + </property> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="1"> + <widget class="QSlider" name="rfBW"> + <property name="maximum"> + <number>8</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="value"> + <number>4</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>RF Bandwidth</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>AF Bandwidth</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Squelch</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Volume</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="rfBWText"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>12.5kHz</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="afBW"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>20</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="value"> + <number>3</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSlider" name="volume"> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>20</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSlider" name="squelch"> + <property name="minimum"> + <number>-100</number> + </property> + <property name="maximum"> + <number>0</number> + </property> + <property name="value"> + <number>-40</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="afBWText"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>3 kHz</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="volumeText"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>2.0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QLabel" name="squelchText"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>-40dB</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GLSpectrum</class> + <extends>QWidget</extends> + <header>gui/glspectrum.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/plugins/demod/nfm/nfmplugin.cpp b/plugins/demod/nfm/nfmplugin.cpp new file mode 100644 index 0000000..6c1c063 --- /dev/null +++ b/plugins/demod/nfm/nfmplugin.cpp @@ -0,0 +1,52 @@ +#include <QtPlugin> +#include <QAction> +#include "plugin/pluginapi.h" +#include "nfmplugin.h" +#include "nfmdemodgui.h" + +const PluginDescriptor NFMPlugin::m_pluginDescriptor = { + displayedName: QString("NFM Demodulator"), + version: QString("---"), + copyright: QString("(c) maintech GmbH (written by Christian Daniel)"), + website: QString("http://www.maintech.de"), + licenseIsGPL: true, + sourceCodeURL: QString("http://www.maintech.de") +}; + +NFMPlugin::NFMPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& NFMPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void NFMPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register NFM demodulator + QAction* action = new QAction(tr("&NFM"), this); + connect(action, SIGNAL(triggered()), this, SLOT(createInstanceNFM())); + m_pluginAPI->registerDemodulator("de.maintech.sdrangelove.demod.nfm", this, action); +} + +PluginGUI* NFMPlugin::createDemod(const QString& demodName) +{ + if(demodName == "de.maintech.sdrangelove.demod.nfm") { + PluginGUI* gui = NFMDemodGUI::create(m_pluginAPI); + m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.nfm", gui); + return gui; + } else { + return NULL; + } +} + +void NFMPlugin::createInstanceNFM() +{ + m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.nfm", NFMDemodGUI::create(m_pluginAPI)); +} + +Q_EXPORT_PLUGIN2(nfmPlugin, NFMPlugin); diff --git a/plugins/demod/nfm/nfmplugin.h b/plugins/demod/nfm/nfmplugin.h new file mode 100644 index 0000000..18e6c40 --- /dev/null +++ b/plugins/demod/nfm/nfmplugin.h @@ -0,0 +1,28 @@ +#ifndef INCLUDE_NFMPLUGIN_H +#define INCLUDE_NFMPLUGIN_H + +#include <QObject> +#include "plugin/plugininterface.h" + +class NFMPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + +public: + explicit NFMPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + PluginGUI* createDemod(const QString& demodName); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; + +private slots: + void createInstanceNFM(); +}; + +#endif // INCLUDE_NFMPLUGIN_H diff --git a/plugins/demod/tetra/CMakeLists.txt b/plugins/demod/tetra/CMakeLists.txt new file mode 100644 index 0000000..7d1612a --- /dev/null +++ b/plugins/demod/tetra/CMakeLists.txt @@ -0,0 +1,44 @@ +project(tetra) + +set(tetra_SOURCES + tetrademod.cpp + tetrademodgui.cpp + tetraplugin.cpp +) + +set(tetra_HEADERS + tetrademod.h + tetrademodgui.h + tetraplugin.h +) + +set(tetra_FORMS + tetrademodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${OPENGL_INCLUDE_DIR} +) + +include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt4_wrap_cpp(tetra_HEADERS_MOC ${tetra_HEADERS}) +qt4_wrap_ui(tetra_FORMS_HEADERS ${tetra_FORMS}) + +add_library(demodtetra SHARED + ${tetra_SOURCES} + ${tetra_HEADERS_MOC} + ${tetra_FORMS_HEADERS} +) + +target_link_libraries(demodtetra + ${QT_LIBRARIES} + ${OPENGL_LIBRARIES} +) diff --git a/plugins/demod/tetra/tetrademod.cpp b/plugins/demod/tetra/tetrademod.cpp new file mode 100644 index 0000000..4cf8ea1 --- /dev/null +++ b/plugins/demod/tetra/tetrademod.cpp @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <stdio.h> +#include "tetrademod.h" +#include "dsp/dspcommands.h" + +MessageRegistrator TetraDemod::MsgConfigureTetraDemod::ID("MsgConfigureTetraDemod"); + +static FILE* f = NULL; + +TetraDemod::TetraDemod(SampleSink* sampleSink) : + m_sampleSink(sampleSink) +{ + m_sampleRate = 500000; + m_frequency = 0; + + m_nco.setFreq(m_frequency, m_sampleRate); + m_interpolator.create(1, 32, 32 * m_sampleRate, 36000); + m_sampleDistanceRemain = (Real)m_sampleRate / 36000.0; +} + +TetraDemod::~TetraDemod() +{ +} + +void TetraDemod::configure(MessageQueue* messageQueue) +{ + Message* cmd = MsgConfigureTetraDemod::create(); + cmd->submit(messageQueue, this); +} + +void TetraDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst) +{ + size_t count = end - begin; + + Complex ci; + bool consumed; + + for(SampleVector::const_iterator it = begin; it < end; ++it) { + Complex c(it->real() / 32768.0, it->imag() / 32768.0); + c *= m_nco.nextIQ(); + + consumed = false; + if(m_interpolator.interpolate(&m_sampleDistanceRemain, c, &consumed, &ci)) { + m_sampleBuffer.push_back(Sample(ci.real() * 32768.0, ci.imag() * 32768.0)); + + m_sampleDistanceRemain += (Real)m_sampleRate / 36000.0; + } + } + + if(f != NULL) { + fwrite(&m_sampleBuffer[0], m_sampleBuffer.size(), sizeof(m_sampleBuffer[0]), f); + } + + if(m_sampleSink != NULL) + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst); + m_sampleBuffer.clear(); +} + +void TetraDemod::start() +{ +} + +void TetraDemod::stop() +{ +} + +bool TetraDemod::handleMessage(Message* cmd) +{ + if(cmd->id() == DSPSignalNotification::ID()) { + DSPSignalNotification* signal = (DSPSignalNotification*)cmd; + qDebug("%d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset()); + m_sampleRate = signal->getSampleRate(); + m_nco.setFreq(-signal->getFrequencyOffset(), m_sampleRate); + m_interpolator.create(51, 32, 32 * m_sampleRate, 25000 / 2); + m_sampleDistanceRemain = m_sampleRate / 36000.0; + cmd->completed(); + return true; + } else if(cmd->id() == MsgConfigureTetraDemod::ID()) { + if(f == NULL) { + f = fopen("/tmp/tetra.iq", "wb"); + qDebug("started writing samples"); + } else { + fclose(f); + f = NULL; + qDebug("stopped writing samples"); + } + } else { + return false; + } +} diff --git a/plugins/demod/tetra/tetrademod.h b/plugins/demod/tetra/tetrademod.h new file mode 100644 index 0000000..a2a474b --- /dev/null +++ b/plugins/demod/tetra/tetrademod.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_TETRADEMOD_H +#define INCLUDE_TETRADEMOD_H + +#include "dsp/samplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "util/message.h" + +class MessageQueue; + +class TetraDemod : public SampleSink { +public: + TetraDemod(SampleSink* sampleSink); + ~TetraDemod(); + + void configure(MessageQueue* messageQueue); + + void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst); + void start(); + void stop(); + bool handleMessage(Message* cmd); + +private: + class MsgConfigureTetraDemod : public Message { + public: + static MessageRegistrator ID; + + static MsgConfigureTetraDemod* create() + { + return new MsgConfigureTetraDemod(); + } + + private: + MsgConfigureTetraDemod() : + Message(ID()) + { } + }; + + int m_sampleRate; + int m_frequency; + + NCO m_nco; + Interpolator m_interpolator; + Real m_sampleDistanceRemain; + + SampleSink* m_sampleSink; + SampleVector m_sampleBuffer; +}; + +#endif // INCLUDE_TETRADEMOD_H diff --git a/plugins/demod/tetra/tetrademodgui.cpp b/plugins/demod/tetra/tetrademodgui.cpp new file mode 100644 index 0000000..a0659b5 --- /dev/null +++ b/plugins/demod/tetra/tetrademodgui.cpp @@ -0,0 +1,110 @@ +#include <QDockWidget> +#include <QMainWindow> +#include "tetrademodgui.h" +#include "ui_tetrademodgui.h" +#include "dsp/threadedsamplesink.h" +#include "dsp/channelizer.h" +#include "tetrademod.h" +#include "dsp/spectrumvis.h" +#include "gui/glspectrum.h" +#include "plugin/pluginapi.h" + +TetraDemodGUI* TetraDemodGUI::create(PluginAPI* pluginAPI) +{ + QDockWidget* dock = pluginAPI->createMainWindowDock(Qt::RightDockWidgetArea, tr("Tetra Demodulator")); + dock->setObjectName(QString::fromUtf8("Tetra Demodulator")); + TetraDemodGUI* gui = new TetraDemodGUI(pluginAPI, dock); + dock->setWidget(gui); + return gui; +} + +void TetraDemodGUI::destroy() +{ + delete m_dockWidget; +} + +void TetraDemodGUI::setWidgetName(const QString& name) +{ + m_dockWidget->setObjectName(name); +} + +void TetraDemodGUI::resetToDefaults() +{ +} + +QByteArray TetraDemodGUI::serializeGeneral() const +{ + return QByteArray(); +} + +bool TetraDemodGUI::deserializeGeneral(const QByteArray& data) +{ + return false; +} + +QByteArray TetraDemodGUI::serialize() const +{ + return QByteArray(); +} + +bool TetraDemodGUI::deserialize(const QByteArray& data) +{ + return false; +} + +bool TetraDemodGUI::handleMessage(Message* message) +{ + return false; +} + +void TetraDemodGUI::viewChanged() +{ + m_channelizer->configure(m_threadedSampleSink->getMessageQueue(), 36000, m_channelMarker->getCenterFrequency()); +} + +TetraDemodGUI::TetraDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent) : + PluginGUI(parent), + ui(new Ui::TetraDemodGUI), + m_pluginAPI(pluginAPI), + m_dockWidget(dockWidget) +{ + ui->setupUi(this); + + m_spectrumVis = new SpectrumVis(ui->glSpectrum); + m_tetraDemod = new TetraDemod(m_spectrumVis); + m_channelizer = new Channelizer(m_tetraDemod); + m_threadedSampleSink = new ThreadedSampleSink(m_channelizer); + m_pluginAPI->addSampleSink(m_threadedSampleSink); + + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(36000); + ui->glSpectrum->setDisplayWaterfall(true); + ui->glSpectrum->setDisplayLiveSpectrum(true); + m_spectrumVis->configure(m_threadedSampleSink->getMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + + m_channelMarker = new ChannelMarker(this); + m_channelMarker->setColor(Qt::darkGreen); + m_channelMarker->setBandwidth(25000); + m_channelMarker->setCenterFrequency(0); + m_channelMarker->setVisible(true); + connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged())); + m_pluginAPI->addChannelMarker(m_channelMarker); + + viewChanged(); +} + +TetraDemodGUI::~TetraDemodGUI() +{ + m_pluginAPI->removeSampleSink(m_threadedSampleSink); + delete m_threadedSampleSink; + delete m_channelizer; + delete m_tetraDemod; + delete m_spectrumVis; + delete m_channelMarker; + delete ui; +} + +void TetraDemodGUI::on_test_clicked() +{ + m_tetraDemod->configure(m_threadedSampleSink->getMessageQueue()); +} diff --git a/plugins/demod/tetra/tetrademodgui.h b/plugins/demod/tetra/tetrademodgui.h new file mode 100644 index 0000000..9eacae6 --- /dev/null +++ b/plugins/demod/tetra/tetrademodgui.h @@ -0,0 +1,56 @@ +#ifndef INCLUDE_TETRADEMODGUI_H +#define INCLUDE_TETRADEMODGUI_H + +#include "plugin/plugingui.h" + +class QDockWidget; + +class PluginAPI; +class ChannelMarker; + +class ThreadedSampleSink; +class Channelizer; +class TetraDemod; +class SpectrumVis; + +namespace Ui { + class TetraDemodGUI; +} + +class TetraDemodGUI : public PluginGUI { + Q_OBJECT + +public: + static TetraDemodGUI* create(PluginAPI* pluginAPI); + void destroy(); + + void setWidgetName(const QString& name); + + void resetToDefaults(); + QByteArray serializeGeneral() const; + bool deserializeGeneral(const QByteArray& data); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + bool handleMessage(Message* message); + +private slots: + void on_test_clicked(); + void viewChanged(); + +private: + explicit TetraDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent = NULL); + ~TetraDemodGUI(); + + Ui::TetraDemodGUI* ui; + PluginAPI* m_pluginAPI; + QDockWidget* m_dockWidget; + ChannelMarker* m_channelMarker; + + ThreadedSampleSink* m_threadedSampleSink; + Channelizer* m_channelizer; + TetraDemod* m_tetraDemod; + SpectrumVis* m_spectrumVis; +}; + +#endif // INCLUDE_TETRADEMODGUI_H diff --git a/plugins/demod/tetra/tetrademodgui.ui b/plugins/demod/tetra/tetrademodgui.ui new file mode 100644 index 0000000..feaef10 --- /dev/null +++ b/plugins/demod/tetra/tetrademodgui.ui @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TetraDemodGUI</class> + <widget class="QWidget" name="TetraDemodGUI"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>200</width> + <height>179</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="GLSpectrum" name="glSpectrum" native="true"> + <property name="minimumSize"> + <size> + <width>200</width> + <height>150</height> + </size> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="test"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GLSpectrum</class> + <extends>QWidget</extends> + <header>gui/glspectrum.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/plugins/demod/tetra/tetraplugin.cpp b/plugins/demod/tetra/tetraplugin.cpp new file mode 100644 index 0000000..fe63e98 --- /dev/null +++ b/plugins/demod/tetra/tetraplugin.cpp @@ -0,0 +1,52 @@ +#include <QtPlugin> +#include <QAction> +#include "plugin/pluginapi.h" +#include "tetraplugin.h" +#include "tetrademodgui.h" + +const PluginDescriptor TetraPlugin::m_pluginDescriptor = { + displayedName: QString("Tetra Demodulator"), + version: QString("---"), + copyright: QString("(c) maintech GmbH (written by Christian Daniel)"), + website: QString("http://www.maintech.de"), + licenseIsGPL: true, + sourceCodeURL: QString("http://www.maintech.de") +}; + +TetraPlugin::TetraPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& TetraPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void TetraPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register Tetra demodulator + QAction* action = new QAction(tr("&Tetra"), this); + connect(action, SIGNAL(triggered()), this, SLOT(createInstanceTetra())); + m_pluginAPI->registerDemodulator("de.maintech.sdrangelove.demod.tetra", this, action); +} + +PluginGUI* TetraPlugin::createDemod(const QString& demodName) +{ + if(demodName == "de.maintech.sdrangelove.demod.tetra") { + PluginGUI* gui = TetraDemodGUI::create(m_pluginAPI); + m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.tetra", gui); + return gui; + } else { + return NULL; + } +} + +void TetraPlugin::createInstanceTetra() +{ + m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.tetra", TetraDemodGUI::create(m_pluginAPI)); +} + +Q_EXPORT_PLUGIN2(tetraPlugin, TetraPlugin); diff --git a/plugins/demod/tetra/tetraplugin.h b/plugins/demod/tetra/tetraplugin.h new file mode 100644 index 0000000..b3b6fd1 --- /dev/null +++ b/plugins/demod/tetra/tetraplugin.h @@ -0,0 +1,28 @@ +#ifndef INCLUDE_TETRAPLUGIN_H +#define INCLUDE_TETRAPLUGIN_H + +#include <QObject> +#include "plugin/plugininterface.h" + +class TetraPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + +public: + explicit TetraPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + PluginGUI* createDemod(const QString& demodName); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; + +private slots: + void createInstanceTetra(); +}; + +#endif // INCLUDE_TETRAPLUGIN_H diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt new file mode 100644 index 0000000..1a82fc6 --- /dev/null +++ b/plugins/samplesource/CMakeLists.txt @@ -0,0 +1,7 @@ +project(samplesource) + +find_package(LibOsmoSDR REQUIRED) +find_package(LibRTLSDR REQUIRED) + +add_subdirectory(osmosdr) +add_subdirectory(rtlsdr) diff --git a/plugins/samplesource/osmosdr/CMakeLists.txt b/plugins/samplesource/osmosdr/CMakeLists.txt new file mode 100644 index 0000000..d3b1b61 --- /dev/null +++ b/plugins/samplesource/osmosdr/CMakeLists.txt @@ -0,0 +1,47 @@ +project(osmosdr) + +set(osmosdr_SOURCES + osmosdrgui.cpp + osmosdrinput.cpp + osmosdrplugin.cpp + osmosdrthread.cpp +) + +set(osmosdr_HEADERS + osmosdrgui.h + osmosdrinput.h + osmosdrplugin.h + osmosdrthread.h +) + +set(osmosdr_FORMS + osmosdrgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${LIBOSMOSDR_INCLUDE_DIR} +) + +include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt4_wrap_cpp(osmosdr_HEADERS_MOC ${osmosdr_HEADERS}) +qt4_wrap_ui(osmosdr_FORMS_HEADERS ${osmosdr_FORMS}) + +add_library(inputosmosdr SHARED + ${osmosdr_SOURCES} + ${osmosdr_HEADERS_MOC} + ${osmosdr_FORMS_HEADERS} +) + +target_link_libraries(inputosmosdr + ${QT_LIBRARIES} + ${LIBOSMOSDR_LIBRARIES} + ${LIBUSB_LIBRARIES} +) diff --git a/plugins/samplesource/osmosdr/osmosdrgui.cpp b/plugins/samplesource/osmosdr/osmosdrgui.cpp new file mode 100644 index 0000000..ddaf4d3 --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrgui.cpp @@ -0,0 +1,305 @@ +#include "osmosdrgui.h" +#include "ui_osmosdrgui.h" +#include "plugin/pluginapi.h" + +OsmoSDRGui::OsmoSDRGui(PluginAPI* pluginAPI, QWidget* parent) : + PluginGUI(parent), + ui(new Ui::OsmoSDRGui), + m_pluginAPI(pluginAPI), + m_settings(), + m_sampleSource(NULL) +{ + ui->setupUi(this); + ui->centerFrequency->setValueRange(7, 20000U, 2200000U); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + displaySettings(); + + m_sampleSource = new OsmoSDRInput(m_pluginAPI->getMainWindowMessageQueue()); + m_pluginAPI->setSampleSource(m_sampleSource); +} + +OsmoSDRGui::~OsmoSDRGui() +{ + delete ui; +} + +void OsmoSDRGui::destroy() +{ + delete this; +} + +void OsmoSDRGui::resetToDefaults() +{ + m_generalSettings.resetToDefaults(); + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +QByteArray OsmoSDRGui::serializeGeneral() const +{ + return m_generalSettings.serialize(); +} + +bool OsmoSDRGui::deserializeGeneral(const QByteArray&data) +{ + if(m_generalSettings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +quint64 OsmoSDRGui::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +QByteArray OsmoSDRGui::serialize() const +{ + return m_settings.serialize(); +} + +bool OsmoSDRGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool OsmoSDRGui::handleMessage(Message* message) +{ + return false; + /* + if(message->id() == OsmoSDRInput::MsgReportOsmoSDR::ID()) { + m_gains = ((RTLSDRInput::MsgReportRTLSDR*)message)->getGains(); + displaySettings(); + message->completed(); + return true; + } else { + return false; + }*/ +} +#if 0 + +OsmoSDRGui::OsmoSDRGui(MessageQueue* msgQueue, QWidget* parent) : + SampleSourceGUI(parent), + ui(new Ui::OsmoSDRGui), + m_msgQueue(msgQueue), + m_settings() +{ + ui->setupUi(this); + ui->centerFrequency->setValueRange(7, 20000U, 2200000U); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + displaySettings(); +} + +OsmoSDRGui::~OsmoSDRGui() +{ + delete ui; +} + +QString OsmoSDRGui::serializeSettings() const +{ + return m_settings.serialize(); +} + +bool OsmoSDRGui::deserializeSettings(const QString& settings) +{ + if(m_settings.deserialize(settings)) { + displaySettings(); + sendSettings(); + return true; + } else { + return false; + } +} + +bool OsmoSDRGui::handleSourceMessage(DSPCmdSourceToGUI* cmd) +{ + return false; +} +#endif + +void OsmoSDRGui::displaySettings() +{ + ui->centerFrequency->setValue(m_generalSettings.m_centerFrequency / 1000); + ui->iqSwap->setChecked(m_settings.m_swapIQ); + ui->decimation->setValue(m_settings.m_decimation); + ui->e4000LNAGain->setValue(e4kLNAGainToIdx(m_settings.m_lnaGain)); + + ui->e4000MixerGain->setCurrentIndex((m_settings.m_mixerGain - 40) / 80); + if(m_settings.m_mixerEnhancement == 0) + ui->e4000MixerEnh->setCurrentIndex(0); + else ui->e4000MixerEnh->setCurrentIndex((m_settings.m_mixerEnhancement + 10) / 20); + + ui->e4000if1->setCurrentIndex((m_settings.m_if1gain + 30) / 90); + ui->e4000if2->setCurrentIndex(m_settings.m_if2gain / 30); + ui->e4000if3->setCurrentIndex(m_settings.m_if3gain / 30); + ui->e4000if4->setCurrentIndex(m_settings.m_if4gain / 10); + ui->e4000if5->setCurrentIndex(m_settings.m_if5gain / 30 - 1); + ui->e4000if6->setCurrentIndex(m_settings.m_if6gain / 30 - 1); + ui->filterI1->setValue(m_settings.m_opAmpI1); + ui->filterI2->setValue(m_settings.m_opAmpI2); + ui->filterQ1->setValue(m_settings.m_opAmpQ1); + ui->filterQ2->setValue(m_settings.m_opAmpQ2); + + ui->e4kI->setValue(m_settings.m_dcOfsI); + ui->e4kQ->setValue(m_settings.m_dcOfsQ); +} + +void OsmoSDRGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +int OsmoSDRGui::e4kLNAGainToIdx(int gain) const +{ + static const quint32 gainList[13] = { + -50, -25, 0, 25, 50, 75, 100, 125, 150, 175, 200, 250, 300 + }; + for(int i = 0; i < 13; i++) { + if(gainList[i] == gain) + return i; + } + return 0; +} + +int OsmoSDRGui::e4kIdxToLNAGain(int idx) const +{ + static const quint32 gainList[13] = { + -50, -25, 0, 25, 50, 75, 100, 125, 150, 175, 200, 250, 300 + }; + if((idx < 0) || (idx >= 13)) + return -50; + else return gainList[idx]; +} + +void OsmoSDRGui::on_iqSwap_toggled(bool checked) +{ + m_settings.m_swapIQ = checked; + sendSettings(); +} + +void OsmoSDRGui::on_e4000MixerGain_currentIndexChanged(int index) +{ + m_settings.m_mixerGain = index * 80 + 40; + sendSettings(); +} + +void OsmoSDRGui::on_e4000MixerEnh_currentIndexChanged(int index) +{ + if(index == 0) + m_settings.m_mixerEnhancement = 0; + else m_settings.m_mixerEnhancement = index * 20 - 10; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if1_currentIndexChanged(int index) +{ + m_settings.m_if1gain = index * 90 - 30; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if2_currentIndexChanged(int index) +{ + m_settings.m_if2gain = index * 30; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if3_currentIndexChanged(int index) +{ + m_settings.m_if3gain = index * 30; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if4_currentIndexChanged(int index) +{ + m_settings.m_if4gain = index * 10; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if5_currentIndexChanged(int index) +{ + m_settings.m_if5gain = (index + 1) * 30; + sendSettings(); +} + +void OsmoSDRGui::on_e4000if6_currentIndexChanged(int index) +{ + m_settings.m_if6gain = (index + 1) * 30; + sendSettings(); +} + +void OsmoSDRGui::on_centerFrequency_changed(quint64 value) +{ + m_generalSettings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void OsmoSDRGui::on_filterI1_valueChanged(int value) +{ + m_settings.m_opAmpI1 = value; + sendSettings(); +} + +void OsmoSDRGui::on_filterI2_valueChanged(int value) +{ + m_settings.m_opAmpI2 = value; + sendSettings(); +} + +void OsmoSDRGui::on_filterQ1_valueChanged(int value) +{ + m_settings.m_opAmpQ1 = value; + sendSettings(); +} + +void OsmoSDRGui::on_filterQ2_valueChanged(int value) +{ + m_settings.m_opAmpQ2 = value; + sendSettings(); +} + +void OsmoSDRGui::on_decimation_valueChanged(int value) +{ + ui->decimationDisplay->setText(tr("1:%1").arg(1 << value)); + m_settings.m_decimation = value; + sendSettings(); +} + +void OsmoSDRGui::on_e4000LNAGain_valueChanged(int value) +{ + int gain = e4kIdxToLNAGain(value); + ui->e4000LNAGainDisplay->setText(tr("%1.%2").arg(gain / 10).arg(abs(gain % 10))); + m_settings.m_lnaGain = gain; + sendSettings(); +} + +void OsmoSDRGui::on_e4kI_valueChanged(int value) +{ + m_settings.m_dcOfsI = value; + sendSettings(); +} + +void OsmoSDRGui::on_e4kQ_valueChanged(int value) +{ + m_settings.m_dcOfsQ = value; + sendSettings(); +} + +void OsmoSDRGui::updateHardware() +{ + m_updateTimer.stop(); + Message* msg = OsmoSDRInput::MsgConfigureOsmoSDR::create(m_generalSettings, m_settings); + msg->submit(m_pluginAPI->getDSPEngineMessageQueue()); +} diff --git a/plugins/samplesource/osmosdr/osmosdrgui.h b/plugins/samplesource/osmosdr/osmosdrgui.h new file mode 100644 index 0000000..770b48e --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrgui.h @@ -0,0 +1,76 @@ +#ifndef INCLUDE_OSMOSDRGUI_H +#define INCLUDE_OSMOSDRGUI_H + +#include <QTimer> +#include "plugin/plugingui.h" +#include "osmosdrinput.h" + +class PluginAPI; + +namespace Ui { + class OsmoSDRGui; +} + +class OsmoSDRGui : public PluginGUI { + Q_OBJECT + +public: + explicit OsmoSDRGui(PluginAPI* pluginAPI, QWidget* parent = NULL); + ~OsmoSDRGui(); + void destroy(); + + void resetToDefaults(); + QByteArray serializeGeneral() const; + bool deserializeGeneral(const QByteArray&data); + quint64 getCenterFrequency() const; + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + bool handleMessage(Message* message); + +private: + /* + Ui::OsmoSDRGui* ui; + + MessageQueue* m_msgQueue; + OsmoSDRInput::Settings m_settings; + QTimer m_updateTimer; + */ + + Ui::OsmoSDRGui* ui; + + PluginAPI* m_pluginAPI; + SampleSource::GeneralSettings m_generalSettings; + OsmoSDRInput::Settings m_settings; + QTimer m_updateTimer; + std::vector<int> m_gains; + SampleSource* m_sampleSource; + + void displaySettings(); + void sendSettings(); + int e4kLNAGainToIdx(int gain) const; + int e4kIdxToLNAGain(int idx) const; + +private slots: + void on_iqSwap_toggled(bool checked); + void on_e4000MixerGain_currentIndexChanged(int index); + void on_e4000MixerEnh_currentIndexChanged(int index); + void on_e4000if1_currentIndexChanged(int index); + void on_e4000if2_currentIndexChanged(int index); + void on_e4000if3_currentIndexChanged(int index); + void on_e4000if4_currentIndexChanged(int index); + void on_e4000if5_currentIndexChanged(int index); + void on_e4000if6_currentIndexChanged(int index); + void on_centerFrequency_changed(quint64 value); + void on_filterI1_valueChanged(int value); + void on_filterI2_valueChanged(int value); + void on_filterQ1_valueChanged(int value); + void on_filterQ2_valueChanged(int value); + void on_decimation_valueChanged(int value); + void on_e4000LNAGain_valueChanged(int value); + void on_e4kI_valueChanged(int value); + void on_e4kQ_valueChanged(int value); + + void updateHardware(); +}; + +#endif // INCLUDE_OSMOSDRGUI_H diff --git a/plugins/samplesource/osmosdr/osmosdrgui.ui b/plugins/samplesource/osmosdr/osmosdrgui.ui new file mode 100644 index 0000000..1ad4a88 --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrgui.ui @@ -0,0 +1,773 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OsmoSDRGui</class> + <widget class="QWidget" name="OsmoSDRGui"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>253</width> + <height>224</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>OsmoSDR</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="margin"> + <number>2</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="ValueDial" name="centerFrequency" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>16</height> + </size> + </property> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>20</pointsize> + </font> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Tuner center frequency in kHz</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Decimation</string> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="decimation"> + <property name="toolTip"> + <string>Signal decimation factor</string> + </property> + <property name="maximum"> + <number>5</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="decimationDisplay"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>1:1</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label_11"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>LNA</string> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="e4000LNAGain"> + <property name="toolTip"> + <string>LNA amplification</string> + </property> + <property name="maximum"> + <number>12</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="e4000LNAGainDisplay"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>-5.0</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>10</number> + </property> + <item> + <widget class="QLabel" name="label_19"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Mix</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="e4000MixerGain"> + <property name="toolTip"> + <string>Tuner mixer amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLength</enum> + </property> + <item> + <property name="text"> + <string>4</string> + </property> + </item> + <item> + <property name="text"> + <string>12</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_20"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Enh.</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="e4000MixerEnh"> + <property name="toolTip"> + <string>Tuner LNA amplification enhancement</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLength</enum> + </property> + <item> + <property name="text"> + <string>off</string> + </property> + </item> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>5</string> + </property> + </item> + <item> + <property name="text"> + <string>7</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="Line" name="line_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="iqSwap"> + <property name="text"> + <string>Swap IQ</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>3</number> + </property> + <item row="1" column="1"> + <widget class="QComboBox" name="e4000if2"> + <property name="toolTip"> + <string>Tuner IF 2 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>0</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + </widget> + </item> + <item row="1" column="3"> + <widget class="QComboBox" name="e4000if4"> + <property name="toolTip"> + <string>Tuner IF 4 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>0</string> + </property> + </item> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>2</string> + </property> + </item> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="label_18"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF4</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="label_25"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF5</string> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QComboBox" name="e4000if5"> + <property name="toolTip"> + <string>Tuner IF 5 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + <item> + <property name="text"> + <string>12</string> + </property> + </item> + <item> + <property name="text"> + <string>15</string> + </property> + </item> + </widget> + </item> + <item row="1" column="4"> + <widget class="QLabel" name="label_26"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF6</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QComboBox" name="e4000if3"> + <property name="toolTip"> + <string>Tuner IF 3 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>0</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_23"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF2</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QComboBox" name="e4000if6"> + <property name="toolTip"> + <string>Tuner IF 6 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + <item> + <property name="text"> + <string>12</string> + </property> + </item> + <item> + <property name="text"> + <string>15</string> + </property> + </item> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_24"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF3</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="e4000if1"> + <property name="toolTip"> + <string>Tuner IF 1 amplification</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + <item> + <property name="text"> + <string>-3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_12"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>IF1</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="2"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Poti 2</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>I</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSlider" name="filterI2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>I DC offset adjustment</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSlider" name="filterI1"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>I path amplification</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="filterQ1"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Q path amplification</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QSlider" name="filterQ2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Q DC offset adjustment</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QSlider" name="e4kQ"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Q DC offset adjustment</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>E4000</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Q</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Poti 1</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QSlider" name="e4kI"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Q path amplification</string> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ValueDial</class> + <extends>QWidget</extends> + <header>gui/valuedial.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/plugins/samplesource/osmosdr/osmosdrinput.cpp b/plugins/samplesource/osmosdr/osmosdrinput.cpp new file mode 100644 index 0000000..8e724cf --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrinput.cpp @@ -0,0 +1,479 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <string.h> +#include <errno.h> +#include "osmosdrinput.h" +#include "osmosdrthread.h" +#include "osmosdrgui.h" +#include "util/simpleserializer.h" + +MessageRegistrator OsmoSDRInput::MsgConfigureOsmoSDR::ID("MsgConfigureOsmoSDR"); + +OsmoSDRInput::Settings::Settings() : + m_swapIQ(false), + m_decimation(3), + m_lnaGain(-50), + m_mixerGain(40), + m_mixerEnhancement(0), + m_if1gain(-30), + m_if2gain(0), + m_if3gain(0), + m_if4gain(0), + m_if5gain(30), + m_if6gain(30), + m_opAmpI1(0), + m_opAmpI2(0), + m_opAmpQ1(0), + m_opAmpQ2(0), + m_dcOfsI(0), + m_dcOfsQ(0) +{ +} + +void OsmoSDRInput::Settings::resetToDefaults() +{ + m_swapIQ = false; + m_decimation = 3; + m_lnaGain = -50; + m_mixerGain = 40; + m_mixerEnhancement = 0; + m_if1gain = -30; + m_if2gain = 0; + m_if3gain = 0; + m_if4gain = 0; + m_if5gain = 30; + m_if6gain = 30; + m_opAmpI1 = 0; + m_opAmpI2 = 0; + m_opAmpQ1 = 0; + m_opAmpQ2 = 0; + m_dcOfsI = 0; + m_dcOfsQ = 0; +} + +QByteArray OsmoSDRInput::Settings::serialize() const +{ + SimpleSerializer s(1); + s.writeBool(1, m_swapIQ); + s.writeS32(2, m_decimation); + s.writeS32(3, m_lnaGain); + s.writeS32(4, m_mixerGain); + s.writeS32(5, m_mixerEnhancement); + s.writeS32(6, m_if1gain); + s.writeS32(7, m_if2gain); + s.writeS32(8, m_if3gain); + s.writeS32(9, m_if4gain); + s.writeS32(10, m_if5gain); + s.writeS32(11, m_if6gain); + s.writeS32(12, m_opAmpI1); + s.writeS32(13, m_opAmpI2); + s.writeS32(14, m_opAmpQ1); + s.writeS32(15, m_opAmpQ2); + s.writeS32(16, m_dcOfsI); + s.writeS32(17, m_dcOfsQ); + return s.final(); +} + +bool OsmoSDRInput::Settings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) { + d.readBool(1, &m_swapIQ, false); + d.readS32(2, &m_decimation, 3); + d.readS32(3, &m_lnaGain, -50); + d.readS32(4, &m_mixerGain, 40); + d.readS32(5, &m_mixerEnhancement, 0); + d.readS32(6, &m_if1gain, -30); + d.readS32(7, &m_if2gain, 0); + d.readS32(8, &m_if3gain, 0); + d.readS32(9, &m_if4gain, 0); + d.readS32(10, &m_if5gain, 30); + d.readS32(11, &m_if6gain, 30); + d.readS32(12, &m_opAmpI1, 0); + d.readS32(13, &m_opAmpI2, 0); + d.readS32(14, &m_opAmpQ1, 0); + d.readS32(15, &m_opAmpQ2, 0); + d.readS32(16, &m_dcOfsI, 0); + d.readS32(17, &m_dcOfsQ, 0); + return true; + } else { + resetToDefaults(); + return false; + } +} +#if 0 +OsmoSDRInput::Settings::Settings() : +{ +} + +QString OsmoSDRInput::Settings::serialize() const +{ + return QString("osmosdr:a:%1:%2:%3:%4:%5:%6:%7:%8:%9:%10:%11:%12:%13:%14:%15:%16:%17:%18") + .arg(centerFrequency) + .arg(swapIQ ? 1 : 0) + .arg(decimation) + .arg(lnaGain) + .arg(mixerGain) + .arg(mixerEnhancement) + .arg(if1gain) + .arg(if2gain) + .arg(if3gain) + .arg(if4gain) + .arg(if5gain) + .arg(if6gain) + .arg(opAmpI1) + .arg(opAmpI2) + .arg(opAmpQ1) + .arg(opAmpQ2) + .arg(dcOfsI) + .arg(dcOfsQ); +} + +bool OsmoSDRInput::Settings::deserialize(const QString& settings) +{ + QStringList list = settings.split(":"); + if(list.size() < 2) + return false; + if(list[0] != "osmosdr") + return false; + + if(list[1] == "a") { + bool ok; + if(list.size() != 20) + return false; + centerFrequency = list[2].toLongLong(&ok); + if(!ok) + return false; + swapIQ = (list[3].toInt(&ok) != 0) ? true : false; + if(!ok) + return false; + decimation = list[4].toInt(&ok); + if(!ok) + return false; + lnaGain = list[5].toInt(&ok); + if(!ok) + return false; + mixerGain = list[6].toInt(&ok); + if(!ok) + return false; + mixerEnhancement = list[7].toInt(&ok); + if(!ok) + return false; + if1gain = list[8].toInt(&ok); + if(!ok) + return false; + if2gain = list[9].toInt(&ok); + if(!ok) + return false; + if3gain = list[10].toInt(&ok); + if(!ok) + return false; + if4gain = list[11].toInt(&ok); + if(!ok) + return false; + if5gain = list[12].toInt(&ok); + if(!ok) + return false; + if6gain = list[13].toInt(&ok); + if(!ok) + return false; + opAmpI1 = list[14].toInt(&ok); + if(!ok) + return false; + opAmpI2 = list[15].toInt(&ok); + if(!ok) + return false; + opAmpQ1 = list[16].toInt(&ok); + if(!ok) + return false; + opAmpQ2 = list[17].toInt(&ok); + if(!ok) + return false; + dcOfsI = list[18].toInt(&ok); + if(!ok) + return false; + dcOfsQ = list[19].toInt(&ok); + if(!ok) + return false; + return true; + } else { + return false; + } +} + +MessageRegistrator OsmoSDRInput::MsgConfigureSourceOsmoSDR::ID("MsgConfigureSourceOsmoSDR"); +#endif +OsmoSDRInput::OsmoSDRInput(MessageQueue* msgQueueToGUI) : + SampleSource(msgQueueToGUI), + m_settings(), + m_dev(NULL), + m_osmoSDRThread(NULL), + m_deviceDescription() +{ +} + +OsmoSDRInput::~OsmoSDRInput() +{ + stopInput(); +} + +bool OsmoSDRInput::startInput(int device) +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_dev != NULL) + stopInput(); + + char vendor[256]; + char product[256]; + char serial[256]; + int res; + + if(!m_sampleFifo.setSize(524288)) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + if((res = osmosdr_open(&m_dev, device)) < 0) { + qCritical("could not open OsmoSDR #%d: %s", device, strerror(errno)); + return false; + } + + vendor[0] = '\0'; + product[0] = '\0'; + serial[0] = '\0'; + if((res = osmosdr_get_usb_strings(m_dev, vendor, product, serial)) < 0) { + qCritical("error accessing USB device"); + goto failed; + } + qDebug("OsmoSDRInput open: %s %s, SN: %s", vendor, product, serial); + m_deviceDescription = QString("%1 (SN %2)").arg(product).arg(serial); + + if((res = osmosdr_set_tuner_gain_mode(m_dev, 1)) < 0) { + qCritical("error setting tuner gain mode"); + goto failed; + } + + if((res = osmosdr_reset_buffer(m_dev)) < 0) { + qCritical("could not reset USB EP buffers: %s", strerror(errno)); + goto failed; + } + + if((m_osmoSDRThread = new OsmoSDRThread(m_dev, &m_sampleFifo)) == NULL) { + qFatal("out of memory"); + goto failed; + } + m_osmoSDRThread->startWork(); + + mutexLocker.unlock(); + applySettings(m_generalSettings, m_settings, true); + + qDebug("OsmoSDRInput: start"); + + return true; + +failed: + stopInput(); + return false; +} + +void OsmoSDRInput::stopInput() +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_osmoSDRThread != NULL) { + m_osmoSDRThread->stopWork(); + delete m_osmoSDRThread; + m_osmoSDRThread = NULL; + } + if(m_dev != NULL) { + osmosdr_close(m_dev); + m_dev = NULL; + } + m_deviceDescription.clear(); +} + +const QString& OsmoSDRInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int OsmoSDRInput::getSampleRate() const +{ + return 4000000 / (1 << m_settings.m_decimation); +} + +quint64 OsmoSDRInput::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +bool OsmoSDRInput::handleMessage(Message* message) +{ + if(message->id() == MsgConfigureOsmoSDR::ID()) { + MsgConfigureOsmoSDR* conf = (MsgConfigureOsmoSDR*)message; + if(!applySettings(conf->getGeneralSettings(), conf->getSettings(), false)) + qDebug("OsmoSDR config error"); + message->completed(); + return true; + } else { + return false; + } + + /* + if(cmd->sourceType() != DSPCmdConfigureSourceOsmoSDR::SourceType) + return false; + if(!applySettings(((DSPCmdConfigureSourceOsmoSDR*)cmd)->getSettings(), false)) + qDebug("OsmoSDR config error"); + cmd->completed(); + return true; + */ + return false; +} + +bool OsmoSDRInput::applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + + if((m_generalSettings.m_centerFrequency != generalSettings.m_centerFrequency) || force) { + m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency; + if(m_dev != NULL) { + if(osmosdr_set_center_freq(m_dev, m_generalSettings.m_centerFrequency) != 0) + qDebug("osmosdr_set_center_freq(%lld) failed", m_generalSettings.m_centerFrequency); + } + } + + if((m_settings.m_swapIQ != settings.m_swapIQ) || force) { + m_settings.m_swapIQ = settings.m_swapIQ; + if(m_dev != NULL) { + if(osmosdr_set_fpga_iq_swap(m_dev, m_settings.m_swapIQ ? 1 : 0) == 0) + qDebug("osmosdr_set_fpga_iq_swap() failed"); + } + } + + if((m_settings.m_decimation != settings.m_decimation) || force) { + m_settings.m_decimation = settings.m_decimation; + if(m_dev != NULL) { + if(!osmosdr_set_fpga_decimation(m_dev, m_settings.m_decimation)) + qDebug("osmosdr_set_fpga_decimation() failed"); + } + } + + if((m_settings.m_lnaGain != settings.m_lnaGain) || force) { + m_settings.m_lnaGain = settings.m_lnaGain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_lna_gain(m_dev, m_settings.m_lnaGain)) + qDebug("osmosdr_set_tuner_lna_gain() failed"); + } + } + + if((m_settings.m_mixerGain != settings.m_mixerGain) || force) { + m_settings.m_mixerGain = settings.m_mixerGain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_mixer_gain(m_dev, m_settings.m_mixerGain)) + qDebug("osmosdr_set_tuner_mixer_gain() failed"); + } + } + + if((m_settings.m_mixerEnhancement != settings.m_mixerEnhancement) || force) { + m_settings.m_mixerEnhancement = settings.m_mixerEnhancement; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_mixer_enh(m_dev, m_settings.m_mixerEnhancement)) + qDebug("osmosdr_set_tuner_mixer_enh() failed"); + } + } + + if((m_settings.m_if1gain != settings.m_if1gain) || force) { + m_settings.m_if1gain = settings.m_if1gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 1, m_settings.m_if1gain)) + qDebug("osmosdr_set_tuner_if_gain(1) failed"); + } + } + + if((m_settings.m_if2gain != settings.m_if2gain) || force) { + m_settings.m_if2gain = settings.m_if2gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 2, m_settings.m_if2gain)) + qDebug("osmosdr_set_tuner_if_gain(2) failed"); + } + } + + if((m_settings.m_if3gain != settings.m_if3gain) || force) { + m_settings.m_if3gain = settings.m_if3gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 3, m_settings.m_if3gain)) + qDebug("osmosdr_set_tuner_if_gain(3) failed"); + } + } + + if((m_settings.m_if4gain != settings.m_if4gain) || force) { + m_settings.m_if4gain = settings.m_if4gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 4, m_settings.m_if4gain)) + qDebug("osmosdr_set_tuner_if_gain(4) failed"); + } + } + + if((m_settings.m_if5gain != settings.m_if5gain) || force) { + m_settings.m_if5gain = settings.m_if5gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 5, m_settings.m_if5gain)) + qDebug("osmosdr_set_tuner_if_gain(5) failed"); + } + } + + if((m_settings.m_if6gain != settings.m_if6gain) || force) { + m_settings.m_if6gain = settings.m_if6gain; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_if_gain(m_dev, 6, m_settings.m_if6gain)) + qDebug("osmosdr_set_tuner_if_gain(6) failed"); + } + } + + if((m_settings.m_opAmpI1 != settings.m_opAmpI1) || (m_settings.m_opAmpI2 != settings.m_opAmpI2) || + (m_settings.m_opAmpQ1 != settings.m_opAmpQ1) || (m_settings.m_opAmpQ2 != settings.m_opAmpQ2) || + force) { + m_settings.m_opAmpI1 = settings.m_opAmpI1; + m_settings.m_opAmpI2 = settings.m_opAmpI2; + m_settings.m_opAmpQ1 = settings.m_opAmpQ1; + m_settings.m_opAmpQ2 = settings.m_opAmpQ2; + if(m_dev != NULL) { + if(!osmosdr_set_iq_amp(m_dev, m_settings.m_opAmpI1, m_settings.m_opAmpI2, m_settings.m_opAmpQ1, m_settings.m_opAmpQ2)) + qDebug("osmosdr_set_iq_amp(1) failed"); + } + } + + if((m_settings.m_dcOfsI != settings.m_dcOfsI) || (m_settings.m_dcOfsQ != settings.m_dcOfsQ) || + force) { + m_settings.m_dcOfsI = settings.m_dcOfsI; + m_settings.m_dcOfsQ = settings.m_dcOfsQ; + if(m_dev != NULL) { + if(!osmosdr_set_tuner_dc_offset(m_dev, m_settings.m_dcOfsI, m_settings.m_dcOfsQ)) + qDebug("osmosdr_set_tuner_dc_offset() failed"); + } + } + + return true; +} diff --git a/plugins/samplesource/osmosdr/osmosdrinput.h b/plugins/samplesource/osmosdr/osmosdrinput.h new file mode 100644 index 0000000..9cea94d --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrinput.h @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_OSMOSDRINPUT_H +#define INCLUDE_OSMOSDRINPUT_H + +#include "dsp/samplesource/samplesource.h" +#include <osmosdr.h> +#include <QString> + +class OsmoSDRThread; + +class OsmoSDRInput : public SampleSource { +public: + struct Settings { + bool m_swapIQ; + qint32 m_decimation; + qint32 m_lnaGain; + qint32 m_mixerGain; + qint32 m_mixerEnhancement; + qint32 m_if1gain; + qint32 m_if2gain; + qint32 m_if3gain; + qint32 m_if4gain; + qint32 m_if5gain; + qint32 m_if6gain; + qint32 m_opAmpI1; + qint32 m_opAmpI2; + qint32 m_opAmpQ1; + qint32 m_opAmpQ2; + qint32 m_dcOfsI; + qint32 m_dcOfsQ; + + Settings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + + class MsgConfigureOsmoSDR : public Message { + public: + static MessageRegistrator ID; + + const GeneralSettings& getGeneralSettings() const { return m_generalSettings; } + const Settings& getSettings() const { return m_settings; } + + static MsgConfigureOsmoSDR* create(const GeneralSettings& generalSettings, const Settings& settings) + { + return new MsgConfigureOsmoSDR(generalSettings, settings); + } + + protected: + GeneralSettings m_generalSettings; + Settings m_settings; + + MsgConfigureOsmoSDR(const GeneralSettings& generalSettings, const Settings& settings) : + Message(ID()), + m_generalSettings(generalSettings), + m_settings(settings) + { } + }; + + OsmoSDRInput(MessageQueue* msgQueueToGUI); + ~OsmoSDRInput(); + + bool startInput(int device); + void stopInput(); + + const QString& getDeviceDescription() const; + int getSampleRate() const; + quint64 getCenterFrequency() const; + + bool handleMessage(Message* message); + +private: + QMutex m_mutex; + Settings m_settings; + osmosdr_dev_t* m_dev; + OsmoSDRThread* m_osmoSDRThread; + QString m_deviceDescription; + + bool applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force); +}; + +#endif // INCLUDE_OSMOSDRINPUT_H diff --git a/plugins/samplesource/osmosdr/osmosdrplugin.cpp b/plugins/samplesource/osmosdr/osmosdrplugin.cpp new file mode 100644 index 0000000..2b06922 --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrplugin.cpp @@ -0,0 +1,67 @@ +#include <QtPlugin> +#include <QAction> +#include <osmosdr.h> +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "osmosdrplugin.h" +#include "osmosdrgui.h" + +const PluginDescriptor OsmoSDRPlugin::m_pluginDescriptor = { + displayedName: QString("OsmoSDR Input"), + version: QString("---"), + copyright: QString("(c) Christian Daniel"), + website: QString("http://sdr.osmocom.org/trac/wiki/osmo-sdr"), + licenseIsGPL: true, + sourceCodeURL: QString("http://cgit.osmocom.org/cgit/osmo-sdr") +}; + +OsmoSDRPlugin::OsmoSDRPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& OsmoSDRPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void OsmoSDRPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.osmo-sdr", this); +} + +PluginInterface::SampleSourceDevices OsmoSDRPlugin::enumSampleSources() +{ + SampleSourceDevices result; + int count = osmosdr_get_device_count(); + char vendor[256]; + char product[256]; + char serial[256]; + + for(int i = 0; i < count; i++) { + vendor[0] = '\0'; + product[0] = '\0'; + serial[0] = '\0'; + + if(osmosdr_get_device_usb_strings(i, vendor, product, serial) != 0) + continue; + QString displayedName(QString("OsmoSDR #%1 (#%2)").arg(i + 1).arg(serial)); + SimpleSerializer s(1); + s.writeS32(1, i); + result.append(SampleSourceDevice(displayedName, "org.osmocom.sdr.samplesource.osmo-sdr", s.final())); + } + return result; +} + +PluginGUI* OsmoSDRPlugin::createSampleSource(const QString& sourceName, const QByteArray& address) +{ + if(sourceName == "org.osmocom.sdr.samplesource.osmo-sdr") { + return new OsmoSDRGui(m_pluginAPI); + } else { + return NULL; + } +} + +Q_EXPORT_PLUGIN2(osmosdrPlugin, OsmoSDRPlugin); diff --git a/plugins/samplesource/osmosdr/osmosdrplugin.h b/plugins/samplesource/osmosdr/osmosdrplugin.h new file mode 100644 index 0000000..65f541b --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrplugin.h @@ -0,0 +1,26 @@ +#ifndef INCLUDE_OSMOSDRPLUGIN_H +#define INCLUDE_OSMOSDRPLUGIN_H + +#include <QObject> +#include "plugin/plugininterface.h" + +class OsmoSDRPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + +public: + explicit OsmoSDRPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + SampleSourceDevices enumSampleSources(); + PluginGUI* createSampleSource(const QString& sourceName, const QByteArray& address); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_OSMOSDRPLUGIN_H diff --git a/plugins/samplesource/osmosdr/osmosdrthread.cpp b/plugins/samplesource/osmosdr/osmosdrthread.cpp new file mode 100644 index 0000000..25fcfdb --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrthread.cpp @@ -0,0 +1,116 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <stdio.h> +#include <errno.h> +#include "osmosdrthread.h" +#include "dsp/samplefifo.h" + +OsmoSDRThread::OsmoSDRThread(osmosdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_sampleFifo(sampleFifo) +{ +} + +OsmoSDRThread::~OsmoSDRThread() +{ + stopWork(); +} + +void OsmoSDRThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void OsmoSDRThread::stopWork() +{ + m_running = false; + wait(); +} + +void OsmoSDRThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + //m_f = fopen("/tmp/samples.bin", "wb"); + + while(m_running) { + if((res = osmosdr_read_async(m_dev, &OsmoSDRThread::callbackHelper, this, 16, sizeof(Sample) * 8192 * 2)) < 0) { + qCritical("OsmoSDRThread: async error: %s", strerror(errno)); + break; + } + } + + m_running = false; +} + +void OsmoSDRThread::checkData(const quint8* buf, qint32 len) +{ + const Sample* s = (const Sample*)buf; + len /= sizeof(Sample); + + while(len) { + if((s->i != m_nextI) || (s->q != m_nextQ)) { + qDebug("continuity error after %llu samples", m_samplePos); + m_samplePos = 0; + m_nextI = s->i - 1; + m_nextQ = s->q + 1; + } else { + m_nextI--; + m_nextQ++; + m_samplePos++; + } + len--; + s++; + } +} + +void OsmoSDRThread::callback(const quint8* buf, qint32 len) +{ + /* + qDebug("%d", len); +*/ + /* + for(int i = 0; i < len / 2; i += 2) { + ((qint16*)buf)[i] = sin((float)(cntr) * 1024* 2.0 * M_PI / 65536.0) * 32000.0; + ((qint16*)buf)[i + 1] = -cos((float)(cntr++) * 1024*2.0 * M_PI / 65536.0) * 32000.0; + } + */ + + //m_sampleFifo->write((SampleVector::const_iterator)((Sample*)buf), (SampleVector::const_iterator)((Sample*)(buf + len))); + //fwrite(buf, 1, len, m_f); + //checkData(buf, len); + + m_sampleFifo->write(buf, len); + if(!m_running) + osmosdr_cancel_async(m_dev); +} + +void OsmoSDRThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx) +{ + OsmoSDRThread* thread = (OsmoSDRThread*)ctx; + thread->callback(buf, len); +} diff --git a/plugins/samplesource/osmosdr/osmosdrthread.h b/plugins/samplesource/osmosdr/osmosdrthread.h new file mode 100644 index 0000000..0aa0142 --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrthread.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_OSMOSDRTHREAD_H +#define INCLUDE_OSMOSDRTHREAD_H + +#include <QThread> +#include <QMutex> +#include <QWaitCondition> +#include <osmosdr.h> + +class SampleFifo; + +class OsmoSDRThread : public QThread { + Q_OBJECT + +public: + OsmoSDRThread(osmosdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent = NULL); + ~OsmoSDRThread(); + + void startWork(); + void stopWork(); + +private: +#pragma pack(push, 1) + struct Sample { + qint16 i; + qint16 q; + }; +#pragma pack(pop) + + qint16 m_nextI; + qint16 m_nextQ; + quint64 m_samplePos; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + FILE* m_f; + + osmosdr_dev_t* m_dev; + SampleFifo* m_sampleFifo; + + void run(); + + void checkData(const quint8* buf, qint32 len); + void callback(const quint8* buf, qint32 len); + + static void callbackHelper(unsigned char* buf, uint32_t len, void* ctx); +}; + +#endif // INCLUDE_OSMOSDRTHREAD_H diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.cpp b/plugins/samplesource/osmosdr/osmosdrupgrade.cpp new file mode 100644 index 0000000..bba600b --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrupgrade.cpp @@ -0,0 +1,518 @@ +#include <QFileDialog> +#include <QMessageBox> +#include <QCursor> +#include <QThread> +#include <libusb.h> +#include "osmosdrupgrade.h" +#include "ui_osmosdrupgrade.h" + +#define OSMOSDR_USB_VID 0x16c0 +#define OSMOSDR_USB_PID 0x0763 + +// DFU commands +#define DFU_DETACH 0x00 +#define DFU_DNLOAD 0x01 +#define DFU_UPLOAD 0x02 +#define DFU_GETSTATUS 0x03 +#define DFU_CLRSTATUS 0x04 +#define DFU_GETSTATE 0x05 +#define DFU_ABORT 0x06 + +// DFU states +#define ST_appIDLE 0 +#define ST_appDETACH 1 +#define ST_dfuIDLE 2 +#define ST_dfuDNLOAD_SYNC 3 +#define ST_dfuDNBUSY 4 +#define ST_dfuDNLOAD_IDLE 5 +#define ST_dfuMANIFEST_SYNC 6 +#define ST_dfuMANIFEST 7 +#define ST_dfuMANIFEST_WAIT_RST 8 +#define ST_dfuUPLOAD_IDLE 9 +#define ST_dfuERROR 10 + +#define DFU_PACKETSIZE 512 + +#pragma pack(push, 1) +struct dfu_getstatus { + quint8 bStatus; + quint8 bwPollTimeout[3]; + quint8 bState; + quint8 iString; +}; +#pragma pack(pop) + + +OsmoSDRUpgrade::OsmoSDRUpgrade(QWidget* parent) : + QDialog(parent), + ui(new Ui::OsmoSDRUpgrade), + m_usb(NULL) +{ + ui->setupUi(this); + connect(&m_searchDeviceTimer, SIGNAL(timeout()), this, SLOT(searchDeviceTick())); +} + +OsmoSDRUpgrade::~OsmoSDRUpgrade() +{ + delete ui; +} + +void OsmoSDRUpgrade::on_browse_clicked() +{ + QString filename = QFileDialog::getOpenFileName(this, tr("OsmoSDR Firmware File"), QString(), tr("OsmoSDR Firmware (*.ofw);;All Files (*)")); + if(filename.isEmpty()) + return; + + QFile file(filename); + if(file.size() > 10 * 1024 * 1024) { + QMessageBox::critical(this, tr("File Open Error"), tr("File is too big to be a firmware update for OsmoSDR.")); + return; + } + + QByteArray dfuApp; + QByteArray radioApp; + QByteArray fpgaBin; + mz_zip_archive zip; + memset(&zip, 0x00, sizeof(zip)); + + if(!mz_zip_reader_init_file(&zip, qPrintable(filename), 0)) { + mz_zip_reader_end(&zip); + QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update.")); + return; + } + + int dfuAppIndex = mz_zip_reader_locate_file(&zip, "dfuapp.bin", NULL, 0); + int radioAppIndex = mz_zip_reader_locate_file(&zip, "radioapp.bin", NULL, 0); + int fpgaIndex = mz_zip_reader_locate_file(&zip, "fpga.bin", NULL, 0); + + if((dfuAppIndex < 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) { + mz_zip_reader_end(&zip); + QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update.")); + return; + } + + if(!mz_zip_reader_extract_to_callback(&zip, dfuAppIndex, zipHelper, &dfuApp, 0)) { + mz_zip_reader_end(&zip); + QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update.")); + return; + } + + if(!mz_zip_reader_extract_to_callback(&zip, radioAppIndex, zipHelper, &radioApp, 0)) { + mz_zip_reader_end(&zip); + QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update.")); + return; + } + + if(!mz_zip_reader_extract_to_callback(&zip, fpgaIndex, zipHelper, &fpgaBin, 0)) { + mz_zip_reader_end(&zip); + QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update.")); + return; + } + + mz_zip_reader_end(&zip); + m_dfuApp = dfuApp; + m_radioApp = radioApp; + m_fpgaBin = fpgaBin; + + if(dfuAppIndex >= 0) { + ui->dfu->setEnabled(true); + ui->dfuSize->setText(tr("%1").arg(m_dfuApp.size())); + } else { + ui->dfu->setEnabled(false); + ui->dfuSize->setText(tr("-")); + } + if(radioAppIndex >= 0) { + ui->radio->setEnabled(true); + ui->radioSize->setText(tr("%1").arg(m_radioApp.size())); + } else { + ui->radio->setEnabled(false); + ui->dfuSize->setText(tr("-")); + } + if(fpgaIndex >= 0) { + ui->fpga->setEnabled(true); + ui->fpgaSize->setText(tr("%1").arg(m_fpgaBin.size())); + } else { + ui->fpga->setEnabled(false); + ui->fpgaSize->setText(tr("-")); + } + + + if((dfuAppIndex >= 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) { + ui->dfu->setChecked(true); + ui->radio->setChecked(false); + ui->fpga->setChecked(false); + } else { + ui->dfu->setChecked(false); + ui->radio->setChecked(radioAppIndex >= 0); + ui->fpga->setChecked(fpgaIndex >= 0); + } + + ui->fwBox->setEnabled(true); + ui->progressBox->setEnabled(true); + + QFileInfo fi(file.fileName()); + ui->filename->setText(fi.fileName()); +} + +void OsmoSDRUpgrade::on_dfu_toggled(bool checked) +{ + updateFlashButton(); +} + +void OsmoSDRUpgrade::on_radio_toggled(bool checked) +{ + updateFlashButton(); +} + +void OsmoSDRUpgrade::on_fpga_toggled(bool checked) +{ + updateFlashButton(); +} + +void OsmoSDRUpgrade::updateFlashButton() +{ + if(ui->dfu->isChecked() || ui->radio->isChecked() || ui->fpga->isChecked()) + ui->progressBox->setEnabled(true); + else ui->progressBox->setEnabled(false); +} + +void OsmoSDRUpgrade::on_start_clicked() +{ + ui->close->setEnabled(false); + ui->start->setEnabled(false); + QApplication::setOverrideCursor(Qt::WaitCursor); + + ui->log->clear(); + log(tr("Starting flash operation...")); + + if(libusb_init(&m_usb) < 0) { + fail(tr("Could not initialize libusb.")); + return; + } + + switchToDFU(); + + m_searchTries = 0; + m_searchDeviceTimer.start(250); +} + +void OsmoSDRUpgrade::searchDeviceTick() +{ + m_searchTries++; + log(tr("Searching device (try %1)").arg(m_searchTries)); + + libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID); + if(device == NULL) { + if(m_searchTries >= 10) { + m_searchDeviceTimer.stop(); + finish(); + } + return; + } + + m_searchDeviceTimer.stop(); + + libusb_device_descriptor deviceDescriptor; + + if(libusb_get_descriptor(device, LIBUSB_DT_DEVICE, 0, (unsigned char*)&deviceDescriptor, sizeof(deviceDescriptor)) < 0) { + libusb_close(device); + fail(tr("Could not read device descriptor.")); + return; + } + + char sn[64]; + memset(sn, 0x00, sizeof(sn)); + libusb_get_string_descriptor_ascii(device, deviceDescriptor.iSerialNumber, (unsigned char*)sn, sizeof(sn)); + sn[sizeof(sn) - 1] = '\0'; + + log(tr("OsmoSDR found (SN %1)").arg(sn)); + + quint8 buffer[255]; + if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) { + libusb_close(device); + fail(tr("Could not get a configuration descriptor")); + return; + } + + if(buffer[15] != 1) { + libusb_close(device); + fail(tr("Device not in DFU mode as it should be")); + return; + } + + quint8 state; + + if(dfuGetState(device, &state) < 0) { + libusb_reset_device(device); + if(dfuGetState(device, &state) < 0) { + libusb_close(device); + fail(tr("Cannot get device DFU state")); + return; + } + } + if((state != ST_dfuIDLE) && (state != ST_dfuDNLOAD_IDLE)) { + dfuGetStatus(device); + libusb_close(device); + fail(tr("Device is not in dfuIDLE or dfuDNLOAD_IDLE state")); + return; + } + + if(libusb_claim_interface(device, 0) < 0) { + libusb_close(device); + fail(tr("Could not claim interface")); + return; + } + + QByteArray* image; + int altSetting; + + while(true) { + if(ui->dfu->isChecked()) { + log(tr("Starting download of DFU application")); + image = &m_dfuApp; + altSetting = 1; + } else if(ui->radio->isChecked()) { + log(tr("Starting download of radio application")); + image = &m_radioApp; + altSetting = 0; + } else if(ui->fpga->isChecked()) { + log(tr("Starting download of FPGA image")); + image = &m_fpgaBin; + altSetting = 2; + } else { + log(tr("Switching back to radio application mode")); + dfuDetach(device); + break; + } + + if(libusb_set_interface_alt_setting(device, 0, altSetting) < 0) { + libusb_close(device); + fail(tr("Could not set alternate interface setting to %1").arg(altSetting)); + return; + } + + ui->progress->setMaximum((image->size() / DFU_PACKETSIZE) + ((image->size() % DFU_PACKETSIZE) ? 1 : 0)); + + int blocknum = 0; + while((state == ST_dfuIDLE) || (state == ST_dfuDNLOAD_IDLE)) { + int offset = blocknum * DFU_PACKETSIZE; + int blocklen = (int)image->size() - offset; + if(blocklen < 0) + blocklen = 0; + if(blocklen > DFU_PACKETSIZE) + blocklen = DFU_PACKETSIZE; + if(dfuDownloadBlock(device, blocknum, (const quint8*)(image->data() + offset), blocklen) < 0) { + if(dfuGetState(device, &state) < 0) { + if(ui->dfu->isChecked()) + break; + libusb_close(device); + fail(tr("Cannot get device DFU state")); + return; + } + dfuGetStatus(device); + libusb_close(device); + fail(tr("Error downloading block %1").arg(blocknum)); + return; + } + dfuGetStatus(device); + if(blocklen == 0) + break; + blocknum++; + ui->progress->setValue(blocknum); + QApplication::processEvents(); + } + log(tr("Download complete (state %1)").arg(state)); + + do { + if(dfuGetState(device, &state) < 0) { + if(ui->dfu->isChecked()) { + break; + } else { + libusb_close(device); + fail(tr("Cannot get device DFU state")); + return; + } + } + dfuGetStatus(device); + } while(state == ST_dfuMANIFEST); + + if(ui->dfu->isChecked()) { + ui->dfu->setChecked(false); + m_searchTries = 0; + m_searchDeviceTimer.start(250); + libusb_close(device); + return; + } else if(ui->radio->isChecked()) { + ui->radio->setChecked(false); + } else if(ui->fpga->isChecked()) { + ui->fpga->setChecked(false); + } + + QApplication::processEvents(); + } + + log(tr("Upgrade finished successfully")); + libusb_close(device); + finish(); +} + +void OsmoSDRUpgrade::switchToDFU() +{ + libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID); + if(device == NULL) { + log(tr("No OsmoSDR VID:PID %1:%2 found").arg(OSMOSDR_USB_VID, 4, 16, QChar('0')).arg(OSMOSDR_USB_PID, 4, 16, QChar('0'))); + return; + } + + quint8 buffer[255]; + if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) { + log(tr("Could not get a valid configuration descriptor")); + libusb_close(device); + return; + } + + if(buffer[15] != 1) { + if(libusb_claim_interface(device, 0) < 0) { + log(tr("Could not claim interface")); + libusb_close(device); + return; + } + + log(tr("Switching device to DFU mode")); + libusb_control_transfer(device, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x07, 0x0003, 0, NULL, 0, 50); + libusb_release_interface(device, 0); + } else { + log(tr("Device is already in DFU mode")); + } + + libusb_close(device); +} + +int OsmoSDRUpgrade::dfuGetState(libusb_device_handle* device, quint8* state) +{ + int res; + + res = libusb_control_transfer( + device, + 0xa1, + DFU_GETSTATE, + 0, + 0, + (unsigned char*)state, + 1, + 1000); + + return res; +} + +int OsmoSDRUpgrade::dfuGetStatus(libusb_device_handle* device) +{ + int res; + struct dfu_getstatus getstatus; +/* + static const char* statedesc[] = { + "appIDLE", + "appDETACH", + "dfuIDLE", + "dfuDNLOAD-SYNC", + "dfuDNBUSY", + "dfuDNLOAD-IDLE", + "dfuMANIFEST-SYNC", + "dfuMANIFEST", + "dfuMANIFEST-WAIT-RESET", + "dfuUPLOAD-IDLE", + "dfuERROR" + }; +*/ + + res = libusb_control_transfer( + device, + 0xa1, + DFU_GETSTATUS, + 0, + 0, + (unsigned char*)&getstatus, + sizeof(getstatus), + 1000); +/* + if(res >= 0) + log(tr("OsmoSDR bStatus: %1, bState: %2 (%3)").arg(getstatus.bStatus).arg(statedesc[getstatus.bState]).arg(getstatus.bState)); +*/ + return res; +} + +int OsmoSDRUpgrade::dfuClrStatus(libusb_device_handle* device) +{ + return libusb_control_transfer( + device, + 0x21, + DFU_CLRSTATUS, + 0, + 0, + NULL, + 0, + 1000); +} + +int OsmoSDRUpgrade::dfuDownloadBlock(libusb_device_handle* device, quint16 block, const quint8* data, quint16 len) +{ + return libusb_control_transfer( + device, + 0x21, + DFU_DNLOAD, + block, + 0, + (unsigned char*)data, + len, + 10000); +} + +int OsmoSDRUpgrade::dfuDetach(libusb_device_handle* device) +{ + return libusb_control_transfer( + device, + 0x21, + DFU_DETACH, + 0, + 0, + NULL, + 0, + 1000); +} + +void OsmoSDRUpgrade::fail(const QString& msg) +{ + log(tr("Fatal error: %1").arg(msg)); + QMessageBox::critical(this, tr("OsmoSDR Upgrade Failed"), msg); + finish(); +} + +void OsmoSDRUpgrade::finish() +{ + if(m_usb != NULL) { + libusb_exit(m_usb); + m_usb = NULL; + } + QApplication::restoreOverrideCursor(); + ui->start->setEnabled(true); + ui->close->setEnabled(true); +} + +void OsmoSDRUpgrade::log(const QString& msg) +{ + ui->log->appendPlainText(msg); + ui->log->moveCursor(QTextCursor::End); +} + +void OsmoSDRUpgrade::reject() +{ + if(ui->close->isEnabled()) + return QDialog::reject(); +} + +size_t OsmoSDRUpgrade::zipHelper(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n) +{ + QByteArray* bytes = (QByteArray*)pOpaque; + bytes->append((const char*)pBuf, n); + return n; +} diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.h b/plugins/samplesource/osmosdr/osmosdrupgrade.h new file mode 100644 index 0000000..efd930b --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrupgrade.h @@ -0,0 +1,60 @@ +#ifndef INCLUDE_OSMOSDRUPGRADE_H +#define INCLUDE_OSMOSDRUPGRADE_H + +#include <QDialog> +#include <QTimer> +#include "util/miniz.h" + +typedef struct libusb_context libusb_context; +typedef struct libusb_device_handle libusb_device_handle; + +namespace Ui { + class OsmoSDRUpgrade; +} + +class OsmoSDRUpgrade : public QDialog { + Q_OBJECT + +public: + explicit OsmoSDRUpgrade(QWidget* parent = NULL); + ~OsmoSDRUpgrade(); + +private slots: + void on_browse_clicked(); + void on_dfu_toggled(bool checked); + void on_radio_toggled(bool checked); + void on_fpga_toggled(bool checked); + void on_start_clicked(); + void searchDeviceTick(); + +private: + Ui::OsmoSDRUpgrade* ui; + + libusb_context* m_usb; + + QByteArray m_dfuApp; + QByteArray m_radioApp; + QByteArray m_fpgaBin; + + int m_searchTries; + QTimer m_searchDeviceTimer; + + void updateFlashButton(); + + void switchToDFU(); + int dfuGetState(libusb_device_handle* device, quint8* state); + int dfuGetStatus(libusb_device_handle* device); + int dfuClrStatus(libusb_device_handle* device); + int dfuDownloadBlock(libusb_device_handle* device, quint16 block, const quint8* data, quint16 len); + int dfuDetach(libusb_device_handle* device); + + void fail(const QString& msg); + void finish(); + void log(const QString& msg); + + void reject(); + + static size_t zipHelper(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n); +}; + +#endif // INCLUDE_OSMOSDRUPGRADE_H diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.ui b/plugins/samplesource/osmosdr/osmosdrupgrade.ui new file mode 100644 index 0000000..fbb2939 --- /dev/null +++ b/plugins/samplesource/osmosdr/osmosdrupgrade.ui @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OsmoSDRUpgrade</class> + <widget class="QDialog" name="OsmoSDRUpgrade"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>382</width> + <height>503</height> + </rect> + </property> + <property name="windowTitle"> + <string>OsmoSDR Firmware Upgrade</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Firmware Archive</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="filename"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>256</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="browse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="fwBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="title"> + <string>Firmware</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="1"> + <widget class="QLabel" name="radioSize"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="fpgaSize"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Size (Bytes)</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="dfu"> + <property name="text"> + <string>DFU Application</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="radio"> + <property name="text"> + <string>Radio Application</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="fpga"> + <property name="text"> + <string>FPGA Image</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="dfuSize"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="progressBox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="title"> + <string>Progress</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QProgressBar" name="progress"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="start"> + <property name="text"> + <string>Flash!</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPlainTextEdit" name="log"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="close"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>close</sender> + <signal>clicked()</signal> + <receiver>OsmoSDRUpgrade</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>331</x> + <y>247</y> + </hint> + <hint type="destinationlabel"> + <x>190</x> + <y>134</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/plugins/samplesource/rtlsdr/CMakeLists.txt b/plugins/samplesource/rtlsdr/CMakeLists.txt new file mode 100644 index 0000000..916bded --- /dev/null +++ b/plugins/samplesource/rtlsdr/CMakeLists.txt @@ -0,0 +1,47 @@ +project(rtlsdr) + +set(rtlsdr_SOURCES + rtlsdrgui.cpp + rtlsdrinput.cpp + rtlsdrplugin.cpp + rtlsdrthread.cpp +) + +set(rtlsdr_HEADERS + rtlsdrgui.h + rtlsdrinput.h + rtlsdrplugin.h + rtlsdrthread.h +) + +set(rtlsdr_FORMS + rtlsdrgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${LIBRTLSDR_INCLUDE_DIR} +) + +include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt4_wrap_cpp(rtlsdr_HEADERS_MOC ${rtlsdr_HEADERS}) +qt4_wrap_ui(rtlsdr_FORMS_HEADERS ${rtlsdr_FORMS}) + +add_library(inputrtlsdr SHARED + ${rtlsdr_SOURCES} + ${rtlsdr_HEADERS_MOC} + ${rtlsdr_FORMS_HEADERS} +) + +target_link_libraries(inputrtlsdr + ${QT_LIBRARIES} + ${LIBRTLSDR_LIBRARIES} + ${LIBUSB_LIBRARIES} +) diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp new file mode 100644 index 0000000..a3d825c --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp @@ -0,0 +1,149 @@ +#include "rtlsdrgui.h" +#include "ui_rtlsdrgui.h" +#include "plugin/pluginapi.h" + +RTLSDRGui::RTLSDRGui(PluginAPI* pluginAPI, QWidget* parent) : + PluginGUI(parent), + ui(new Ui::RTLSDRGui), + m_pluginAPI(pluginAPI), + m_settings(), + m_sampleSource(NULL) +{ + ui->setupUi(this); + ui->centerFrequency->setValueRange(7, 20000U, 2200000U); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + displaySettings(); + + m_sampleSource = new RTLSDRInput(m_pluginAPI->getMainWindowMessageQueue()); + m_pluginAPI->setSampleSource(m_sampleSource); +} + +RTLSDRGui::~RTLSDRGui() +{ + delete ui; +} + +void RTLSDRGui::destroy() +{ + delete this; +} + +void RTLSDRGui::resetToDefaults() +{ + m_generalSettings.resetToDefaults(); + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +QByteArray RTLSDRGui::serializeGeneral() const +{ + return m_generalSettings.serialize(); +} + +bool RTLSDRGui::deserializeGeneral(const QByteArray&data) +{ + if(m_generalSettings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +quint64 RTLSDRGui::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +QByteArray RTLSDRGui::serialize() const +{ + return m_settings.serialize(); +} + +bool RTLSDRGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool RTLSDRGui::handleMessage(Message* message) +{ + if(message->id() == RTLSDRInput::MsgReportRTLSDR::ID()) { + m_gains = ((RTLSDRInput::MsgReportRTLSDR*)message)->getGains(); + displaySettings(); + message->completed(); + return true; + } else { + return false; + } +} + +void RTLSDRGui::displaySettings() +{ + ui->centerFrequency->setValue(m_generalSettings.m_centerFrequency / 1000); + ui->decimation->setValue(m_settings.m_decimation); + + if(m_gains.size() > 0) { + int dist = abs(m_settings.m_gain - m_gains[0]); + int pos = 0; + for(uint i = 1; i < m_gains.size(); i++) { + if(abs(m_settings.m_gain - m_gains[i]) < dist) { + dist = abs(m_settings.m_gain - m_gains[i]); + pos = i; + } + } + ui->gainText->setText(tr("%1.%2").arg(m_gains[pos] / 10).arg(abs(m_gains[pos] % 10))); + ui->gain->setMaximum(m_gains.size() - 1); + ui->gain->setEnabled(true); + ui->gain->setValue(pos); + } else { + ui->gain->setMaximum(0); + ui->gain->setEnabled(false); + ui->gain->setValue(0); + } +} + +void RTLSDRGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void RTLSDRGui::on_centerFrequency_changed(quint64 value) +{ + m_generalSettings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void RTLSDRGui::on_gain_valueChanged(int value) +{ + if(value > m_gains.size()) + return; + int gain = m_gains[value]; + ui->gainText->setText(tr("%1.%2").arg(gain / 10).arg(abs(gain % 10))); + m_settings.m_gain = gain; + sendSettings(); +} + +void RTLSDRGui::on_decimation_valueChanged(int value) +{ + ui->decimationText->setText(tr("1:%1").arg(1 << value)); + m_settings.m_decimation = value; + sendSettings(); +} + +void RTLSDRGui::updateHardware() +{ + RTLSDRInput::MsgConfigureRTLSDR* message = RTLSDRInput::MsgConfigureRTLSDR::create(m_generalSettings, m_settings); + message->submit(m_pluginAPI->getDSPEngineMessageQueue()); + m_updateTimer.stop(); +} diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.h b/plugins/samplesource/rtlsdr/rtlsdrgui.h new file mode 100644 index 0000000..5a1944d --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.h @@ -0,0 +1,51 @@ +#ifndef INCLUDE_RTLSDRGUI_H +#define INCLUDE_RTLSDRGUI_H + +#include <QTimer> +#include "plugin/plugingui.h" +#include "rtlsdrinput.h" + +class PluginAPI; + +namespace Ui { + class RTLSDRGui; +} + +class RTLSDRGui : public PluginGUI { + Q_OBJECT + +public: + explicit RTLSDRGui(PluginAPI* pluginAPI, QWidget* parent = NULL); + ~RTLSDRGui(); + void destroy(); + + void resetToDefaults(); + QByteArray serializeGeneral() const; + bool deserializeGeneral(const QByteArray&data); + quint64 getCenterFrequency() const; + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + bool handleMessage(Message* message); + +private: + Ui::RTLSDRGui* ui; + + PluginAPI* m_pluginAPI; + SampleSource::GeneralSettings m_generalSettings; + RTLSDRInput::Settings m_settings; + QTimer m_updateTimer; + std::vector<int> m_gains; + SampleSource* m_sampleSource; + + void displaySettings(); + void sendSettings(); + +private slots: + void on_centerFrequency_changed(quint64 value); + void on_gain_valueChanged(int value); + void on_decimation_valueChanged(int value); + + void updateHardware(); +}; + +#endif // INCLUDE_RTLSDRGUI_H diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui new file mode 100644 index 0000000..ce2ea4c --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui @@ -0,0 +1,229 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RTLSDRGui</class> + <widget class="QWidget" name="RTLSDRGui"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>132</width> + <height>82</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>RTL-SDR</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="ValueDial" name="centerFrequency" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>16</height> + </size> + </property> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>20</pointsize> + </font> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Tuner center frequency in kHz</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="1"> + <widget class="QSlider" name="decimation"> + <property name="toolTip"> + <string>Signal decimation factor</string> + </property> + <property name="maximum"> + <number>4</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_12"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Decimation</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="decimationText"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>1:1</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label_11"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Gain</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSlider" name="gain"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>LNA amplification</string> + </property> + <property name="maximum"> + <number>0</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="gainText"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>---</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ValueDial</class> + <extends>QWidget</extends> + <header>gui/valuedial.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp new file mode 100644 index 0000000..655d8d7 --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -0,0 +1,230 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <string.h> +#include <errno.h> +#include "rtlsdrinput.h" +#include "rtlsdrthread.h" +#include "rtlsdrgui.h" +#include "util/simpleserializer.h" + +MessageRegistrator RTLSDRInput::MsgConfigureRTLSDR::ID("MsgConfigureRTLSDR"); +MessageRegistrator RTLSDRInput::MsgReportRTLSDR::ID("MsgReportRTLSDR"); + +RTLSDRInput::Settings::Settings() : + m_gain(0), + m_decimation(0) +{ +} + +void RTLSDRInput::Settings::resetToDefaults() +{ + m_gain = 0; + m_decimation = 0; +} + +QByteArray RTLSDRInput::Settings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_gain); + s.writeS32(2, m_decimation); + return s.final(); +} + +bool RTLSDRInput::Settings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) { + d.readS32(1, &m_gain, 0); + d.readS32(2, &m_decimation, 0); + return true; + } else { + resetToDefaults(); + return false; + } +} + +RTLSDRInput::RTLSDRInput(MessageQueue* msgQueueToGUI) : + SampleSource(msgQueueToGUI), + m_settings(), + m_dev(NULL), + m_rtlSDRThread(NULL), + m_deviceDescription() +{ +} + +RTLSDRInput::~RTLSDRInput() +{ + stopInput(); +} + +bool RTLSDRInput::startInput(int device) +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_dev != NULL) + stopInput(); + + char vendor[256]; + char product[256]; + char serial[256]; + int res; + int numberOfGains; + + if(!m_sampleFifo.setSize(524288)) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + if((res = rtlsdr_open(&m_dev, device)) < 0) { + qCritical("could not open RTLSDR #%d: %s", device, strerror(errno)); + return false; + } + + vendor[0] = '\0'; + product[0] = '\0'; + serial[0] = '\0'; + if((res = rtlsdr_get_usb_strings(m_dev, vendor, product, serial)) < 0) { + qCritical("error accessing USB device"); + goto failed; + } + qDebug("RTLSDRInput open: %s %s, SN: %s", vendor, product, serial); + m_deviceDescription = QString("%1 (SN %2)").arg(product).arg(serial); + + if((res = rtlsdr_set_sample_rate(m_dev, 2000000)) < 0) { + qCritical("could not set sample rate: %s", strerror(errno)); + goto failed; + } + + if((res = rtlsdr_set_tuner_gain_mode(m_dev, 1)) < 0) { + qCritical("error setting tuner gain mode"); + goto failed; + } + if((res = rtlsdr_set_agc_mode(m_dev, 0)) < 0) { + qCritical("error setting agc mode"); + goto failed; + } + + numberOfGains = rtlsdr_get_tuner_gains(m_dev, NULL); + if(numberOfGains < 0) { + qCritical("error getting number of gain values supported"); + goto failed; + } + m_gains.resize(numberOfGains); + if(rtlsdr_get_tuner_gains(m_dev, &m_gains[0]) < 0) { + qCritical("error getting gain values"); + goto failed; + } + if((res = rtlsdr_reset_buffer(m_dev)) < 0) { + qCritical("could not reset USB EP buffers: %s", strerror(errno)); + goto failed; + } + + if((m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo)) == NULL) { + qFatal("out of memory"); + goto failed; + } + m_rtlSDRThread->startWork(); + + mutexLocker.unlock(); + applySettings(m_generalSettings, m_settings, true); + + qDebug("RTLSDRInput: start"); + MsgReportRTLSDR::create(m_gains)->submit(m_guiMessageQueue); + + return true; + +failed: + stopInput(); + return false; +} + +void RTLSDRInput::stopInput() +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_rtlSDRThread != NULL) { + m_rtlSDRThread->stopWork(); + delete m_rtlSDRThread; + m_rtlSDRThread = NULL; + } + if(m_dev != NULL) { + rtlsdr_close(m_dev); + m_dev = NULL; + } + m_deviceDescription.clear(); +} + +const QString& RTLSDRInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int RTLSDRInput::getSampleRate() const +{ + return 2000000 / (1 << m_settings.m_decimation); +} + +quint64 RTLSDRInput::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +bool RTLSDRInput::handleMessage(Message* message) +{ + if(message->id() == MsgConfigureRTLSDR::ID()) { + MsgConfigureRTLSDR* conf = (MsgConfigureRTLSDR*)message; + if(!applySettings(conf->getGeneralSettings(), conf->getSettings(), false)) + qDebug("RTLSDR config error"); + message->completed(); + return true; + } else { + return false; + } +} + +bool RTLSDRInput::applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + + if((m_generalSettings.m_centerFrequency != generalSettings.m_centerFrequency) || force) { + m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency; + if(m_dev != NULL) { + if(rtlsdr_set_center_freq(m_dev, m_generalSettings.m_centerFrequency) != 0) + qDebug("osmosdr_set_center_freq(%lld) failed", m_generalSettings.m_centerFrequency); + } + } + if((m_settings.m_gain != settings.m_gain) || force) { + m_settings.m_gain = settings.m_gain; + if(m_dev != NULL) { + if(rtlsdr_set_tuner_gain(m_dev, m_settings.m_gain) != 0) + qDebug("rtlsdr_set_tuner_gain() failed"); + } + } + if((m_settings.m_decimation != settings.m_decimation) || force) { + m_settings.m_decimation = settings.m_decimation; + if(m_dev != NULL) + m_rtlSDRThread->setDecimation(m_settings.m_decimation); + } + return true; +} diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.h b/plugins/samplesource/rtlsdr/rtlsdrinput.h new file mode 100644 index 0000000..a92702e --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.h @@ -0,0 +1,105 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_RTLSDRINPUT_H +#define INCLUDE_RTLSDRINPUT_H + +#include "dsp/samplesource/samplesource.h" +#include <rtl-sdr.h> +#include <QString> + +class RTLSDRThread; + +class RTLSDRInput : public SampleSource { +public: + struct Settings { + qint32 m_gain; + qint32 m_decimation; + + Settings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + + class MsgConfigureRTLSDR : public Message { + public: + static MessageRegistrator ID; + + const GeneralSettings& getGeneralSettings() const { return m_generalSettings; } + const Settings& getSettings() const { return m_settings; } + + static MsgConfigureRTLSDR* create(const GeneralSettings& generalSettings, const Settings& settings) + { + return new MsgConfigureRTLSDR(generalSettings, settings); + } + + private: + GeneralSettings m_generalSettings; + Settings m_settings; + + MsgConfigureRTLSDR(const GeneralSettings& generalSettings, const Settings& settings) : + Message(ID()), + m_generalSettings(generalSettings), + m_settings(settings) + { } + }; + + class MsgReportRTLSDR : public Message { + public: + static MessageRegistrator ID; + + const std::vector<int>& getGains() const { return m_gains; } + + static MsgReportRTLSDR* create(const std::vector<int>& gains) + { + return new MsgReportRTLSDR(gains); + } + + protected: + std::vector<int> m_gains; + + MsgReportRTLSDR(const std::vector<int>& gains) : + Message(ID()), + m_gains(gains) + { } + }; + + RTLSDRInput(MessageQueue* msgQueueToGUI); + ~RTLSDRInput(); + + bool startInput(int device); + void stopInput(); + + const QString& getDeviceDescription() const; + int getSampleRate() const; + quint64 getCenterFrequency() const; + + bool handleMessage(Message* message); + +private: + QMutex m_mutex; + Settings m_settings; + rtlsdr_dev_t* m_dev; + RTLSDRThread* m_rtlSDRThread; + QString m_deviceDescription; + std::vector<int> m_gains; + + bool applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force); +}; + +#endif // INCLUDE_RTLSDRINPUT_H diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp new file mode 100644 index 0000000..b5ad76c --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -0,0 +1,67 @@ +#include <QtPlugin> +#include <QAction> +#include <rtl-sdr.h> +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "rtlsdrplugin.h" +#include "rtlsdrgui.h" + +const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { + displayedName: QString("RTL-SDR Input"), + version: QString("---"), + copyright: QString("(c) librtlsdr Authors (see source URL)"), + website: QString("http://sdr.osmocom.org/trac/wiki/rtl-sdr"), + licenseIsGPL: true, + sourceCodeURL: QString("http://cgit.osmocom.org/cgit/rtl-sdr") +}; + +RTLSDRPlugin::RTLSDRPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& RTLSDRPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void RTLSDRPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.rtl-sdr", this); +} + +PluginInterface::SampleSourceDevices RTLSDRPlugin::enumSampleSources() +{ + SampleSourceDevices result; + int count = rtlsdr_get_device_count(); + char vendor[256]; + char product[256]; + char serial[256]; + + for(int i = 0; i < count; i++) { + vendor[0] = '\0'; + product[0] = '\0'; + serial[0] = '\0'; + + if(rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0) + continue; + QString displayedName(QString("RTL-SDR #%1 (%2 #%3)").arg(i + 1).arg(product).arg(serial)); + SimpleSerializer s(1); + s.writeS32(1, i); + result.append(SampleSourceDevice(displayedName, "org.osmocom.sdr.samplesource.rtl-sdr", s.final())); + } + return result; +} + +PluginGUI* RTLSDRPlugin::createSampleSource(const QString& sourceName, const QByteArray& address) +{ + if(sourceName == "org.osmocom.sdr.samplesource.rtl-sdr") { + return new RTLSDRGui(m_pluginAPI); + } else { + return NULL; + } +} + +Q_EXPORT_PLUGIN2(rtlsdrPlugin, RTLSDRPlugin); diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.h b/plugins/samplesource/rtlsdr/rtlsdrplugin.h new file mode 100644 index 0000000..f559a97 --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.h @@ -0,0 +1,26 @@ +#ifndef INCLUDE_RTLSDRPLUGIN_H +#define INCLUDE_RTLSDRPLUGIN_H + +#include <QObject> +#include "plugin/plugininterface.h" + +class RTLSDRPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + +public: + explicit RTLSDRPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + SampleSourceDevices enumSampleSources(); + PluginGUI* createSampleSource(const QString& sourceName, const QByteArray& address); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_RTLSDRPLUGIN_H diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.cpp b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp new file mode 100644 index 0000000..8c8a17c --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <stdio.h> +#include <errno.h> +#include "rtlsdrthread.h" +#include "dsp/samplefifo.h" + +#define BLOCKSIZE 16384 + +RTLSDRThread::RTLSDRThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_convertBuffer(BLOCKSIZE), + m_sampleFifo(sampleFifo), + m_decimation(1) +{ +} + +RTLSDRThread::~RTLSDRThread() +{ + stopWork(); +} + +void RTLSDRThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void RTLSDRThread::stopWork() +{ + m_running = false; + wait(); +} + +void RTLSDRThread::setDecimation(int decimation) +{ + m_decimation = decimation; +} + +void RTLSDRThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + while(m_running) { + if((res = rtlsdr_read_async(m_dev, &RTLSDRThread::callbackHelper, this, 16, 2 * BLOCKSIZE)) < 0) { + qCritical("RTLSDRThread: async error: %s", strerror(errno)); + break; + } + } + + m_running = false; +} + +void RTLSDRThread::decimate2(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + for(int pos = 0; pos < len; pos += 2) { + Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8); + if(m_decimator2.workDecimateCenter(&s)) { + **it = s; + ++(*it); + } + } +} + +void RTLSDRThread::decimate4(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + for(int pos = 0; pos < len; pos += 2) { + Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8); + if(m_decimator2.workDecimateCenter(&s)) { + if(m_decimator4.workDecimateCenter(&s)) { + **it = s; + ++(*it); + } + } + } +} + +void RTLSDRThread::decimate8(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + for(int pos = 0; pos < len; pos += 2) { + Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8); + if(m_decimator2.workDecimateCenter(&s)) { + if(m_decimator4.workDecimateCenter(&s)) { + if(m_decimator8.workDecimateCenter(&s)) { + **it = s; + ++(*it); + } + } + } + } +} + +void RTLSDRThread::decimate16(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + for(int pos = 0; pos < len; pos += 2) { + Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8); + if(m_decimator2.workDecimateCenter(&s)) { + if(m_decimator4.workDecimateCenter(&s)) { + if(m_decimator8.workDecimateCenter(&s)) { + if(m_decimator16.workDecimateCenter(&s)) { + **it = s; + ++(*it); + } + } + } + } + } +} + +void RTLSDRThread::callback(const quint8* buf, qint32 len) +{ + SampleVector::iterator it = m_convertBuffer.begin(); + + switch(m_decimation) { + case 0: // 1:1 = no decimation + for(int pos = 0; pos < len; pos += 2) { + *it = Sample((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8); + ++it; + } + break; + + case 1: // 1:2 + decimate2(&it, buf, len); + break; + + case 2: // 1:4 + decimate4(&it, buf, len); + break; + + case 3: // 1:8 + decimate8(&it, buf, len); + break; + + case 4: // 1:16 + decimate16(&it, buf, len); + break; + } + + m_sampleFifo->write(m_convertBuffer.begin(), it); + + if(!m_running) + rtlsdr_cancel_async(m_dev); +} + +void RTLSDRThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx) +{ + RTLSDRThread* thread = (RTLSDRThread*)ctx; + thread->callback(buf, len); +} diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.h b/plugins/samplesource/rtlsdr/rtlsdrthread.h new file mode 100644 index 0000000..08837ef --- /dev/null +++ b/plugins/samplesource/rtlsdr/rtlsdrthread.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // +// written by Christian Daniel // +// // +// 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 <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_RTLSDRTHREAD_H +#define INCLUDE_RTLSDRTHREAD_H + +#include <QThread> +#include <QMutex> +#include <QWaitCondition> +#include <rtl-sdr.h> +#include "dsp/samplefifo.h" +#include "dsp/inthalfbandfilter.h" + +class RTLSDRThread : public QThread { + Q_OBJECT + +public: + RTLSDRThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent = NULL); + ~RTLSDRThread(); + + void startWork(); + void stopWork(); + + void setDecimation(int decimation); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + rtlsdr_dev_t* m_dev; + SampleVector m_convertBuffer; + SampleFifo* m_sampleFifo; + + int m_decimation; + + IntHalfbandFilter m_decimator2; + IntHalfbandFilter m_decimator4; + IntHalfbandFilter m_decimator8; + IntHalfbandFilter m_decimator16; + + void run(); + + void decimate2(SampleVector::iterator* it, const quint8* buf, qint32 len); + void decimate4(SampleVector::iterator* it, const quint8* buf, qint32 len); + void decimate8(SampleVector::iterator* it, const quint8* buf, qint32 len); + void decimate16(SampleVector::iterator* it, const quint8* buf, qint32 len); + void callback(const quint8* buf, qint32 len); + + static void callbackHelper(unsigned char* buf, uint32_t len, void* ctx); +}; + +#endif // INCLUDE_RTLSDRTHREAD_H |