Adds airspy USB tuner support and updates the spectral display

component.

-Airspy supported with 10 and 2.5 MSps sample rates.  Additional
decimation may be added later.

-Spectral display updated to include smoothing and filtering options.

Note: the decimated output of a tuner channel from the airspy is
48,076.9 samples per second, versus the normal 48 kHz decimated rate.
This is 1.0015 times the normal rate and doesn't appear to impact the
decoders or audio processing.  Therefore, no fractional resampling has
been added.
This commit is contained in:
denny
2015-10-30 20:49:22 -04:00
parent 5a91630996
commit 70e62ebb35
80 changed files with 4781 additions and 1014 deletions

View File

@@ -28,7 +28,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.adapter.SampleAdapter;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import source.SourceException;
@@ -45,7 +45,7 @@ public class ComplexMixer
private AudioFormat mAudioFormat;
private String mName;
private int mBytesPerFrame = 0;
private SampleAdapter mSampleAdapter;
private ISampleAdapter mSampleAdapter;
private Listener<ComplexBuffer> mListener;
/**
@@ -68,7 +68,7 @@ public class ComplexMixer
public ComplexMixer( TargetDataLine targetDataLine,
AudioFormat format,
String name,
SampleAdapter sampleAdapter,
ISampleAdapter sampleAdapter,
Listener<ComplexBuffer> listener )
{
mTargetDataLine = targetDataLine;

View File

@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
import dsp.gain.AutomaticGainControl;
import sample.Listener;
import sample.adapter.SampleAdapter;
import sample.adapter.ISampleAdapter;
import sample.real.RealBuffer;
import source.RealSource;
import source.SourceException;
@@ -49,7 +49,7 @@ public class RealMixerSource extends RealSource
private AudioFormat mAudioFormat;
private String mDisplayName;
private int mBytesPerFrame = 0;
private SampleAdapter mSampleAdapter;
private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners =
@@ -74,7 +74,7 @@ public class RealMixerSource extends RealSource
public RealMixerSource( TargetDataLine targetDataLine,
AudioFormat format,
String name,
SampleAdapter sampleAdapter )
ISampleAdapter sampleAdapter )
{
super( name );

View File

@@ -25,7 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.adapter.SampleAdapter;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListListener;
import source.mixer.ComplexMixer;
@@ -48,7 +48,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
public MixerTuner( String name,
MixerTunerType mixerTunerType,
TargetDataLine targetDataLine,
SampleAdapter sampleAdapter )
ISampleAdapter sampleAdapter )
{
super( name );
@@ -63,7 +63,7 @@ public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer
public MixerTuner( String name,
MixerTunerDataLine mixerTuner,
SampleAdapter sampleAdapter )
ISampleAdapter sampleAdapter )
{
this( name,
mixerTuner.getMixerTunerType(),

View File

@@ -124,6 +124,11 @@ public abstract class Tuner implements FrequencyChangeBroadcaster,
* @return
*/
public abstract int getSampleRate();
/**
* Sample size in bits
*/
public abstract double getSampleSize();
/**
* Tuner Local Oscillator Frequency

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 Dennis Sheirer
*
* 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
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Buffer;
import sample.Listener;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
@@ -36,12 +37,12 @@ import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Attribute;
import source.tuner.frequency.FrequencyChangeListener;
import util.Oscillator;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate;
import dsp.mixer.Oscillator;
public class TunerChannelSource extends ComplexSource
implements FrequencyChangeListener,
@@ -292,7 +293,7 @@ public class TunerChannelSource extends ComplexSource
{
mBuffer.drainTo( sampleBuffers, 4 );
for( ComplexBuffer buffer: sampleBuffers )
for( Buffer buffer: sampleBuffers )
{
/* Check to see if we've been shutdown */
if( mDispose.get() )

View File

@@ -21,6 +21,7 @@ import javax.usb.UsbDeviceDescriptor;
public enum TunerClass
{
AIRSPY( TunerType.AIRSPY_R820T, "1D50", "60A1", "Airspy.com", "Airspy" ),
GENERIC_2832( TunerType.RTL2832_VARIOUS, "0BDA", "2832", "RTL2832", "SDR" ),
GENERIC_2838( TunerType.RTL2832_VARIOUS, "0BDA", "2838", "RTL2832", "SDR" ),
COMPRO_VIDEOMATE_U620F( TunerType.ELONICS_E4000, "185B", "0620", "Compro", "Videomate U620F" ),
@@ -180,6 +181,10 @@ public enum TunerClass
{
retVal = HACKRF_ONE;
}
else if( productID == 24737 ) //60A1
{
retVal = AIRSPY;
}
break;
case 8013: //1F4D
if( productID == 47107 ) //B803

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 Dennis Sheirer
*
* 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
@@ -35,6 +35,8 @@ import source.SourceException;
import source.config.SourceConfigTuner;
import source.config.SourceConfiguration;
import source.mixer.MixerManager;
import source.tuner.airspy.AirspyTuner;
import source.tuner.airspy.AirspyTunerController;
import source.tuner.fcd.FCDTuner;
import source.tuner.fcd.proV1.FCD1TunerController;
import source.tuner.fcd.proplusV2.FCD2TunerController;
@@ -208,6 +210,8 @@ public class TunerManager
switch( tunerClass )
{
case AIRSPY:
return initAirspyTuner( device, descriptor );
case ETTUS_USRP_B100:
return initEttusB100Tuner( device, descriptor );
case FUNCUBE_DONGLE_PRO:
@@ -250,6 +254,38 @@ public class TunerManager
return new TunerInitStatus( null, "Unknown Device" );
}
private TunerInitStatus initAirspyTuner( Device device,
DeviceDescriptor descriptor )
{
try
{
AirspyTunerController airspyController =
new AirspyTunerController( device, mResourceManager.getThreadPoolManager() );
airspyController.init();
AirspyTuner tuner = new AirspyTuner( airspyController );
TunerConfiguration config = getTunerConfiguration( tuner );
if( config != null )
{
tuner.apply( config );
}
return new TunerInitStatus( tuner, "LOADED" );
}
catch( SourceException se )
{
mLog.error( "couldn't construct Airspy controller/tuner", se );
return new TunerInitStatus( null,
"error constructing Airspy tuner controller" );
}
}
private TunerInitStatus initEttusB100Tuner( Device device,
DeviceDescriptor descriptor )

View File

@@ -19,6 +19,7 @@ package source.tuner;
public enum TunerType
{
AIRSPY_R820T( "R820T" ),
ELONICS_E4000( "E4000" ),
ETTUS_WBX( "WBX" ),
ETTUS_VARIOUS( "Ettus Tuner" ),

View File

@@ -0,0 +1,159 @@
package source.tuner.airspy;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.tuner.airspy.AirspyTunerController.BoardID;
public class AirspyDeviceInformation
{
private final static Logger mLog =
LoggerFactory.getLogger( AirspyDeviceInformation.class );
private BoardID mBoardID = BoardID.UNKNOWN;
private String mFirmwareVersion = "Unknown";
private String mPartNumber = "Unknown";
private String mSerialNumber = "Unknown";
/**
* Airspy Device Information - provides parsing and access to device
* information components.
*/
public AirspyDeviceInformation()
{
}
public void setBoardID( int boardID )
{
mBoardID = BoardID.fromValue( boardID );
}
public BoardID getBoardID()
{
return mBoardID;
}
public void setVersion( byte[] data )
{
if( data != null && data.length > 0 )
{
/* Truncate the version string if we have a line break - ASCII 10 */
for( int x = 0; x < data.length; x++ )
{
if( data[ x ] == (byte)10 )
{
mFirmwareVersion = new String( Arrays.copyOf( data, x ) );
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
return;
}
}
mFirmwareVersion = new String( data );
mFirmwareVersion = mFirmwareVersion.replace( "AirSpy NOS ", "" );
}
else
{
mLog.error( "Error setting airspy version byte data - null:" +
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
}
}
public String getVersion()
{
return mFirmwareVersion;
}
public void setPartAndSerialNumber( byte[] data )
{
if( data != null && data.length == 24 )
{
//Note: values are byte-reversed (big-endian) 32-bit chunks
StringBuilder part = new StringBuilder();
part.append( format( data[ 3 ] ) );
part.append( format( data[ 2 ] ) );
part.append( format( data[ 1 ] ) );
part.append( format( data[ 0 ] ) );
part.append( format( data[ 7 ] ) );
part.append( format( data[ 6 ] ) );
part.append( format( data[ 5 ] ) );
part.append( format( data[ 4 ] ) );
mPartNumber = part.toString();
StringBuilder serial = new StringBuilder();
//Note: current airspy library only exposes 64-bits of serial number
//address space, but appears to have room for 128-bits. We won't
//add the first 64 bits if it's zeros
if( !( data[ 8 ] == 0 && data[ 9 ] == 0 && data[ 10 ] == 0 && data[ 11 ] == 0 ) )
{
serial.append( format( data[ 11 ] ) );
serial.append( format( data[ 10 ] ) );
serial.append( format( data[ 9 ] ) );
serial.append( format( data[ 8 ] ) );
serial.append( "-" );
}
if( !( data[ 12 ] == 0 && data[ 13 ] == 0 && data[ 14 ] == 0 && data[ 15 ] == 0 ) )
{
serial.append( format( data[ 15 ] ) );
serial.append( format( data[ 14 ] ) );
serial.append( format( data[ 13 ] ) );
serial.append( format( data[ 12 ] ) );
serial.append( "-" );
}
serial.append( format( data[ 19 ] ) );
serial.append( format( data[ 18 ] ) );
serial.append( format( data[ 17 ] ) );
serial.append( format( data[ 16 ] ) );
serial.append( "-" );
serial.append( format( data[ 23 ] ) );
serial.append( format( data[ 22 ] ) );
serial.append( format( data[ 21 ] ) );
serial.append( format( data[ 20 ] ) );
mSerialNumber = serial.toString();
}
else
{
mLog.error( "Error setting airspy part and serial byte data - null:" +
( data == null ? " TRUE" : " FALSE length:" + data.length ) );
}
}
private String format( byte value )
{
return String.format( "%02X", value );
}
public String getSerialNumber()
{
return mSerialNumber;
}
public String getPartNumber()
{
return mPartNumber;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Airspy Device Information\n" );
sb.append( "Board: " );
sb.append( getBoardID().getLabel() );
sb.append( "\nPart Number: " );
sb.append( getPartNumber() );
sb.append( "\nSerial Number: " );
sb.append( getSerialNumber() );
sb.append( "\nFirmware: " );
sb.append( getVersion() );
return sb.toString();
}
}

View File

@@ -0,0 +1,77 @@
package source.tuner.airspy;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
public class AirspyInformationPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private AirspyTunerController mController;
/**
* Displays Airspy firmware, serial number and part identifier
*/
public AirspyInformationPanel( AirspyTunerController controller )
{
mController = controller;
initGUI();
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 2",
"[right,grow][grow]",
"[][][][][grow]" ) );
AirspyDeviceInformation info = mController.getDeviceInfo();
add( new JLabel( "Serial:" ) );
add( new JLabel( info.getSerialNumber() ) );
add( new JLabel( "Firmware:" ) );
//Split the firmware on spaces and use first value -- expose full
//string in a tool tip
String[] firmware = info.getVersion().split( " " );
JLabel firmwareLabel;
if( firmware.length > 1 )
{
firmwareLabel = new JLabel( firmware[ 0 ] );
}
else
{
firmwareLabel = new JLabel( info.getVersion() );
}
firmwareLabel.setToolTipText( info.getVersion() );
add( firmwareLabel );
add( new JLabel( "Part:" ) );
add( new JLabel( info.getPartNumber() ) );
add( new JLabel( "Board ID:" ) );
add( new JLabel( info.getBoardID().getLabel() ) );
}
}

View File

@@ -0,0 +1,149 @@
package source.tuner.airspy;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.adapter.ISampleAdapter;
public class AirspySampleAdapter implements ISampleAdapter
{
private final static Logger mLog = LoggerFactory.getLogger( AirspySampleAdapter.class );
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
private boolean mSamplePacking = false;
/**
* Adapter to translate byte buffers received from the airspy tuner into
* float buffers for processing.
*/
public AirspySampleAdapter()
{
}
/**
* Sample packing places two 12-bit samples into 3 bytes when enabled or
* places two 12-bit samples into 4 bytes when disabled.
*
* @param enabled
*/
public void setSamplePacking( boolean enabled )
{
mSamplePacking = enabled;
}
@Override
public float[] convert( byte[] samples )
{
if( mSamplePacking )
{
return convertPacked( samples );
}
else
{
return convertUnpacked( samples );
}
}
/**
* Converts the byte array containing unsigned 12-bit short values into
* signed float values in the range -1 to 1;
*
* @param data - byte array of unsigned 16-bit values
* @return converted float values
*/
private float[] convertUnpacked( byte[] data )
{
float[] samples = new float[ data.length / 2 ];
int pointer = 0;
for( int x = 0; x < data.length; x += 2 )
{
samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) |
( data[ x + 1 ] << 8 ) );
}
return samples;
}
/**
* Converts every 3 bytes containing a pair of 12-bit unsigned values into
* a pair of float values in the range -1 to 1;
*
* @param data - byte array of unsigned 12-bit values
* @return converted float values
*/
private float[] convertPacked( byte[] data1 )
{
byte[] data = new byte[ data1.length ];
//Convert big-endian to little-endian
for( int x = 0; x < data1.length; x += 4 )
{
data[ x ] = data1[ x + 3 ];
data[ x + 1 ] = data1[ x + 2 ];
data[ x + 2 ] = data1[ x + 1 ];
data[ x + 3 ] = data1[ x ];
}
int count = (int)( (float)data.length / 1.5f );
/* Ensure we have an even number of samples */
if( count % 2 == 1 )
{
count--;
}
int bytes = (int)((float)count * 1.5f);
float[] samples = new float[ count ];
int pointer = 0;
int first;
int second;
for( int x = 0; x < bytes; x += 3 )
{
first = ( ( data[ x ] << 4 ) & 0xFF0 ) |
( ( data[ x + 1 ] >> 4 ) & 0xF );
samples[ pointer++ ] = scale( first );
second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) |
( data[ x + 2 ] & 0xFF );
samples[ pointer++ ] = scale( second );
}
return samples;
}
/**
* Converts unsigned 12-bit values to signed 12-bit values and then scales
* the signed value to a signed float value in range: -1.0 : +1.0
*/
public static float scale( int value )
{
return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT;
}
}

View File

@@ -0,0 +1,168 @@
package source.tuner.airspy;
import java.util.concurrent.RejectedExecutionException;
import javax.swing.JPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.Tuner;
import source.tuner.TunerChannel;
import source.tuner.TunerChannelSource;
import source.tuner.TunerClass;
import source.tuner.TunerConfiguration;
import source.tuner.TunerType;
import source.tuner.airspy.AirspyTunerController.BoardID;
import controller.ResourceManager;
import controller.ThreadPoolManager;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
public class AirspyTuner extends Tuner
{
private final static Logger mLog = LoggerFactory.getLogger( AirspyTuner.class );
AirspyTunerController mController;
public AirspyTuner( AirspyTunerController controller )
{
super( "Airspy" );
mController = controller;
/* Register for frequency/sample rate changes so that we can rebroadcast
* to any registered listeners */
mController.addListener( this );
}
@Override
public JPanel getEditor( ResourceManager resourceManager )
{
return new AirspyTunerEditorPanel( this, resourceManager );
}
public AirspyTunerController getController()
{
return mController;
}
@Override
public void apply( TunerConfiguration config ) throws SourceException
{
mController.apply( config );
}
@Override
public String getUniqueID()
{
try
{
return mController.getDeviceInfo().getSerialNumber();
}
catch( Exception e )
{
mLog.error( "error gettting serial number", e );
}
return BoardID.AIRSPY.getLabel();
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.AIRSPY;
}
@Override
public TunerType getTunerType()
{
return TunerType.AIRSPY_R820T;
}
@Override
public int getSampleRate()
{
int rate = 0;
try
{
rate = mController.getCurrentSampleRate();
}
catch ( SourceException e )
{
mLog.debug( "Error while getting sample rate from controller", e );
}
return rate;
}
@Override
public double getSampleSize()
{
return 12.0;
}
@Override
public long getFrequency() throws SourceException
{
return mController.getFrequency();
}
@Override
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
TunerChannel channel ) throws RejectedExecutionException, SourceException
{
//TODO: this channel has a decimated sample rate of:
// 10.0 MSps = 10,000,000 / 208 = 48076.923
// 5.0 MSps = 5,000,000 / 104 = 48076.923
// 2.5 MSps = 2,500,000 / 52 = 48076.923
//Consider implementing a fractional resampler to get a correct 48 kHz
//output sample rate
return mController.getChannel( threadPoolManager, this, channel );
}
@Override
public void releaseChannel( TunerChannelSource source )
{
/* Unregister for receiving samples */
removeListener( (Listener<ComplexBuffer>)source );
/* Tell the controller to release the channel and cleanup */
if( source != null )
{
mController.releaseChannel( source );
}
}
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
mController.addListener( listener );
}
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
mController.removeListener( listener );
}
}

View File

@@ -0,0 +1,119 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
package source.tuner.airspy;
import javax.xml.bind.annotation.XmlAttribute;
import source.tuner.TunerConfiguration;
import source.tuner.TunerType;
public class AirspyTunerConfiguration extends TunerConfiguration
{
private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate();
private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT;
private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT;
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
private boolean mMixerAGC = false;
private boolean mLNAAGC = false;
/**
* Default constructor for JAXB
*/
public AirspyTunerConfiguration()
{
this( "Default" );
}
public AirspyTunerConfiguration( String name )
{
super( name );
}
@Override
public TunerType getTunerType()
{
return TunerType.AIRSPY_R820T;
}
@XmlAttribute( name = "sample_rate" )
public int getSampleRate()
{
return mSampleRate;
}
public void setSampleRate( int sampleRate )
{
mSampleRate = sampleRate;
}
@XmlAttribute( name = "if_gain" )
public int getIFGain()
{
return mIFGain;
}
public void setIFGain( int gain )
{
mIFGain = gain;
}
@XmlAttribute( name = "mixer_gain" )
public int getMixerGain()
{
return mMixerGain;
}
public void setMixerGain( int gain )
{
mMixerGain = gain;
}
@XmlAttribute( name = "lna_gain" )
public int getLNAGain()
{
return mLNAGain;
}
public void setLNAGain( int gain )
{
mLNAGain = gain;
}
@XmlAttribute( name = "mixer_agc" )
public boolean isMixerAGC()
{
return mMixerAGC;
}
public void setMixerAGC( boolean enabled )
{
mMixerAGC = enabled;
}
@XmlAttribute( name = "lna_agc" )
public boolean isLNAAGC()
{
return mLNAAGC;
}
public void setLNAAGC( boolean enabled )
{
mLNAAGC = enabled;
}
}

View File

@@ -0,0 +1,587 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
package source.tuner.airspy;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.usb.UsbException;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import source.SourceException;
import source.tuner.TunerConfiguration;
import source.tuner.TunerConfigurationAssignment;
import source.tuner.TunerType;
import controller.ResourceManager;
public class AirspyTunerConfigurationPanel extends JPanel
{
private final static Logger mLog =
LoggerFactory.getLogger( AirspyTunerConfigurationPanel.class );
private static final long serialVersionUID = 1L;
private ResourceManager mResourceManager;
private AirspyTunerController mController;
private AirspyTunerConfiguration mSelectedConfig;
private JButton mNewConfiguration;
private JButton mDeleteConfiguration;
private JComboBox<AirspyTunerConfiguration> mComboConfigurations;
private JTextField mName;
private JComboBox<AirspySampleRate> mSampleRateCombo;
private JComboBox mDecimationCombo;
private JLabel mEffectiveRate;
private JLabel mIFGainLabel;
private JSlider mIFGain;
private JLabel mLNAGainLabel;
private JSlider mLNAGain;
private JLabel mMixerGainLabel;
private JSlider mMixerGain;
private JCheckBox mMixerAGC;
private JCheckBox mLNAAGC;
public AirspyTunerConfigurationPanel( ResourceManager resourceManager,
AirspyTunerController controller )
{
mResourceManager = resourceManager;
mController = controller;
init();
}
/**
* Initializes the gui components
*/
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[left][grow][grow][right]",
"[][][][][][][grow]" ) );
/**
* Tuner configuration selector
*/
mComboConfigurations = new JComboBox<AirspyTunerConfiguration>();
mComboConfigurations.setModel( getModel() );
/* Determine which tuner configuration should be selected/displayed */
TunerConfigurationAssignment savedConfig = null;
String serial = null;
serial = mController.getDeviceInfo().getSerialNumber();
if( serial != null )
{
savedConfig = mResourceManager
.getSettingsManager().getSelectedTunerConfiguration(
TunerType.AIRSPY_R820T, serial );
}
if( savedConfig != null )
{
mSelectedConfig = getNamedConfiguration(
savedConfig.getTunerConfigurationName() );
}
/* If we couldn't determine the saved/selected config, use the first one */
if( mSelectedConfig == null )
{
mSelectedConfig = mComboConfigurations.getItemAt( 0 );
//Store this config as the default for this tuner at this address
if( serial != null )
{
mResourceManager.getSettingsManager()
.setSelectedTunerConfiguration( TunerType.AIRSPY_R820T,
serial, mSelectedConfig );
}
}
mComboConfigurations.setSelectedItem( mSelectedConfig );
mComboConfigurations.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AirspyTunerConfiguration selected =
(AirspyTunerConfiguration)mComboConfigurations
.getItemAt( mComboConfigurations.getSelectedIndex() );
if( selected != null )
{
update( selected );
}
}
});
add( new JLabel( "Config:" ) );
add( mComboConfigurations, "span,growx,push" );
add( new JSeparator(), "span,growx,push" );
/**
* Tuner Configuration Name
*/
mName = new JTextField();
mName.setText( mSelectedConfig.getName() );
mName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
mSelectedConfig.setName( mName.getText() );
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mName, "span,growx,push" );
/**
* Sample Rate
*/
add( new JLabel( "Sample Rate:" ) );
List<AirspySampleRate> rates = mController.getSampleRates();
mSampleRateCombo = new JComboBox<AirspySampleRate>(
new DefaultComboBoxModel<AirspySampleRate>( rates.toArray(
new AirspySampleRate[ rates.size() ] ) ) );
mSampleRateCombo.setSelectedItem( mController.getSampleRate() );
mSampleRateCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AirspySampleRate rate = (AirspySampleRate)mSampleRateCombo.getSelectedItem();
try
{
mController.setSampleRate( rate );
mSelectedConfig.setSampleRate( rate.getRate() );
save();
mEffectiveRate.setText( rate.getLabel() );
}
catch ( LibUsbException | UsbException e1 )
{
JOptionPane.showMessageDialog( AirspyTunerConfigurationPanel.this,
"Couldn't set sample rate to " + rate.getLabel() );
mLog.error( "Error setting airspy sample rate", e1 );
}
}
} );
add( mSampleRateCombo, "span 3,growx" );
/**
* Decimation
*/
add( new JLabel( "Decimation:" ) );
mDecimationCombo = new JComboBox();
add( mDecimationCombo, "span 3,growx" );
mDecimationCombo.setEnabled( false );
/**
* Effective Rate
*/
add( new JLabel( "Effective Rate:" ) );
mEffectiveRate = new JLabel( "10,000,000" );
add( mEffectiveRate, "span 3,growx" );
add( new JSeparator(), "span,growx,push" );
/**
* IF/VGA Gain
*/
add( new JLabel( "IF Gain" ) );
add( new JLabel( "" ), "span 2" );
mIFGainLabel = new JLabel( String.valueOf( mSelectedConfig.getIFGain() ) );
add( mIFGainLabel, "wrap" );
mIFGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.IF_GAIN_MIN,
AirspyTunerController.IF_GAIN_MAX,
mSelectedConfig.getIFGain() );
mIFGain.setMajorTickSpacing( 1 );
mIFGain.setPaintTicks( true );
mIFGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mIFGain.getValue();
try
{
mController.setIFGain( gain );
mSelectedConfig.setIFGain( gain );
save();
mIFGainLabel.setText( String.valueOf( gain ) );
}
catch ( Exception e )
{
mLog.error( "Couldn't set airspy IF gain to:" + gain, e );
JOptionPane.showMessageDialog( mIFGain,
"Couldn't set IF gain value to " + gain );
mIFGain.setValue( mSelectedConfig.getIFGain() );
}
}
} );
add( mIFGain, "span,grow" );
add( new JLabel( "Mixer Gain" ) );
mMixerAGC = new JCheckBox( "AGC" );
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
mMixerAGC.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
mController.setMixerAGC( !mSelectedConfig.isMixerAGC() );
mSelectedConfig.setMixerAGC( !mSelectedConfig.isMixerAGC() );
save();
}
catch ( Exception e1 )
{
mLog.error( "Error setting Mixer AGC Enabled:" + !mSelectedConfig.isMixerAGC() );
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
}
}
} );
add( mMixerAGC, "span 2,center" );
mMixerGainLabel = new JLabel( String.valueOf( mSelectedConfig.getMixerGain() ) );
add( mMixerGainLabel, "wrap" );
mMixerGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.MIXER_GAIN_MIN,
AirspyTunerController.MIXER_GAIN_MAX,
mSelectedConfig.getMixerGain() );
mMixerGain.setMajorTickSpacing( 1 );
mMixerGain.setPaintTicks( true );
mMixerGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mMixerGain.getValue();
try
{
mController.setMixerGain( gain );
mSelectedConfig.setMixerGain( gain );
save();
mMixerGainLabel.setText( String.valueOf( gain ) );
}
catch ( Exception e )
{
mLog.error( "Couldn't set airspy Mixer gain to:" + gain, e );
JOptionPane.showMessageDialog( mIFGain, "Couldn't set Mixer gain value to " + gain );
mMixerGain.setValue( mSelectedConfig.getMixerGain() );
}
}
} );
add( mMixerGain, "span,grow" );
add( new JLabel( "LNA Gain" ) );
mLNAAGC = new JCheckBox( "AGC" );
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
mLNAAGC.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
mController.setLNAAGC( !mSelectedConfig.isLNAAGC() );
mSelectedConfig.setLNAAGC( !mSelectedConfig.isLNAAGC() );
save();
}
catch ( Exception e1 )
{
mLog.error( "Error setting LNA AGC Enabled:" + !mSelectedConfig.isLNAAGC() );
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
}
}
} );
add( mLNAAGC, "span 2,center" );
mLNAGainLabel = new JLabel( String.valueOf( mSelectedConfig.getLNAGain() ) );
add( mLNAGainLabel, "wrap" );
mLNAGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.LNA_GAIN_MIN,
AirspyTunerController.LNA_GAIN_MAX,
mSelectedConfig.getLNAGain() );
mLNAGain.setMajorTickSpacing( 1 );
mLNAGain.setPaintTicks( true );
mLNAGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mLNAGain.getValue();
try
{
mController.setLNAGain( gain );
mSelectedConfig.setLNAGain( gain );
save();
mLNAGainLabel.setText( String.valueOf( gain ) );
}
catch ( Exception e )
{
mLog.error( "Couldn't set airspy LNA gain to:" + gain, e );
JOptionPane.showMessageDialog( mIFGain, "Couldn't set LNA gain value to " + gain );
mLNAGain.setValue( mSelectedConfig.getLNAGain() );
}
}
} );
add( mLNAGain, "span,grow" );
/**
* Create a new configuration
*/
mNewConfiguration = new JButton( "New" );
mNewConfiguration.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
TunerConfiguration config =
mResourceManager.getSettingsManager()
.addNewTunerConfiguration(
TunerType.AIRSPY_R820T,
"New Configuration" );
mComboConfigurations.setModel( getModel() );
mComboConfigurations.setSelectedItem( config );
repaint();
}
} );
add( mNewConfiguration, "span 2,growx,push" );
/**
* Delete the currently selected configuration
*/
mDeleteConfiguration = new JButton( "Delete" );
mDeleteConfiguration.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AirspyTunerConfiguration selected =
(AirspyTunerConfiguration)mComboConfigurations
.getItemAt( mComboConfigurations.getSelectedIndex() );
if( selected != null )
{
int n = JOptionPane.showConfirmDialog(
AirspyTunerConfigurationPanel.this,
"Are you sure you want to delete '"
+ selected.getName() + "'?",
"Are you sure?",
JOptionPane.YES_NO_OPTION );
if( n == JOptionPane.YES_OPTION )
{
mResourceManager.getSettingsManager()
.deleteTunerConfiguration( selected );
mComboConfigurations.setModel( getModel() );
mComboConfigurations.setSelectedIndex( 0 );
repaint();
}
}
}
} );
add( mDeleteConfiguration, "span 2,growx,push" );
}
/**
* Updates gui controls with the values from the tuner configuration
* @param config - tuner configuration
*/
private void update( AirspyTunerConfiguration config )
{
mSelectedConfig = config;
try
{
mController.apply( config );
mName.setText( config.getName() );
AirspySampleRate rate = mController.getSampleRate(
mSelectedConfig.getSampleRate() );
if( rate != null )
{
mSampleRateCombo.setSelectedItem( rate );
}
//TODO: set decimation and effective rate here
mIFGain.setValue( mSelectedConfig.getIFGain() );
mMixerGain.setValue( mSelectedConfig.getMixerGain() );
mLNAGain.setValue( mSelectedConfig.getLNAGain() );
mMixerAGC.setSelected( mSelectedConfig.isMixerAGC() );
mLNAAGC.setSelected( mSelectedConfig.isLNAAGC() );
mResourceManager.getSettingsManager().setSelectedTunerConfiguration(
TunerType.AIRSPY_R820T, mController.getDeviceInfo().getSerialNumber(), config );
save();
}
catch ( /*UsbException |*/ SourceException e1 )
{
JOptionPane.showMessageDialog(
AirspyTunerConfigurationPanel.this,
"Airspy Tuner Controller - couldn't "
+ "apply the tuner configuration settings - " +
e1.getLocalizedMessage() );
mLog.error( "Airspy Tuner Controller - couldn't apply "
+ "config [" + config.getName() + "]", e1 );
}
}
/**
* Constructs a combo box model for the tuner configuration combo component
* by assembling a list of the tuner configurations appropriate for this
* specific tuner
*/
private ComboBoxModel<AirspyTunerConfiguration> getModel()
{
ArrayList<TunerConfiguration> configs =
mResourceManager.getSettingsManager()
.getTunerConfigurations( TunerType.AIRSPY_R820T );
DefaultComboBoxModel<AirspyTunerConfiguration> model =
new DefaultComboBoxModel<AirspyTunerConfiguration>();
for( TunerConfiguration config: configs )
{
model.addElement( (AirspyTunerConfiguration)config );
}
return model;
}
/**
* Retrieves a specific (named) tuner configuration
*/
private AirspyTunerConfiguration getNamedConfiguration( String name )
{
ArrayList<TunerConfiguration> configs =
mResourceManager.getSettingsManager()
.getTunerConfigurations( TunerType.AIRSPY_R820T );
for( TunerConfiguration config: configs )
{
if( config.getName().contentEquals( name ) )
{
return (AirspyTunerConfiguration)config;
}
}
return null;
}
/**
* Saves current tuner configuration settings
*/
private void save()
{
mResourceManager.getSettingsManager().save();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* 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, either 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 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/>
******************************************************************************/
package source.tuner.airspy;
import gui.control.JFrequencyControl;
import java.awt.Color;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Attribute;
import source.tuner.frequency.FrequencyChangeListener;
import com.jidesoft.swing.JideTabbedPane;
import controller.ResourceManager;
public class AirspyTunerEditorPanel extends JPanel
implements FrequencyChangeListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( AirspyTunerEditorPanel.class );
private JFrequencyControl mFrequencyControl;
private AirspyTuner mTuner;
private AirspyTunerController mController;
private ResourceManager mResourceManager;
public AirspyTunerEditorPanel( AirspyTuner tuner,
ResourceManager resourceManager )
{
mTuner = tuner;
mController = mTuner.getController();
mResourceManager = resourceManager;
initGUI();
}
private void initGUI()
{
setLayout( new MigLayout( "fill,wrap 2",
"[right,grow][grow]",
"[][][][][][grow]" ) );
add( new JLabel( "Airspy Tuner" ), "span,align center" );
mFrequencyControl = new JFrequencyControl();
mFrequencyControl.addListener( this );
/* Add frequency control as frequency change listener. This creates a
* feedback loop, so the control does not rebroadcast the event */
mTuner.addListener( mFrequencyControl );
mFrequencyControl.setFrequency( mController.getFrequency(), false );
add( mFrequencyControl, "span,align center" );
JideTabbedPane tabs = new JideTabbedPane();
tabs.setFont( this.getFont() );
tabs.setForeground( Color.BLACK );
add( tabs, "span,grow,push" );
tabs.add( "Config", new AirspyTunerConfigurationPanel(
mResourceManager, mTuner.getController() ) );
tabs.add( "Info", new AirspyInformationPanel( mTuner.getController() ) );
}
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
if( event.getAttribute() == Attribute.FREQUENCY )
{
try
{
mController.setFrequency( event.getValue().longValue() );
}
catch ( SourceException e )
{
mLog.error( "error setting frequency [" +
event.getValue().longValue() + "]", e );
}
}
}
}

View File

@@ -86,6 +86,12 @@ public class B100Tuner extends Tuner
return 0;
}
@Override
public double getSampleSize()
{
return 16.0;
}
@Override
public long getFrequency() throws SourceException
{

View File

@@ -99,6 +99,12 @@ public class FCDTuner extends MixerTuner
return (int)mMixerTunerType.getAudioFormat().getSampleRate();
}
@Override
public double getSampleSize()
{
return 16.0;
}
@Override
public long getFrequency() throws SourceException
{

View File

@@ -20,7 +20,6 @@ package source.tuner.hackrf;
import java.util.concurrent.RejectedExecutionException;
import javax.swing.JPanel;
import javax.usb.UsbException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +34,6 @@ import source.tuner.TunerClass;
import source.tuner.TunerConfiguration;
import source.tuner.TunerType;
import source.tuner.hackrf.HackRFTunerController.BoardID;
import source.tuner.hackrf.HackRFTunerController.Serial;
import controller.ResourceManager;
import controller.ThreadPoolManager;
@@ -53,23 +51,6 @@ public class HackRFTuner extends Tuner
/* Register for frequency/sample rate changes */
mController.addListener( this );
try
{
BoardID id = mController.getBoardID();
super.setName( id.getLabel() );
String version = mController.getFirmwareVersion();
mLog.info( "HackRF firmware: " + version );
Serial serial = mController.getSerial();
mLog.info( "HackRF Part ID: " + serial.getPartID() );
mLog.info( "HackRF Serial Number: " + serial.getSerialNumber() );
}
catch( UsbException e )
{
mLog.error( "couldn't read HackRF board id", e );
}
}
public void dispose()
@@ -112,6 +93,12 @@ public class HackRFTuner extends Tuner
return mController.getSampleRate();
}
@Override
public double getSampleSize()
{
return 8.0;
}
@Override
public long getFrequency() throws SourceException
{

View File

@@ -142,8 +142,21 @@ public class HackRFTunerController extends TunerController
{
mDeviceHandle = new DeviceHandle();
LibUsb.open( mDevice, mDeviceHandle );
int result = LibUsb.open( mDevice, mDeviceHandle );
if( result != 0 )
{
if( result == LibUsb.ERROR_ACCESS )
{
mLog.error( "Unable to access HackRF - insufficient permissions. "
+ "If you are running a Linux OS, have you installed the "
+ "hackRF rules file in \\etc\\udev\\rules.d ??" );
}
throw new SourceException( "Couldn't open hackrf device - " +
LibUsb.strError( result ) );
}
try
{
claimInterface();
@@ -298,7 +311,7 @@ public class HackRFTunerController extends TunerController
*/
public long getTunedFrequency() throws SourceException
{
return 0;
return mFrequencyController.getTunedFrequency();
}
@Override

View File

@@ -127,6 +127,12 @@ public class RTL2832Tuner extends Tuner
+ "sample rate", e );
}
}
@Override
public double getSampleSize()
{
return 8.0;
}
@Override
public long getFrequency() throws SourceException