2026-05-27 10:06:21 +03:00
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QtGlobal>
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QMessageBox>
#include <QFileDialog>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QComboBox>
#include <QCheckBox>
#include <QSpinBox>
#include <QSlider>
#include <QLabel>
#include <QPushButton>
#include <QPair>
#include <QList>
2026-06-01 09:20:01 +03:00
#include "ui_fobosgui.h"
2026-05-27 10:06:21 +03:00
#include "gui/colormapper.h"
#include "gui/glspectrum.h"
#include "gui/basicdevicesettingsdialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
#include "mainspectrum/mainspectrumgui.h"
#include "dsp/dspcommands.h"
#include "util/db.h"
2026-06-01 09:20:01 +03:00
#include "fobosgui.h"
2026-05-27 10:06:21 +03:00
#include "device/deviceapi.h"
#include "device/deviceuiset.h"
FOBOSGui :: FOBOSGui ( DeviceUISet * deviceUISet , QWidget * parent ) :
DeviceGUI ( parent ),
ui ( new Ui :: FOBOSGui ),
m_settings (),
m_doApplySettings ( true ),
m_forceSettings ( true ),
m_sampleSource ( 0 ),
m_tickCount ( 0 ),
m_lastEngineState ( DeviceAPI :: StNotStarted ),
m_backendStatus ( QStringLiteral ( "Not started" )),
m_backendDetails ( QStringLiteral ( "Backend mode: Auto. Start the source to detect Agile or Regular API." ))
{
qDebug ( "FOBOSGui::FOBOSGui" );
m_deviceUISet = deviceUISet ;
setAttribute ( Qt :: WA_DeleteOnClose , true );
m_sampleSource = m_deviceUISet -> m_deviceAPI -> getSampleSource ();
ui -> setupUi ( getContents ());
sizeToContents ();
getContents () -> setStyleSheet ( "#FOBOSGui { background-color: rgb(64, 64, 64); }" );
m_helpURL = "plugins/samplesource/FOBOS/readme.md" ;
ui -> centerFrequency -> setColorMapper ( ColorMapper ( ColorMapper :: GrayGold ));
ui -> centerFrequency -> setValueRange ( 9 , 0 , 999999999 );
ui -> sampleRate -> setColorMapper ( ColorMapper ( ColorMapper :: GrayGreenYellow ));
ui -> sampleRate -> setValueRange ( 8 , 8000000 , 50000000 );
ui -> frequencyShift -> setColorMapper ( ColorMapper ( ColorMapper :: GrayGold ));
ui -> frequencyShift -> setValueRange ( false , 7 , - 9999999 , 9999999 );
ui -> frequencyShiftLabel -> setText ( QString ( "%1" ). arg ( QChar ( 0x94 , 0x03 )));
// Fobos SDR: Auto backend selection with controlled restart, live gain control and device-busy diagnostics.
// Device stop/start lifecycle is handled by FOBOSWorker.
ui -> autoCorrLabel -> setText ( QStringLiteral ( "LNA" ));
ui -> autoCorr -> clear ();
ui -> autoCorr -> addItem ( QStringLiteral ( "0" ));
ui -> autoCorr -> addItem ( QStringLiteral ( "1" ));
ui -> autoCorr -> addItem ( QStringLiteral ( "2" ));
ui -> modulationLabel -> setText ( QStringLiteral ( "VGA" ));
ui -> modulation -> clear ();
for ( int i = 0 ; i <= 31 ; ++ i ) {
ui -> modulation -> addItem ( QString :: number ( i ));
}
ui -> samplerateLabel -> setText ( QStringLiteral ( "SR" ));
ui -> sampleRateUnit -> setText ( QStringLiteral ( "S/s" ));
ui -> amplitudeCoarseLabel -> setText ( QStringLiteral ( "IQ scale coarse" ));
ui -> amplitudeFineLabel -> setText ( QStringLiteral ( "IQ scale fine" ));
// Hide TestSource-only controls that are not part of the real Fobos source.
// Planned uSDR-style controls for later sub-iterations: Input RF/HF modes, BW %, GPO, External clock.
ui -> decimationLabel -> setVisible ( false );
ui -> decimation -> setVisible ( false );
ui -> fcPosLabel -> setVisible ( false );
ui -> fcPos -> setVisible ( false );
ui -> sampleSzieLabel -> setVisible ( false );
ui -> sampleSize -> setVisible ( false );
ui -> sampleSizeUnits -> setVisible ( false );
ui -> frequencyShiftLabel -> setVisible ( false );
ui -> frequencyShift -> setVisible ( false );
ui -> frequencyShiftUnits -> setVisible ( false );
ui -> modulationFrequency -> setVisible ( false );
ui -> modulationFrequencyText -> setVisible ( false );
ui -> amModulationLabel -> setVisible ( false );
ui -> amModulation -> setVisible ( false );
ui -> amModulationText -> setVisible ( false );
ui -> fmDeviationLabel -> setVisible ( false );
ui -> fmDeviation -> setVisible ( false );
ui -> fmDeviationText -> setVisible ( false );
ui -> dcBiasLabel -> setVisible ( false );
ui -> dcBiasMinusLabel -> setVisible ( false );
ui -> dcBias -> setVisible ( false );
ui -> dcBiasPlusLabel -> setVisible ( false );
ui -> dcBiasText -> setVisible ( false );
ui -> iBiasLabel -> setVisible ( false );
ui -> iBiasMinusLabel -> setVisible ( false );
ui -> iBias -> setVisible ( false );
ui -> iBiasText -> setVisible ( false );
ui -> iBiasPlusLabel -> setVisible ( false );
ui -> qBiasLabel -> setVisible ( false );
ui -> qBiasMinusLabel -> setVisible ( false );
ui -> qBias -> setVisible ( false );
ui -> qBiasText -> setVisible ( false );
ui -> qBiasPlusQLabel -> setVisible ( false );
ui -> phaseImbalanceLabel -> setVisible ( false );
ui -> phaseImbalanceMinusLabel -> setVisible ( false );
ui -> phaseImbalance -> setVisible ( false );
ui -> phaseImbalanceText -> setVisible ( false );
ui -> phaseImbalancePlusLabel -> setVisible ( false );
ui -> line -> setVisible ( false );
ui -> line_4 -> setVisible ( false );
ui -> power -> setText ( QStringLiteral ( "Stopped" ));
ui -> autoCorr -> setToolTip ( QStringLiteral ( "Fobos LNA gain 0..2" ));
ui -> modulation -> setToolTip ( QStringLiteral ( "Fobos VGA gain 0..31" ));
ui -> sampleRate -> setToolTip ( QStringLiteral ( "Fobos sample rate in S/s. Supported rates are read from API on Start. Changing sample rate while running requires restart." ));
ui -> amplitudeCoarse -> setToolTip ( QStringLiteral ( "Digital IQ display scale coarse: value/100. Live software display gain." ));
ui -> amplitudeFine -> setToolTip ( QStringLiteral ( "Digital IQ display scale fine: value/100. Live software display gain." ));
ui -> centerFrequency -> setToolTip ( QStringLiteral ( "Center frequency. Live retune is supported in RF mode." ));
ui -> power -> setToolTip ( QStringLiteral ( "Fobos source status." ));
displaySettings ();
makeUIConnections ();
connect ( & m_updateTimer , SIGNAL ( timeout ()), this , SLOT ( updateHardware ()));
connect ( & m_statusTimer , SIGNAL ( timeout ()), this , SLOT ( updateStatus ()));
m_statusTimer . start ( 500 );
connect ( & m_inputMessageQueue , SIGNAL ( messageEnqueued ()), this , SLOT ( handleInputMessages ()), Qt :: QueuedConnection );
m_sampleSource -> setMessageQueueToGUI ( & m_inputMessageQueue );
connect ( this , SIGNAL ( customContextMenuRequested ( const QPoint & )), this , SLOT ( openDeviceSettingsDialog ( const QPoint & )));
DialPopup :: addPopupsToChildDials ( this );
m_resizer . enableChildMouseTracking ();
}
FOBOSGui ::~ FOBOSGui ()
{
m_statusTimer . stop ();
m_updateTimer . stop ();
delete ui ;
}
void FOBOSGui :: destroy ()
{
delete this ;
}
void FOBOSGui :: resetToDefaults ()
{
m_settings . resetToDefaults ();
displaySettings ();
m_forceSettings = true ;
sendSettings ();
}
QByteArray FOBOSGui :: serialize () const
{
return m_settings . serialize ();
}
bool FOBOSGui :: deserialize ( const QByteArray & data )
{
if ( m_settings . deserialize ( data ))
{
displaySettings ();
m_forceSettings = true ;
sendSettings ();
return true ;
}
else
{
resetToDefaults ();
return false ;
}
}
void FOBOSGui :: on_startStop_toggled ( bool checked )
{
if ( m_doApplySettings )
{
FOBOSInput :: MsgStartStop * message = FOBOSInput :: MsgStartStop :: create ( checked );
m_sampleSource -> getInputMessageQueue () -> push ( message );
}
}
void FOBOSGui :: on_centerFrequency_changed ( quint64 value )
{
m_settings . m_centerFrequency = value * 1000 ;
m_settingsKeys . append ( "centerFrequency" );
sendSettings ();
}
void FOBOSGui :: on_autoCorr_currentIndexChanged ( int index )
{
if (( index < 0 ) || ( index > 2 )) {
return ;
}
m_settings . m_autoCorrOptions = static_cast < FOBOSSettings :: AutoCorrOptions > ( index );
m_settingsKeys . append ( "autoCorrOptions" );
sendSettings ();
}
void FOBOSGui :: on_frequencyShift_changed ( qint64 value )
{
m_settings . m_frequencyShift = value ;
m_settingsKeys . append ( "frequencyShift" );
sendSettings ();
}
void FOBOSGui :: on_decimation_currentIndexChanged ( int index )
{
if (( index < 0 ) || ( index > 6 )) {
return ;
}
m_settings . m_log2Decim = index ;
m_settingsKeys . append ( "log2Decim" );
sendSettings ();
}
void FOBOSGui :: on_fcPos_currentIndexChanged ( int index )
{
if (( index < 0 ) || ( index > 2 )) {
return ;
}
m_settings . m_fcPos = ( FOBOSSettings :: fcPos_t ) index ;
m_settingsKeys . append ( "fcPos" );
sendSettings ();
}
void FOBOSGui :: on_sampleRate_changed ( quint64 value )
{
updateFrequencyShiftLimit ();
m_settings . m_frequencyShift = ui -> frequencyShift -> getValueNew ();
m_settings . m_sampleRate = value ;
m_settingsKeys . append ( "frequencyShift" );
m_settingsKeys . append ( "sampleRate" );
sendSettings ();
}
void FOBOSGui :: on_sampleSize_currentIndexChanged ( int index )
{
if (( index < 0 ) || ( index > 2 )) {
return ;
}
updateAmpCoarseLimit ();
updateAmpFineLimit ();
displayAmplitude ();
m_settings . m_amplitudeBits = ui -> amplitudeCoarse -> value () * 100 + ui -> amplitudeFine -> value ();
m_settings . m_sampleSizeIndex = index ;
m_settingsKeys . append ( "amplitudeBits" );
m_settingsKeys . append ( "sampleSizeIndex" );
sendSettings ();
}
void FOBOSGui :: on_amplitudeCoarse_valueChanged ( int value )
{
( void ) value ;
updateAmpFineLimit ();
displayAmplitude ();
m_settings . m_amplitudeBits = ui -> amplitudeCoarse -> value () * 100 + ui -> amplitudeFine -> value ();
m_settingsKeys . append ( "amplitudeBits" );
sendSettings ();
}
void FOBOSGui :: on_amplitudeFine_valueChanged ( int value )
{
( void ) value ;
displayAmplitude ();
m_settings . m_amplitudeBits = ui -> amplitudeCoarse -> value () * 100 + ui -> amplitudeFine -> value ();
m_settingsKeys . append ( "amplitudeBits" );
sendSettings ();
}
void FOBOSGui :: on_modulation_currentIndexChanged ( int index )
{
if (( index < 0 ) || ( index > 31 )) {
return ;
}
m_settings . m_modulation = static_cast < FOBOSSettings :: Modulation > ( index );
m_settingsKeys . append ( "modulation" );
sendSettings ();
}
void FOBOSGui :: on_modulationFrequency_valueChanged ( int value )
{
m_settings . m_modulationTone = value ;
ui -> modulationFrequencyText -> setText ( QString ( "%1" ). arg ( m_settings . m_modulationTone / 100.0 , 0 , 'f' , 2 ));
m_settingsKeys . append ( "modulationTone" );
sendSettings ();
}
void FOBOSGui :: on_amModulation_valueChanged ( int value )
{
m_settings . m_amModulation = value ;
ui -> amModulationText -> setText ( QString ( "%1" ). arg ( m_settings . m_amModulation ));
m_settingsKeys . append ( "amModulation" );
sendSettings ();
}
void FOBOSGui :: on_fmDeviation_valueChanged ( int value )
{
m_settings . m_fmDeviation = value ;
ui -> fmDeviationText -> setText ( QString ( "%1" ). arg ( m_settings . m_fmDeviation / 10.0 , 0 , 'f' , 1 ));
m_settingsKeys . append ( "fmDeviation" );
sendSettings ();
}
void FOBOSGui :: on_dcBias_valueChanged ( int value )
{
ui -> dcBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( value )));
m_settings . m_dcFactor = value / 100.0f ;
m_settingsKeys . append ( "dcFactor" );
sendSettings ();
}
void FOBOSGui :: on_iBias_valueChanged ( int value )
{
ui -> iBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( value )));
m_settings . m_iFactor = value / 100.0f ;
m_settingsKeys . append ( "iFactor" );
sendSettings ();
}
void FOBOSGui :: on_qBias_valueChanged ( int value )
{
ui -> qBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( value )));
m_settings . m_qFactor = value / 100.0f ;
m_settingsKeys . append ( "qFactor" );
sendSettings ();
}
void FOBOSGui :: on_phaseImbalance_valueChanged ( int value )
{
ui -> phaseImbalanceText -> setText ( QString ( tr ( "%1 %" ). arg ( value )));
m_settings . m_phaseImbalance = value / 100.0f ;
m_settingsKeys . append ( "phaseImbalance" );
sendSettings ();
}
void FOBOSGui :: displayAmplitude ()
{
int amplitudeInt = ui -> amplitudeCoarse -> value () * 100 + ui -> amplitudeFine -> value ();
double power ;
switch ( ui -> sampleSize -> currentIndex ())
{
case 0 : // 8 bits: 128
power = ( double ) amplitudeInt * amplitudeInt / ( double ) ( 1 << 14 );
break ;
case 1 : // 12 bits 2048
power = ( double ) amplitudeInt * amplitudeInt / ( double ) ( 1 << 22 );
break ;
case 2 : // 16 bits 32768
default :
power = ( double ) amplitudeInt * amplitudeInt / ( double ) ( 1 << 30 );
break ;
}
const double iqGain = amplitudeInt / 100.0 ;
ui -> amplitudeBits -> setText ( QString ( tr ( "x%1" ). arg ( QString :: number ( iqGain , 'f' , 2 ))));
( void ) power ;
// Fobos SDR: ui->power is reserved for device status.
// Do not overwrite Running/Stopped/Error with a legacy TestSource dB display.
ui -> power -> setToolTip ( QString ( tr ( "Digital IQ display scale x%1. This is software display gain, not RF gain." ))
. arg ( QString :: number ( iqGain , 'f' , 2 )));
}
void FOBOSGui :: updateAmpCoarseLimit ()
{
switch ( ui -> sampleSize -> currentIndex ())
{
case 0 : // 8 bits: 128
ui -> amplitudeCoarse -> setMaximum ( 1 );
break ;
case 1 : // 12 bits 2048
ui -> amplitudeCoarse -> setMaximum ( 20 );
break ;
case 2 : // 16 bits 32768
default :
ui -> amplitudeCoarse -> setMaximum ( 327 );
break ;
}
}
void FOBOSGui :: updateAmpFineLimit ()
{
switch ( ui -> sampleSize -> currentIndex ())
{
case 0 : // 8 bits: 128
if ( ui -> amplitudeCoarse -> value () == 1 ) {
ui -> amplitudeFine -> setMaximum ( 27 );
} else {
ui -> amplitudeFine -> setMaximum ( 99 );
}
break ;
case 1 : // 12 bits 2048
if ( ui -> amplitudeCoarse -> value () == 20 ) {
ui -> amplitudeFine -> setMaximum ( 47 );
} else {
ui -> amplitudeFine -> setMaximum ( 99 );
}
break ;
case 2 : // 16 bits 32768
default :
if ( ui -> amplitudeCoarse -> value () == 327 ) {
ui -> amplitudeFine -> setMaximum ( 67 );
} else {
ui -> amplitudeFine -> setMaximum ( 99 );
}
break ;
}
}
void FOBOSGui :: updateFrequencyShiftLimit ()
{
int sampleRate = ui -> sampleRate -> getValueNew ();
ui -> frequencyShift -> setValueRange ( false , 7 , - sampleRate , sampleRate );
}
void FOBOSGui :: displaySettings ()
{
blockApplySettings ( true );
ui -> sampleSize -> blockSignals ( true );
setTitle ( m_settings . m_title );
getDeviceUISet () -> m_mainSpectrumGUI -> setTitle ( m_settings . m_title );
ui -> centerFrequency -> setValue ( m_settings . m_centerFrequency / 1000 );
ui -> decimation -> setCurrentIndex ( m_settings . m_log2Decim );
ui -> fcPos -> setCurrentIndex (( int ) m_settings . m_fcPos );
ui -> sampleRate -> setValue ( m_settings . m_sampleRate );
updateFrequencyShiftLimit ();
ui -> frequencyShift -> setValue ( m_settings . m_frequencyShift );
ui -> sampleSize -> setCurrentIndex ( m_settings . m_sampleSizeIndex );
updateAmpCoarseLimit ();
int amplitudeBits = m_settings . m_amplitudeBits ;
ui -> amplitudeCoarse -> setValue ( amplitudeBits / 100 );
updateAmpFineLimit ();
ui -> amplitudeFine -> setValue ( amplitudeBits % 100 );
displayAmplitude ();
int dcBiasPercent = roundf ( m_settings . m_dcFactor * 100.0f );
ui -> dcBias -> setValue (( int ) dcBiasPercent );
ui -> dcBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( dcBiasPercent )));
int iBiasPercent = roundf ( m_settings . m_iFactor * 100.0f );
ui -> iBias -> setValue (( int ) iBiasPercent );
ui -> iBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( iBiasPercent )));
int qBiasPercent = roundf ( m_settings . m_qFactor * 100.0f );
ui -> qBias -> setValue (( int ) qBiasPercent );
ui -> qBiasText -> setText ( QString ( tr ( "%1 %" ). arg ( qBiasPercent )));
int phaseImbalancePercent = roundf ( m_settings . m_phaseImbalance * 100.0f );
ui -> phaseImbalance -> setValue (( int ) phaseImbalancePercent );
ui -> phaseImbalanceText -> setText ( QString ( tr ( "%1 %" ). arg ( phaseImbalancePercent )));
ui -> autoCorr -> setCurrentIndex ( m_settings . m_autoCorrOptions );
ui -> sampleSize -> blockSignals ( false );
ui -> modulation -> setCurrentIndex (( int ) m_settings . m_modulation );
ui -> modulationFrequency -> setValue ( m_settings . m_modulationTone );
ui -> modulationFrequencyText -> setText ( QString ( "%1" ). arg ( m_settings . m_modulationTone / 100.0 , 0 , 'f' , 2 ));
ui -> amModulation -> setValue ( m_settings . m_amModulation );
ui -> amModulationText -> setText ( QString ( "%1" ). arg ( m_settings . m_amModulation ));
ui -> fmDeviation -> setValue ( m_settings . m_fmDeviation );
ui -> fmDeviationText -> setText ( QString ( "%1" ). arg ( m_settings . m_fmDeviation / 10.0 , 0 , 'f' , 1 ));
blockApplySettings ( false );
}
void FOBOSGui :: sendSettings ()
{
if ( ! m_updateTimer . isActive ()) {
m_updateTimer . start ( 100 );
}
}
void FOBOSGui :: updateHardware ()
{
if ( m_doApplySettings )
{
FOBOSInput :: MsgConfigureFOBOS * message = FOBOSInput :: MsgConfigureFOBOS :: create ( m_settings , m_settingsKeys , m_forceSettings );
m_sampleSource -> getInputMessageQueue () -> push ( message );
m_forceSettings = false ;
m_settingsKeys . clear ();
m_updateTimer . stop ();
}
}
void FOBOSGui :: updateStatus ()
{
int state = m_deviceUISet -> m_deviceAPI -> state ();
if ( m_lastEngineState != state )
{
switch ( state )
{
case DeviceAPI :: StNotStarted :
ui -> startStop -> setStyleSheet ( "QToolButton { background:rgb(79,79,79); }" );
ui -> power -> setText ( QStringLiteral ( "Stopped" ));
break ;
case DeviceAPI :: StIdle :
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : blue; }" );
ui -> power -> setText ( QStringLiteral ( "Idle" ));
break ;
case DeviceAPI :: StRunning :
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : green; }" );
ui -> power -> setText ( QStringLiteral ( "Running (%1)" ). arg ( m_backendStatus ));
break ;
case DeviceAPI :: StError :
ui -> startStop -> setStyleSheet ( "QToolButton { background-color : red; }" );
ui -> power -> setText ( QStringLiteral ( "Error" ));
QMessageBox :: information ( this , tr ( "Message" ), m_deviceUISet -> m_deviceAPI -> errorMessage ());
break ;
default :
break ;
}
m_lastEngineState = state ;
}
}
bool FOBOSGui :: handleMessage ( const Message & message )
{
if ( FOBOSInput :: MsgConfigureFOBOS :: match ( message ))
{
qDebug ( "FOBOSGui::handleMessage: MsgConfigureFOBOS" );
const FOBOSInput :: MsgConfigureFOBOS & cfg = ( FOBOSInput :: MsgConfigureFOBOS & ) message ;
if ( cfg . getForce ()) {
m_settings = cfg . getSettings ();
} else {
m_settings . applySettings ( cfg . getSettingsKeys (), cfg . getSettings ());
}
displaySettings ();
return true ;
}
else if ( FOBOSInput :: MsgReportFOBOSBackend :: match ( message ))
{
const FOBOSInput :: MsgReportFOBOSBackend & report = ( const FOBOSInput :: MsgReportFOBOSBackend & ) message ;
m_backendStatus = report . getBackend ();
m_backendDetails = report . getDetails ();
if ( m_deviceUISet -> m_deviceAPI -> state () == DeviceAPI :: StRunning ) {
ui -> power -> setText ( QStringLiteral ( "Running (%1)" ). arg ( m_backendStatus ));
}
return true ;
}
else if ( FOBOSInput :: MsgStartStop :: match ( message ))
{
qDebug ( "FOBOSGui::handleMessage: MsgStartStop" );
FOBOSInput :: MsgStartStop & notif = ( FOBOSInput :: MsgStartStop & ) message ;
blockApplySettings ( true );
ui -> startStop -> setChecked ( notif . getStartStop ());
blockApplySettings ( false );
return true ;
}
else
{
return false ;
}
}
void FOBOSGui :: handleInputMessages ()
{
Message * message ;
while (( message = m_inputMessageQueue . pop ()) != 0 )
{
if ( DSPSignalNotification :: match ( * message ))
{
DSPSignalNotification * notif = ( DSPSignalNotification * ) message ;
m_deviceSampleRate = notif -> getSampleRate ();
m_deviceCenterFrequency = notif -> getCenterFrequency ();
qDebug ( "FOBOSGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu" ,
notif -> getSampleRate (),
notif -> getCenterFrequency ());
updateSampleRateAndFrequency ();
delete message ;
}
else
{
if ( handleMessage ( * message ))
{
delete message ;
}
}
}
}
void FOBOSGui :: updateSampleRateAndFrequency ()
{
m_deviceUISet -> getSpectrum () -> setSampleRate ( m_deviceSampleRate );
m_deviceUISet -> getSpectrum () -> setCenterFrequency ( m_deviceCenterFrequency );
ui -> deviceRateText -> setText ( tr ( "%1k" ). arg (( float ) m_deviceSampleRate / 1000 ));
}
bool FOBOSGui :: openFobosOperatorSettingsDialog ( const QPoint & p )
{
QDialog dialog ( this );
dialog . setWindowTitle ( tr ( "Fobos SDR settings" ));
dialog . setMinimumWidth ( 420 );
QVBoxLayout * mainLayout = new QVBoxLayout ( & dialog );
QGroupBox * idGroup = new QGroupBox ( tr ( "Identification" ), & dialog );
QFormLayout * idLayout = new QFormLayout ( idGroup );
QLineEdit * nameEdit = new QLineEdit ( m_settings . m_title , idGroup );
QLabel * backendModeLabel = new QLabel ( tr ( "Auto" ), idGroup );
QLabel * detectedBackendLabel = new QLabel ( m_backendStatus , idGroup );
QLabel * apiLabel = new QLabel ( m_backendDetails , idGroup );
apiLabel -> setWordWrap ( true );
idLayout -> addRow ( tr ( "Name" ), nameEdit );
idLayout -> addRow ( tr ( "Backend mode" ), backendModeLabel );
idLayout -> addRow ( tr ( "Detected backend" ), detectedBackendLabel );
idLayout -> addRow ( tr ( "API / board" ), apiLabel );
mainLayout -> addWidget ( idGroup );
QGroupBox * rxGroup = new QGroupBox ( tr ( "Receiver" ), & dialog );
QFormLayout * rxLayout = new QFormLayout ( rxGroup );
QComboBox * inputCombo = new QComboBox ( rxGroup );
inputCombo -> addItem ( tr ( "RF" ), ( int ) FOBOSSettings :: InputRF );
inputCombo -> addItem ( tr ( "IQ (HF1+j*HF2) direct sampling" ), ( int ) FOBOSSettings :: InputIQDirect );
inputCombo -> addItem ( tr ( "HF1 direct sampling" ), ( int ) FOBOSSettings :: InputHF1Direct );
inputCombo -> addItem ( tr ( "HF2 direct sampling" ), ( int ) FOBOSSettings :: InputHF2Direct );
inputCombo -> setCurrentIndex ( qBound ( 0 , ( int ) m_settings . m_inputMode , 3 ));
inputCombo -> setToolTip ( tr ( "Input mode is applied on next Start. HF1/HF2 direct modes use software extraction from direct-sampling IQ." ));
QComboBox * srCombo = new QComboBox ( rxGroup );
srCombo -> addItem ( QStringLiteral ( "8" ), 8000000 );
srCombo -> addItem ( QStringLiteral ( "10" ), 10000000 );
srCombo -> addItem ( QStringLiteral ( "12.5" ), 12500000 );
srCombo -> addItem ( QStringLiteral ( "16" ), 16000000 );
srCombo -> addItem ( QStringLiteral ( "20" ), 20000000 );
srCombo -> addItem ( QStringLiteral ( "25" ), 25000000 );
srCombo -> addItem ( QStringLiteral ( "32" ), 32000000 );
srCombo -> addItem ( QStringLiteral ( "40" ), 40000000 );
srCombo -> addItem ( QStringLiteral ( "50" ), 50000000 );
int srIndex = srCombo -> findData (( int ) m_settings . m_sampleRate );
if ( srIndex < 0 ) { srIndex = srCombo -> findData ( 25000000 ); }
if ( srIndex < 0 ) { srIndex = 0 ; }
srCombo -> setCurrentIndex ( srIndex );
srCombo -> setToolTip ( tr ( "Sample Rate is changed through a controlled restart while running. Supported values are logged from the active Fobos API." ));
QComboBox * bwCombo = new QComboBox ( rxGroup );
bwCombo -> addItem ( tr ( "100%" ), 100 );
bwCombo -> addItem ( tr ( "90%" ), 90 );
bwCombo -> addItem ( tr ( "80%" ), 80 );
bwCombo -> addItem ( tr ( "70%" ), 70 );
bwCombo -> addItem ( tr ( "60%" ), 60 );
bwCombo -> addItem ( tr ( "50%" ), 50 );
bwCombo -> addItem ( tr ( "40%" ), 40 );
bwCombo -> addItem ( tr ( "30%" ), 30 );
bwCombo -> addItem ( tr ( "20%" ), 20 );
int bwIndex = bwCombo -> findData (( int ) m_settings . m_bandwidthPercent );
bwCombo -> setCurrentIndex ( bwIndex >= 0 ? bwIndex : 1 );
bwCombo -> setToolTip ( tr ( "Relative analog bandwidth. Applied by the Agile backend; shown for compatibility and ignored by the Regular backend." ));
rxLayout -> addRow ( tr ( "Input" ), inputCombo );
rxLayout -> addRow ( tr ( "Sample Rate, MSPS" ), srCombo );
rxLayout -> addRow ( tr ( "Bandwidth (Agile only)" ), bwCombo );
mainLayout -> addWidget ( rxGroup );
QGroupBox * gainGroup = new QGroupBox ( tr ( "Gain / display scale" ), & dialog );
QFormLayout * gainLayout = new QFormLayout ( gainGroup );
// Fobos SDR: uSDR-style gain sliders. LNA has only 0..2 steps, VGA has 0..31 steps.
QWidget * lnaWidget = new QWidget ( gainGroup );
QHBoxLayout * lnaRow = new QHBoxLayout ( lnaWidget );
lnaRow -> setContentsMargins ( 0 , 0 , 0 , 0 );
QSlider * lnaSlider = new QSlider ( Qt :: Horizontal , lnaWidget );
lnaSlider -> setRange ( 0 , 2 );
lnaSlider -> setSingleStep ( 1 );
lnaSlider -> setPageStep ( 1 );
lnaSlider -> setTickPosition ( QSlider :: TicksBelow );
lnaSlider -> setTickInterval ( 1 );
lnaSlider -> setValue ( qBound ( 0 , ( int ) m_settings . m_autoCorrOptions , 2 ));
QLabel * lnaValue = new QLabel ( QStringLiteral ( "#%1" ). arg ( lnaSlider -> value ()), lnaWidget );
lnaValue -> setMinimumWidth ( 28 );
lnaRow -> addWidget ( lnaSlider , 1 );
lnaRow -> addWidget ( lnaValue );
connect ( lnaSlider , & QSlider :: valueChanged , lnaValue , [ lnaValue ]( int v ) {
lnaValue -> setText ( QStringLiteral ( "#%1" ). arg ( v ));
});
QWidget * vgaWidget = new QWidget ( gainGroup );
QHBoxLayout * vgaRow = new QHBoxLayout ( vgaWidget );
vgaRow -> setContentsMargins ( 0 , 0 , 0 , 0 );
QSlider * vgaSlider = new QSlider ( Qt :: Horizontal , vgaWidget );
vgaSlider -> setRange ( 0 , 31 );
vgaSlider -> setSingleStep ( 1 );
vgaSlider -> setPageStep ( 4 );
vgaSlider -> setTickPosition ( QSlider :: TicksBelow );
vgaSlider -> setTickInterval ( 4 );
vgaSlider -> setValue ( qBound ( 0 , ( int ) m_settings . m_modulation , 31 ));
QLabel * vgaValue = new QLabel ( QStringLiteral ( "#%1" ). arg ( vgaSlider -> value ()), vgaWidget );
vgaValue -> setMinimumWidth ( 32 );
vgaRow -> addWidget ( vgaSlider , 1 );
vgaRow -> addWidget ( vgaValue );
connect ( vgaSlider , & QSlider :: valueChanged , vgaValue , [ vgaValue ]( int v ) {
vgaValue -> setText ( QStringLiteral ( "#%1" ). arg ( v ));
});
QComboBox * scaleCombo = new QComboBox ( gainGroup );
const QList < int > scaleList = { 1 , 2 , 4 , 8 , 16 , 32 , 64 };
int scaleIndex = 0 ;
const int currentScale = qBound ( 1 , m_settings . m_amplitudeBits / 100 , 64 );
for ( int i = 0 ; i < scaleList . size (); ++ i ) {
scaleCombo -> addItem ( QStringLiteral ( "x%1" ). arg ( scaleList [ i ]), scaleList [ i ]);
if ( scaleList [ i ] == currentScale ) {
scaleIndex = i ;
}
}
scaleCombo -> setCurrentIndex ( scaleIndex );
gainLayout -> addRow ( tr ( "LNA Gain" ), lnaWidget );
gainLayout -> addRow ( tr ( "VGA Gain" ), vgaWidget );
gainLayout -> addRow ( tr ( "IQ Scale" ), scaleCombo );
mainLayout -> addWidget ( gainGroup );
QGroupBox * policyGroup = new QGroupBox ( tr ( "Apply behavior" ), & dialog );
QVBoxLayout * policyLayout = new QVBoxLayout ( policyGroup );
QLabel * liveNote = new QLabel ( tr ( "Live while running: Frequency in RF mode, LNA, VGA, IQ Scale, GPO, and bandwidth where supported by the active backend." ), policyGroup );
QLabel * restartNote = new QLabel ( tr ( "Controlled restart while running: Sample Rate, Input mode and External clock. SDRangel briefly stops and restarts Fobos so the spectrum scale matches real hardware settings." ), policyGroup );
liveNote -> setWordWrap ( true );
restartNote -> setWordWrap ( true );
policyLayout -> addWidget ( liveNote );
policyLayout -> addWidget ( restartNote );
mainLayout -> addWidget ( policyGroup );
QGroupBox * gpioGroup = new QGroupBox ( tr ( "GPO / clock" ), & dialog );
QVBoxLayout * gpioLayout = new QVBoxLayout ( gpioGroup );
QHBoxLayout * gpoRow = new QHBoxLayout ();
QList < QCheckBox *> gpoChecks ;
for ( int i = 0 ; i < 8 ; ++ i ) {
QCheckBox * cb = new QCheckBox ( QString :: number ( i ), gpioGroup );
cb -> setChecked (( m_settings . m_gpoMask & ( 1u << i )) != 0u );
gpoChecks . append ( cb );
gpoRow -> addWidget ( cb );
}
QCheckBox * externalClockCheck = new QCheckBox ( tr ( "External clock" ), gpioGroup );
externalClockCheck -> setChecked ( m_settings . m_externalClock );
externalClockCheck -> setToolTip ( tr ( "External 10 MHz reference. Applied through controlled restart while running." ));
// Live-apply and controlled-restart controls. OK closes with the latest values; Cancel no longer reverts
// already-applied live changes. This avoids the operator having to close the dialog for LNA/VGA/IQ/GPO/BW tweaks.
auto liveApply = [ this ]() {
displaySettings ();
sendSettings ();
};
connect ( lnaSlider , & QSlider :: valueChanged , this , [ this , liveApply ]( int v ) {
m_settings . m_autoCorrOptions = static_cast < FOBOSSettings :: AutoCorrOptions > ( qBound ( 0 , v , 2 ));
m_settingsKeys . append ( "autoCorrOptions" );
liveApply ();
});
connect ( vgaSlider , & QSlider :: valueChanged , this , [ this , liveApply ]( int v ) {
m_settings . m_modulation = static_cast < FOBOSSettings :: Modulation > ( qBound ( 0 , v , 31 ));
m_settingsKeys . append ( "modulation" );
liveApply ();
});
connect ( scaleCombo , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , [ this , scaleCombo , liveApply ]( int ) {
const int iqScale = scaleCombo -> currentData (). toInt ();
m_settings . m_amplitudeBits = qBound ( 1 , iqScale , 64 ) * 100 ;
m_settingsKeys . append ( "amplitudeBits" );
liveApply ();
});
connect ( bwCombo , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , [ this , bwCombo , liveApply ]( int ) {
m_settings . m_bandwidthPercent = bwCombo -> currentData (). toUInt ();
m_settingsKeys . append ( "bandwidthPercent" );
liveApply ();
});
connect ( inputCombo , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , [ this , inputCombo , liveApply ]( int ) {
const int inputMode = inputCombo -> currentData (). toInt ();
m_settings . m_inputMode = static_cast < FOBOSSettings :: InputMode > ( qBound ( 0 , inputMode , 3 ));
m_settingsKeys . append ( "inputMode" );
liveApply ();
});
connect ( srCombo , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , [ this , srCombo , liveApply ]( int ) {
m_settings . m_sampleRate = srCombo -> currentData (). toUInt ();
m_settingsKeys . append ( "sampleRate" );
liveApply ();
});
connect ( externalClockCheck , & QCheckBox :: toggled , this , [ this , externalClockCheck , liveApply ]( bool ) {
m_settings . m_externalClock = externalClockCheck -> isChecked ();
m_settingsKeys . append ( "externalClock" );
liveApply ();
});
for ( int i = 0 ; i < gpoChecks . size (); ++ i ) {
connect ( gpoChecks [ i ], & QCheckBox :: toggled , this , [ this , gpoChecks , liveApply ]( bool ) {
unsigned int gpoMask = 0 ;
for ( int bit = 0 ; bit < gpoChecks . size (); ++ bit ) {
if ( gpoChecks [ bit ] -> isChecked ()) { gpoMask |= ( 1u << bit ); }
}
m_settings . m_gpoMask = gpoMask & 0xffu ;
m_settingsKeys . append ( "gpoMask" );
liveApply ();
});
}
QLabel * apiNote = new QLabel ( tr ( "Fobos SDR source. Backend mode: Auto. Agile API is tried first; regular/classic API is used as fallback. Bandwidth control is applied by Agile and shown only as an informational setting for Regular." ), gpioGroup );
apiNote -> setWordWrap ( true );
gpioLayout -> addLayout ( gpoRow );
gpioLayout -> addWidget ( externalClockCheck );
gpioLayout -> addWidget ( apiNote );
mainLayout -> addWidget ( gpioGroup );
QDialogButtonBox * buttons = new QDialogButtonBox ( QDialogButtonBox :: Ok | QDialogButtonBox :: Cancel , & dialog );
mainLayout -> addWidget ( buttons );
connect ( buttons , & QDialogButtonBox :: accepted , & dialog , & QDialog :: accept );
connect ( buttons , & QDialogButtonBox :: rejected , & dialog , & QDialog :: reject );
dialog . move ( p );
new DialogPositioner ( & dialog , false );
if ( dialog . exec () != QDialog :: Accepted ) {
return false ;
}
const QString newTitle = nameEdit -> text (). trimmed (). isEmpty () ? QStringLiteral ( "Fobos SDR" ) : nameEdit -> text (). trimmed ();
m_settings . m_title = newTitle ;
setTitle ( m_settings . m_title );
getDeviceUISet () -> m_mainSpectrumGUI -> setTitle ( m_settings . m_title );
m_settingsKeys . append ( "title" );
const int inputMode = inputCombo -> currentData (). toInt ();
m_settings . m_inputMode = ( FOBOSSettings :: InputMode ) qBound ( 0 , inputMode , 3 );
m_settingsKeys . append ( "inputMode" );
m_settings . m_sampleRate = srCombo -> currentData (). toUInt ();
m_settingsKeys . append ( "sampleRate" );
const unsigned int bw = bwCombo -> currentData (). toUInt ();
m_settings . m_bandwidthPercent = bw ;
m_settingsKeys . append ( "bandwidthPercent" );
m_settings . m_autoCorrOptions = static_cast < FOBOSSettings :: AutoCorrOptions > ( qBound ( 0 , lnaSlider -> value (), 2 ));
m_settingsKeys . append ( "autoCorrOptions" );
m_settings . m_modulation = static_cast < FOBOSSettings :: Modulation > ( qBound ( 0 , vgaSlider -> value (), 31 ));
m_settingsKeys . append ( "modulation" );
const int iqScale = scaleCombo -> currentData (). toInt ();
m_settings . m_amplitudeBits = iqScale * 100 ;
m_settingsKeys . append ( "amplitudeBits" );
unsigned int gpoMask = 0 ;
for ( int i = 0 ; i < gpoChecks . size (); ++ i ) {
if ( gpoChecks [ i ] -> isChecked ()) {
gpoMask |= ( 1u << i );
}
}
m_settings . m_gpoMask = gpoMask & 0xffu ;
m_settingsKeys . append ( "gpoMask" );
m_settings . m_externalClock = externalClockCheck -> isChecked ();
m_settingsKeys . append ( "externalClock" );
displaySettings ();
sendSettings ();
return true ;
}
void FOBOSGui :: openDeviceSettingsDialog ( const QPoint & p )
{
if ( m_contextMenuType == ContextMenuDeviceSettings )
{
// Fobos SDR: uSDR-style operator settings dialog with controlled restart for critical settings.
openFobosOperatorSettingsDialog ( p );
}
resetContextMenuType ();
}
void FOBOSGui :: makeUIConnections ()
{
QObject :: connect ( ui -> startStop , & ButtonSwitch :: toggled , this , & FOBOSGui :: on_startStop_toggled );
QObject :: connect ( ui -> centerFrequency , & ValueDial :: changed , this , & FOBOSGui :: on_centerFrequency_changed );
QObject :: connect ( ui -> autoCorr , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , & FOBOSGui :: on_autoCorr_currentIndexChanged );
QObject :: connect ( ui -> frequencyShift , & ValueDialZ :: changed , this , & FOBOSGui :: on_frequencyShift_changed );
QObject :: connect ( ui -> decimation , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , & FOBOSGui :: on_decimation_currentIndexChanged );
QObject :: connect ( ui -> fcPos , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , & FOBOSGui :: on_fcPos_currentIndexChanged );
QObject :: connect ( ui -> sampleRate , & ValueDial :: changed , this , & FOBOSGui :: on_sampleRate_changed );
QObject :: connect ( ui -> sampleSize , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , & FOBOSGui :: on_sampleSize_currentIndexChanged );
QObject :: connect ( ui -> amplitudeCoarse , & QSlider :: valueChanged , this , & FOBOSGui :: on_amplitudeCoarse_valueChanged );
QObject :: connect ( ui -> amplitudeFine , & QSlider :: valueChanged , this , & FOBOSGui :: on_amplitudeFine_valueChanged );
QObject :: connect ( ui -> modulation , QOverload < int >:: of ( & QComboBox :: currentIndexChanged ), this , & FOBOSGui :: on_modulation_currentIndexChanged );
QObject :: connect ( ui -> modulationFrequency , & QDial :: valueChanged , this , & FOBOSGui :: on_modulationFrequency_valueChanged );
QObject :: connect ( ui -> amModulation , & QDial :: valueChanged , this , & FOBOSGui :: on_amModulation_valueChanged );
QObject :: connect ( ui -> fmDeviation , & QDial :: valueChanged , this , & FOBOSGui :: on_fmDeviation_valueChanged );
QObject :: connect ( ui -> dcBias , & QSlider :: valueChanged , this , & FOBOSGui :: on_dcBias_valueChanged );
QObject :: connect ( ui -> iBias , & QSlider :: valueChanged , this , & FOBOSGui :: on_iBias_valueChanged );
QObject :: connect ( ui -> qBias , & QSlider :: valueChanged , this , & FOBOSGui :: on_qBias_valueChanged );
QObject :: connect ( ui -> phaseImbalance , & QSlider :: valueChanged , this , & FOBOSGui :: on_phaseImbalance_valueChanged );
}