diff options
Diffstat (limited to 'plugins/samplesource')
24 files changed, 3995 insertions, 0 deletions
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 |