Updates pull request from Roman Tsypuk to convert the build system from

ant to gradle.  Removes linux and windows application launch scripts and
produces an executable jar with all resources included.
This commit is contained in:
Denny
2017-12-17 07:28:03 -05:00
parent a1535c4157
commit 9316599dec
981 changed files with 8368 additions and 9049 deletions

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import java.util.concurrent.RejectedExecutionException;
public interface ITunerChannelProvider
{
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* @param frequency - desired frequency
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsb;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Runnable for executing LibUsb processing of bulk transfer buffers
*/
public class LibUSBTransferProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(LibUSBTransferProcessor.class);
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
private List<USBTransferProcessor> mRegisteredProcessors = new CopyOnWriteArrayList<>();
private ByteBuffer mStatusBuffer = ByteBuffer.allocateDirect(4);
private AtomicBoolean mRunning = new AtomicBoolean();
private Processor mProcessor = new Processor();
private ScheduledFuture mProcessorFuture;
/**
* Registers the transfer processor so that LibUSB timeout processing will auto-start.
* @param processor to register
*/
public void registerTransferProcessor(USBTransferProcessor processor)
{
if(!mRegisteredProcessors.contains(processor))
{
mRegisteredProcessors.add(processor);
}
start();
}
/**
* Unregisters the transfer processor so that LibUSB timeout processing will auto-stop once all processors have
* been unregistered.
*
* @param processor to unregister
*/
public void unregisterTransferProcessor(USBTransferProcessor processor)
{
mRegisteredProcessors.remove(processor);
if(mRegisteredProcessors.isEmpty())
{
stop();
}
}
/**
* Starts processing of LibUSB timeout events
*/
private void start()
{
if(mRunning.compareAndSet(false, true))
{
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 5L, TimeUnit.MILLISECONDS);
}
}
/**
* Stops processing of LibUSB timeout events
*/
private void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mProcessorFuture != null)
{
mProcessorFuture.cancel(true);
mProcessorFuture = null;
}
}
}
/**
* Runnable to invoke LibUSB timeout handler. All downstream completed transfer processing will occur on this
* thread instance.
*/
class Processor implements Runnable
{
@Override
public void run()
{
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, mStatusBuffer.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("Error processing timeout events for LibUSB - error code:" + result);
}
mStatusBuffer.rewind();
}
}
}

View File

@@ -0,0 +1,129 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexSampleListListener;
import io.github.dsheirer.source.mixer.ComplexMixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.TargetDataLine;
import java.util.ArrayList;
public abstract class MixerTuner extends Tuner implements Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( MixerTuner.class );
protected MixerTunerType mMixerTunerType;
protected ComplexMixer mComplexMixer;
protected ArrayList<ComplexSampleListListener> mListeners =
new ArrayList<ComplexSampleListListener>();
/**
* Wrapper class to add basic Tuner functionality to the ComplexMixer.
* Subclasses should couple this functionality with a tuner controller class
* to support tuning.
*/
public MixerTuner( String name,
TunerController tunerController,
MixerTunerType mixerTunerType,
TargetDataLine targetDataLine,
ISampleAdapter sampleAdapter )
{
super( name, tunerController );
mMixerTunerType = mixerTunerType;
mComplexMixer = new ComplexMixer( targetDataLine,
mMixerTunerType.getAudioFormat(),
name,
sampleAdapter,
(Listener<ComplexBuffer>)this );
}
public MixerTuner( String name,
TunerController tunerController,
MixerTunerDataLine mixerTuner,
ISampleAdapter sampleAdapter )
{
this( name,
tunerController,
mixerTuner.getMixerTunerType(),
mixerTuner.getTargetDataLine(),
sampleAdapter );
}
public void dispose()
{
mComplexMixer.stop();
super.dispose();
}
public MixerTunerType getMixerTunerType()
{
return mMixerTunerType;
}
public TargetDataLine getTargetDataLine()
{
return mComplexMixer.getTargetDataLine();
}
/**
* Dispatches the received complex buffers from the mComplexMixer to all
* listeners registered on the parent Tuner class
*/
@Override
public void receive( ComplexBuffer buffer )
{
super.broadcast( buffer );
}
/**
* Overrides the parent Tuner class listener management method to allow
* starting the buffer processing thread
*/
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
super.addListener( listener );
/* We can call start multiple times -- it ignores additional requests */
mComplexMixer.start();
}
/**
* Overrides the parent Tuner class listener management method to allow
* stopping the buffer processing thread when there are no more listeners
*/
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
super.removeListener( listener );
if( mSampleListeners.isEmpty() )
{
mComplexMixer.stop();
}
}
}

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
import javax.sound.sampled.TargetDataLine;
public class MixerTunerDataLine
{
private TargetDataLine mTargetDataLine;
protected MixerTunerType mMixerTunerType;
public MixerTunerDataLine( TargetDataLine line, MixerTunerType type )
{
mTargetDataLine = line;
mMixerTunerType = type;
}
public TargetDataLine getTargetDataLine()
{
return mTargetDataLine;
}
public MixerTunerType getMixerTunerType()
{
return mMixerTunerType;
}
}

View File

@@ -0,0 +1,117 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Mixer;
public enum MixerTunerType
{
FUNCUBE_DONGLE_PRO( TunerType.FUNCUBE_DONGLE_PRO,
"V10",
"FUNcube Dongle",
"FUNcube Dongle Pro V1.0",
new AudioFormat( 96000, //SampleRate
16, //Sample Size
2, //Channels
true, //Signed
false ) ), //Little Endian )
FUNCUBE_DONGLE_PRO_PLUS( TunerType.FUNCUBE_DONGLE_PRO_PLUS,
"V20",
"FUNcube Dongle V2.0",
"FUNcube Dongle Pro Plus V2.0",
new AudioFormat( 192000, //SampleRate
16, //Sample Size
2, //Channels
true, //Signed
false ) ), //Little Endian )
UNKNOWN( TunerType.UNKNOWN,
"UNK",
"Unknown",
"Unknown Mixer Tuner",
new AudioFormat( 48000, //SampleRate
16, //Sample Size
2, //Channels
true, //Signed
false ) ); //Little Endian )
private TunerType mTunerClass;
private String mMixerName;
private String mPartialDescription;
private String mDisplayString;
private AudioFormat mAudioFormat;
MixerTunerType( TunerType tunerClass,
String mixerName,
String partialDescription,
String displayString,
AudioFormat format )
{
mTunerClass = tunerClass;
mMixerName = mixerName;
mPartialDescription = partialDescription;
mDisplayString = displayString;
mAudioFormat = format;
}
public TunerType getTunerClass()
{
return mTunerClass;
}
public String getMixerName()
{
return mMixerName;
}
public String getPartialDescription()
{
return mPartialDescription;
}
public String getDisplayString()
{
return mDisplayString;
}
public AudioFormat getAudioFormat()
{
return mAudioFormat;
}
public static MixerTunerType getMixerTunerType( Mixer.Info info )
{
MixerTunerType retVal = UNKNOWN;
if( info.getName().contains( FUNCUBE_DONGLE_PRO_PLUS.mPartialDescription ) ||
info.getDescription().contains( FUNCUBE_DONGLE_PRO_PLUS.mPartialDescription ) )
{
retVal = FUNCUBE_DONGLE_PRO_PLUS;
}
else if( info.getName().contains( FUNCUBE_DONGLE_PRO.mPartialDescription ) ||
info.getDescription().contains( FUNCUBE_DONGLE_PRO.mPartialDescription ) )
{
retVal = FUNCUBE_DONGLE_PRO;
}
return retVal;
}
}

View File

@@ -0,0 +1,212 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.RejectedExecutionException;
/**
* Tuner - provides tuner channel sources, representing a channel frequency
*/
public abstract class Tuner implements ITunerChannelProvider
{
private final static Logger mLog = LoggerFactory.getLogger( Tuner.class );
private String mName;
private TunerController mTunerController;
/**
* Sample Listeners - these will typically be the DFT processor for spectral
* display, or it will be one or more tuner channel sources
*/
protected List<Listener<ComplexBuffer>> mSampleListeners =
new CopyOnWriteArrayList<>();
protected List<Listener<TunerEvent>> mTunerChangeListeners =
new ArrayList<>();
public Tuner( String name, TunerController tunerController )
{
mName = name;
mTunerController = tunerController;
//Rebroadcast frequency and sample rate change events as tuner events
mTunerController.addListener( new IFrequencyChangeProcessor()
{
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
switch( event.getEvent() )
{
case NOTIFICATION_FREQUENCY_CHANGE:
broadcast( new TunerEvent( Tuner.this,
TunerEvent.Event.FREQUENCY ));
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
broadcast( new TunerEvent( Tuner.this,
TunerEvent.Event.SAMPLE_RATE ));
break;
default:
break;
}
}
} );
}
public TunerController getTunerController()
{
return mTunerController;
}
public String toString()
{
return mName;
}
public void dispose()
{
mSampleListeners.clear();
}
public void setName( String name )
{
mName = name;
}
/**
* Unique identifier for this tuner, used to lookup a tuner configuration
* from the settings manager.
*
* @return - unique identifier like a serial number, or a usb bus location
* or ip address and port. Return some form of unique identification that
* allows this tuner to be identified from among several of the same types
* of tuners.
*/
public abstract String getUniqueID();
/**
* @return - tuner class enum entry
*/
public abstract TunerClass getTunerClass();
/**
* @return - tuner type enum entry
*/
public abstract TunerType getTunerType();
/**
* Name of this tuner object
* @return - string name of this tuner object
*/
public String getName()
{
return mName;
}
/**
* Sample size in bits
*/
public abstract double getSampleSize();
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* Note: ensure the concrete implementation registers the returned
* tuner channel source as a listener on the tuner, to receive frequency
* change events.
*
* @param frequency - desired frequency
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* Note: ensure the concrete implementation unregisters the tuner channel
* source from the tuner as a frequency change listener
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );
/**
* Registers the listener to receive complex float sample arrays
*/
public void addListener( Listener<ComplexBuffer> listener )
{
mSampleListeners.add( listener );
}
/**
* Removes the registered listener
*/
public void removeListener( Listener<ComplexBuffer> listener )
{
mSampleListeners.remove( listener );
}
/**
* Broadcasts the samples to all registered listeners
*/
public void broadcast( ComplexBuffer sampleBuffer )
{
for( Listener<ComplexBuffer> listener: mSampleListeners )
{
listener.receive( sampleBuffer );
}
}
/**
* Registers the listener
*/
public void addTunerChangeListener( Listener<TunerEvent> listener )
{
mTunerChangeListeners.add( listener );
}
/**
* Removes the registered listener
*/
public void removeTunerChangeListener( Listener<TunerEvent> listener )
{
mTunerChangeListeners.remove( listener );
}
/**
* Broadcasts the tuner change event
*/
public void broadcast( TunerEvent event )
{
for( Listener<TunerEvent> listener: mTunerChangeListeners )
{
listener.receive( event );
}
}
}

View File

@@ -0,0 +1,122 @@
/*******************************************************************************
* SDR Trunk
* 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
* 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 io.github.dsheirer.source.tuner;
public class TunerChannel implements Comparable<TunerChannel>
{
private Type mType;
private long mFrequency;
private int mBandwidth;
public TunerChannel( Type type, long frequency, int bandwidth )
{
mType = type;
mFrequency = frequency;
mBandwidth = bandwidth;
}
public Type getType()
{
return mType;
}
public void setType( Type type )
{
mType = type;
}
public long getFrequency()
{
return mFrequency;
}
public void setFrequency( long frequency )
{
mFrequency = frequency;
}
public int getBandwidth()
{
return mBandwidth;
}
public void setBandwidth( int bandwidth )
{
mBandwidth = bandwidth;
}
public long getMinFrequency()
{
return mFrequency - ( mBandwidth / 2 );
}
public long getMaxFrequency()
{
return mFrequency + ( mBandwidth / 2 );
}
/**
* Indicates if any part of this tuner channel overlaps the specified range
*/
public boolean overlaps( long minimum, long maximum )
{
long channelMin = getMinFrequency();
long channelMax = getMaxFrequency();
return ( ( minimum <= channelMin && channelMin <= maximum ) ||
( minimum <= channelMax && channelMax <= maximum ) ||
( minimum <= channelMin && channelMax <= maximum ) ||
( channelMin <= minimum && maximum <= channelMax ) );
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Channel: " );
sb.append( getMinFrequency() );
sb.append( "-" );
sb.append( getMaxFrequency() );
return sb.toString();
}
@Override
public int compareTo( TunerChannel otherLock )
{
if( this.equals( otherLock ) )
{
return 0;
}
else if( mFrequency < otherLock.getFrequency() )
{
return -1;
}
else
{
return 1;
}
}
public enum Type
{
LOCKED, //Use for decoding channels, so that they cannot be tuned out of range
TRAFFIC, //Like locked, but for temporary allocated traffic channels
TUNABLE; //Tunable -- can be tuned out of range
}
}

View File

@@ -0,0 +1,27 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import java.util.concurrent.RejectedExecutionException;
public interface TunerChannelProvider
{
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* @param frequency - desired frequency
*
* @return - source for 48k sample rate
*/
public abstract TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException;
/**
* Releases the tuned channel resources
*
* @param channel - previously obtained tuner channel
*/
public abstract void releaseChannel( TunerChannelSource source );
}

View File

@@ -0,0 +1,572 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.channel.heartbeat.Heartbeat;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.mixer.Oscillator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
//Maximum number of filled buffers for the blocking queue
private static final int BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
private Tuner mTuner;
private TunerChannel mTunerChannel;
private Oscillator mMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener;
private Listener<Heartbeat> mHeartbeatListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle;
private long mTunerFrequency = 0;
private int mTunerSampleRate;
private int mChannelFrequencyCorrection = 0;
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false;
/**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
* tuner down to a 48 kHz IQ channel rate.
*
* Note: this class can only be used once (started and stopped) and a new
* tuner channel source must be requested from the tuner once this object
* has been stopped. This is because channels are managed dynamically and
* center tuned frequency may have changed since this source was obtained
* and thus the tuner might no longer be able to source this channel once it
* has been stopped.
*
* @param tuner to obtain wideband IQ samples from
* @param tunerChannel specifying the center frequency for the DDC
* @throws RejectedExecutionException if the thread pool manager cannot
* accept the decimation processing task
* @throws SourceException if the tuner has an issue providing IQ samples
*/
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
{
mTuner = tuner;
mTunerChannel = tunerChannel;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerFrequency = mTuner.getTunerController().getFrequency();
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
/* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
mTuner.getTunerController().getSampleRate()));
}
/**
* Overrides the default source overflow listener management to delegate responsibility to the overflow buffer
*/
@Override
public void setOverflowListener(IOverflowListener listener)
{
mBuffer.setOverflowListener(listener);
}
public void start(ScheduledExecutorService executor)
{
if(mExpended)
{
throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
" objects can only be used once. ");
}
if(mRunning.compareAndSet(false, true))
{
//Broadcast current frequency and sample rate settings so all downstream components are aware
mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
/* Finally, register to receive samples from the tuner */
mTuner.addListener((Listener<ComplexBuffer>) this);
}
else
{
mLog.warn("Attempt to start() an already running tuner channel source was ignored");
}
}
@Override
public void reset()
{
}
@Override
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
mTuner.releaseChannel(this);
mDecimationProcessor.shutdown();
if(mTaskHandle != null)
{
mTaskHandle.cancel(true);
mTaskHandle = null;
}
mBuffer.clear();
mExpended = true;
}
else
{
mLog.warn("Attempt to stop() an already stopped tuner channel source was ignored");
}
}
@Override
public void dispose()
{
if(!mRunning.get())
{
/* Tell the tuner to release/unregister our resources */
mTuner.getTunerController().removeListener(this);
}
}
/**
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
* @param correction current frequency correction value.
*/
private void setFrequencyCorrection(int correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
mDownstreamFrequencyEventProcessor.broadcast(
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
public Tuner getTuner()
{
return mTuner;
}
public TunerChannel getTunerChannel()
{
return mTunerChannel;
}
@Override
public void receive(ComplexBuffer buffer)
{
if(mRunning.get())
{
mBuffer.offer(buffer);
}
}
public void setFrequencyChangeListener(IFrequencyChangeProcessor processor)
{
mFrequencyChangeProcessor = processor;
}
@Override
public void setListener(Listener<ComplexBuffer> listener)
{
/* Save a pointer to the listener so that if we have to change the
* decimation filter, we can re-add the listener */
mListener = listener;
mDecimationFilter.setListener(listener);
}
@Override
public void removeListener(Listener<ComplexBuffer> listener)
{
mDecimationFilter.removeListener();
}
/**
* Handler for frequency change events received from the tuner and channel
* frequency correction events received from the channel consumer/listener
*/
@Override
public void frequencyChanged(FrequencyChangeEvent event) throws SourceException
{
// Echo the event to the registered event listener
if(mFrequencyChangeProcessor != null)
{
mFrequencyChangeProcessor.frequencyChanged(event);
}
switch(event.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
mTunerFrequency = event.getValue().longValue();
updateMixerFrequencyOffset();
//Reset frequency correction so that downstream components can recalculate the value
setFrequencyCorrection(0);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
int sampleRate = event.getValue().intValue();
setSampleRate(sampleRate);
break;
default:
break;
}
}
/**
* Updates the sample rate to the requested value and notifies any downstream components of the change
* @param sampleRate to set
*/
private void setSampleRate(int sampleRate)
{
if(mTunerSampleRate != sampleRate)
{
mMixer.setSampleRate(sampleRate);
/* Get new decimation filter */
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
/* re-add the original output listener */
mDecimationFilter.setListener(mListener);
mTunerSampleRate = sampleRate;
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
}
}
/**
* Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction.
*/
private void updateMixerFrequencyOffset()
{
long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
mMixer.setFrequency(offset);
}
public int getSampleRate() throws SourceException
{
return CHANNEL_RATE;
}
public long getFrequency() throws SourceException
{
return mTunerChannel.getFrequency();
}
/**
* Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
* listeners.
*
* @param listener to receive downstream events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
}
/**
* Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
* change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
}
/**
* Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
* listeners to change frequency values.
* @return listener
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
}
/**
* Registers the listener to receive heartbeats from this source.
* @param listener to receive heartbeats
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatListener = listener;
}
/**
* Removes the currently registered heartbeat listener
*/
@Override
public void removeHeartbeatListener()
{
mHeartbeatListener = null;
}
/**
* Managers frequency change requests and notifications from/to any downstream component. Downstream
* components are those that receive samples from this tuner channel source. These downstream components will be
* notified of any frequency or sample rate change events and will also be able to request frequency correction
* updates.
*/
public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
Listener<FrequencyChangeEvent>
{
//Listener to receive downstream events
private Listener<FrequencyChangeEvent> mListener;
/**
* Broadcasts the frequency change event to the downstream frequency change listener
* @param event to broadcast
*/
public void broadcast(FrequencyChangeEvent event)
{
if(mListener != null)
{
mListener.receive(event);
}
}
/**
* Broadcasts the current frequency of this tuner channel source to the downstream listener
*/
public void broadcastCurrentFrequency()
{
try
{
long frequency = getFrequency();
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
}
catch(SourceException se)
{
mLog.error("Error obtaining frequency from tuner to broadcast downstream");
}
}
/**
* Broadcasts the current decimated sample rate of this tuner channel source
*/
public void broadcastCurrentSampleRate()
{
try
{
//Note: downstream sample rate is currently a fixed value -- it will change in the future
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
}
catch(SourceException se)
{
mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
}
}
/**
* Sets the downstream listener to receive frequency change events from this tuner channel source
* @param listener to receive events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mListener = listener;
}
/**
* Removes the downstream listener from receiving frequency change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mListener = null;
}
/**
* Listener for receiving frequency change events from downstream components
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return this;
}
/**
* Processes frequency change events from downstream components.
* @param event to process
*/
@Override
public void receive(FrequencyChangeEvent event)
{
switch(event.getEvent())
{
//Frequency correction requests are the only change requests supported from downstream components
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setFrequencyCorrection(event.getValue().intValue());
break;
}
}
}
/**
* Decimates an inbound buffer of I/Q samples from the source down to the
* standard 48000 channel sample rate
*/
public class DecimationProcessor implements Runnable
{
private boolean mProcessing = true;
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
public void shutdown()
{
mProcessing = false;
}
@Override
public void run()
{
/* General exception handler so that any errors won't kill the
* decimation thread and cause the input buffers to fill up and
* run the program out of memory */
try
{
if(mProcessing)
{
//Send a heartbeat every time this runs to allow downstream components to perform periodic
//state monitoring functions on this thread
if(mHeartbeatListener != null)
{
mHeartbeatListener.receive(HEARTBEAT);
}
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
{
/* Check to see if we've been shutdown */
if(!mProcessing)
{
mBuffer.clear();
return;
}
else
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
float[] translated = new float[samples.length];
/* Perform frequency translation */
for(int x = 0; x < samples.length; x += 2)
{
mMixer.rotate();
translated[x] = Complex.multiplyInphase(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
translated[x + 1] = Complex.multiplyQuadrature(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
}
if(mProcessing)
{
final ComplexPrimeCICDecimate filter = mDecimationFilter;
filter.receive(new ComplexBuffer(translated));
}
}
}
mSampleBuffers.clear();
}
}
catch(Exception e)
{
/* Only log the stack trace if we're still processing */
if(mProcessing)
{
mLog.error("Error encountered during decimation process", e);
}
}
catch(Throwable throwable)
{
mLog.error("Code error encountered during decimation process - channel thread will probably die", throwable);
}
/* Check to see if we've been shutdown */
if(!mProcessing)
{
mBuffer.clear();
mSampleBuffers.clear();
}
}
}
}

View File

@@ -0,0 +1,277 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
import javax.usb.UsbDeviceDescriptor;
public enum TunerClass
{
AIRSPY( TunerType.AIRSPY_R820T, "1D50", "60A1", "Airspy", "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" ),
COMPRO_VIDEOMATE_U650F( TunerType.ELONICS_E4000, "185B", "0650", "Compro", "Videomate U620F" ),
COMPRO_VIDEOMATE_U680F( TunerType.ELONICS_E4000, "185B", "0680", "Compro", "Videomate U620F" ),
DEXATEK_LOGILINK_VG002A( TunerType.FCI_FC2580, "1D19", "1101", "Dexatek", "Logilink VG0002A" ),
DEXATEK_DIGIVOX_MINI_II_REV3( TunerType.FCI_FC2580, "1D19", "1102", "Dexatek", "MSI Digivox Mini II v3.0" ),
DEXATEK_5217_DVBT( TunerType.FCI_FC2580, "1D19", "1103", "Dexatek", "5217 DVB-T" ),
ETTUS_USRP_B100( TunerType.ETTUS_VARIOUS, "2500", "0002", "Ettus Research", "USRP B100" ),
FUNCUBE_DONGLE_PRO( TunerType.FUNCUBE_DONGLE_PRO, "04D8", "FB56", "Hamlincrest", "Funcube Dongle Pro" ),
FUNCUBE_DONGLE_PRO_PLUS( TunerType.FUNCUBE_DONGLE_PRO_PLUS, "04D8", "FB31", "Hamlincrest", "Funcube Dongle Pro Plus" ),
GIGABYTE_GTU7300( TunerType.FITIPOWER_FC0012, "1B80", "D393", "Gigabyte", "GT-U7300" ),
GTEK_T803( TunerType.FITIPOWER_FC0012, "1F4D", "B803", "GTek", "T803" ),
HACKRF_ONE( TunerType.HACKRF, "1D50", "6089", "Great Scott Gadgets", "HackRF One" ),
RAD1O( TunerType.HACKRF, "1D50", "CC15", "Munich hackerspace", "Rad1o" ),
LIFEVIEW_LV5T_DELUXE( TunerType.FITIPOWER_FC0012, "1F4D", "C803", "Liveview", "LV5T Deluxe" ),
MYGICA_TD312( TunerType.FITIPOWER_FC0012, "1F4D", "D286", "MyGica", "TD312" ),
PEAK_102569AGPK( TunerType.FITIPOWER_FC0012, "1B80", "D395", "Peak", "102569AGPK" ),
PROLECTRIX_DV107669( TunerType.FITIPOWER_FC0012, "1F4D", "D803", "Prolectrix", "DV107669" ),
SVEON_STV20( TunerType.FITIPOWER_FC0012, "1B80", "D39D", "Sveon", "STV20 DVB-T USB & FM" ),
TERRATEC_CINERGY_T_REV1( TunerType.FITIPOWER_FC0012, "0CCD", "00A9", "Terratec", "Cinergy T R1" ),
TERRATEC_CINERGY_T_REV3( TunerType.ELONICS_E4000, "0CCD", "00D3", "Terratec", "Cinergy T R3" ),
TERRATEC_NOXON_REV1_B3( TunerType.FITIPOWER_FC0013, "0CCD", "00B3", "Terratec", "NOXON R1 (B3)" ),
TERRATEC_NOXON_REV1_B4( TunerType.FITIPOWER_FC0013, "0CCD", "00B4", "Terratec", "NOXON R1 (B4)" ),
TERRATEC_NOXON_REV1_B7( TunerType.FITIPOWER_FC0013, "0CCD", "00B7", "Terratec", "NOXON R1 (B7)" ),
TERRATEC_NOXON_REV1_C6( TunerType.FITIPOWER_FC0013, "0CCD", "00C6", "Terratec", "NOXON R1 (C6)" ),
TERRATEC_NOXON_REV2( TunerType.ELONICS_E4000, "0CCD", "00E0", "Terratec", "NOXON R2" ),
TERRATEC_T_STICK_PLUS( TunerType.ELONICS_E4000, "0CCD", "00D7", "Terratec", "T Stick Plus" ),
TWINTECH_UT40( TunerType.FITIPOWER_FC0013, "1B80", "D3A4", "Twintech", "UT-40" ),
ZAAPA_ZTMINDVBZP( TunerType.FITIPOWER_FC0012, "1B80", "D398", "Zaapa", "ZT-MINDVBZP" ),
UNKNOWN( TunerType.UNKNOWN, "0", "0", "Unknown Manufacturer", "Unknown Device" );
private TunerType mTunerType;
private String mVendorID;
private String mDeviceID;
private String mVendorDescription;
private String mDeviceDescription;
private TunerClass( TunerType tunerType,
String vendorID,
String deviceID,
String vendorDescription,
String deviceDescription )
{
mTunerType = tunerType;
mVendorID = vendorID;
mDeviceID = deviceID;
mVendorDescription = vendorDescription;
mDeviceDescription = deviceDescription;
}
public String toString()
{
return "USB" +
" Tuner:" + mTunerType.toString() +
" Vendor:" + mVendorDescription +
" Device:" + mDeviceDescription +
" Address:" + mVendorID + ":" + mDeviceID;
}
public String getVendorDeviceLabel()
{
return mVendorDescription + " " + mDeviceDescription;
}
public TunerType getTunerType()
{
return mTunerType;
}
public static TunerClass valueOf( UsbDeviceDescriptor descriptor )
{
return valueOf( descriptor.idVendor(), descriptor.idProduct() );
}
public static TunerClass valueOf( short vendor, short product )
{
TunerClass retVal = TunerClass.UNKNOWN;
//Cast the short to integer so that we can switch on unsigned numbers
int vendorID = vendor & 0xFFFF;
int productID = product & 0xFFFF;
switch( vendorID )
{
case 1240: //04DA
if( productID == 64305 ) //FB31
{
retVal = FUNCUBE_DONGLE_PRO_PLUS;
}
else if( productID == 64342 ) //FB56
{
retVal = FUNCUBE_DONGLE_PRO;
}
break;
case 3034: //0BDA
if( productID == 10290 ) //2832
{
retVal = GENERIC_2832;
}
else if( productID == 10296 ) //2838
{
retVal = GENERIC_2838;
}
break;
case 6235: //185B
if( productID == 1568 ) //0620
{
retVal = COMPRO_VIDEOMATE_U620F;
}
else if( productID == 1616 ) //0650
{
retVal = COMPRO_VIDEOMATE_U650F;
}
else if( productID == 1664 ) //0680
{
retVal = COMPRO_VIDEOMATE_U680F;
}
break;
case 7040: //1B80
if( productID == 54163 ) //D393
{
retVal = GIGABYTE_GTU7300;
}
else if( productID == 54165 ) //D395
{
retVal = PEAK_102569AGPK;
}
else if( productID == 54168 ) //D398
{
retVal = ZAAPA_ZTMINDVBZP;
}
else if( productID == 54173 ) //D39D
{
retVal = SVEON_STV20;
}
else if( productID == 54180 ) //D3A4
{
retVal = TWINTECH_UT40;
}
break;
case 7449: //1D19
if( productID == 4353 ) //1101
{
retVal = DEXATEK_LOGILINK_VG002A;
}
else if( productID == 4354 ) //1102
{
retVal = DEXATEK_DIGIVOX_MINI_II_REV3;
}
else if( productID == 4355 ) //1103
{
retVal = DEXATEK_5217_DVBT;
}
break;
case 7504: //1D50
if( productID == 24713 ) //6089
{
retVal = HACKRF_ONE;
}
else if( productID == 24737 ) //60A1
{
retVal = AIRSPY;
}
else if( productID == 52245 ) //CC15
{
retVal = HACKRF_ONE;
}
break;
case 8013: //1F4D
if( productID == 47107 ) //B803
{
retVal = GTEK_T803;
}
else if( productID == 51203 ) //C803
{
retVal = LIFEVIEW_LV5T_DELUXE;
}
else if( productID == 53894 ) //D286
{
retVal = MYGICA_TD312;
}
else if( productID == 55299 ) //D803
{
retVal = PROLECTRIX_DV107669;
}
break;
case 3277: //0CCD
if( productID == 169 ) //00A9
{
retVal = TERRATEC_CINERGY_T_REV1;
}
else if( productID == 179 ) //00B3
{
retVal = TERRATEC_NOXON_REV1_B3;
}
else if( productID == 180 ) //00B4
{
retVal = TERRATEC_NOXON_REV1_B4;
}
else if( productID == 181 ) //00B5
{
retVal = TERRATEC_NOXON_REV1_B7;
}
else if( productID == 198 ) //00C6
{
retVal = TERRATEC_NOXON_REV1_C6;
}
else if( productID == 211 ) //00D3
{
retVal = TERRATEC_CINERGY_T_REV3;
}
else if( productID == 215 ) //00D7
{
retVal = TERRATEC_T_STICK_PLUS;
}
else if( productID == 224) //00E0
{
retVal = TERRATEC_NOXON_REV2;
}
break;
case 9472: //2500
if( productID == 2 ) //0002
{
retVal = ETTUS_USRP_B100;
}
break;
default:
}
return retVal;
}
public String getVendorID()
{
return mVendorID;
}
public String getDeviceID()
{
return mDeviceID;
}
public String getVendorDescription()
{
return mVendorDescription;
}
public String getDeviceDescription()
{
return mDeviceDescription;
}
}

View File

@@ -0,0 +1,370 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.RejectedExecutionException;
public abstract class TunerController implements Tunable, IFrequencyChangeProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
/* List of currently tuned channels being served to demod channels */
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;
/**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
* that are being fed samples from the tuner.
*
* @param minimumFrequency minimum uncorrected tunable frequency
* @param maximumFrequency maximum uncorrected tunable frequency
* @param middleUnusable is the +/- value center DC spike to avoid for channels
* @param usableBandwidth percentage of usable bandwidth relative to space at the extreme ends of the spectrum
* @throws SourceException - for any issues related to constructing the
* class, tuning a frequency, or setting the bandwidth
*/
public TunerController(long minimumFrequency, long maximumFrequency, int middleUnusable, double usableBandwidth)
{
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
mMiddleUnusable = middleUnusable;
mUsableBandwidthPercentage = usableBandwidth;
}
/**
* Applies the settings in the tuner configuration
*/
public abstract void apply(TunerConfiguration config) throws SourceException;
/**
* Responds to requests to set the frequency
*/
@Override
public void frequencyChanged(FrequencyChangeEvent event) throws SourceException
{
if(event.getEvent() == Event.REQUEST_FREQUENCY_CHANGE)
{
setFrequency(event.getValue().longValue());
}
}
public int getBandwidth()
{
return mFrequencyController.getBandwidth();
}
/**
* Sets the center frequency of the local oscillator.
*
* @param frequency in hertz
* @throws SourceException - if the tuner has any issues
*/
public void setFrequency(long frequency) throws SourceException
{
mFrequencyController.setFrequency(frequency);
}
/**
* Gets the center frequency of the local oscillator
*
* @return frequency in hertz
*/
public long getFrequency()
{
return mFrequencyController.getFrequency();
}
public int getSampleRate()
{
return mFrequencyController.getBandwidth();
}
public double getFrequencyCorrection()
{
return mFrequencyController.getFrequencyCorrection();
}
public void setFrequencyCorrection(double correction) throws SourceException
{
mFrequencyController.setFrequencyCorrection(correction);
}
public long getMinFrequency()
{
return mFrequencyController.getMinimumFrequency();
}
public long getMaxFrequency()
{
return mFrequencyController.getMaximumFrequency();
}
private long getMinTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
}
private long getMaxTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
}
/**
* Indicates if channel along with all of the other currently sourced
* channels can fit within the tunable bandwidth.
*/
private boolean canTune(TunerChannel channel)
{
//Make sure we're within the tunable frequency range of this tuner
if(getMinFrequency() < channel.getMinFrequency() && getMaxFrequency() > channel.getMaxFrequency())
{
//If this is the first lock, then we're good
if(mTunedChannels.isEmpty())
{
return true;
}
else
{
int usableBandwidth = getUsableBandwidth();
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Requested channel is within current locked channel frequency range
if(minLockedFrequency <= channel.getMinFrequency() && channel.getMaxFrequency() <= maxLockedFrequency)
{
return true;
}
//Requested channel is higher than min locked frequency
if(channel.getMaxFrequency() > minLockedFrequency &&
channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth)
{
return true;
}
//Requested channel is lower than the max locked frequency
if(channel.getMinFrequency() <= maxLockedFrequency &&
maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth)
{
return true;
}
}
}
return false;
}
/**
* Constructs a digital drop channel (DDC) as a tuner channel from from the tuner, or returns null if the channel
* cannot be sourced from the tuner.
*
* This controller can't provide a channel if the channel, along with the current set of sourced channels, can't be
* accomodated within the current tuner bandwidth, or if a center frequency cannot be calculated that will ensure
* all of the channels can be accomodated and any defined central DC spike avoided.
*
* @param tuner to source the channel from
* @param channel with defined center frequency and bandwidth
* @return fully constructed tuner channel or null
* @throws RejectedExecutionException if the decimation processor has an error
*/
public TunerChannelSource getChannel(Tuner tuner, TunerChannel channel) throws RejectedExecutionException
{
TunerChannelSource source = null;
if(canTune(channel))
{
try
{
mTunedChannels.add(channel);
if(requiresLOUpdate(channel))
{
updateLOFrequency();
}
source = new TunerChannelSource(tuner, channel);
}
catch(SourceException se)
{
mTunedChannels.remove(channel);
source = null;
}
}
return source;
}
public int getChannelCount()
{
return mTunedChannels.size();
}
/**
* Indicates if the tuner's LO frequency must be updated in order to accommodate the tuner channel
*/
private boolean requiresLOUpdate(TunerChannel channel) throws SourceException
{
boolean isBelow = channel.getMinFrequency() < getMinTunedFrequency();
boolean isAbove = channel.getMaxFrequency() > getMaxTunedFrequency();
boolean overlapsCenter = mMiddleUnusable > 0 &&
channel.overlaps(getFrequency() - mMiddleUnusable, getFrequency() + mMiddleUnusable);
return isAbove || isBelow || overlapsCenter;
}
/**
* Releases the currently sourced tuner channel from this tuner and shuts down the tuner if no other sources exist.
*/
public void releaseChannel(TunerChannelSource tunerChannelSource)
{
if(tunerChannelSource != null)
{
mTunedChannels.remove(tunerChannelSource.getTunerChannel());
}
}
/**
* Sets the Local Oscillator frequency to accomodate the current set of tuned channels.
*
* If there is only a single tuned channel, it is placed immediately to the right of the usable bandwidth right
* of the central DC spike.
*
* Otherwise, it places the highest channel frequency at the upper end of the tuner bandwidth, and then iteratively
* moves the center frequency higher until all channels fit within the bandwidth and none of the channels overlap
* any defined central DC spike unusable region. If a center tune frequency cannot be calculated, throw an
* exception so that the most recently added channel can be removed.
*
* Note: the tuned frequency is not changed until a legitimate new frequency can be calculated. If an exception
* is thrown, the current frequency is retained, so that the recently added channel can be removed and all other
* channels can continue as previously arranged.
*
* @throws SourceException if the set of tuner channels, including a recently added channel, cannot be tuned
* within the current tuner bandwidth and any central DC spike unusable region.
*/
private void updateLOFrequency() throws SourceException
{
long candidateFrequency;
boolean isValidCandidateFrequency = true;
//If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
if(mTunedChannels.size() == 1)
{
candidateFrequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable + 1;
}
else
{
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Start by placing the highest frequency channel at the high end of the spectrum
candidateFrequency = maxLockedFrequency - (getUsableBandwidth() / 2);
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
if(mMiddleUnusable > 0)
{
boolean processingRequired = true;
while(isValidCandidateFrequency && processingRequired)
{
processingRequired = false;
long minAvoid = candidateFrequency - mMiddleUnusable;
long maxAvoid = candidateFrequency + mMiddleUnusable;
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : mTunedChannels)
{
if(channel.overlaps(minAvoid, maxAvoid))
{
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
//left of the central DC spike avoid zone
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
if(candidateFrequency + adjustment - (getUsableBandwidth() / 2) <= minLockedFrequency)
{
candidateFrequency += adjustment;
processingRequired = true;
}
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//break out of the for/each loop, so that we can start over again with all of the channels
break;
}
}
}
}
}
if(isValidCandidateFrequency)
{
mFrequencyController.setFrequency(candidateFrequency);
}
else
{
throw new SourceException("Couldn't calculate viable center frequency from set of tuner channels");
}
}
/**
* Usable bandwidth - total bandwidth minus the unusable space at either end of the spectrum.
*/
private int getUsableBandwidth()
{
return (int)(getBandwidth() * mUsableBandwidthPercentage);
}
/**
* Sets the listener to be notified any time that the tuner changes frequency or bandwidth/sample rate.
*
* Note: this is normally used by the Tuner. Any additional listeners can be registered on the tuner.
*/
public void addListener(IFrequencyChangeProcessor processor)
{
mFrequencyController.addListener(processor);
}
/**
* Removes the frequency change listener
*/
public void removeListener(IFrequencyChangeProcessor processor)
{
mFrequencyController.removeFrequencyChangeProcessor(processor);
}
}

View File

@@ -0,0 +1,344 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.gui.control.JFrequencyControl;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EmptyEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationFactory;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TunerEditor extends Editor<Tuner>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( TunerEditor.class );
private TunerConfigurationModel mTunerConfigurationModel;
private JTable mTunerConfigurationTable;
private TableRowSorter<TunerConfigurationModel> mRowSorter;
private JFrequencyControl mFrequencyControl = new JFrequencyControl();
private JLabel mSelectedTunerType = new JLabel( "No Tuner Selected" );
private JButton mNewConfigurationButton = new JButton( "New" );
private JButton mDeleteConfigurationButton = new JButton( "Delete" );
private JButton mAssignConfigurationButton = new JButton( "Assign" );
private JScrollPane mEditorScroller;
private Editor<TunerConfiguration> mEditor = new EmptyEditor<>( "a tuner" );
private JideSplitPane mEditorSplitPane = new JideSplitPane();
public TunerEditor( TunerConfigurationModel tunerConfigurationModel )
{
mTunerConfigurationModel = tunerConfigurationModel;
init();
}
public void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[grow,fill]" ) );
JPanel listPanel = new JPanel();
listPanel.setLayout( new MigLayout( "fill,wrap 3", "[grow,fill]", "[][][][grow,fill][]" ) );
listPanel.add( mSelectedTunerType, "span" );
mFrequencyControl.setEnabled( false );
listPanel.add( mFrequencyControl, "span" );
mRowSorter = new TableRowSorter<>( mTunerConfigurationModel );
mTunerConfigurationTable = new JTable( mTunerConfigurationModel );
mTunerConfigurationTable.setRowSorter( mRowSorter );
mTunerConfigurationTable.setEnabled( false );
mTunerConfigurationTable.getSelectionModel().addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent event )
{
if( !event.getValueIsAdjusting() )
{
int row = mTunerConfigurationTable.getSelectedRow();
if( row >= 0 )
{
int modelRow = mTunerConfigurationTable.convertRowIndexToModel( row );
if( modelRow >= 0 )
{
setTunerConfiguration( mTunerConfigurationModel.getTunerConfiguration( modelRow ) );
}
}
}
}
} );
JScrollPane tableScroller = new JScrollPane( mTunerConfigurationTable );
tableScroller.setHorizontalScrollBarPolicy(
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
listPanel.add( new JLabel( "Tuner Configurations" ), "span" );
listPanel.add( tableScroller, "span" );
mNewConfigurationButton.setEnabled( false );
mNewConfigurationButton.setToolTipText( "Create a new configuration for the currently selected tuner" );
mNewConfigurationButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
Tuner tuner = getItem();
String name = mTunerConfigurationModel
.getDistinctName( tuner.getTunerType(), tuner.getUniqueID() );
TunerConfiguration config = TunerConfigurationFactory
.getTunerConfiguration( tuner.getTunerType(),
tuner.getUniqueID(), name );
mTunerConfigurationModel.addTunerConfiguration( config );
}
} );
listPanel.add( mNewConfigurationButton );
mDeleteConfigurationButton.setEnabled( false );
mDeleteConfigurationButton.setToolTipText( "Deletes the currently selected tuner" );
mDeleteConfigurationButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
int choice = JOptionPane.showConfirmDialog( TunerEditor.this,
"Do you want to delete this tuner configuration?",
"Delete Tuner Configuration?", JOptionPane.YES_NO_OPTION );
if( choice == JOptionPane.YES_OPTION )
{
TunerConfiguration selected = getSelectedTunerConfiguration();
mTunerConfigurationModel.removeTunerConfiguration( selected );
mTunerConfigurationTable.setRowSelectionInterval( 0, 0 );
}
}
} );
listPanel.add( mDeleteConfigurationButton );
mAssignConfigurationButton.setEnabled( false );
mAssignConfigurationButton.setToolTipText( "Assigns to the currently selected tuner configuration to the selected tuner" );
mAssignConfigurationButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
TunerConfiguration selected = getSelectedTunerConfiguration();
mTunerConfigurationModel.assignTunerConfiguration( selected );
setTunerConfiguration( getSelectedTunerConfiguration() );
}
} );
listPanel.add( mAssignConfigurationButton );
//Set preferred size on both scrollers to same width, so they split the space
listPanel.setPreferredSize( new Dimension( 100, 80 ) );
JScrollPane listScroller = new JScrollPane( listPanel );
listScroller.setPreferredSize( new Dimension( 100, 80 ) );
mEditorScroller = new JScrollPane( mEditor );
mEditorScroller.setPreferredSize( new Dimension( 100, 80 ) );
mEditorSplitPane = new JideSplitPane( JideSplitPane.HORIZONTAL_SPLIT );
mEditorSplitPane.add( listScroller );
mEditorSplitPane.add( mEditorScroller );
add( mEditorSplitPane );
}
private TunerConfiguration getSelectedTunerConfiguration()
{
int tableRow = mTunerConfigurationTable.getSelectedRow();
if( tableRow >= 0 )
{
int modelRow = mTunerConfigurationTable.convertRowIndexToModel( tableRow );
if( modelRow >= 0 )
{
return mTunerConfigurationModel.getTunerConfiguration( modelRow );
}
}
return null;
}
@Override
public void save()
{
//Unused
}
@Override
public void setItem( Tuner tuner )
{
//Cleanup previously selected tuner
if( hasItem() )
{
Tuner previous = getItem();
previous.getTunerController().removeListener( mFrequencyControl );
mFrequencyControl.removeListener( previous.getTunerController() );
}
super.setItem( tuner );
if( hasItem() )
{
mSelectedTunerType.setText( tuner.getName() );
mFrequencyControl.setEnabled( true );
mTunerConfigurationTable.setEnabled( true );
mNewConfigurationButton.setEnabled( true );
//Change to an editor for this config's tuner type
mEditor = TunerConfigurationFactory.getEditor( getItem(), mTunerConfigurationModel );
mRowSorter.setRowFilter( new ConfigurationRowFilter( tuner.getTunerType(),
tuner.getUniqueID() ) );
TunerConfiguration assigned = mTunerConfigurationModel
.getTunerConfiguration( tuner.getTunerType(), tuner.getUniqueID() );
int modelIndex = mTunerConfigurationModel.getRowIndex( assigned );
if( modelIndex >= 0 )
{
int viewIndex = mTunerConfigurationTable
.convertRowIndexToView( modelIndex );
if( viewIndex >= 0 )
{
mTunerConfigurationTable
.setRowSelectionInterval( viewIndex, viewIndex );
}
}
//Link frequency control to the tuner
mFrequencyControl.addListener( tuner.getTunerController() );
//Link tuner to frequency control
tuner.getTunerController().addListener( mFrequencyControl );
//Set the displayed frequency without adjusting the tuner's frequency
mFrequencyControl.setFrequency(
tuner.getTunerController().getFrequency(), false );
}
else
{
mSelectedTunerType.setText( "No Tuner Selected" );
mFrequencyControl.setEnabled( false );
mTunerConfigurationTable.setEnabled( false );
mNewConfigurationButton.setEnabled( false );
mRowSorter.setRowFilter( null );
mEditor = new EmptyEditor<TunerConfiguration>();
}
//Swap out the editor
int split = mEditorSplitPane.getDividerLocation( 0 );
mEditorSplitPane.remove( mEditorScroller );
mEditorScroller = new JScrollPane( mEditor );
mEditorSplitPane.add( mEditorScroller );
mEditorSplitPane.setDividerLocation( 0, split );
}
private void setTunerConfiguration( TunerConfiguration config )
{
if( config != null )
{
if( !config.isAssigned() )
{
if( !mAssignConfigurationButton.isEnabled() )
{
mAssignConfigurationButton.setEnabled( true );
}
if( !mDeleteConfigurationButton.isEnabled() )
{
mDeleteConfigurationButton.setEnabled( true );
}
}
else
{
if( mAssignConfigurationButton.isEnabled() )
{
mAssignConfigurationButton.setEnabled( false );
}
if( mDeleteConfigurationButton.isEnabled() )
{
mDeleteConfigurationButton.setEnabled( false );
}
}
}
else
{
if( mDeleteConfigurationButton.isEnabled() )
{
mDeleteConfigurationButton.setEnabled( false );
}
if( mAssignConfigurationButton.isEnabled() )
{
mAssignConfigurationButton.setEnabled( false );
}
}
mEditor.setItem( config );
}
public class ConfigurationRowFilter extends RowFilter<TunerConfigurationModel,Integer>
{
private TunerType mTunerType;
private String mUniqueID;
public ConfigurationRowFilter( TunerType type, String uniqueID )
{
mTunerType = type;
mUniqueID = uniqueID;
}
@Override
public boolean include( javax.swing.RowFilter.Entry<? extends TunerConfigurationModel,
? extends Integer> entry )
{
TunerConfiguration config = entry.getModel()
.getTunerConfiguration( entry.getIdentifier() );
return config != null &&
config.getTunerType() == mTunerType &&
config.getUniqueID() != null &&
config.getUniqueID().contentEquals( mUniqueID );
}
}
}

View File

@@ -0,0 +1,34 @@
package io.github.dsheirer.source.tuner;
public class TunerEvent
{
private Tuner mTuner;
private Event mEvent;
public TunerEvent( Tuner tuner, Event event )
{
mTuner = tuner;
mEvent = event;
}
public Tuner getTuner()
{
return mTuner;
}
public Event getEvent()
{
return mEvent;
}
public enum Event
{
ADD,
REMOVE,
CHANNEL_COUNT,
FREQUENCY,
SAMPLE_RATE,
REQUEST_MAIN_SPECTRAL_DISPLAY,
REQUEST_NEW_SPECTRAL_DISPLAY;
}
}

View File

@@ -0,0 +1,472 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.source.tuner.airspy.AirspyTuner;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController;
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController;
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerController;
import io.github.dsheirer.source.tuner.hackrf.HackRFTuner;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import java.util.Collection;
public class TunerManager
{
private final static Logger mLog = LoggerFactory.getLogger(TunerManager.class);
private MixerManager mMixerManager;
private TunerModel mTunerModel;
/**
* Application-wide LibUSB timeout processor for transfer buffers. All classes that need to use USB transfer
* buffers can register with this processor and the processor will auto-start and auto-stop while USB transfer
* processors are registered.
*/
public static final LibUSBTransferProcessor LIBUSB_TRANSFER_PROCESSOR;
static
{
LIBUSB_TRANSFER_PROCESSOR = new LibUSBTransferProcessor();
}
public TunerManager(MixerManager mixerManager, TunerModel tunerModel)
{
mMixerManager = mixerManager;
mTunerModel = tunerModel;
initTuners();
}
/**
* Performs cleanup of USB related issues
*/
public void dispose()
{
LibUsb.exit(null);
}
/**
* Loads all USB tuners and USB/Mixer tuner devices
*/
private void initTuners()
{
DeviceList deviceList = new DeviceList();
int result = LibUsb.init(null);
if(result != LibUsb.SUCCESS)
{
mLog.error("unable to initialize libusb [" +
LibUsb.errorName(result) + "]");
}
else
{
mLog.info("LibUSB API Version: " + LibUsb.getApiVersion());
mLog.info("LibUSB Version: " + LibUsb.getVersion());
result = LibUsb.getDeviceList(null, deviceList);
if(result < 0)
{
mLog.error("unable to get device list from libusb [" + result + " / " +
LibUsb.errorName(result) + "]");
}
else
{
mLog.info("discovered [" + result + "] attached USB devices");
}
}
for(Device device : deviceList)
{
DeviceDescriptor descriptor = new DeviceDescriptor();
result = LibUsb.getDeviceDescriptor(device, descriptor);
if(result != LibUsb.SUCCESS)
{
mLog.error("unable to read device descriptor [" +
LibUsb.errorName(result) + "]");
}
else
{
TunerInitStatus status = initTuner(device, descriptor);
StringBuilder sb = new StringBuilder();
sb.append("usb device [");
sb.append(String.format("%04X", descriptor.idVendor()));
sb.append(":");
sb.append(String.format("%04X", descriptor.idProduct()));
if(status.isLoaded())
{
Tuner tuner = status.getTuner();
try
{
mTunerModel.addTuner(tuner);
sb.append("] LOADED: ");
sb.append(tuner.toString());
}
catch(Exception e)
{
sb.append("] NOT LOADED: ");
sb.append(status.getInfo());
sb.append(" Error:" + e.getMessage());
}
}
else
{
sb.append("] NOT LOADED: ");
sb.append(status.getInfo());
}
mLog.info(sb.toString());
}
}
LibUsb.freeDeviceList(deviceList, true);
}
private TunerInitStatus initTuner(Device device,
DeviceDescriptor descriptor)
{
if(device != null && descriptor != null)
{
TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(),
descriptor.idProduct());
switch(tunerClass)
{
case AIRSPY:
return initAirspyTuner(device, descriptor);
case ETTUS_USRP_B100:
return initEttusB100Tuner(device, descriptor);
case FUNCUBE_DONGLE_PRO:
return initFuncubeProTuner(device, descriptor);
case FUNCUBE_DONGLE_PRO_PLUS:
return initFuncubeProPlusTuner(device, descriptor);
case HACKRF_ONE:
case RAD1O:
return initHackRFTuner(device, descriptor);
case COMPRO_VIDEOMATE_U620F:
case COMPRO_VIDEOMATE_U650F:
case COMPRO_VIDEOMATE_U680F:
case GENERIC_2832:
case GENERIC_2838:
case DEXATEK_5217_DVBT:
case DEXATEK_DIGIVOX_MINI_II_REV3:
case DEXATEK_LOGILINK_VG002A:
case GIGABYTE_GTU7300:
case GTEK_T803:
case LIFEVIEW_LV5T_DELUXE:
case MYGICA_TD312:
case PEAK_102569AGPK:
case PROLECTRIX_DV107669:
case SVEON_STV20:
case TERRATEC_CINERGY_T_REV1:
case TERRATEC_CINERGY_T_REV3:
case TERRATEC_NOXON_REV1_B3:
case TERRATEC_NOXON_REV1_B4:
case TERRATEC_NOXON_REV1_B7:
case TERRATEC_NOXON_REV1_C6:
case TERRATEC_NOXON_REV2:
case TERRATEC_T_STICK_PLUS:
case TWINTECH_UT40:
case ZAAPA_ZTMINDVBZP:
return initRTL2832Tuner(tunerClass, device, descriptor);
case UNKNOWN:
default:
break;
}
}
return new TunerInitStatus(null, "Unknown Device");
}
private TunerInitStatus initAirspyTuner(Device device,
DeviceDescriptor descriptor)
{
try
{
AirspyTunerController airspyController = new AirspyTunerController(device);
airspyController.init();
AirspyTuner tuner = new AirspyTuner(airspyController);
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)
{
return new TunerInitStatus(null, "Ettus B100 tuner not currently "
+ "supported");
}
private TunerInitStatus initFuncubeProTuner(Device device,
DeviceDescriptor descriptor)
{
String reason = "NOT LOADED";
MixerTunerDataLine dataline = getMixerTunerDataLine(
TunerClass.FUNCUBE_DONGLE_PRO.getTunerType());
if(dataline != null)
{
FCD1TunerController controller =
new FCD1TunerController(device, descriptor);
try
{
controller.init();
FCDTuner tuner =
new FCDTuner(dataline, controller);
return new TunerInitStatus(tuner, "LOADED");
}
catch(SourceException e)
{
mLog.error("couldn't load funcube dongle pro tuner", e);
reason = "error during initialization - " + e.getLocalizedMessage();
}
}
else
{
reason = "couldn't find matching mixer dataline";
}
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
+ "loaded - " + reason);
}
private TunerInitStatus initFuncubeProPlusTuner(Device device,
DeviceDescriptor descriptor)
{
String reason = "NOT LOADED";
MixerTunerDataLine dataline = getMixerTunerDataLine(
TunerClass.FUNCUBE_DONGLE_PRO_PLUS.getTunerType());
if(dataline != null)
{
FCD2TunerController controller =
new FCD2TunerController(device, descriptor);
try
{
controller.init();
FCDTuner tuner =
new FCDTuner(dataline, controller);
return new TunerInitStatus(tuner, "LOADED");
}
catch(SourceException e)
{
mLog.error("couldn't load funcube dongle pro plus tuner", e);
reason = "error during initialization - " +
e.getLocalizedMessage();
}
}
else
{
reason = "couldn't find matching mixer dataline";
}
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
+ "loaded - " + reason);
}
private TunerInitStatus initHackRFTuner(Device device,
DeviceDescriptor descriptor)
{
try
{
HackRFTunerController hackRFController = new HackRFTunerController(device, descriptor);
hackRFController.init();
HackRFTuner tuner = new HackRFTuner(hackRFController);
return new TunerInitStatus(tuner, "LOADED");
}
catch(SourceException se)
{
mLog.error("couldn't construct HackRF controller/tuner", se);
return new TunerInitStatus(null,
"error constructing HackRF tuner controller");
}
}
private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass,
Device device,
DeviceDescriptor deviceDescriptor)
{
String reason = "NOT LOADED";
TunerType tunerType = tunerClass.getTunerType();
if(tunerType == TunerType.RTL2832_VARIOUS)
{
try
{
tunerType = RTL2832TunerController.identifyTunerType(device);
}
catch(SourceException e)
{
mLog.error("couldn't determine RTL2832 tuner type", e);
tunerType = TunerType.UNKNOWN;
}
}
switch(tunerType)
{
case ELONICS_E4000:
try
{
E4KTunerController controller = new E4KTunerController(device, deviceDescriptor);
controller.init();
RTL2832Tuner rtlTuner = new RTL2832Tuner(tunerClass, controller);
return new TunerInitStatus(rtlTuner, "LOADED");
}
catch(SourceException se)
{
return new TunerInitStatus(null, "Error constructing E4K tuner controller - " +
se.getLocalizedMessage());
}
case RAFAELMICRO_R820T:
try
{
R820TTunerController controller = new R820TTunerController(device, deviceDescriptor);
controller.init();
RTL2832Tuner rtlTuner = new RTL2832Tuner(tunerClass, controller);
return new TunerInitStatus(rtlTuner, "LOADED");
}
catch(SourceException se)
{
mLog.error("error constructing tuner", se);
return new TunerInitStatus(null, "Error constructing R820T "
+ "tuner controller - " + se.getLocalizedMessage());
}
case FITIPOWER_FC0012:
case FITIPOWER_FC0013:
case RAFAELMICRO_R828D:
case UNKNOWN:
default:
reason = "SDRTRunk doesn't currently support RTL2832 "
+ "Dongle with [" + tunerType.toString() +
"] tuner for tuner class[" + tunerClass.toString() + "]";
break;
}
return new TunerInitStatus(null, reason);
}
/**
* Gets the first tuner mixer dataline that corresponds to the tuner class.
*
* Note: this method is not currently able to align multiple tuner mixer
* data lines of the same tuner type. If you have multiple Funcube Dongle
* tuners of the same TYPE, there is no guarantee that you will get the
* correct mixer.
*
* @param tunerClass
* @return
*/
private MixerTunerDataLine getMixerTunerDataLine(TunerType tunerClass)
{
Collection<MixerTunerDataLine> datalines =
mMixerManager.getMixerTunerDataLines();
for(MixerTunerDataLine mixerTDL : datalines)
{
if(mixerTDL.getMixerTunerType().getTunerClass() == tunerClass)
{
return mixerTDL;
}
}
return null;
}
public class TunerInitStatus
{
private Tuner mTuner;
private String mInfo;
public TunerInitStatus(Tuner tuner, String info)
{
mTuner = tuner;
mInfo = info;
}
public Tuner getTuner()
{
return mTuner;
}
public String getInfo()
{
return mInfo;
}
public boolean isLoaded()
{
return mTuner != null;
}
}
}

View File

@@ -0,0 +1,296 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
public class TunerModel extends AbstractTableModel implements Listener<TunerEvent>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( TunerModel.class );
public static final int TUNER_TYPE = 0;
public static final int TUNER_ID = 1;
public static final int SAMPLE_RATE = 2;
public static final int FREQUENCY = 3;
public static final int CHANNEL_COUNT = 4;
public static final int SPECTRAL_DISPLAY_MAIN = 5;
public static final int SPECTRAL_DISPLAY_NEW = 6;
private static final String MHZ = " MHz";
private static final String[] COLUMNS =
{ "Tuner", "ID", "Sample Rate", "Frequency", "Channels", "Spectral", "Display" };
private List<Tuner> mTuners = new ArrayList<>();
private List<Listener<TunerEvent>> mTunerEventListeners = new ArrayList<>();
private DecimalFormat mFrequencyFormat = new DecimalFormat( "0.00000" );
private DecimalFormat mSampleRateFormat = new DecimalFormat( "0.000" );
private TunerConfigurationModel mTunerConfigurationModel;
public TunerModel( TunerConfigurationModel tunerConfigurationModel )
{
mTunerConfigurationModel = tunerConfigurationModel;
}
public TunerConfigurationModel getTunerConfigurationModel()
{
return mTunerConfigurationModel;
}
/**
* List of Tuners currently in the model
*/
public List<Tuner> getTuners()
{
return mTuners;
}
public Tuner getTuner( int index )
{
if( index < mTuners.size() )
{
return mTuners.get( index );
}
return null;
}
public void addTuners( List<Tuner> Tuners )
{
for( Tuner tuner: Tuners )
{
addTuner( tuner );
}
}
/**
* Adds the Tuner to this model
*/
public void addTuner( Tuner tuner )
{
if( !mTuners.contains( tuner ) )
{
//Get the tuner configuration and apply it to the tuner - this
//call should always produce a tuner configuration
TunerConfiguration config = mTunerConfigurationModel
.getTunerConfiguration( tuner.getTunerType(), tuner.getUniqueID() );
try
{
tuner.getTunerController().apply( config );
mTuners.add( tuner );
int index = mTuners.indexOf( tuner );
fireTableRowsInserted( index, index );
tuner.addTunerChangeListener( this );
}
catch( SourceException se )
{
mLog.error( "Couldn't apply tuner configuration to tuner - ["
+ tuner.getTunerType().name() + "] with id ["
+ tuner.getUniqueID() + "] - tuner will not be included" );
}
}
}
/**
* Removes the Tuner from this model
*/
public void removeTuner( Tuner tuner )
{
if( mTuners.contains( tuner ) )
{
tuner.removeTunerChangeListener( this );
int index = mTuners.indexOf( tuner );
mTuners.remove( tuner );
fireTableRowsDeleted( index, index );
}
}
public void addListener( Listener<TunerEvent> listener )
{
mTunerEventListeners.add( listener );
}
public void removeListener( Listener<TunerEvent> listener )
{
mTunerEventListeners.remove( listener );
}
public void broadcast( TunerEvent event )
{
for( Listener<TunerEvent> listener: mTunerEventListeners )
{
listener.receive( event );
}
}
/**
* Requests to display the first tuner in this model. Invoke this method
* after all listeners have registered and tuners have been added to this
* model, in order to inform the primary display to use the first tuner.
*/
public void requestFirstTunerDisplay()
{
if( mTuners.size() > 0 )
{
broadcast( new TunerEvent( mTuners.get( 0 ),
Event.REQUEST_MAIN_SPECTRAL_DISPLAY ) );
}
}
@Override
public void receive( TunerEvent event )
{
if( event.getTuner() != null )
{
int index = mTuners.indexOf( event.getTuner() );
if( index >= 0 )
{
switch( event.getEvent() )
{
case CHANNEL_COUNT:
fireTableCellUpdated( index, CHANNEL_COUNT );
break;
case FREQUENCY:
fireTableCellUpdated( index, FREQUENCY );
mTunerConfigurationModel.tunerFrequencyChanged( event.getTuner() );
break;
case SAMPLE_RATE:
fireTableCellUpdated( index, SAMPLE_RATE );
break;
case REQUEST_MAIN_SPECTRAL_DISPLAY:
fireTableCellUpdated( index, SPECTRAL_DISPLAY_MAIN );
default:
break;
}
}
}
broadcast( event );
}
@Override
public int getRowCount()
{
return mTuners.size();
}
@Override
public int getColumnCount()
{
return COLUMNS.length;
}
@Override
public Object getValueAt( int rowIndex, int columnIndex )
{
if( rowIndex < mTuners.size() )
{
Tuner tuner = mTuners.get( rowIndex );
switch( columnIndex )
{
case TUNER_TYPE:
return tuner.getTunerType().getLabel();
case TUNER_ID:
return tuner.getUniqueID();
case SAMPLE_RATE:
int sampleRate = tuner.getTunerController().getSampleRate();
return mSampleRateFormat.format( sampleRate / 1E6D ) + MHZ;
case FREQUENCY:
try
{
long frequency = tuner.getTunerController().getFrequency();
return mFrequencyFormat.format( frequency / 1E6D ) + MHZ;
}
catch( Exception e )
{
return 0;
}
case CHANNEL_COUNT:
return tuner.getTunerController().getChannelCount();
case SPECTRAL_DISPLAY_MAIN:
return "Main";
case SPECTRAL_DISPLAY_NEW:
return "New";
default:
break;
}
}
return null;
}
@Override
public String getColumnName( int columnIndex )
{
return COLUMNS[ columnIndex ];
}
/**
* Iterates current tuners to get a tuner channel source for the frequency
* specified in the channel config's source config object.
*
* Returns null if no tuner can source the channel
*/
public Source getSource(SourceConfigTuner config, int bandwidth )
{
TunerChannelSource retVal = null;
TunerChannel tunerChannel = config.getTunerChannel();
tunerChannel.setBandwidth( bandwidth );
Iterator<Tuner> it = mTuners.iterator();
Tuner tuner;
while( it.hasNext() && retVal == null )
{
tuner = it.next();
try
{
retVal = tuner.getChannel( tunerChannel );
}
catch ( RejectedExecutionException ree )
{
mLog.error( "couldn't provide tuner channel source", ree );
}
catch ( SourceException e )
{
mLog.error( "error obtaining channel from tuner [" +
tuner.getName() + "]", e );
}
}
return retVal;
}
}

View File

@@ -0,0 +1,95 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.gui.control.JFrequencyControl;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import net.miginfocom.swing.MigLayout;
public class TunerSourceEditor extends Editor<Channel>
{
private static final long serialVersionUID = 1L;
private JFrequencyControl mFrequencyControl;
public TunerSourceEditor()
{
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[left]", "" ) );
mFrequencyControl = new JFrequencyControl();
mFrequencyControl.setEnabled( false );
mFrequencyControl.addListener( new IFrequencyChangeProcessor()
{
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
setModified( true );
}
} );
add( mFrequencyControl );
}
public void save()
{
if( hasItem() && isModified() )
{
SourceConfigTuner config = new SourceConfigTuner();
config.setFrequency( mFrequencyControl.getFrequency() );
getItem().setSourceConfiguration( config );
}
setModified( false );
}
@Override
public void setItem( Channel item )
{
super.setItem( item );
if( hasItem() )
{
SourceConfiguration config = getItem().getSourceConfiguration();
mFrequencyControl.setEnabled( true );
if( config instanceof SourceConfigTuner )
{
mFrequencyControl.setFrequency( ((SourceConfigTuner)config).getFrequency(), false );
setModified( false );
}
else
{
mFrequencyControl.setFrequency( 101000000, false );
setModified( true );
}
}
else
{
mFrequencyControl.setEnabled( false );
setModified( false );
}
}
}

View File

@@ -0,0 +1,55 @@
package io.github.dsheirer.source.tuner;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.spectrum.SpectrumFrame;
import java.awt.*;
public class TunerSpectralDisplayManager implements Listener<TunerEvent>
{
private SpectralDisplayPanel mSpectralDisplayPanel;
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private SettingsManager mSettingsManager;
public TunerSpectralDisplayManager( SpectralDisplayPanel panel,
ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
SettingsManager settingsManager )
{
mSpectralDisplayPanel = panel;
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mSettingsManager = settingsManager;
}
@Override
public void receive( TunerEvent event )
{
switch( event.getEvent() )
{
case REQUEST_MAIN_SPECTRAL_DISPLAY:
mSpectralDisplayPanel.showTuner( event.getTuner() );
break;
case REQUEST_NEW_SPECTRAL_DISPLAY:
final SpectrumFrame frame = new SpectrumFrame( mChannelModel,
mChannelProcessingManager, mSettingsManager, event.getTuner() );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
frame.setVisible( true );
}
} );
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner;
public enum TunerType
{
AIRSPY_R820T( "Airspy R820T" ),
ELONICS_E4000( "E4000" ),
ETTUS_WBX( "WBX" ),
ETTUS_VARIOUS( "Ettus Tuner" ),
FCI_FC2580( "FC2580" ),
FITIPOWER_FC0012( "FC0012" ),
FITIPOWER_FC0013( "FC0013" ),
FUNCUBE_DONGLE_PRO( "Funcube Dongle Pro" ),
FUNCUBE_DONGLE_PRO_PLUS( "Funcube Dongle Pro Plus" ),
HACKRF( "HackRF" ),
RAFAELMICRO_R820T( "R820T" ),
RAFAELMICRO_R828D( "R828D" ),
RTL2832_VARIOUS( "Generic" ),
UNKNOWN( "Unknown" );
private String mLabel;
private TunerType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
}

View File

@@ -0,0 +1,166 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
public class TunerViewPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( TunerViewPanel.class );
private TunerModel mTunerModel;
private JTable mTunerTable;
private TableRowSorter<TunerModel> mRowSorter;
private JideSplitPane mSplitPane;
private TunerEditor mTunerEditor;
public TunerViewPanel( TunerModel tunerModel )
{
mTunerModel = tunerModel;
mTunerEditor = new TunerEditor( mTunerModel.getTunerConfigurationModel() );
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0", "[fill,grow]", "[fill,grow]" ) );
mRowSorter = new TableRowSorter<>( mTunerModel );
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
sortKeys.add( new RowSorter.SortKey( TunerModel.TUNER_TYPE, SortOrder.ASCENDING ) );
sortKeys.add( new RowSorter.SortKey( TunerModel.TUNER_ID, SortOrder.ASCENDING ) );
mRowSorter.setSortKeys( sortKeys );
mTunerTable = new JTable( mTunerModel );
mTunerTable.setRowSorter( mRowSorter );
mTunerTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
mTunerTable.getSelectionModel().addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent event )
{
if( !event.getValueIsAdjusting() )
{
int row = mTunerTable.getSelectedRow();
int modelRow = mTunerTable.convertRowIndexToModel( row );
mTunerEditor.setItem( mTunerModel.getTuner( modelRow ) );
}
}
} );
mTunerTable.addMouseListener( new MouseAdapter()
{
@Override
public void mouseClicked( MouseEvent e )
{
int column = mTunerTable.columnAtPoint( e.getPoint() );
if( column == TunerModel.SPECTRAL_DISPLAY_MAIN )
{
int tableRow = mTunerTable.rowAtPoint( e.getPoint() );
int modelRow = mTunerTable.convertRowIndexToModel( tableRow );
Tuner tuner = mTunerModel.getTuner( modelRow );
if( tuner != null )
{
mTunerModel.broadcast( new TunerEvent( tuner,
Event.REQUEST_MAIN_SPECTRAL_DISPLAY ) );
}
}
else if( column == TunerModel.SPECTRAL_DISPLAY_NEW )
{
int tableRow = mTunerTable.rowAtPoint( e.getPoint() );
int modelRow = mTunerTable.convertRowIndexToModel( tableRow );
Tuner tuner = mTunerModel.getTuner( modelRow );
if( tuner != null )
{
mTunerModel.broadcast( new TunerEvent( tuner,
Event.REQUEST_NEW_SPECTRAL_DISPLAY ) );
}
}
}
} );
TableCellRenderer renderer = new LinkCellRenderer();
mTunerTable.getColumnModel().getColumn( 5 ).setCellRenderer( renderer );
mTunerTable.getColumnModel().getColumn( 6 ).setCellRenderer( renderer );
JScrollPane listScroller = new JScrollPane( mTunerTable );
listScroller.setPreferredSize( new Dimension( 400, 20 ) );
JScrollPane editorScroller = new JScrollPane( mTunerEditor );
editorScroller.setPreferredSize( new Dimension( 400, 80 ) );
mSplitPane = new JideSplitPane();
mSplitPane.setOrientation( JideSplitPane.VERTICAL_SPLIT );
mSplitPane.add( listScroller );
mSplitPane.add( editorScroller );
add( mSplitPane );
}
public class LinkCellRenderer extends DefaultTableCellRenderer
{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent( JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column )
{
JLabel label = (JLabel)super.getTableCellRendererComponent( table,
value, isSelected, hasFocus, row, column );
label.setForeground( Color.BLUE.brighter() );
if( column == TunerModel.SPECTRAL_DISPLAY_MAIN )
{
label.setToolTipText( "Show this tuner in the main spectral display" );
}
else if( column == TunerModel.SPECTRAL_DISPLAY_NEW )
{
label.setToolTipText( "Show this tuner in a new spectral display" );
}
return label;
}
}
}

View File

@@ -0,0 +1,157 @@
package io.github.dsheirer.source.tuner.airspy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class AirspyDeviceInformation
{
private final static Logger mLog =
LoggerFactory.getLogger( AirspyDeviceInformation.class );
private AirspyTunerController.BoardID mBoardID = AirspyTunerController.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 = AirspyTunerController.BoardID.fromValue( boardID );
}
public AirspyTunerController.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,155 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter_RB;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
public class AirspySampleAdapter implements ISampleAdapter
{
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
private HilbertTransform mHilbertTransform = new HilbertTransform();
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)
{
float[] realSamples;
if(mSamplePacking)
{
realSamples = convertPacked(samples);
}
else
{
realSamples = convertUnpacked(samples);
}
mDCFilter.filter(realSamples);
return mHilbertTransform.filter(realSamples);
}
/**
* 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 data1 - 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,35 @@
package io.github.dsheirer.source.tuner.airspy;
public class AirspySampleRate
{
private int mIndex;
private int mValue;
private String mLabel;
public AirspySampleRate( int index, int value, String label )
{
mIndex = index;
mValue = value;
mLabel = label;
}
public int getIndex()
{
return mIndex;
}
public int getRate()
{
return mValue;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
}

View File

@@ -0,0 +1,127 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.BoardID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.RejectedExecutionException;
public class AirspyTuner extends Tuner
{
private final static Logger mLog = LoggerFactory.getLogger( AirspyTuner.class );
public AirspyTuner( AirspyTunerController controller )
{
super( "Airspy " + controller.getDeviceInfo().getSerialNumber(), controller );
}
public AirspyTunerController getController()
{
return (AirspyTunerController)getTunerController();
}
@Override
public String getUniqueID()
{
try
{
return getController().getDeviceInfo().getSerialNumber();
}
catch( Exception e )
{
mLog.error( "error getting serial number", e );
}
return BoardID.AIRSPY.getLabel();
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.AIRSPY;
}
@Override
public TunerType getTunerType()
{
return TunerType.AIRSPY_R820T;
}
@Override
public double getSampleSize()
{
return 13.0;
}
@Override
public TunerChannelSource getChannel( 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
TunerChannelSource source = getController().getChannel( this, channel );
if( source != null )
{
broadcast( new TunerEvent( this, TunerEvent.Event.CHANNEL_COUNT ) );
}
return source;
}
@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 )
{
getController().releaseChannel( source );
}
}
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
getController().addListener( listener );
}
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
getController().removeListener( listener );
}
}

View File

@@ -0,0 +1,144 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.Gain;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import javax.xml.bind.annotation.XmlAttribute;
public class AirspyTunerConfiguration extends TunerConfiguration
{
private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate();
private Gain mGain = AirspyTunerController.LINEARITY_GAIN_DEFAULT;
private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT;
private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT;
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
private double mFrequencyCorrection = 0.0d;
private boolean mMixerAGC = false;
private boolean mLNAAGC = false;
/**
* Default constructor for JAXB
*/
public AirspyTunerConfiguration()
{
}
public AirspyTunerConfiguration( String uniqueID, String name )
{
super( uniqueID, 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 = "gain" )
public Gain getGain()
{
return mGain;
}
public void setGain( Gain gain )
{
mGain = gain;
}
@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;
}
@XmlAttribute( name = "frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,754 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015-2016 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 io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.Gain;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.GainMode;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.usb.UsbException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
import java.util.List;
public class AirspyTunerEditor extends TunerConfigurationEditor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( AirspyTunerEditor.class );
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JComboBox<AirspySampleRate> mSampleRateCombo;
private JSpinner mFrequencyCorrection;
private JComboBox<GainMode> mGainModeCombo;
private JSlider mMasterGain;
private JLabel mMasterGainValueLabel;
private JSlider mIFGain;
private JLabel mIFGainValueLabel;
private JSlider mLNAGain;
private JLabel mLNAGainValueLabel;
private JSlider mMixerGain;
private JLabel mMixerGainValueLabel;
private JCheckBox mLNAAGC;
private JCheckBox mMixerAGC;
private AirspyTunerController mController;
private boolean mLoading;
public AirspyTunerEditor( TunerConfigurationModel tunerConfigurationModel,
AirspyTuner tuner )
{
super( tunerConfigurationModel );
mController = tuner.getController();
init();
}
private AirspyTunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (AirspyTunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][grow]" ) );
add( new JLabel( "Airspy Tuner Configuration" ), "span,align center" );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( AirspyTunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
/**
* 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.setEnabled( false );
mSampleRateCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
AirspySampleRate rate = (AirspySampleRate)mSampleRateCombo.getSelectedItem();
try
{
mController.setSampleRate( rate );
save();
}
catch ( LibUsbException | UsbException | SourceException e1 )
{
JOptionPane.showMessageDialog( AirspyTunerEditor.this,
"Couldn't set sample rate to " + rate.getLabel() );
mLog.error( "Error setting airspy sample rate", e1 );
}
}
} );
add( mSampleRateCombo );
/**
* Frequency Correction
*/
SpinnerModel model =
new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
final double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
}
catch ( SourceException e1 )
{
mLog.error( "Error setting frequency correction value", e1 );
}
save();
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection );
add( new JSeparator(), "span,growx,push" );
/**
* Gain Mode
*/
add( new JLabel( "Gain Mode:" ) );
mGainModeCombo = new JComboBox<AirspyTunerController.GainMode>( GainMode.values() );
mGainModeCombo.setEnabled( false );
mGainModeCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
updateGainComponents( mode );
if( hasItem() )
{
AirspyTunerConfiguration airspy = getConfiguration();
Gain gain = airspy.getGain();
if( mode == GainMode.CUSTOM )
{
mIFGain.setValue( airspy.getIFGain() );
mMixerGain.setValue( airspy.getMixerGain() );
mMixerAGC.setSelected( airspy.isMixerAGC() );
mLNAGain.setValue( airspy.getLNAGain() );
mLNAAGC.setSelected( airspy.isLNAAGC() );
}
else
{
mMixerAGC.setSelected( false );
mLNAAGC.setSelected( false );
mMasterGain.setValue( gain.getValue() );
mIFGain.setValue( gain.getIF() );
mMixerGain.setValue( gain.getMixer() );
mLNAGain.setValue( gain.getLNA() );
}
}
save();
}
} );
add( mGainModeCombo, "wrap" );
/**
* Gain
*/
add( new JLabel( "Master:" ) );
mMasterGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.GAIN_MIN,
AirspyTunerController.GAIN_MAX,
AirspyTunerController.GAIN_MIN );
mMasterGain.setMajorTickSpacing( 1 );
mMasterGain.setPaintTicks( true );
mMasterGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
int value = mMasterGain.getValue();
Gain gain = Gain.getGain( mode, value );
try
{
mController.setGain( gain );
save();
mMasterGainValueLabel.setText( String.valueOf( gain.getValue() ) );
}
catch ( Exception e )
{
mLog.error( "Couldn't set airspy gain to:" + gain.name(), e );
JOptionPane.showMessageDialog( mMasterGain, "Couldn't set gain value to " +
gain.getValue() );
}
}
} );
add( mMasterGain, "span 2" );
mMasterGainValueLabel = new JLabel( "0" );
add( mMasterGainValueLabel );
add( new JLabel( "IF:" ) );
mIFGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.IF_GAIN_MIN,
AirspyTunerController.IF_GAIN_MAX,
AirspyTunerController.IF_GAIN_MIN );
mIFGain.setMajorTickSpacing( 1 );
mIFGain.setPaintTicks( true );
mIFGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mIFGain.getValue();
try
{
mController.setIFGain( gain );
save();
mIFGainValueLabel.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 );
}
}
} );
add( mIFGain, "span 2" );
mIFGainValueLabel = new JLabel( "0" );
add( mIFGainValueLabel );
/**
* Mixer Gain
*/
add( new JLabel( "Mixer:" ) );
mMixerGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.MIXER_GAIN_MIN,
AirspyTunerController.MIXER_GAIN_MAX,
AirspyTunerController.MIXER_GAIN_MIN );
mMixerGain.setMajorTickSpacing( 1 );
mMixerGain.setPaintTicks( true );
mMixerGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mMixerGain.getValue();
try
{
mController.setMixerGain( gain );
save();
mMixerGainValueLabel.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 );
}
}
} );
add( mMixerGain, "span 2" );
mMixerGainValueLabel = new JLabel( "0" );
add( mMixerGainValueLabel );
add( new JLabel( "LNA:" ) );
mLNAGain = new JSlider( JSlider.HORIZONTAL,
AirspyTunerController.LNA_GAIN_MIN,
AirspyTunerController.LNA_GAIN_MAX,
AirspyTunerController.LNA_GAIN_MIN );
mLNAGain.setMajorTickSpacing( 1 );
mLNAGain.setPaintTicks( true );
mLNAGain.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent event )
{
int gain = mLNAGain.getValue();
try
{
mController.setLNAGain( gain );
save();
mLNAGainValueLabel.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 );
}
}
} );
add( mLNAGain, "span 2" );
mLNAGainValueLabel = new JLabel( "0" );
add( mLNAGainValueLabel );
mMixerAGC = new JCheckBox( "Mixer AGC" );
mMixerAGC.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
mController.setMixerAGC( mMixerAGC.isSelected() );
mMixerGain.setEnabled( !mMixerAGC.isSelected() );
save();
}
catch ( Exception e1 )
{
mLog.error( "Error setting Mixer AGC Enabled" );
}
}
} );
add( mMixerAGC, "span 2,center" );
mLNAAGC = new JCheckBox( "LNA AGC" );
mLNAAGC.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
try
{
mController.setLNAAGC( mLNAAGC.isSelected() );
mLNAGain.setEnabled( !mLNAAGC.isSelected() );
save();
}
catch ( Exception e1 )
{
mLog.error( "Error setting LNA AGC Enabled" );
}
}
} );
add( mLNAAGC, "span 2,center" );
}
/**
* Updates the enabled state of each of the gain controls according to the
* specified gain mode. The master gain control is enabled for linearity
* and sensitivity and the individual gain controls are disabled, and
* vice-versa for custom mode.
*/
private void updateGainComponents( GainMode mode )
{
switch( mode )
{
case LINEARITY:
if( !mMasterGain.isEnabled() )
{
mMasterGain.setEnabled( true );
mMasterGainValueLabel.setEnabled( true );
}
if( mIFGain.isEnabled() )
{
mIFGain.setEnabled( false );
mIFGainValueLabel.setEnabled( false );
}
if( mLNAGain.isEnabled() )
{
mLNAAGC.setEnabled( false );
mLNAGainValueLabel.setEnabled( false );
mLNAGain.setEnabled( false );
}
if( mMixerGain.isEnabled() )
{
mMixerGainValueLabel.setEnabled( false );
mMixerAGC.setEnabled( false );
mMixerGain.setEnabled( false );
}
break;
case SENSITIVITY:
if( !mMasterGain.isEnabled() )
{
mMasterGain.setEnabled( true );
mMasterGainValueLabel.setEnabled( true );
}
if( mIFGain.isEnabled() )
{
mIFGain.setEnabled( false );
mIFGainValueLabel.setEnabled( false );
}
if( mLNAGain.isEnabled() )
{
mLNAAGC.setEnabled( false );
mLNAGainValueLabel.setEnabled( false );
mLNAGain.setEnabled( false );
}
if( mMixerGain.isEnabled() )
{
mMixerGainValueLabel.setEnabled( false );
mMixerAGC.setEnabled( false );
mMixerGain.setEnabled( false );
}
break;
case CUSTOM:
if( mMasterGain.isEnabled() )
{
mMasterGain.setEnabled( false );
mMasterGainValueLabel.setEnabled( false );
}
if( !mIFGain.isEnabled() )
{
mIFGain.setEnabled( true );
mIFGainValueLabel.setEnabled( true );
}
if( !mLNAGain.isEnabled() )
{
mLNAAGC.setEnabled( true );
mLNAGainValueLabel.setEnabled( true );
mLNAGain.setEnabled( true );
}
if( !mMixerGain.isEnabled() )
{
mMixerGainValueLabel.setEnabled( true );
mMixerAGC.setEnabled( true );
mMixerGain.setEnabled( true );
}
default:
break;
}
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
AirspyTunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
config.setSampleRate( ((AirspySampleRate)mSampleRateCombo.getSelectedItem()).getRate() );
double value = ((SpinnerNumberModel)mFrequencyCorrection.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
Gain gain = Gain.getGain( (GainMode)mGainModeCombo.getSelectedItem(), mMasterGain.getValue() );
config.setGain( gain );
config.setIFGain( mIFGain.getValue() );
config.setMixerGain( mMixerGain.getValue() );
config.setLNAGain( mLNAGain.getValue() );
config.setMixerAGC( mMixerAGC.isSelected() );
config.setLNAAGC( mLNAAGC.isSelected() );
getTunerConfigurationModel().broadcast( new TunerConfigurationEvent( config, TunerConfigurationEvent.Event.CHANGE ) );
}
}
private AirspySampleRate fromValue( int value )
{
List<AirspySampleRate> rates = mController.getSampleRates();
for( AirspySampleRate rate: rates )
{
if( rate.getRate() == value )
{
return rate;
}
}
if( rates.size() > 0 )
{
return rates.get( 0 );
}
return null;
}
/**
* Sets all controls to the argument enabled state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mSampleRateCombo.isEnabled() != enabled )
{
mSampleRateCombo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mGainModeCombo.isEnabled() != enabled )
{
mGainModeCombo.setEnabled( enabled );
}
if( mMasterGain.isEnabled() != enabled )
{
mMasterGain.setEnabled( enabled );
}
if( mIFGain.isEnabled() != enabled )
{
mIFGain.setEnabled( enabled );
}
if( mMixerGain.isEnabled() != enabled )
{
mMixerGain.setEnabled( enabled );
}
if( mMixerAGC.isEnabled() != enabled )
{
mMixerAGC.setEnabled( enabled );
}
if( mLNAGain.isEnabled() != enabled )
{
mLNAGain.setEnabled( enabled );
}
if( mLNAAGC.isEnabled() != enabled )
{
mLNAAGC.setEnabled( enabled );
}
}
/**
* Applies the tuner configuration to the editor. If the configuration is assigned to the
* currently selected tuner, then the settings are applied.
*/
@Override
public void setItem( TunerConfiguration config )
{
super.setItem( config );
mLoading = true;
if( hasItem() && getItem().isAssigned() )
{
AirspyTunerConfiguration airspy = getConfiguration();
setControlsEnabled( true );
mConfigurationName.setText( airspy.getName() );
AirspySampleRate rate = fromValue( airspy.getSampleRate() );
if( rate != null )
{
mSampleRateCombo.setSelectedItem( rate );
}
mFrequencyCorrection.setValue( airspy.getFrequencyCorrection() );
GainMode mode = Gain.getGainMode( airspy.getGain() );
//This will set the master, IF, Mixer, LNA and AGC controls
mGainModeCombo.setSelectedItem( mode );
if( mode == GainMode.CUSTOM )
{
mMasterGain.setValue( airspy.getGain().getValue() );
mIFGain.setValue( airspy.getIFGain() );
if( airspy.isMixerAGC() )
{
mMixerAGC.setSelected( airspy.isMixerAGC() );
mMixerGain.setEnabled( false );
}
else
{
mMixerGain.setValue( airspy.getMixerGain() );
}
if( airspy.isLNAAGC() )
{
mLNAAGC.setSelected( airspy.isLNAAGC() );
mLNAGain.setEnabled( false );
}
else
{
mLNAGain.setValue( airspy.getLNAGain() );
}
}
else
{
mMixerAGC.setSelected( false );
mLNAAGC.setSelected( false );
Gain gain = airspy.getGain();
mMasterGain.setValue( gain.getValue() );
mIFGain.setValue( gain.getIF() );
mMixerGain.setValue( gain.getMixer() );
mLNAGain.setValue( gain.getLNA() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( hasItem() ? getItem().getName() : "" );
}
mLoading = false;
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
AirspyDeviceInformation info = mController.getDeviceInfo();
sb.append( "<html><h3>Airspy Tuner</h3>" );
sb.append( "<b>Serial: </b>" );
sb.append( info.getSerialNumber() );
sb.append( "<br>" );
sb.append( "<b>Firmware: </b>" );
String[] firmware = info.getVersion().split( " " );
sb.append( firmware.length > 1 ? firmware[ 0 ] : info.getVersion() );
sb.append( "<br>" );
sb.append( "<b>Part: </b>" );
sb.append( info.getPartNumber() );
sb.append( "<br>" );
sb.append( "<b>Board ID: </b>" );
sb.append( info.getBoardID().getLabel() );
sb.append( "<br>" );
return sb.toString();
}
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.configuration;
import io.github.dsheirer.source.tuner.TunerType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
/**
* Abstract class to hold a named configuration for a specific type of tuner
*/
@XmlType( name = "tuner_configuration" )
public abstract class TunerConfiguration
{
protected String mName;
protected String mUniqueID;
protected boolean mAssigned;
protected long mFrequency;
/**
* Default constructor to support JAXB
*/
public TunerConfiguration()
{
}
/**
* Normal constructor
*/
public TunerConfiguration( String uniqueID, String name )
{
mUniqueID = uniqueID;
mName = name;
}
public String toString()
{
return mName;
}
@XmlAttribute( name = "name" )
public String getName()
{
return mName;
}
public void setName( String name )
{
mName = name;
}
@XmlAttribute( name = "unique_id" )
public String getUniqueID()
{
return mUniqueID;
}
public void setUniqueID( String id )
{
mUniqueID = id;;
}
@XmlAttribute( name = "assigned" )
public boolean isAssigned()
{
return mAssigned;
}
public void setAssigned( boolean assigned )
{
mAssigned = assigned;
}
@XmlAttribute( name = "frequency" )
public long getFrequency()
{
return mFrequency;
}
public void setFrequency( long frequency )
{
mFrequency = frequency;
}
@XmlAttribute( name = "tuner_type" )
public abstract TunerType getTunerType();
}

View File

@@ -0,0 +1,20 @@
package io.github.dsheirer.source.tuner.configuration;
import io.github.dsheirer.gui.editor.Editor;
public abstract class TunerConfigurationEditor extends Editor<TunerConfiguration>
{
private static final long serialVersionUID = 1L;
private TunerConfigurationModel mTunerConfigurationModel;
public TunerConfigurationEditor( TunerConfigurationModel model )
{
mTunerConfigurationModel = model;
}
public TunerConfigurationModel getTunerConfigurationModel()
{
return mTunerConfigurationModel;
}
}

View File

@@ -0,0 +1,30 @@
package io.github.dsheirer.source.tuner.configuration;
public class TunerConfigurationEvent
{
private TunerConfiguration mTunerConfiguration;
private Event mEvent;
public TunerConfigurationEvent( TunerConfiguration configuration, Event event )
{
mTunerConfiguration = configuration;
mEvent = event;
}
public TunerConfiguration getConfiguration()
{
return mTunerConfiguration;
}
public Event getEvent()
{
return mEvent;
}
public enum Event
{
ADD,
REMOVE,
CHANGE;
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.configuration;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.airspy.AirspyTuner;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerConfiguration;
import io.github.dsheirer.source.tuner.airspy.AirspyTunerEditor;
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerEditor;
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerEditor;
import io.github.dsheirer.source.tuner.hackrf.HackRFTuner;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerEditor;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerEditor;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerEditor;
public class TunerConfigurationFactory
{
/**
* Creates a tuner configuration for the specified tuner type, unique ID and name
*/
public static TunerConfiguration getTunerConfiguration(TunerType type, String uniqueID,
String name )
{
switch( type )
{
case AIRSPY_R820T:
return new AirspyTunerConfiguration( uniqueID, name );
case ELONICS_E4000:
return new E4KTunerConfiguration( uniqueID, name );
case FUNCUBE_DONGLE_PRO:
return new FCD1TunerConfiguration( uniqueID, name );
case FUNCUBE_DONGLE_PRO_PLUS:
return new FCD2TunerConfiguration( uniqueID, name );
case HACKRF:
return new HackRFTunerConfiguration( uniqueID, name );
case RAFAELMICRO_R820T:
return new R820TTunerConfiguration( uniqueID, name );
default:
throw new IllegalArgumentException( "Unrecognized tuner type ["
+ type.name() + "] - can't create named [" + name + "] tuner"
+ " configuration" );
}
}
/**
* Creates a tuner editor gui for the specified tuner
*/
public static Editor<TunerConfiguration> getEditor(Tuner tuner, TunerConfigurationModel model )
{
switch( tuner.getTunerType() )
{
case AIRSPY_R820T:
return new AirspyTunerEditor( model, (AirspyTuner)tuner );
case ELONICS_E4000:
return new E4KTunerEditor( model, (RTL2832Tuner)tuner );
case FUNCUBE_DONGLE_PRO:
return new FCD1TunerEditor( model, (FCDTuner)tuner );
case FUNCUBE_DONGLE_PRO_PLUS:
return new FCD2TunerEditor( model, (FCDTuner)tuner );
case HACKRF:
return new HackRFTunerEditor( model, (HackRFTuner)tuner );
case RAFAELMICRO_R820T:
return new R820TTunerEditor( model, (RTL2832Tuner)tuner );
case UNKNOWN:
default:
throw new IllegalArgumentException( "Unrecognized Tuner: " + tuner.getName() );
}
}
}

View File

@@ -0,0 +1,381 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.configuration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
public class TunerConfigurationModel extends AbstractTableModel
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( TunerConfigurationModel.class );
public static final int TUNER_TYPE = 0;
public static final int CONFIGURATION_NAME = 1;
public static final int ASSIGNED = 2;
private static final String[] COLUMN_HEADERS = { "Tuner Type", "Name", "Assigned" };
private List<Listener<TunerConfigurationEvent>> mConfigurationChangeListeners =
new ArrayList<>();
private List<TunerConfiguration> mTunerConfigurations = new ArrayList<>();
/**
* Table model for managing tuner configurations. Provides tuner configuration events to
* registered listeners
*/
public TunerConfigurationModel()
{
}
/**
* Adds the list of tuner configurations to the model
*/
public void addTunerConfigurations( List<TunerConfiguration> configs )
{
for( TunerConfiguration config: configs )
{
addTunerConfiguration( config );
}
}
/**
* Returns of list of tuner configurations from the model
*/
public List<TunerConfiguration> getTunerConfigurations()
{
return mTunerConfigurations;
}
/**
* Adds the tuner configuration to the model firing a table model row add
* event and broadcasting a tuner configuration event.
*/
public void addTunerConfiguration( TunerConfiguration config )
{
if( !mTunerConfigurations.contains( config ) &&
config.getUniqueID() != null &&
config.getName() != null )
{
mTunerConfigurations.add( config );
int index = mTunerConfigurations.indexOf( config );
fireTableRowsInserted( index, index );
broadcast( new TunerConfigurationEvent( config, TunerConfigurationEvent.Event.ADD ) );
}
}
/**
* Removes the tuner configuration from the model firing a table model row
* delete event and broadcasting a tuner configuration event.
*/
public void removeTunerConfiguration( TunerConfiguration config )
{
if( mTunerConfigurations.contains( config ) )
{
int index = mTunerConfigurations.indexOf( config );
mTunerConfigurations.remove( config );
fireTableRowsDeleted( index, index );
broadcast( new TunerConfigurationEvent( config, TunerConfigurationEvent.Event.REMOVE ) );
}
}
/**
* Removes all tuner configurations and assignments from the model and
* fires a table clear event and broadcasts a tuner configuration event for
* each row removed.
*/
public void clear()
{
for( TunerConfiguration config: mTunerConfigurations )
{
broadcast( new TunerConfigurationEvent( config, TunerConfigurationEvent.Event.REMOVE ) );
}
int size = mTunerConfigurations.size();
mTunerConfigurations.clear();
fireTableRowsDeleted( 0, size - 1 );
}
/**
* Registers a tuner configuration event listener
*/
public void addListener( Listener<TunerConfigurationEvent> listener )
{
mConfigurationChangeListeners.add( listener );
}
/**
* Removes the tuner configuration event listener
*/
public void removeListener( Listener<TunerConfigurationEvent> listener )
{
mConfigurationChangeListeners.remove( listener );
}
/**
* Broadcasts a tuner configuration event
*/
public void broadcast( TunerConfigurationEvent event )
{
for( Listener<TunerConfigurationEvent> listener: mConfigurationChangeListeners )
{
listener.receive( event );
}
}
/**
* Designates the configuration argument as the currently assigned configuration and unassigns
* any other configurations with the same tuner type and unique ID.
*/
public void assignTunerConfiguration( TunerConfiguration configToAssign )
{
if( configToAssign != null )
{
List<TunerConfiguration> configs =
getTunerConfigurations( configToAssign.getTunerType(), configToAssign.getUniqueID() );
for( TunerConfiguration config: configs )
{
if( config.isAssigned() )
{
config.setAssigned( false );
int index = mTunerConfigurations.indexOf( config );
fireTableCellUpdated( index, ASSIGNED );
broadcast( new TunerConfigurationEvent( config, TunerConfigurationEvent.Event.CHANGE ) );
}
}
configToAssign.setAssigned( true );
int index = mTunerConfigurations.indexOf( configToAssign );
fireTableCellUpdated( index, ASSIGNED );
broadcast( new TunerConfigurationEvent( configToAssign, TunerConfigurationEvent.Event.CHANGE ) );
}
}
/**
* Returns the tuner configuration that is assigned to the tuner with the
* tuner type and uniqueID, or selects the first existing configuration that
* matches the tuner type and unique ID and assigns it, or creates a new
* tuner configuration and assigns it to the tuner.
*
* This method will always return a tuner configuration that is assigned to
* the tuner type and unique ID.
*/
public TunerConfiguration getTunerConfiguration( TunerType type, String uniqueID )
{
List<TunerConfiguration> configs = getTunerConfigurations( type, uniqueID );
for( TunerConfiguration config: configs )
{
if( config.isAssigned() &&
config.getUniqueID() != null &&
config.getUniqueID().contentEquals( uniqueID ) )
{
return config;
}
}
//Reuse an existing configuration to assign
if( !configs.isEmpty() )
{
configs.get( 0 ).setAssigned( true );
return configs.get( 0 );
}
String name = getDistinctName( type, uniqueID );
//We didn't find the config so create a new one and assign it
TunerConfiguration config = TunerConfigurationFactory
.getTunerConfiguration( type, uniqueID, name );
config.setAssigned( true );
addTunerConfiguration( config );
return config;
}
/**
* Indicates if a configuration exists in this model matching the tuner type
* and unique ID and name.
*/
public boolean hasTunerConfiguration( TunerType type, String uniqueID, String name )
{
for( TunerConfiguration config: mTunerConfigurations )
{
if( config.getTunerType() == type &&
config.getUniqueID() != null &&
config.getUniqueID().contentEquals( uniqueID ) &&
config.getName() != null &&
config.getName().contentEquals( name ) )
{
return true;
}
}
return false;
}
/**
* Creates a configuration name for the tuner configuration that is distinct
* among the existing tuner configurations of the same type and uniqueID
*/
public String getDistinctName( TunerType type, String uniqueID )
{
int counter = 0;
String name = "New";
while( hasTunerConfiguration( type, uniqueID, name ) )
{
counter++;
name = "New (" + counter + ")";
}
return name;
}
/**
* Returns all tuner configurations for the specified tuner type
*/
public List<TunerConfiguration> getTunerConfigurations( TunerType type, String uniqueID )
{
List<TunerConfiguration> configs = new ArrayList<>();
for( TunerConfiguration config: mTunerConfigurations )
{
if( config.getTunerType() == type &&
config.getUniqueID() != null &&
config.getUniqueID().contentEquals( uniqueID ) )
{
configs.add( config );
}
}
return configs;
}
/**
* Column names for the table model
*/
@Override
public String getColumnName( int column )
{
return COLUMN_HEADERS[ column ];
}
/**
* Tuner configuration model row count
*/
@Override
public int getRowCount()
{
return mTunerConfigurations.size();
}
/**
* Tuner configuration model column count
*/
@Override
public int getColumnCount()
{
return COLUMN_HEADERS.length;
}
/**
* Returns the row index for the specified configuration or -1
*/
public int getRowIndex( TunerConfiguration configuration )
{
if( mTunerConfigurations.contains( configuration ) )
{
return mTunerConfigurations.indexOf( configuration );
}
return -1;
}
/**
* Returns the configuration at the specified index or null
*/
public TunerConfiguration getTunerConfiguration( int index )
{
if( index >= 0 && index < mTunerConfigurations.size() )
{
return mTunerConfigurations.get( index );
}
return null;
}
/**
* Returns the value at the indicated table cell
*/
@Override
public Object getValueAt( int row, int column )
{
if( row >= 0 && row < mTunerConfigurations.size() )
{
TunerConfiguration config = mTunerConfigurations.get( row );
switch( column )
{
case TUNER_TYPE:
return config.getTunerType().getLabel();
case CONFIGURATION_NAME:
return config.getName();
case ASSIGNED:
return config.isAssigned() ? "*" : null;
default:
break;
}
}
return null;
}
/**
* Stores the latest tuned frequency for the tuner in the currently assigned configuration
*/
public void tunerFrequencyChanged( Tuner tuner )
{
TunerConfiguration assigned = getTunerConfiguration( tuner.getTunerType(), tuner.getUniqueID() );
assigned.setFrequency( tuner.getTunerController().getFrequency() );
broadcast( new TunerConfigurationEvent( assigned, TunerConfigurationEvent.Event.CHANGE ) );
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.ettus;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.usb.USBTunerDevice;
import java.util.concurrent.RejectedExecutionException;
public class B100Tuner extends Tuner
{
public B100Tuner( USBTunerDevice device )
{
//TODO: this should extend a USBTuner class, so that we can push the
//complex sample listener and the frequency change listener up to that class
super( "Ettus B100 with WBX Tuner", null );
}
public void dispose()
{
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.ETTUS_USRP_B100;
}
@Override
public TunerType getTunerType()
{
return TunerType.ETTUS_WBX;
}
@Override
public double getSampleSize()
{
return 16.0;
}
@Override
public TunerChannelSource getChannel( TunerChannel channel )
throws RejectedExecutionException, SourceException
{
//TODO: pass this call to the tuner controller
return null;
}
@Override
public void releaseChannel( TunerChannelSource source )
{
//TODO: pass this call to the tuner controller
}
@Override
public String getUniqueID()
{
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.ettus;
import io.github.dsheirer.settings.SettingsManager;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class B100TunerEditorPanel extends JPanel
{
private static final long serialVersionUID = 1L;
public B100TunerEditorPanel( B100Tuner tuner, SettingsManager settingsManager )
{
init();
}
private void init()
{
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill]" ) );
setBorder( BorderFactory.createTitledBorder( "Ettus B100 Tuner" ) );
add( new JLabel( "Note: SDRTrunk can only recognize the B100 at this time. It cannot use this tuner") );
}
}

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.fcd;
public enum FCDCommand
{
/**
* Note: the javaxUSB and usb4Java libraries are slightly different than
* the signal9 HID libraries used in C, so you will see variations in the
* byte array length and indexes, when comparing this setup to the qthid
* source code.
*/
//Bootloader Mode Commands
BL_QUERY( (byte) 0x01, 64 ), //01 Decimal
BL_RESET( (byte) 0x08, 2 ), //08
BL_ERASE( (byte) 0x19, 2 ), //24
BL_SET_BYTE_ADDR( (byte) 0x1A, 6 ), //25
BL_GET_BYTE_ADDR_RANGE( (byte) 0x1B, 6 ), //26
BL_WRITE_FLASH_BLOCK( (byte) 0x1C, 52 ), //27
BL_READ_FLASH_BLOCK( (byte) 0x1D, 48 ), //28
//Application Mode Commands
APP_SET_FREQUENCY_KHZ( (byte)0x64, 5 ), //100
APP_SET_FREQUENCY_HZ( (byte)0x65, 6 ), //101
APP_GET_FREQUENCY_HZ( (byte)0x66, 6 ), //102
APP_GET_IF_RSSI( (byte) 0x68, 3 ), //104
APP_GET_PLL_LOCKED( (byte) 0x69, 3 ), //105
APP_SET_DC_CORRECTION( (byte) 0x6A, 5 ), //106
APP_GET_DC_CORRECTION( (byte) 0x6B, 6 ), //107
APP_SET_IQ_CORRECTION( (byte) 0x6C, 5 ), //108
APP_GET_IQ_CORRECTION( (byte) 0x6D, 6 ), //109
APP_SET_LNA_GAIN( (byte)0x6E, 2 ), //110
APP_SET_LNA_ENHANCE( (byte)0x6F, 2 ), //111
APP_SET_BAND( (byte)0x70, 2 ), //112
APP_SET_RF_FILTER( (byte)0x71, 2 ), //113
APP_SET_MIXER_GAIN( (byte)0x72, 2 ), //114
APP_SET_BIAS_CURRENT( (byte)0x73, 2 ), //115
APP_SET_MIXER_FILTER( (byte)0x74, 2 ), //116
APP_SET_IF_GAIN1( (byte)0x75, 2 ), //117
APP_SET_IF_GAIN_MODE( (byte)0x76, 2 ), //118
APP_SET_IF_RC_FILTER( (byte)0x77, 2 ), //119
APP_SET_IF_GAIN2( (byte)0x78, 2 ), //120
APP_SET_IF_GAIN3( (byte)0x79, 2 ), //121
APP_SET_IF_FILTER( (byte)0x7A, 2 ), //122
APP_SET_IF_GAIN4( (byte)0x7B, 2 ), //123
APP_SET_IF_GAIN5( (byte)0x7C, 2 ), //124
APP_SET_IF_GAIN6( (byte)0x7D, 2 ), //125
APP_SET_BIAS_TEE( (byte)0x7E, 2 ), //126
APP_GET_LNA_GAIN( (byte)0x96, 3 ), //150
APP_GET_LNA_ENHANCE( (byte)0x97, 3 ), //151
APP_GET_BAND( (byte)0x98, 3 ), //152
APP_GET_RF_FILTER( (byte)0x99, 3 ), //153
APP_GET_MIXER_GAIN( (byte)0x9A, 3 ), //154
APP_GET_BIAS_CURRENT( (byte)0x9B, 3 ), //155
APP_GET_MIXER_FILTER( (byte)0x9C, 3 ), //156
APP_GET_IF_GAIN1( (byte)0x9D, 3 ), //157
APP_GET_IF_GAIN_MODE( (byte)0x9E, 3 ), //158
APP_GET_IF_RC_FILTER( (byte)0x9F, 3 ), //159
APP_GET_IF_GAIN2( (byte)0xA0, 3 ), //160
APP_GET_IF_GAIN3( (byte)0xA1, 3 ), //161
APP_GET_IF_FILTER( (byte)0xA2, 3 ), //162
APP_GET_IF_GAIN4( (byte)0xA3, 3 ), //163
APP_GET_IF_GAIN5( (byte)0xA4, 3 ), //164
APP_GET_IF_GAIN6( (byte)0xA5, 3 ), //165
APP_GET_BIAS_TEE( (byte)0xA6, 3 ), //166
APP_SEND_I2C_BYTE( (byte)0xC8, 2 ), //200
APP_RECV_I2C_BYTE( (byte)0xC9, 3 ), //201
APP_RESET( (byte)0xFF, 1 ); //255
/**
* Single-byte value to use for the command
*/
private byte mCommandByte;
/**
* Required array length
*/
private int mArrayLength;
private FCDCommand( byte value, int responseLength )
{
mCommandByte = value;
mArrayLength = responseLength;
}
/**
* Returns a byte array of the specified length with the command byte value
* set in index position zero. Fill in additional command arguments prior
* to sending to the device
*/
public byte[] getRequestTemplate()
{
byte[] retVal = new byte[ 64 ];
retVal[ 0 ] = mCommandByte;
retVal[ 1 ] = (byte)0x0;
return retVal;
}
/**
* Returns an empty byte array of the correct length to receive the response
* from the associated command
*/
public byte[] getResponseTemplate()
{
return new byte[ 64 ];
}
/**
* Byte value of the command
*/
public byte getCommand()
{
return mCommandByte;
}
/**
* Total array length required to send the command or receive the response.
*
* Index 0: command is echoed back
* Index 1: 0-fail, 1-success
*/
public int getArrayLength()
{
return mArrayLength;
}
/**
* Checks the response array to verify the pass/fail value that is placed
* in array index position 1, in responses from the device
*/
public static boolean checkResponse( FCDCommand command, byte[] response )
{
boolean valid = false;
if( command != null && response != null )
{
valid = response[ 0 ] == command.getCommand() &&
response[ 1 ] == 1;
}
return valid;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.fcd;
public enum FCDModel
{
FUNCUBE_DONGLE_PRO( "FunCube Dongle Pro" ),
FUNCUBE_DONGLE_PRO_PLUS( "FunCube Dongle Pro Plus"),
FUNCUBE_UNKNOWN( "Unknown" );
private String mLabel;
private FCDModel( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static FCDModel getFCD( String board )
{
FCDModel retVal = FUNCUBE_UNKNOWN;
if( board.equalsIgnoreCase( "Brd 1.1" ) )
{
retVal = FUNCUBE_DONGLE_PRO;
}
else if( board.equalsIgnoreCase( "Brd 2.0" ) )
{
retVal = FUNCUBE_DONGLE_PRO_PLUS;
}
return retVal;
}
}

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.fcd;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ShortAdapter;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.MixerTuner;
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.TunerType;
import java.util.concurrent.RejectedExecutionException;
public class FCDTuner extends MixerTuner
{
public FCDTuner( MixerTunerDataLine mixerTDL,
FCDTunerController controller )
{
super( controller.getConfiguration().toString(),
controller,
mixerTDL,
new ShortAdapter() );
}
public void dispose()
{
//TODO: release the mixer tuner data line as well
getController().dispose();
}
public FCDTunerController getController()
{
return (FCDTunerController)getTunerController();
}
@Override
public TunerClass getTunerClass()
{
return getController().getTunerClass();
}
@Override
public TunerType getTunerType()
{
return getController().getTunerType();
}
@Override
public String getUniqueID()
{
return getController().getUSBAddress();
}
@Override
public double getSampleSize()
{
return 16.0;
}
@Override
public TunerChannelSource getChannel( TunerChannel tunerChannel )
throws RejectedExecutionException, SourceException
{
TunerChannelSource source = getController().getChannel( this, tunerChannel );
if( source != null )
{
broadcast( new TunerEvent( this, TunerEvent.Event.CHANNEL_COUNT ) );
}
return source;
}
/**
* Releases the tuned channel so that the tuner controller can tune to
* other frequencies as needed.
*/
@Override
public void releaseChannel( TunerChannelSource source )
{
if( source != null )
{
/* Unregister for receiving samples */
removeListener( (Listener<ComplexBuffer>)source );
/* Tell the controller to release the channel and cleanup */
/* This will release the channel as a frequency change listener */
getController().releaseChannel( source );
}
}
}

View File

@@ -0,0 +1,618 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner.fcd;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
public abstract class FCDTunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
public static final double USABLE_BANDWIDTH_PERCENT = 1.0;
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
public final static byte FCD_INTERFACE = (byte)0x2;
public final static byte FCD_ENDPOINT_IN = (byte)0x82;
public final static byte FCD_ENDPOINT_OUT = (byte)0x2;
private Device mDevice;
private DeviceDescriptor mDeviceDescriptor;
private DeviceHandle mDeviceHandle;
private FCDConfiguration mConfiguration = new FCDConfiguration();
/**
* Generic FCD tuner controller - contains functionality common across both
* funcube dongle tuners
*
* @param device
* @param descriptor
* @param minTunableFrequency
* @param maxTunableFrequency
*/
public FCDTunerController(Device device,
DeviceDescriptor descriptor,
int sampleRate,
int minTunableFrequency,
int maxTunableFrequency)
{
super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
mDevice = device;
mDeviceDescriptor = descriptor;
try
{
mFrequencyController.setSampleRate(sampleRate);
}
catch(SourceException se)
{
mLog.error("Error setting sample rate to [" + sampleRate + "]", se);
}
}
/**
* Initializes the controller by opening the USB device and claiming the
* HID interface.
*
* Invoke this method after constructing this class to setup the
* controller.
*
* @throws SourceException if cannot open and claim the USB device
*/
public void init() throws SourceException
{
mDeviceHandle = new DeviceHandle();
int result = LibUsb.open(mDevice, mDeviceHandle);
if(result != LibUsb.SUCCESS)
{
mDeviceHandle = null;
throw new SourceException("libusb couldn't open funcube usb device [" + LibUsb.errorName(result) + "]");
}
claimInterface();
}
/**
* Disposes of resources. Closes the USB device and interface.
*/
public void dispose()
{
if(mDeviceHandle != null)
{
try
{
LibUsb.close(mDeviceHandle);
}
catch(Exception e)
{
mLog.error("error while closing device handle", e);
}
mDeviceHandle = null;
}
mDeviceDescriptor = null;
mDevice = null;
}
/**
* Sample rate of the tuner
*/
public abstract int getCurrentSampleRate() throws SourceException;
/**
* Tuner class
*/
public abstract TunerClass getTunerClass();
/**
* Tuner type
*/
public abstract TunerType getTunerType();
/**
* Applies the settings in the tuner configuration
*/
public abstract void apply(TunerConfiguration config) throws SourceException;
/**
* USB address (bus/port)
*/
public String getUSBAddress()
{
if(mDevice != null)
{
StringBuilder sb = new StringBuilder();
sb.append("Bus:");
int bus = LibUsb.getBusNumber(mDevice);
sb.append(bus);
sb.append(" Port:");
int port = LibUsb.getPortNumber(mDevice);
sb.append(port);
return sb.toString();
}
return "UNKNOWN";
}
/**
* USB Vendor and Product ID
*/
public String getUSBID()
{
if(mDeviceDescriptor != null)
{
StringBuilder sb = new StringBuilder();
sb.append(String.format("%04X", (int)(mDeviceDescriptor.idVendor() & 0xFFFF)));
sb.append(":");
sb.append(String.format("%04X", (int)(mDeviceDescriptor.idProduct() & 0xFFFF)));
return sb.toString();
}
return "UNKNOWN";
}
/**
* USB Port Speed. Should be 2.0 for both types of funcube dongles
*/
public String getUSBSpeed()
{
if(mDevice != null)
{
int speed = LibUsb.getDeviceSpeed(mDevice);
switch(speed)
{
case 0:
return "1.1 LOW";
case 1:
return "1.1 FULL";
case 2:
return "2.0 HIGH";
case 3:
return "3.0 SUPER";
default:
}
}
return "UNKNOWN";
}
/**
* Set fcd interface mode
*/
public void setFCDMode(Mode mode) throws UsbException, UsbClaimException
{
ByteBuffer response = null;
switch(mode)
{
case APPLICATION:
response = send(FCDCommand.BL_QUERY);
break;
case BOOTLOADER:
response = send(FCDCommand.APP_RESET);
break;
default:
break;
}
if(response != null)
{
mConfiguration.set(response);
}
else
{
mConfiguration.setModeUnknown();
}
}
/**
* Sets the actual (uncorrected) device frequency
*/
public void setTunedFrequency(long frequency) throws SourceException
{
try
{
send(FCDCommand.APP_SET_FREQUENCY_HZ, frequency);
}
catch(Exception e)
{
throw new SourceException("Couldn't set FCD Local " +
"Oscillator Frequency [" + frequency + "]", e);
}
}
/**
* Gets the actual (uncorrected) device frequency
*/
public long getTunedFrequency() throws SourceException
{
try
{
ByteBuffer buffer = send(FCDCommand.APP_GET_FREQUENCY_HZ);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return (int)(buffer.getInt(2) & 0xFFFFFFFF);
}
catch(Exception e)
{
throw new SourceException("FCDTunerController - "
+ "couldn't get LO frequency", e);
}
}
/**
* Returns the FCD device configuration
*/
public FCDConfiguration getConfiguration()
{
return mConfiguration;
}
/**
* Claims the USB interface. Attempts to detach the active kernel driver if
* one is currently attached.
*/
private void claimInterface() throws SourceException
{
if(mDeviceHandle != null)
{
int result = LibUsb.kernelDriverActive(mDeviceHandle, FCD_INTERFACE);
if(result == 1)
{
result = LibUsb.detachKernelDriver(mDeviceHandle, FCD_INTERFACE);
if(result != LibUsb.SUCCESS)
{
mLog.error("failed attempt to detach kernel driver [" + LibUsb.errorName(result) + "]");
}
}
result = LibUsb.claimInterface(mDeviceHandle, FCD_INTERFACE);
if(result != LibUsb.SUCCESS)
{
throw new SourceException("couldn't claim usb interface [" + LibUsb.errorName(result) + "]");
}
}
else
{
throw new SourceException("couldn't claim usb hid interface - no device handle");
}
}
/**
* Performs an interrupt write to the OUT endpoint.
*
* @param buffer - direct allocated buffer. Must be 64 bytes in length.
* @throws LibUsbException on error
*/
private void write(ByteBuffer buffer) throws LibUsbException
{
if(mDeviceHandle != null)
{
IntBuffer transferred = IntBuffer.allocate(1);
int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_OUT, buffer, transferred, 500l);
if(result != LibUsb.SUCCESS)
{
throw new LibUsbException("error writing byte buffer", result);
}
}
else
{
throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
}
}
/**
* Performs an interrupt write to the OUT endpoint for the FCD command.
*
* @param command - no-argument command to write
* @throws LibUsbException - on error
*/
private void write(FCDCommand command) throws LibUsbException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
buffer.put(0, command.getCommand());
buffer.put(1, (byte)0x00);
write(buffer);
}
/**
* Convenience logger for debugging read/write operations
*/
@SuppressWarnings("unused")
private void log(String label, ByteBuffer buffer)
{
StringBuilder sb = new StringBuilder();
sb.append(label);
sb.append(" ");
sb.append(buffer.get(0));
sb.append(" | ");
for(int x = 0; x < 64; x++)
{
sb.append(String.format("%02X", (int)(buffer.get(x) & 0xFF)));
sb.append(" ");
}
mLog.debug(sb.toString());
}
/**
* Performs an interrupt write to the OUT endpoint for the FCD command.
*
* @param command - command to write
* @param argument - value to write with the command
* @throws LibUsbException - on error
*/
private void write(FCDCommand command, long argument) throws LibUsbException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
/* The FCD expects little-endian formatted values */
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put(0, command.getCommand());
buffer.putLong(1, argument);
write(buffer);
}
/**
* Performs an interrupt read against the endpoint
*
* @return buffer read from FCD
* @throws LibUsbException on error
*/
private ByteBuffer read() throws LibUsbException
{
if(mDeviceHandle != null)
{
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
IntBuffer transferred = IntBuffer.allocate(1);
int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_IN, buffer, transferred, 500l);
if(result != LibUsb.SUCCESS)
{
throw new LibUsbException("error reading byte buffer", result);
}
else if(transferred.get(0) != buffer.capacity())
{
throw new LibUsbException("received bytes [" + transferred.get(0) +
"] didn't match expected length [" + buffer.capacity() + "]", result);
}
return buffer;
}
throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
}
/**
* Sends the FCD command and argument. Performs a read to complete the
* command.
*
* @param command - command to send
* @param argument - command argument to send
* @throws LibUsbException - on error
*/
protected void send(FCDCommand command, long argument) throws LibUsbException
{
write(command, argument);
read();
}
/**
* Sends the no-argument FCD command. Performs a read to complete the
* command.
*
* @param command - command to send
* @throws LibUsbException - on error
*/
protected ByteBuffer send(FCDCommand command) throws LibUsbException
{
write(command);
return read();
}
/**
* FCD configuration string parsing class
*/
public class FCDConfiguration
{
private String mConfig;
private Mode mMode;
public FCDConfiguration()
{
mConfig = null;
mMode = Mode.UNKNOWN;
}
private void setModeUnknown()
{
mConfig = null;
mMode = Mode.UNKNOWN;
}
/**
* Extracts the configuration string from the buffer
*/
public void set(ByteBuffer buffer)
{
if(buffer.capacity() == 64)
{
byte[] data = new byte[64];
for(int x = 0; x < 64; x++)
{
data[x] = buffer.get(x);
}
mConfig = new String(data);
mMode = Mode.getMode(mConfig);
}
else
{
mConfig = null;
mMode = Mode.ERROR;
}
}
public Mode getMode()
{
return mMode;
}
public FCDModel getModel()
{
FCDModel retVal = FCDModel.FUNCUBE_UNKNOWN;
switch(mMode)
{
case APPLICATION:
retVal = FCDModel.getFCD(mConfig.substring(15, 22));
break;
case BOOTLOADER:
case UNKNOWN:
case ERROR:
break;
}
return retVal;
}
public FCD1TunerController.Block getBandBlocking()
{
FCD1TunerController.Block retVal = FCD1TunerController.Block.UNKNOWN;
switch(mMode)
{
case APPLICATION:
retVal = FCD1TunerController.Block.getBlock(mConfig.substring(23, 33).trim());
break;
case BOOTLOADER:
case UNKNOWN:
case ERROR:
break;
}
return retVal;
}
public String getFirmware()
{
String retVal = null;
switch(mMode)
{
case APPLICATION:
retVal = mConfig.substring(9, 14);
break;
case BOOTLOADER:
case UNKNOWN:
case ERROR:
break;
}
return retVal;
}
public String toString()
{
return getModel().getLabel();
}
}
public enum Mode
{
APPLICATION,
BOOTLOADER,
ERROR,
UNKNOWN;
public static Mode getMode(String config)
{
Mode retVal = UNKNOWN;
if(config == null)
{
retVal = ERROR;
}
else
{
if(config.length() >= 8)
{
String mode = config.substring(2, 8).trim();
if(mode.equalsIgnoreCase("FCDAPP"))
{
retVal = APPLICATION;
}
else if(mode.equalsIgnoreCase("FCDBL"))
{
retVal = BOOTLOADER;
}
}
}
return retVal;
}
}
}

View File

@@ -0,0 +1,145 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.fcd.proV1;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAEnhance;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAGain;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.MixerGain;
import javax.xml.bind.annotation.XmlAttribute;
public class FCD1TunerConfiguration extends TunerConfiguration
{
private double mFrequencyCorrection = 22.0d;
private double mInphaseDCCorrection = 0.0d;
private double mQuadratureDCCorrection = 0.0d;
private double mPhaseCorrection = 0.0d;
private double mGainCorrection = 0.0d;
private LNAGain mLNAGain = LNAGain.LNA_GAIN_PLUS_20_0;
private LNAEnhance mLNAEnhance = LNAEnhance.LNA_ENHANCE_OFF;
private MixerGain mMixerGain = MixerGain.MIXER_GAIN_PLUS_12_0;
/**
* Default constructor for JAXB
*/
public FCD1TunerConfiguration()
{
}
public FCD1TunerConfiguration( String uniqueID, String name )
{
super( uniqueID, name );
}
@Override
public TunerType getTunerType()
{
return TunerType.FUNCUBE_DONGLE_PRO;
}
@XmlAttribute( name = "lna_gain" )
public LNAGain getLNAGain()
{
return mLNAGain;
}
public void setLNAGain( LNAGain gain )
{
mLNAGain = gain;
}
public LNAEnhance getLNAEnhance()
{
return mLNAEnhance;
}
@XmlAttribute( name = "lna_enhance" )
public void setLNAEnhance( LNAEnhance enhance )
{
mLNAEnhance = enhance;
}
@XmlAttribute( name = "mixer_gain" )
public MixerGain getMixerGain()
{
return mMixerGain;
}
public void setMixerGain( MixerGain gain )
{
mMixerGain = gain;
}
@XmlAttribute( name = "frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
@XmlAttribute( name = "inphase_dc_correction" )
public double getInphaseDCCorrection()
{
return mInphaseDCCorrection;
}
public void setInphaseDCCorrection( double value )
{
mInphaseDCCorrection = value;
}
@XmlAttribute( name = "quadrature_dc_correction" )
public double getQuadratureDCCorrection()
{
return mQuadratureDCCorrection;
}
public void setQuadratureDCCorrection( double value )
{
mQuadratureDCCorrection = value;
}
@XmlAttribute( name = "phase_correction" )
public double getPhaseCorrection()
{
return mPhaseCorrection;
}
public void setPhaseCorrection( double value )
{
mPhaseCorrection = value;
}
@XmlAttribute( name = "gain_correction" )
public double getGainCorrection()
{
return mGainCorrection;
}
public void setGainCorrection( double value )
{
mGainCorrection = value;
}
}

View File

@@ -0,0 +1,717 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.fcd.proV1;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.MixerTunerType;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.FCDCommand;
import io.github.dsheirer.source.tuner.fcd.FCDTunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FCD1TunerController extends FCDTunerController
{
private final static Logger mLog =
LoggerFactory.getLogger( FCD1TunerController.class );
public static final int sMINIMUM_TUNABLE_FREQUENCY = 64000000;
public static final int sMAXIMUM_TUNABLE_FREQUENCY = 1700000000;
public static final int sSAMPLE_RATE = 96000;
private double mDCCorrectionInPhase = 0.0;
private double mDCCorrectionQuadrature = 0.0;
private double mPhaseCorrection = 0.0;
private double mGainCorrection = 0.0;
private LNAGain mLNAGain;
private LNAEnhance mLNAEnhance;
private MixerGain mMixerGain;
private FCD1TunerConfiguration mTunerConfiguration;
private FCD1TunerEditor mEditor;
public FCD1TunerController( Device device, DeviceDescriptor descriptor )
{
super( device,
descriptor,
(int)MixerTunerType.FUNCUBE_DONGLE_PRO.getAudioFormat().getSampleRate(),
sMINIMUM_TUNABLE_FREQUENCY,
sMAXIMUM_TUNABLE_FREQUENCY );
}
public void init() throws SourceException
{
super.init();
mFrequencyController.setSampleRate( sSAMPLE_RATE );
try
{
setFCDMode( Mode.APPLICATION );
getPhaseAndGainCorrection();
getDCIQCorrection();
getLNAGainSetting();
getLNAEnhanceSetting();
getMixerGainSetting();
send( FCDCommand.APP_SET_MIXER_GAIN, 1l );
}
catch( Exception e )
{
e.printStackTrace();
throw new SourceException( "FCDTunerController error " +
"during construction", e );
}
}
public int getCurrentSampleRate()
{
return sSAMPLE_RATE;
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.FUNCUBE_DONGLE_PRO;
}
@Override
public TunerType getTunerType()
{
return TunerType.FUNCUBE_DONGLE_PRO;
}
/**
* Get the tuner configuration
*
* @return current config or null if one hasn't been applied yet
*/
public FCD1TunerConfiguration getTunerConfiguration()
{
return mTunerConfiguration;
}
@Override
public void apply( TunerConfiguration tunerConfig )
throws SourceException
{
FCD1TunerConfiguration config = (FCD1TunerConfiguration)tunerConfig;
try
{
setFrequencyCorrection( config.getFrequencyCorrection() );
setLNAGain( config.getLNAGain() );
setLNAEnhance( config.getLNAEnhance() );
setMixerGain( config.getMixerGain() );
setDCCorrectionInPhase( config.getInphaseDCCorrection() );
setDCCorrectionQuadrature( config.getQuadratureDCCorrection() );
setGainCorrection( config.getGainCorrection() );
setPhaseCorrection( config.getPhaseCorrection() );
//If we get to here without exception, store this config
mTunerConfiguration = config;
}
catch( Exception e)
{
//Re-cast any usb exceptions as source exceptions
throw new SourceException( "FCD 1 Controller encountered an "
+ "exception while applying tuner config - " +
e.getLocalizedMessage() );
}
try
{
setFrequency( config.getFrequency() );
}
catch( SourceException se )
{
//Do nothing, we couldn't set the frequency
}
}
/**
* Gets the current LNA Gain value from the controller and stores it
*/
private void getLNAGainSetting() throws UsbClaimException, UsbException
{
ByteBuffer buffer = send( FCDCommand.APP_GET_LNA_GAIN );
buffer.order( ByteOrder.LITTLE_ENDIAN );
int gain = buffer.getInt( 2 );
mLNAGain = LNAGain.valueOf( gain );
}
/**
* @return - current lna gain setting
*/
public LNAGain getLNAGain()
{
return mLNAGain;
}
/**
* Sets lna gain for the controller
* @param gain
* @throws UsbException
* @throws UsbClaimException
*/
public void setLNAGain( LNAGain gain )
throws UsbException, UsbClaimException
{
send( FCDCommand.APP_SET_LNA_GAIN, gain.getSetting() );
}
/**
* @return - current phase correction setting
*/
public double getPhaseCorrection()
{
return mPhaseCorrection;
}
/**
* Sets the phase correction
* @param value - new phase correction
* @throws IllegalArgumentException for value outside limits (0.0 <> 1.0)
* @throws UsbClaimException - if cannot claim the FCD HID controller
* @throws UsbException - if there was a usb error
*/
public void setPhaseCorrection( double value )
throws UsbClaimException, UsbException
{
if( value < -1.0 || value > 1.0 )
{
throw new IllegalArgumentException( "Phase correction value [" +
value + "] outside limits ( -1.0 <> 1.0 )" );
}
mPhaseCorrection = value;
setPhaseAndGainCorrection();
}
/**
* @return current gain correction setting
*/
public double getGainCorrection()
{
return mGainCorrection;
}
/**
*
* @param value - gain correction value
* @throws IllegalArgumentException for value outside limits (0.0 <> 1.0)
* @throws UsbClaimException - if cannot claim the FCD HID controller
* @throws UsbException - if there was a usb error
*/
public void setGainCorrection( double value )
throws UsbClaimException, UsbException
{
if( value < -1.0 || value > 1.0 )
{
throw new IllegalArgumentException( "Gain correction value [" +
value + "] outside limits ( -1.0 <> 1.0 )" );
}
mGainCorrection = value;
setPhaseAndGainCorrection();
}
/**
* Sends the phase and gain correction values to FCD.
*
* The interface expects an argument with the SIGNED phase value in the first
* 16 bits and the UNSIGNED gain value in the second 16 bits, both in
* big-endian format. Since we're sending the argument in java little endian
* format, we place the phase in the high-order bits and the gain in the
* low-order bits, so that the send(command) function will reorder the bytes
* into big endian format, and place the arguments correctly.
*
* Gain correction values range -1.0 to 0.0 to 1.0 and applied to the dongle
* as values 0 to 65534
*
* Phase correction values range -1.0 to 0.0 to 1.0 and applied to the
* dongle as signed values -32768 to 32767
*/
private void setPhaseAndGainCorrection() throws UsbClaimException,
UsbException
{
//UNSIGNED short gain value - masked into a long to avoid sign-extension
long gain = (long)( ( 1.0 + mGainCorrection ) * Short.MAX_VALUE );
//Left shift gain to place value in upper 16 bits
long longGain = Long.rotateLeft( ( 0xFFFF & gain ), 16 );
//SIGNED short phase value
short phase = (short)( mPhaseCorrection * Short.MAX_VALUE );
//Merge the results
long correction = longGain | phase;
send( FCDCommand.APP_SET_IQ_CORRECTION, correction );
}
/**
* Retrieves the stored phase and gain correction values from the FCD.
*
* Note: testing shows that when the dongle is unplugged, any stored phase
* and gain correction values are reset. Reading the dongle after a fresh
* plugin shows the values reset.
*
* @throws UsbClaimException
* @throws UsbException
*/
private void getPhaseAndGainCorrection() throws UsbClaimException,
UsbException
{
ByteBuffer buffer = send( FCDCommand.APP_GET_IQ_CORRECTION );
buffer.order( ByteOrder.LITTLE_ENDIAN );
int correction = buffer.getInt( 2 );
/**
* Gain: mask the upper 16 phase bits and right shift to get the
* unsigned short value and then divide by 32768 to get the double
* value. We place the unsigned short value in an int to avoid sign
* issues.
*/
int gain = (int)( Long.rotateRight( correction & 0xFFFF0000, 16 ) );
mGainCorrection = ( (double)gain / (double)Short.MAX_VALUE ) - 1.0;
/**
* Phase: mask the lower 16 bits and divide by 32768 to get the double
* value
*/
short phase = (short)( correction & 0x0000FFFF );
mPhaseCorrection = ( (double)phase / (double)Short.MAX_VALUE );
}
/**
* @return current DC correction for the I (inphase) component
*/
public double getDCCorrectionInPhase()
{
return mDCCorrectionInPhase;
}
/**
* Sets DC correction for the I (inphase) component and sends the new value
* to the FCD controller
* @param value - new DC correction value for the inphase component
* @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0)
* @throws UsbClaimException - if cannot claim the FCD HID controller
* @throws UsbException - if there was a usb error
*/
public void setDCCorrectionInPhase( double value )
throws UsbClaimException, UsbException
{
if( value < -1.0 || value > 1.0 )
{
throw new IllegalArgumentException( "DC inphase correction value "
+ "[" + value + "] outside limits ( 0.0 <> 1.0 )" );
}
mDCCorrectionInPhase = value;
setDCIQCorrection();
}
/**
* @return current DC correction for the Q (quadrature) component
*/
public double getDCCorrectionQuadrature()
{
return mDCCorrectionQuadrature;
}
/**
* Sets the DC correction for the Q (quadrature) component and send the
* new value to the FCD controller
* @param value - new DC correction value for the quadrature component
* @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0)
* @throws UsbClaimException - if cannot claim the FCD HID controller
* @throws UsbException - if there was a usb error
*/
public void setDCCorrectionQuadrature( double value )
throws UsbClaimException, UsbException
{
if( value < -1.0 || value > 1.0 )
{
throw new IllegalArgumentException( "DC quadrature correction "
+ "value [" + value + "] outside limits ( 0.0 <> 1.0 )" );
}
mDCCorrectionQuadrature = value;
setDCIQCorrection();
}
/**
* Sends the I/Q DC offset correction values to FCD.
*
* The interface expects an argument with the signed inphase value in the
* first 16 bits and the signed quadrature value in the second 16 bits,
* both in big-endian format.
*
* Since we're sending the argument in java little endian format, we place
* the quadrature in the high-order bits and the inphase in the low-order
* bits, so that the send(command) function will reorder the bytes into big
* endian format, and place the arguments correctly.
*
* Both Inphase and Quadrature correction values range -1.0 to 0.0 to 1.0
* and are applied to the dongle as signed short values -32768 to 0 to 32767
*/
private void setDCIQCorrection() throws UsbClaimException, UsbException
{
//I & Q DC offset correction values are signed short values
short inphase = (short)( mDCCorrectionInPhase * Short.MAX_VALUE );
short quadrature = (short)( mDCCorrectionQuadrature * Short.MAX_VALUE );
//Mask the shorts into longs to preserve the sign bit and prepare
//for merging to a single 32-bit value
long maskedInphase = inphase & 0x0000FFFF;
long maskedQuadrature = quadrature & 0x0000FFFF;
//Left shift quadrature to place value in upper 16 bits
long shiftedQuadrature = Long.rotateLeft( maskedQuadrature, 16 );
//Merge the results
long correction = shiftedQuadrature | maskedInphase;
// Log.info( "FCD1: setting iq correction," +
// " inphase:" + inphase +
// " masked inphase:" + maskedInphase +
// " quad:" + quadrature +
// " masked quad:" + maskedQuadrature +
// " shifted quadrature:" + shiftedQuadrature +
// " correction:" + correction );
send( FCDCommand.APP_SET_DC_CORRECTION, correction );
}
private void getDCIQCorrection() throws UsbClaimException,
UsbException
{
ByteBuffer buffer = send( FCDCommand.APP_GET_DC_CORRECTION );
buffer.order( ByteOrder.LITTLE_ENDIAN );
int correction = buffer.getInt( 2 );
/**
* Quadrature: mask the upper 16 bits and divide by 32768 to get the
* stored quadrature value. Cast the 16-bit value to a short to get
* the signed short value
*/
long shiftedQuadrature = correction & 0xFFFF0000;
long quadrature = Long.rotateRight( shiftedQuadrature, 16 );
mDCCorrectionQuadrature = ( (short)quadrature / (double)Short.MAX_VALUE );
/**
* InPhase: mask the lower 16 bits to get the short
* value and then divide by 32768 to get the stored inphase value.
* Cast the 16-bit value to a short to get the signed short value
*/
long inphase = correction & 0x0000FFFF;
mDCCorrectionInPhase = ( (short)inphase / (double)Short.MAX_VALUE );
// Log.info( "FCD1:" +
// " correction:" + correction +
// " Q:" + quadrature +
// " I:" + inphase +
// " QCorr:" + mDCCorrectionQuadrature +
// " ICorr:" + mDCCorrectionInPhase );
}
private void getLNAEnhanceSetting() throws UsbClaimException, UsbException
{
ByteBuffer buffer = send( FCDCommand.APP_GET_LNA_ENHANCE );
buffer.order( ByteOrder.LITTLE_ENDIAN );
int enhance = buffer.getInt( 2 );
mLNAEnhance = LNAEnhance.valueOf( enhance );
}
public LNAEnhance getLNAEnhance()
{
return mLNAEnhance;
}
public void setLNAEnhance( LNAEnhance enhance )
throws UsbClaimException, UsbException
{
send( FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting() );
}
private void getMixerGainSetting() throws UsbClaimException, UsbException
{
ByteBuffer buffer = send( FCDCommand.APP_GET_MIXER_GAIN );
buffer.order( ByteOrder.LITTLE_ENDIAN );
int gain = buffer.getInt( 2 );
mMixerGain = MixerGain.valueOf( gain );
}
public MixerGain getMixerGain()
{
return mMixerGain;
}
public void setMixerGain( MixerGain gain ) throws UsbClaimException, UsbException
{
send( FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting() );
}
public enum Block
{
CELLULAR_BAND_BLOCKED( "Blocked" ),
NO_BAND_BLOCK( "Unblocked" ),
UNKNOWN( "Unknown" );
private String mLabel;
private Block( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static Block getBlock( String block )
{
Block retVal = UNKNOWN;
if( block.equalsIgnoreCase( "No blk" ) )
{
retVal = NO_BAND_BLOCK;
}
else if( block.equalsIgnoreCase( "Cell blk" ) )
{
retVal = CELLULAR_BAND_BLOCKED;
}
return retVal;
}
}
/**
* LNA Gain values suppported by the FCD Pro 1.0
*/
public enum LNAGain
{
LNA_GAIN_MINUS_5_0( 0, "-5.0 dB" ),
LNA_GAIN_MINUS_2_5( 1, "-2.5 dB" ),
LNA_GAIN_PLUS_0_0( 4, "0.0 dB" ),
LNA_GAIN_PLUS_2_5( 5, "2.5 dB" ),
LNA_GAIN_PLUS_5_0( 6, "5.0 dB" ),
LNA_GAIN_PLUS_7_5( 7, "7.5 dB" ),
LNA_GAIN_PLUS_10_0( 8, "10.0 dB" ),
LNA_GAIN_PLUS_12_5( 9, "12.5 dB" ),
LNA_GAIN_PLUS_15_0( 10, "15.0 dB" ),
LNA_GAIN_PLUS_17_5( 11, "17.5 dB" ),
LNA_GAIN_PLUS_20_0( 12, "20.0 dB" ),
LNA_GAIN_PLUS_25_0( 13, "25.0 dB" ),
LNA_GAIN_PLUS_30_0( 14, "30.0 dB" );
private int mSetting;
private String mLabel;
private LNAGain( int setting, String label )
{
mSetting = setting;
mLabel = label;
}
public static LNAGain valueOf( int setting )
{
switch( setting )
{
case 0:
return LNA_GAIN_MINUS_5_0;
case 1:
return LNA_GAIN_MINUS_2_5;
case 4:
return LNA_GAIN_PLUS_0_0;
case 5:
return LNA_GAIN_PLUS_2_5;
case 6:
return LNA_GAIN_PLUS_5_0;
case 7:
return LNA_GAIN_PLUS_7_5;
case 8:
return LNA_GAIN_PLUS_10_0;
case 9:
return LNA_GAIN_PLUS_12_5;
case 10:
return LNA_GAIN_PLUS_15_0;
case 11:
return LNA_GAIN_PLUS_17_5;
case 12:
return LNA_GAIN_PLUS_20_0;
case 13:
return LNA_GAIN_PLUS_25_0;
case 14:
return LNA_GAIN_PLUS_30_0;
default:
throw new IllegalArgumentException( "FCD 1.0 Tuner "
+ "Controller - unrecognized LNA gain setting "
+ "value [" + setting + "]" );
}
}
public int getSetting()
{
return mSetting;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
}
/**
* LNA Enhance values suppported by the FCD Pro 1.0
*/
public enum LNAEnhance
{
LNA_ENHANCE_OFF( 0, "Off" ),
LNA_ENHANCE_0( 1, "0" ),
LNA_ENHANCE_1( 3, "1" ),
LNA_ENHANCE_2( 5, "2" ),
LNA_ENHANCE_3( 7, "3" );
private int mSetting;
private String mLabel;
private LNAEnhance( int setting, String label )
{
mSetting = setting;
mLabel = label;
}
public static LNAEnhance valueOf( int setting )
{
switch( setting )
{
case 0:
return LNA_ENHANCE_OFF;
case 1:
return LNA_ENHANCE_0;
case 3:
return LNA_ENHANCE_1;
case 5:
return LNA_ENHANCE_2;
case 7:
return LNA_ENHANCE_3;
default:
throw new IllegalArgumentException( "FCD 1.0 Tuner "
+ "Controller - unrecognized LNA enhance setting "
+ "value [" + setting + "]" );
}
}
public int getSetting()
{
return mSetting;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
}
/**
* Mixer Gain values suppported by the FCD Pro 1.0
*/
public enum MixerGain
{
MIXER_GAIN_PLUS_4_0( 0, "4.0 dB" ),
MIXER_GAIN_PLUS_12_0( 1, "12.0 dB" );
private int mSetting;
private String mLabel;
private MixerGain( int setting, String label )
{
mSetting = setting;
mLabel = label;
}
public static MixerGain valueOf( int setting )
{
switch( setting )
{
case 0:
return MIXER_GAIN_PLUS_4_0;
case 1:
return MIXER_GAIN_PLUS_12_0;
default:
throw new IllegalArgumentException( "FCD 1.0 Tuner "
+ "Controller - unrecognized mixer gain setting "
+ "value [" + setting + "]" );
}
}
public int getSetting()
{
return mSetting;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
}
}

View File

@@ -0,0 +1,512 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.fcd.proV1;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
public class FCD1TunerEditor extends TunerConfigurationEditor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( FCD1TunerEditor.class );
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JSpinner mFrequencyCorrection;
private JComboBox<FCD1TunerController.LNAGain> mComboLNAGain;
private JComboBox<FCD1TunerController.LNAEnhance> mComboLNAEnhance;
private JComboBox<FCD1TunerController.MixerGain> mComboMixerGain;
private CorrectionSpinner mCorrectionDCI;
private CorrectionSpinner mCorrectionDCQ;
private CorrectionSpinner mCorrectionGain;
private CorrectionSpinner mCorrectionPhase;
private boolean mLoading;
private FCD1TunerController mController;
public FCD1TunerEditor(TunerConfigurationModel tunerConfigurationModel, FCDTuner tuner )
{
super( tunerConfigurationModel );
mController = (FCD1TunerController)tuner.getController();
init();
}
private FCD1TunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (FCD1TunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][][][][][][][grow]" ) );
add( new JLabel( "FCD Pro Tuner Configuration" ), "span,align center" );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
SpinnerModel model = new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, "FCD Pro Tuner "
+ "Controller - couldn't apply frequency correction value: " + value +
e1.getLocalizedMessage() );
mLog.error( "FuncubeDonglePro Controller - couldn't apply "
+ "frequency correction value: " + value, e1 );
}
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection, "wrap" );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
add( new JLabel( "Gain" ), "span,align center" );
mComboLNAGain = new JComboBox<FCD1TunerController.LNAGain>( FCD1TunerController.LNAGain.values() );
mComboLNAGain.setEnabled( false );
mComboLNAGain.setToolTipText( "Adjust the low noise amplifier gain setting." );
mComboLNAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
FCD1TunerController.LNAGain gain = (FCD1TunerController.LNAGain)mComboLNAGain.getSelectedItem();
try
{
mController.setLNAGain( gain );
save();
}
catch ( Exception e )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, "FCD Pro Tuner Controller"
+ " - error setting LNA gain [" + gain.toString() + "]" );
mLog.error( "FuncubeDonglePro Controller - error setting gain [" + gain.toString() + "]", e );
}
}
} );
add( new JLabel( "LNA:" ) );
add( mComboLNAGain );
/**
* LNA Enhance
*/
mComboLNAEnhance = new JComboBox<FCD1TunerController.LNAEnhance>( FCD1TunerController.LNAEnhance.values() );
mComboLNAEnhance.setToolTipText( "Adjust the LNA enhance setting. Default value is OFF" );
mComboLNAEnhance.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
FCD1TunerController.LNAEnhance enhance = (FCD1TunerController.LNAEnhance)mComboLNAEnhance.getSelectedItem();
try
{
mController.setLNAEnhance( enhance );
save();
}
catch ( Exception e1 )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, "FCD Pro Tuner"
+ " error setting LNA enhance gain [" + enhance.toString() + "]" );
mLog.error( "FCDPro - error setting LNA enhance [" + enhance.toString() + "]", e1 );
}
}
} );
add( new JLabel( "Enhance:" ) );
add( mComboLNAEnhance );
/**
* Mixer Gain
*/
mComboMixerGain = new JComboBox<FCD1TunerController.MixerGain>( FCD1TunerController.MixerGain.values() );
mComboMixerGain.setToolTipText( "Adjust mixer gain setting" );
mComboMixerGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
FCD1TunerController.MixerGain gain = (FCD1TunerController.MixerGain)mComboMixerGain.getSelectedItem();
try
{
mController.setMixerGain( gain );
save();
}
catch ( Exception e1 )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, "FCDPro - error setting"
+ " mixer gain [" + gain.toString() + "]" );
mLog.error( "FCDPro - error setting mixer gain [" + gain.toString() + "]", e1 );
}
}
} );
add( new JLabel( "Mixer:" ) );
add( mComboMixerGain, "wrap" );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
add( new JLabel( "Correction" ), "span,align center" );
/**
* Inphase DC Correction
*/
mCorrectionDCI = new CorrectionSpinner( Correction.DC_INPHASE, 0.0, 0.00001, 5 );
mCorrectionDCI.setEnabled( false );
mCorrectionDCI.setToolTipText( "DC Bias Correction/Inphase "
+ "Component: valid values are -1.0 to 1.0 (default: 0.0)" );
add( new JLabel( "DC Inphase:" ) );
add( mCorrectionDCI );
/**
* Quadrature DC Correction
*/
mCorrectionDCQ = new CorrectionSpinner( Correction.DC_QUADRATURE, 0.0, 0.00001, 5 );
mCorrectionDCQ.setToolTipText( "DC Bias Correction/Quadrature "
+ "Component: valid values are -1.0 to 1.0 (default: 0.0)" );
add( new JLabel( "DC Quadrature:" ) );
add( mCorrectionDCQ );
/**
* Gain Correction
*/
mCorrectionGain = new CorrectionSpinner( Correction.GAIN, 0.0, 0.00001, 5 );
mCorrectionGain.setToolTipText( "Gain Correction: valid values are "
+ "-1.0 to 1.0 (default: 0.0)" );
add( new JLabel( "Gain:" ) );
add( mCorrectionGain );
/**
* Phase Correction
*/
mCorrectionPhase = new CorrectionSpinner( Correction.PHASE, 0.0, 0.00001, 5 );
mCorrectionPhase.setToolTipText( "Phase Correction: valid values are "
+ "-1.0 to 1.0 (default: 0.0)" );
add( new JLabel( "Phase:" ) );
add( mCorrectionPhase );
}
/**
* Sets each of the tuner configuration controls to the enabled argument state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mComboLNAEnhance.isEnabled() != enabled )
{
mComboLNAEnhance.setEnabled( enabled );
}
if( mComboLNAGain.isEnabled() != enabled )
{
mComboLNAGain.setEnabled( enabled );
}
if( mComboMixerGain.isEnabled() != enabled )
{
mComboMixerGain.setEnabled( enabled );
}
if( mCorrectionDCI.isEnabled() != enabled )
{
mCorrectionDCI.setEnabled( enabled );
}
if( mCorrectionDCQ.isEnabled() != enabled )
{
mCorrectionDCQ.setEnabled( enabled );
}
if( mCorrectionGain.isEnabled() != enabled )
{
mCorrectionGain.setEnabled( enabled );
}
if( mCorrectionPhase.isEnabled() != enabled )
{
mCorrectionPhase.setEnabled( enabled );
}
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
sb.append( "<html><h3>Funcube Dongle Pro Tuner</h3>" );
sb.append( "<b>USB ID: </b>" );
sb.append( mController.getUSBID() );
sb.append( "<br>" );
sb.append( "<b>USB Address: </b>" );
sb.append( mController.getUSBAddress() );
sb.append( "<br>" );
sb.append( "<b>USB Speed: </b>" );
sb.append( mController.getUSBSpeed() );
sb.append( "<br>" );
sb.append( "<b>Cellular Band: </b>" );
sb.append( mController.getConfiguration().getBandBlocking() );
sb.append( "<br>" );
sb.append( "<b>Firmware: </b>" );
sb.append( mController.getConfiguration().getFirmware() );
sb.append( "<br>" );
return sb.toString();
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
FCD1TunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
config.setLNAEnhance( (FCD1TunerController.LNAEnhance)mComboLNAEnhance.getSelectedItem() );
config.setLNAGain( (FCD1TunerController.LNAGain)mComboLNAGain.getSelectedItem() );
config.setMixerGain( (FCD1TunerController.MixerGain)mComboMixerGain.getSelectedItem() );
double dci = ((SpinnerNumberModel)mCorrectionDCI.getModel()).getNumber().doubleValue();
config.setInphaseDCCorrection( dci );
double dcq = ((SpinnerNumberModel)mCorrectionDCQ.getModel()).getNumber().doubleValue();
config.setQuadratureDCCorrection( dcq );
double gain = ((SpinnerNumberModel)mCorrectionGain.getModel()).getNumber().doubleValue();
config.setGainCorrection( gain );
double phase = ((SpinnerNumberModel)mCorrectionPhase.getModel()).getNumber().doubleValue();
config.setPhaseCorrection( phase );
getTunerConfigurationModel().broadcast( new TunerConfigurationEvent( getConfiguration(), TunerConfigurationEvent.Event.CHANGE ) );
}
}
@Override
public void setItem( TunerConfiguration tunerConfiguration )
{
super.setItem( tunerConfiguration );
//Toggle loading so that we don't fire a change event and schedule a settings file save
mLoading = true;
if( hasItem() )
{
FCD1TunerConfiguration config = getConfiguration();
if( tunerConfiguration.isAssigned() )
{
setControlsEnabled( true );
mConfigurationName.setText( config.getName() );
mFrequencyCorrection.setValue( config.getFrequencyCorrection() );
mComboLNAEnhance.setSelectedItem( config.getLNAEnhance() );
mComboLNAGain.setSelectedItem( config.getLNAGain() );
mComboMixerGain.setSelectedItem( config.getMixerGain() );
mCorrectionDCI.setValue( config.getInphaseDCCorrection() );
mCorrectionDCQ.setValue( config.getQuadratureDCCorrection() );
mCorrectionGain.setValue( config.getGainCorrection() );
mCorrectionPhase.setValue( config.getPhaseCorrection() );
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( config.getName() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( "" );
}
mLoading = false;
}
public enum Correction { GAIN, PHASE, DC_INPHASE, DC_QUADRATURE };
public class CorrectionSpinner extends JSpinner
{
private static final long serialVersionUID = 1L;
private static final double MIN_VALUE = -1.0d;
private static final double MAX_VALUE = 1.0d;
private Correction mCorrectionComponent;
public CorrectionSpinner( Correction component, double initialValue, double step, int decimalPlaces )
{
mCorrectionComponent = component;
SpinnerModel model = new SpinnerNumberModel( initialValue, MIN_VALUE, MAX_VALUE, step );
setModel( model );
JSpinner.NumberEditor editor = (JSpinner.NumberEditor)getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( decimalPlaces );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
double value =
((SpinnerNumberModel)getModel()).getNumber().doubleValue();
try
{
switch( mCorrectionComponent )
{
case DC_INPHASE:
mController.setDCCorrectionInPhase( value );
break;
case DC_QUADRATURE:
mController.setDCCorrectionQuadrature( value );
break;
case GAIN:
mController.setGainCorrection( value );
break;
case PHASE:
mController.setPhaseCorrection( value );
break;
}
save();
}
catch ( Exception e1 )
{
JOptionPane.showMessageDialog( FCD1TunerEditor.this, "FCDPro - error "
+ "applying " + mCorrectionComponent.toString() + " correction value ["
+ value + "]" );
mLog.error( "FCDPro - error applying "+ mCorrectionComponent.toString()
+ " correction value [" + value + "]", e1 );
}
}
} );
}
}
}

View File

@@ -0,0 +1,81 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.fcd.proplusV2;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import javax.xml.bind.annotation.XmlAttribute;
public class FCD2TunerConfiguration extends TunerConfiguration
{
private boolean mGainLNA = true;
private boolean mGainMixer = true;
private double mFrequencyCorrection = -2.2d;
/**
* Default constructor for JAXB
*/
public FCD2TunerConfiguration()
{
}
public FCD2TunerConfiguration( String uniqueID, String name )
{
super( uniqueID, name );
}
@Override
public TunerType getTunerType()
{
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
}
@XmlAttribute( name = "lna_gain" )
public boolean getGainLNA()
{
return mGainLNA;
}
public void setGainLNA( boolean gain )
{
mGainLNA = gain;
}
@XmlAttribute( name = "mixer_gain" )
public boolean getGainMixer()
{
return mGainMixer;
}
public void setGainMixer( boolean gain )
{
mGainMixer = gain;
}
@XmlAttribute( name = "frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
}

View File

@@ -0,0 +1,238 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner.fcd.proplusV2;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.MixerTunerType;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.FCDCommand;
import io.github.dsheirer.source.tuner.fcd.FCDTunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FCD2TunerController extends FCDTunerController
{
private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerController.class);
public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
public static final int SAMPLE_RATE = 192000;
public FCD2TunerController(Device device, DeviceDescriptor descriptor)
{
super(device, descriptor,
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
}
public void init() throws SourceException
{
super.init();
mFrequencyController.setSampleRate(SAMPLE_RATE);
try
{
setFCDMode(Mode.APPLICATION);
}
catch(Exception e)
{
throw new SourceException("error setting Mode to APPLICATION", e);
}
}
public int getCurrentSampleRate()
{
return SAMPLE_RATE;
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
}
@Override
public TunerType getTunerType()
{
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
}
public void setLNAGain(boolean enabled) throws SourceException
{
try
{
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
}
catch(Exception e)
{
throw new SourceException("error while setting LNA Gain", e);
}
}
public void setMixerGain(boolean enabled) throws SourceException
{
try
{
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
}
catch(Exception e)
{
throw new SourceException("error while setting Mixer Gain", e);
}
}
@Override
public void apply(TunerConfiguration config) throws SourceException
{
if(config instanceof FCD2TunerConfiguration)
{
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection(fcd2.getFrequencyCorrection());
long frequency = MINIMUM_TUNABLE_FREQUENCY;
if(fcd2.getFrequency() >= MINIMUM_TUNABLE_FREQUENCY &&
fcd2.getFrequency() <= MAXIMUM_TUNABLE_FREQUENCY)
{
frequency = fcd2.getFrequency();
}
setFrequency(frequency);
setLNAGain(fcd2.getGainLNA());
setMixerGain(fcd2.getGainMixer());
try
{
setFrequency(fcd2.getFrequency());
}
catch(SourceException se)
{
//Do nothing, we couldn't set the frequency
}
}
}
public int getDCCorrection()
{
int dcCorrection = -999;
try
{
ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt(2);
}
catch(Exception e)
{
mLog.error("error getting dc correction value", e);
}
return dcCorrection;
}
public void setDCCorrection(int value)
{
try
{
send(FCDCommand.APP_SET_DC_CORRECTION, value);
}
catch(Exception e)
{
mLog.error("error setting dc correction to [" + value + "]", e);
}
}
public int getIQCorrection()
{
int iqCorrection = -999;
try
{
ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt(2);
}
catch(Exception e)
{
mLog.error("error reading IQ correction value", e);
}
return iqCorrection;
}
public void setIQCorrection(int value)
{
try
{
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
}
catch(Exception e)
{
mLog.error("error setting IQ correction to [" + value + "]", e);
}
}
public enum Block
{
CELLULAR_BAND_BLOCKED("Blocked"),
NO_BAND_BLOCK("Unblocked"),
UNKNOWN("Unknown");
private String mLabel;
private Block(String label)
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static Block getBlock(String block)
{
Block retVal = UNKNOWN;
if(block.equalsIgnoreCase("No blk"))
{
retVal = NO_BAND_BLOCK;
}
else if(block.equalsIgnoreCase("Cell blk"))
{
retVal = CELLULAR_BAND_BLOCKED;
}
return retVal;
}
}
}

View File

@@ -0,0 +1,316 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.fcd.proplusV2;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent.Event;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
public class FCD2TunerEditor extends TunerConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger( FCD2TunerEditor.class );
private static final long serialVersionUID = 1L;
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JSpinner mFrequencyCorrection;
private JCheckBox mLNAGain;
private JCheckBox mMixerGain;
private boolean mLoading;
private FCD2TunerController mController;
public FCD2TunerEditor( TunerConfigurationModel tunerConfigurationModel, FCDTuner tuner )
{
super( tunerConfigurationModel );
mController = (FCD2TunerController)tuner.getController();
init();
}
private FCD2TunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (FCD2TunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][grow]" ) );
add( new JLabel( "FCD Pro+ Tuner Configuration" ), "span,align center" );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( FCD2TunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
SpinnerModel model = new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )
{
JOptionPane.showMessageDialog( FCD2TunerEditor.this, "FCD Pro Plus Tuner "
+ "Controller - couldn't apply frequency correction value: " + value +
e1.getLocalizedMessage() );
mLog.error( "FuncubeDongleProPlus Controller - couldn't apply "
+ "frequency correction value: " + value, e1 );
}
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection, "wrap" );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
mLNAGain = new JCheckBox( "LNA Gain" );
mLNAGain.setEnabled( false );
mLNAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent event )
{
try
{
mController.setLNAGain( mLNAGain.isSelected() );
save();
}
catch( SourceException e )
{
mLog.error( "Couldn't set LNA gain for FCD2", e );
}
}
} );
add( new JLabel( "" ) );
add( mLNAGain );
/**
* Mixer Gain
*/
mMixerGain = new JCheckBox( "Mixer Gain" );
mMixerGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent event )
{
try
{
mController.setMixerGain( mMixerGain.isSelected() );
save();
}
catch( SourceException e )
{
mLog.error( "Couldn't set mixer gain for FCD2", e );
}
}
} );
add( new JLabel( "" ) );
add( mMixerGain );
}
/**
* Sets each of the tuner configuration controls to the enabled argument state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mLNAGain.isEnabled() != enabled )
{
mLNAGain.setEnabled( enabled );
}
if( mMixerGain.isEnabled() != enabled )
{
mMixerGain.setEnabled( enabled );
}
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
sb.append( "<html><h3>Funcube Dongle Pro Plus Tuner</h3>" );
sb.append( "<b>USB ID: </b>" );
sb.append( mController.getUSBID() );
sb.append( "<br>" );
sb.append( "<b>USB Address: </b>" );
sb.append( mController.getUSBAddress() );
sb.append( "<br>" );
sb.append( "<b>USB Speed: </b>" );
sb.append( mController.getUSBSpeed() );
sb.append( "<br>" );
sb.append( "<b>Cellular Band: </b>" );
sb.append( mController.getConfiguration().getBandBlocking() );
sb.append( "<br>" );
sb.append( "<b>Firmware: </b>" );
sb.append( mController.getConfiguration().getFirmware() );
sb.append( "<br>" );
return sb.toString();
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
FCD2TunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
config.setGainLNA( mLNAGain.isSelected() );
config.setGainMixer( mMixerGain.isSelected() );
getTunerConfigurationModel().broadcast(
new TunerConfigurationEvent( getConfiguration(), Event.CHANGE ) );
}
}
@Override
public void setItem( TunerConfiguration tunerConfiguration )
{
super.setItem( tunerConfiguration );
//Toggle loading so that we don't fire a change event and schedule a settings file save
mLoading = true;
if( hasItem() )
{
FCD2TunerConfiguration config = getConfiguration();
if( tunerConfiguration.isAssigned() )
{
setControlsEnabled( true );
mConfigurationName.setText( config.getName() );
mFrequencyCorrection.setValue( config.getFrequencyCorrection() );
mLNAGain.setSelected( config.getGainLNA() );
mMixerGain.setSelected( config.getGainMixer() );
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( config.getName() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( "" );
}
mLoading = false;
}
}

View File

@@ -0,0 +1,129 @@
package io.github.dsheirer.source.tuner.frequency;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2016 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 io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import io.github.dsheirer.module.Module;
import java.util.concurrent.ScheduledExecutorService;
public class DCTrackingFrequencyControl extends Module
implements Listener<RealBuffer>, IFrequencyChangeProvider
{
private static float COARSE_THRESHOLD = 0.1f;
private static float LOOP_GAIN = 0.1f;
private static float COARSE_GAIN = 1000.0f;
private static float FINE_GAIN = 100.0f;
private int mErrorCorrection = 0;
private int mMaximumFrequencyCorrection = 0;
private Listener<FrequencyChangeEvent> mListener;
/**
* DC offset Automatic Frequency Control. Monitors DC bias present in an FM
* demodulated audio stream and issues automatic frequency corrections to
* maintain the DC bias close to zero.
*
* @param maximum - maximum allowable (+/-) frequency correction value.
*/
public DCTrackingFrequencyControl( int maximum )
{
mMaximumFrequencyCorrection = maximum;
}
public void dispose()
{
mListener = null;
}
@Override
public void receive( RealBuffer buffer )
{
double sum = 0.0d;
for( float sample: buffer.getSamples() )
{
sum += sample;
}
float mean = (float)( sum / (double)buffer.getSamples().length );
float adjustment = 0.0f;
if( mean > COARSE_THRESHOLD || mean < - COARSE_THRESHOLD )
{
adjustment = mean * COARSE_GAIN;
}
else
{
adjustment = mean * FINE_GAIN;
}
setErrorCorrection( mErrorCorrection + (int)( (float)adjustment * LOOP_GAIN ) );
}
private void setErrorCorrection( int correction )
{
if( mListener != null )
{
if( correction > mMaximumFrequencyCorrection )
{
correction = mMaximumFrequencyCorrection;
}
else if( correction < -mMaximumFrequencyCorrection )
{
correction = -mMaximumFrequencyCorrection;
}
if( correction != mErrorCorrection )
{
mListener.receive( new FrequencyChangeEvent(
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
}
}
}
@Override
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
{
mListener = listener;
}
@Override
public void removeFrequencyChangeListener()
{
mListener = null;
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
@Override
public void reset()
{
setErrorCorrection( 0 );
}
}

View File

@@ -0,0 +1,34 @@
package io.github.dsheirer.source.tuner.frequency;
public class FrequencyChangeEvent
{
public enum Event
{
NOTIFICATION_FREQUENCY_CHANGE,
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_SAMPLE_RATE_CHANGE,
REQUEST_FREQUENCY_CHANGE,
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE;
}
private Event mEvent;
private Number mValue;
public FrequencyChangeEvent( Event event, Number value )
{
mEvent = event;
mValue = value;
}
public Event getEvent()
{
return mEvent;
}
public Number getValue()
{
return mValue;
}
}

View File

@@ -0,0 +1,44 @@
package io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FrequencyChangeProcessorWrapper
implements IFrequencyChangeListener, Listener<FrequencyChangeEvent>
{
private final static Logger mLog = LoggerFactory.getLogger( FrequencyChangeProcessorWrapper.class );
private IFrequencyChangeProcessor mProcessor;
/**
* Provides IFrequencyChangeListener wrapper for an IFrequencyChangeProcessor
*/
public FrequencyChangeProcessorWrapper( IFrequencyChangeProcessor handler )
{
mProcessor = handler;
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return this;
}
@Override
public void receive( FrequencyChangeEvent event )
{
if( mProcessor != null )
{
try
{
mProcessor.frequencyChanged( event );
}
catch( SourceException se )
{
mLog.error("Error sending frequency change event to embedded processor", se);
}
}
}
}

View File

@@ -0,0 +1,255 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
public class FrequencyController
{
private final static Logger mLog = LoggerFactory.getLogger( FrequencyController.class );
private Tunable mTunable;
private long mFrequency = 101100000;
private long mTunedFrequency = 101100000;
private long mMinimumFrequency;
private long mMaximumFrequency;
private double mFrequencyCorrection = 0d;
private int mSampleRate = 0;
private ArrayList<IFrequencyChangeProcessor> mProcessors = new ArrayList<>();
public FrequencyController( Tunable tunable,
long minFrequency,
long maxFrequency,
double frequencyCorrection )
{
mTunable = tunable;
mMinimumFrequency = minFrequency;
mMaximumFrequency = maxFrequency;
mFrequencyCorrection = frequencyCorrection;
}
/**
* Get bandwidth/sample rate in hertz
*/
public int getBandwidth()
{
return mSampleRate;
}
/**
* Set sample rate in hertz
*/
public void setSampleRate( int sampleRate ) throws SourceException
{
mSampleRate = sampleRate;
broadcastSampleRateChange();
}
/**
* Get frequency in hertz
*/
public long getFrequency()
{
return mFrequency;
}
/**
* Set frequency
* @param frequency in hertz
* @throws SourceException if tunable doesn't support tuning a corrected
* version of the requested frequency
*/
public void setFrequency( long frequency ) throws SourceException
{
setFrequency( frequency, true );
}
/**
* Set frequency with optional broadcast of frequency change event. This
* method supports changing the frequency correction value without
* broadcasting a frequency change event.
*/
private void setFrequency( long frequency, boolean broadcastChange ) throws SourceException
{
long tunedFrequency = getTunedFrequency( frequency );
if( tunedFrequency < mMinimumFrequency )
{
throw new InvalidFrequencyException( "Requested frequency not valid", frequency, mMinimumFrequency );
}
if( tunedFrequency > mMaximumFrequency )
{
throw new InvalidFrequencyException( "Requested frequency not valid", frequency, mMaximumFrequency );
}
mFrequency = frequency;
mTunedFrequency = tunedFrequency;
if( mTunable != null )
{
mTunable.setTunedFrequency( mTunedFrequency );
}
/* Broadcast to all listeners that the frequency has changed */
if( broadcastChange )
{
broadcastFrequencyChange();
}
}
public long getTunedFrequency()
{
return mTunedFrequency;
}
public long getMinimumFrequency()
{
return mMinimumFrequency;
}
public long getMaximumFrequency()
{
return mMaximumFrequency;
}
/**
* Calculate the tuned frequency by adding frequency correction to the
* corrected frequency.
* @param correctedFrequency
*/
private long getTunedFrequency( long correctedFrequency )
{
return (long)( (double)correctedFrequency /
( 1.0 + ( mFrequencyCorrection / 1000000.0 ) ) );
}
/**
* Calculate the corrected frequency by subtracting frequency correction
* from the tuned frequency.
* @param tunedFrequency
*/
@SuppressWarnings("unused")
private long getCorrectedFrequency( long tunedFrequency )
{
return (long)( (double)tunedFrequency /
( 1.0 - ( mFrequencyCorrection / 1000000.0 ) ) );
}
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double correction ) throws SourceException
{
mFrequencyCorrection = correction;
if( mFrequency > 0 )
{
setFrequency( mFrequency, true );
}
broadcastFrequencyErrorChange();
}
/**
* Adds listener to receive frequency change events
*/
//TODO: rename this to addFrequencyChangeProcessor()
public void addListener( IFrequencyChangeProcessor processor )
{
if( !mProcessors.contains( processor ) )
{
mProcessors.add( processor );
}
}
/**
* Removes listener from receiving frequency change events
*/
public void removeFrequencyChangeProcessor( IFrequencyChangeProcessor processor )
{
mProcessors.remove( processor );
}
/**
* Broadcasts a change/update to the current (uncorrected) frequency or the
* bandwidth/sample rate value.
*/
protected void broadcastFrequencyChange() throws SourceException
{
broadcastFrequencyChangeEvent(
new FrequencyChangeEvent( Event.NOTIFICATION_FREQUENCY_CHANGE, mFrequency ) );
}
/**
* Broadcast a frequency error/correction value change
*/
protected void broadcastFrequencyErrorChange() throws SourceException
{
broadcastFrequencyChangeEvent(
new FrequencyChangeEvent( Event.NOTIFICATION_FREQUENCY_CORRECTION_CHANGE, mFrequencyCorrection ) );
}
/**
* Broadcasts a sample rate change
*/
protected void broadcastSampleRateChange() throws SourceException
{
broadcastFrequencyChangeEvent(
new FrequencyChangeEvent( Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mSampleRate ) );
}
public void broadcastFrequencyChangeEvent( FrequencyChangeEvent event ) throws SourceException
{
for( IFrequencyChangeProcessor processor: mProcessors )
{
processor.frequencyChanged( event );
}
}
public interface Tunable
{
/**
* Gets the tuned frequency of the device
*/
public long getTunedFrequency() throws SourceException;
/**
* Sets the tuned frequency of the device
*/
public void setTunedFrequency( long frequency ) throws SourceException;
/**
* Gets the current bandwidth setting of the device
*/
public int getCurrentSampleRate() throws SourceException;
}
}

View File

@@ -0,0 +1,36 @@
package io.github.dsheirer.source.tuner.frequency;
public class FrequencyLimitException extends Exception
{
private static final long serialVersionUID = 1L;
private long mFrequencyLimit;
private long mRequestedFrequency;
/**
* Indicates that the requested frequency is outside of the tunable bounds
* of the tuner.
* @param message
* @param frequency
*/
public FrequencyLimitException( long requestedFrequency,
long frequencyLimit )
{
super( "Requested frequency [" + requestedFrequency +
"] exceeds the limit [" + frequencyLimit + "]. Tuned "
+ "frequency was set to the limit value" );
mRequestedFrequency = requestedFrequency;
mFrequencyLimit = frequencyLimit;
}
public long getRequestedFrequency()
{
return mRequestedFrequency;
}
public long getFrequencyLimit()
{
return mFrequencyLimit;
}
}

View File

@@ -0,0 +1,34 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.sample.Listener;
/**
* Interface for broadcasting changes to frequency, bandwidth, sample rate,
* and actual sample rate values to all registered listeners.
*
* Note: broadcasted frequency should be the uncorrected, or displayable
* frequency value. All frequency correction aspects should be handled within
* the device that implements the tuning of the frequency.
*/
public interface IFrequencyChangeListener
{
public Listener<FrequencyChangeEvent> getFrequencyChangeListener();
}

View File

@@ -0,0 +1,8 @@
package io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.source.SourceException;
public interface IFrequencyChangeProcessor
{
public void frequencyChanged( FrequencyChangeEvent event ) throws SourceException;
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner.frequency;
import io.github.dsheirer.sample.Listener;
/**
* Provider of Frequency Change Events
*/
public interface IFrequencyChangeProvider
{
void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
void removeFrequencyChangeListener();
}

View File

@@ -0,0 +1,124 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.hackrf;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.TunerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.RejectedExecutionException;
public class HackRFTuner extends Tuner
{
private final static Logger mLog = LoggerFactory.getLogger( HackRFTuner.class );
public HackRFTuner( HackRFTunerController controller ) throws SourceException
{
super( "HackRF", controller );
}
public HackRFTunerController getController()
{
return (HackRFTunerController)getTunerController();
}
public void dispose()
{
//TODO: dispose of something here
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.HACKRF_ONE;
}
@Override
public TunerType getTunerType()
{
return TunerType.HACKRF;
}
@Override
public double getSampleSize()
{
return 11.0;
}
@Override
public TunerChannelSource getChannel(TunerChannel channel )
throws RejectedExecutionException, SourceException
{
TunerChannelSource source = getController().getChannel( this, channel );
if( source != null )
{
broadcast( new TunerEvent( this, TunerEvent.Event.CHANNEL_COUNT ) );
}
return source;
}
@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 )
{
getController().releaseChannel( source );
}
}
@Override
public String getUniqueID()
{
try
{
return getController().getSerial().getSerialNumber();
}
catch( Exception e )
{
mLog.error( "error gettting serial number", e );
}
return HackRFTunerController.BoardID.HACKRF_ONE.getLabel();
}
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
getController().addListener( listener );
}
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
getController().removeListener( listener );
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.hackrf;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFLNAGain;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFSampleRate;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFVGAGain;
import javax.xml.bind.annotation.XmlAttribute;
public class HackRFTunerConfiguration extends TunerConfiguration
{
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
private HackRFLNAGain mLNAGain = HackRFLNAGain.GAIN_16;
private HackRFVGAGain mVGAGain = HackRFVGAGain.GAIN_10;
private boolean mAmplifierEnabled = true;
private double mFrequencyCorrection = 0.0d;
/**
* Default constructor for JAXB
*/
public HackRFTunerConfiguration()
{
}
public HackRFTunerConfiguration( String uniqueID, String name )
{
super( uniqueID, name );
}
@Override
public TunerType getTunerType()
{
return TunerType.HACKRF;
}
@XmlAttribute( name = "amplifier_enabled" )
public boolean getAmplifierEnabled()
{
return mAmplifierEnabled;
}
public void setAmplifierEnabled( boolean enabled )
{
mAmplifierEnabled = enabled;
}
@XmlAttribute( name = "lna_gain" )
public HackRFLNAGain getLNAGain()
{
return mLNAGain;
}
public void setLNAGain( HackRFLNAGain lnaGain )
{
mLNAGain = lnaGain;
}
@XmlAttribute( name = "vga_gain" )
public HackRFVGAGain getVGAGain()
{
return mVGAGain;
}
public void setVGAGain( HackRFVGAGain vgaGain )
{
mVGAGain = vgaGain;
}
@XmlAttribute( name = "frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
@XmlAttribute( name = "sample_rate" )
public HackRFSampleRate getSampleRate()
{
return mSampleRate;
}
public void setSampleRate( HackRFSampleRate sampleRate )
{
mSampleRate = sampleRate;
}
}

View File

@@ -0,0 +1,894 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* Ported from libhackrf at:
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf
*
* Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
* Copyright (c) 2013, Benjamin Vernoux <titanmkd@gmail.com>
* copyright (c) 2013, Michael Ossmann <mike@ossmann.com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Great Scott Gadgets nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* 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 io.github.dsheirer.source.tuner.hackrf;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ByteSampleAdapter;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
import org.apache.commons.io.EndianUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
public class HackRFTunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
public static final byte USB_ENDPOINT = (byte)0x81;
public static final byte USB_INTERFACE = (byte)0x0;
public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final long MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l;
public static final long DEFAULT_FREQUENCY = 101100000;
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private USBTransferProcessor mUSBTransferProcessor;
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
private boolean mAmplifierEnabled = false;
private Device mDevice;
private DeviceDescriptor mDeviceDescriptor;
private DeviceHandle mDeviceHandle;
public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException
{
super(MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
mDevice = device;
mDeviceDescriptor = descriptor;
}
public void init() throws SourceException
{
mDeviceHandle = new DeviceHandle();
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();
setMode(Mode.RECEIVE);
setFrequency(DEFAULT_FREQUENCY);
}
catch(Exception e)
{
throw new SourceException("HackRF Tuner Controller - couldn't "
+ "claim USB interface or get endpoint or pipe", e);
}
String name;
try
{
name = "HackRF " + getSerial().getSerialNumber();
}
catch(UsbException ue)
{
//Do nothing, we couldn't determine the serial number
name = "HackRF - Unidentified Serial";
}
mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE);
}
/**
* Claims the USB interface. If another application currently has
* the interface claimed, the USB_FORCE_CLAIM_INTERFACE setting
* will dictate if the interface is forcibly claimed from the other
* application
*/
private void claimInterface() throws SourceException
{
if(mDeviceHandle != null)
{
int result = LibUsb.kernelDriverActive(mDeviceHandle, USB_INTERFACE);
if(result == 1)
{
result = LibUsb.detachKernelDriver(mDeviceHandle, USB_INTERFACE);
if(result != LibUsb.SUCCESS)
{
mLog.error("failed attempt to detach kernel driver [" +
LibUsb.errorName(result) + "]");
throw new SourceException("couldn't detach kernel driver "
+ "from device");
}
}
result = LibUsb.claimInterface(mDeviceHandle, USB_INTERFACE);
if(result != LibUsb.SUCCESS)
{
throw new SourceException("couldn't claim usb interface [" +
LibUsb.errorName(result) + "]");
}
}
else
{
throw new SourceException("couldn't claim usb interface - no "
+ "device handle");
}
}
/**
* HackRF board identifier/type
*/
public BoardID getBoardID() throws UsbException
{
int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false);
return BoardID.lookup(id);
}
/**
* HackRF firmware version string
*/
public String getFirmwareVersion() throws UsbException
{
ByteBuffer buffer = readArray(Request.VERSION_STRING_READ, 0, 0, 255);
byte[] data = new byte[255];
buffer.get(data);
return new String(data);
}
/**
* HackRF part id number and serial number
*/
public Serial getSerial() throws UsbException
{
ByteBuffer buffer = readArray(Request.BOARD_PARTID_SERIALNO_READ, 0, 0, 24);
return new Serial(buffer);
}
/**
* Sets the HackRF transceiver mode
*/
public void setMode(Mode mode) throws UsbException
{
write(Request.SET_TRANSCEIVER_MODE, mode.getNumber(), 0);
}
/**
* Sets the HackRF baseband filter
*/
public void setBasebandFilter(BasebandFilter filter) throws UsbException
{
write(Request.BASEBAND_FILTER_BANDWIDTH_SET,
filter.getLowValue(),
filter.getHighValue());
}
/**
* Enables (true) or disables (false) the amplifier
*/
public void setAmplifierEnabled(boolean enabled) throws UsbException
{
write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0);
mAmplifierEnabled = enabled;
}
public boolean getAmplifier()
{
return mAmplifierEnabled;
}
/**
* Sets the IF LNA Gain
*/
public void setLNAGain(HackRFLNAGain gain) throws UsbException
{
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true);
if(result != 1)
{
throw new UsbException("couldn't set lna gain to " + gain);
}
}
/**
* Sets the Baseband VGA Gain
*/
public void setVGAGain(HackRFVGAGain gain) throws UsbException
{
int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true);
if(result != 1)
{
throw new UsbException("couldn't set vga gain to " + gain);
}
}
/**
* Not implemented
*/
public long getTunedFrequency() throws SourceException
{
return mFrequencyController.getTunedFrequency();
}
@Override
public void setTunedFrequency(long frequency) throws SourceException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
int mhz = (int)(frequency / 1E6);
int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
buffer.rewind();
try
{
write(Request.SET_FREQUENCY, 0, 0, buffer);
}
catch(UsbException e)
{
mLog.error("error setting frequency [" + frequency + "]", e);
throw new SourceException("error setting frequency [" +
frequency + "]", e);
}
}
@Override
public int getCurrentSampleRate() throws SourceException
{
return mSampleRate.getRate();
}
@Override
public void apply(TunerConfiguration config) throws SourceException
{
if(config instanceof HackRFTunerConfiguration)
{
HackRFTunerConfiguration hackRFConfig =
(HackRFTunerConfiguration)config;
try
{
setSampleRate(hackRFConfig.getSampleRate());
setFrequencyCorrection(hackRFConfig.getFrequencyCorrection());
setAmplifierEnabled(hackRFConfig.getAmplifierEnabled());
setLNAGain(hackRFConfig.getLNAGain());
setVGAGain(hackRFConfig.getVGAGain());
setFrequency(getFrequency());
}
catch(UsbException e)
{
throw new SourceException("Error while applying tuner "
+ "configuration", e);
}
try
{
setFrequency(hackRFConfig.getFrequency());
}
catch(SourceException se)
{
//Do nothing, we couldn't set the frequency
}
}
else
{
throw new IllegalArgumentException("Invalid tuner configuration "
+ "type [" + config.getClass() + "]");
}
}
public ByteBuffer readArray(Request request,
int value,
int index,
int length) throws UsbException
{
if(mDeviceHandle != null)
{
ByteBuffer buffer = ByteBuffer.allocateDirect(length);
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_IN,
request.getRequestNumber(),
(short)value,
(short)index,
buffer,
USB_TIMEOUT_US);
if(transferred < 0)
{
throw new LibUsbException("read error", transferred);
}
return buffer;
}
else
{
throw new LibUsbException("device handle is null",
LibUsb.ERROR_NO_DEVICE);
}
}
public int read(Request request, int value, int index, int length)
throws UsbException
{
if(!(length == 1 || length == 2 || length == 4))
{
throw new IllegalArgumentException("invalid length [" + length +
"] must be: byte=1, short=2, int=4 to read a primitive");
}
ByteBuffer buffer = readArray(request, value, index, length);
byte[] data = new byte[buffer.capacity()];
buffer.get(data);
switch(data.length)
{
case 1:
return data[0];
case 2:
return EndianUtils.readSwappedShort(data, 0);
case 4:
return EndianUtils.readSwappedInteger(data, 0);
default:
throw new UsbException("read() primitive returned an "
+ "unrecognized byte array " + Arrays.toString(data));
}
}
public int readByte(Request request, int value, int index, boolean signed)
throws UsbException
{
ByteBuffer buffer = readArray(request, value, index, 1);
if(signed)
{
return (int)(buffer.get());
}
else
{
return (int)(buffer.get() & 0xFF);
}
}
public void write(Request request,
int value,
int index,
ByteBuffer buffer) throws UsbException
{
if(mDeviceHandle != null)
{
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_OUT,
request.getRequestNumber(),
(short)value,
(short)index,
buffer,
USB_TIMEOUT_US);
if(transferred < 0)
{
throw new LibUsbException("error writing byte buffer",
transferred);
}
else if(transferred != buffer.capacity())
{
throw new LibUsbException("transferred bytes [" +
transferred + "] is not what was expected [" +
buffer.capacity() + "]", transferred);
}
}
else
{
throw new LibUsbException("device handle is null",
LibUsb.ERROR_NO_DEVICE);
}
}
/**
* Sends a request that doesn't have a data payload
*/
public void write(Request request,
int value,
int index) throws UsbException
{
write(request, value, index, ByteBuffer.allocateDirect(0));
}
/**
* Sample Rate
*
* Note: the libhackrf set sample rate method is designed to allow fractional
* sample rates. However, since we're only using integral sample rates, we
* simply invoke the setSampleRateManual method directly.
*/
public void setSampleRate(HackRFSampleRate rate) throws UsbException, SourceException
{
setSampleRateManual(rate.getRate(), 1);
mFrequencyController.setSampleRate(rate.getRate());
setBasebandFilter(rate.getFilter());
mSampleRate = rate;
}
public void setSampleRateManual(int frequency, int divider)
throws UsbException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(frequency);
buffer.putInt(divider);
write(Request.SET_SAMPLE_RATE, 0, 0, buffer);
}
public int getSampleRate()
{
return mSampleRate.getRate();
}
public enum Request
{
SET_TRANSCEIVER_MODE(1),
MAX2837_TRANSCEIVER_WRITE(2),
MAX2837_TRANSCEIVER_READ(3),
SI5351C_CLOCK_GENERATOR_WRITE(4),
SI5351C_CLOCK_GENERATOR_READ(5),
SET_SAMPLE_RATE(6),
BASEBAND_FILTER_BANDWIDTH_SET(7),
RFFC5071_MIXER_WRITE(8),
RFFC5071_MIXER_READ(9),
SPIFLASH_ERASE(10),
SPIFLASH_WRITE(11),
SPIFLASH_READ(12),
BOARD_ID_READ(14),
VERSION_STRING_READ(15),
SET_FREQUENCY(16),
AMP_ENABLE(17),
BOARD_PARTID_SERIALNO_READ(18),
SET_LNA_GAIN(19),
SET_VGA_GAIN(20),
SET_TXVGA_GAIN(21),
ANTENNA_ENABLE(23),
SET_FREQUENCY_EXPLICIT(24);
private byte mRequestNumber;
private Request(int number)
{
mRequestNumber = (byte)number;
}
public byte getRequestNumber()
{
return mRequestNumber;
}
}
public enum HackRFSampleRate
{
RATE2_016MHZ(2016000, "2.016 MHz", BasebandFilter.F3_50),
RATE3_024MHZ(3024000, "3.024 MHz", BasebandFilter.F5_00),
RATE4_464MHZ(4464000, "4.464 MHz", BasebandFilter.F6_00),
RATE5_376MHZ(5376000, "5.376 MHz", BasebandFilter.F7_00),
RATE7_488MHZ(7488000, "7.488 MHz", BasebandFilter.F9_00),
RATE10_080MHZ(10080000, "10.080 MHz", BasebandFilter.F12_00),
RATE12_000MHZ(12000000, "12.000 MHz", BasebandFilter.F14_00),
RATE13_440MHZ(13440000, "13.440 MHz", BasebandFilter.F15_00),
RATE14_976MHZ(14976000, "14.976 MHz", BasebandFilter.F20_00),
RATE19_968MHZ(19968000, "19.968 MHz", BasebandFilter.F24_00);
private int mRate;
private String mLabel;
private BasebandFilter mFilter;
private HackRFSampleRate(int rate,
String label,
BasebandFilter filter)
{
mRate = rate;
mLabel = label;
mFilter = filter;
}
public int getRate()
{
return mRate;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
public BasebandFilter getFilter()
{
return mFilter;
}
}
public enum BasebandFilter
{
FAUTO(0, "AUTO"),
F1_75(1750000, "1.75 MHz"),
F2_50(2500000, "2.50 MHz"),
F3_50(3500000, "3.50 MHz"),
F5_00(5000000, "5.00 MHz"),
F5_50(5500000, "5.50 MHz"),
F6_00(6000000, "6.00 MHz"),
F7_00(7000000, "7.00 MHz"),
F8_00(8000000, "8.00 MHz"),
F9_00(9000000, "9.00 MHz"),
F10_00(10000000, "10.00 MHz"),
F12_00(12000000, "12.00 MHz"),
F14_00(14000000, "14.00 MHz"),
F15_00(15000000, "15.00 MHz"),
F20_00(20000000, "20.00 MHz"),
F24_00(24000000, "24.00 MHz"),
F28_00(28000000, "28.00 MHz");
private int mBandwidth;
private String mLabel;
private BasebandFilter(int bandwidth, String label)
{
mBandwidth = bandwidth;
mLabel = label;
}
public int getBandwidth()
{
return mBandwidth;
}
public int getHighValue()
{
return mBandwidth >> 16;
}
public int getLowValue()
{
return mBandwidth & 0xFFFF;
}
public String getLabel()
{
return mLabel;
}
}
public enum BoardID
{
JELLYBEAN(0x00, "HackRF Jelly Bean"),
JAWBREAKER(0x01, "HackRF Jaw Breaker"),
HACKRF_ONE(0x02, "HackRF One"),
INVALID(0xFF, "HackRF Unknown Board");
private byte mIDNumber;
private String mLabel;
private BoardID(int number, String label)
{
mIDNumber = (byte)number;
mLabel = label;
}
public String toString()
{
return mLabel;
}
public String getLabel()
{
return mLabel;
}
public byte getNumber()
{
return mIDNumber;
}
public static BoardID lookup(int value)
{
switch(value)
{
case 0:
return JELLYBEAN;
case 1:
return JAWBREAKER;
case 2:
return HACKRF_ONE;
default:
return INVALID;
}
}
}
public enum Mode
{
OFF(0, "Off"),
RECEIVE(1, "Receive"),
TRANSMIT(2, "Transmit"),
SS(3, "SS");
private byte mNumber;
private String mLabel;
private Mode(int number, String label)
{
mNumber = (byte)number;
mLabel = label;
}
public byte getNumber()
{
return mNumber;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
}
public enum HackRFLNAGain
{
GAIN_0(0),
GAIN_8(8),
GAIN_16(16),
GAIN_24(24),
GAIN_32(32),
GAIN_40(40);
private int mValue;
private HackRFLNAGain(int value)
{
mValue = value;
}
public int getValue()
{
return mValue;
}
public String toString()
{
return String.valueOf(mValue) + " dB";
}
}
/**
* Receive (baseband) VGA Gain values
*/
public enum HackRFVGAGain
{
GAIN_0(0),
GAIN_2(2),
GAIN_4(4),
GAIN_6(6),
GAIN_8(8),
GAIN_10(10),
GAIN_12(12),
GAIN_14(14),
GAIN_16(16),
GAIN_18(18),
GAIN_20(20),
GAIN_22(22),
GAIN_23(24),
GAIN_26(26),
GAIN_28(28),
GAIN_30(30),
GAIN_32(32),
GAIN_34(34),
GAIN_36(36),
GAIN_38(38),
GAIN_40(40),
GAIN_42(42),
GAIN_44(44),
GAIN_46(46),
GAIN_48(48),
GAIN_50(50),
GAIN_52(52),
GAIN_54(54),
GAIN_56(56),
GAIN_58(58),
GAIN_60(60),
GAIN_62(62);
private int mValue;
private HackRFVGAGain(int value)
{
mValue = value;
}
public int getValue()
{
return mValue;
}
public String toString()
{
return String.valueOf(mValue) + " dB";
}
}
/**
* HackRF part id and serial number parsing class
*/
public class Serial
{
private byte[] mData;
public Serial(ByteBuffer buffer)
{
mData = new byte[buffer.capacity()];
buffer.get(mData);
}
public String getPartID()
{
int part0 = EndianUtils.readSwappedInteger(mData, 0);
int part1 = EndianUtils.readSwappedInteger(mData, 4);
StringBuilder sb = new StringBuilder();
sb.append(String.format("%08X", part0));
sb.append("-");
sb.append(String.format("%08X", part1));
return sb.toString();
}
public String getSerialNumber()
{
int serial0 = EndianUtils.readSwappedInteger(mData, 8);
int serial1 = EndianUtils.readSwappedInteger(mData, 12);
int serial2 = EndianUtils.readSwappedInteger(mData, 16);
int serial3 = EndianUtils.readSwappedInteger(mData, 20);
StringBuilder sb = new StringBuilder();
sb.append(String.format("%08X", serial0));
sb.append("-");
sb.append(String.format("%08X", serial1));
sb.append("-");
sb.append(String.format("%08X", serial2));
sb.append("-");
sb.append(String.format("%08X", serial3));
return sb.toString();
}
}
/**
* Adds a sample listener. If the buffer processing thread is
* not currently running, starts it running in a new thread.
*/
public void addListener(Listener<ComplexBuffer> listener)
{
mUSBTransferProcessor.addListener(listener);
}
/**
* Removes the sample listener. If this is the last registered listener,
* shuts down the buffer processing thread.
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
mUSBTransferProcessor.removeListener(listener);
}
}

View File

@@ -0,0 +1,445 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.hackrf;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.usb.UsbException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
public class HackRFTunerEditor extends TunerConfigurationEditor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( HackRFTunerEditor.class );
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JComboBox<HackRFTunerController.HackRFSampleRate> mComboSampleRate;
private JSpinner mFrequencyCorrection;
private JToggleButton mAmplifier;
private JComboBox<HackRFTunerController.HackRFLNAGain> mComboLNAGain;
private JComboBox<HackRFTunerController.HackRFVGAGain> mComboVGAGain;
private boolean mLoading;
private HackRFTunerController mController;
public HackRFTunerEditor(TunerConfigurationModel tunerConfigurationModel, HackRFTuner tuner )
{
super( tunerConfigurationModel );
mController = tuner.getController();
init();
}
private HackRFTunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (HackRFTunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][][grow]" ) );
add( new JLabel( "HackRF Tuner Configuration" ), "span,align center" );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( HackRFTunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
mComboSampleRate = new JComboBox<>( HackRFTunerController.HackRFSampleRate.values() );
mComboSampleRate.setEnabled( false );
mComboSampleRate.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
HackRFTunerController.HackRFSampleRate sampleRate =
(HackRFTunerController.HackRFSampleRate)mComboSampleRate.getSelectedItem();
try
{
mController.setSampleRate( sampleRate );
save();
}
catch ( SourceException |UsbException e2 )
{
JOptionPane.showMessageDialog( HackRFTunerEditor.this, "HackRF Tuner Controller"
+ " - couldn't apply the sample rate setting [" + sampleRate.getLabel() +
"] " + e2.getLocalizedMessage() );
mLog.error( "HackRF Tuner Controller - couldn't apply sample rate setting [" +
sampleRate.getLabel() + "]", e );
}
}
} );
add( new JLabel( "Sample Rate:" ) );
add( mComboSampleRate );
SpinnerModel model = new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor = (JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
final double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )
{
JOptionPane.showMessageDialog( HackRFTunerEditor.this, "HackRF Tuner Controller"
+ " - couldn't apply frequency correction value: " + value +
e1.getLocalizedMessage() );
mLog.error( "HackRF Tuner Controller - couldn't apply frequency correction "
+ "value: " + value, e1 );
}
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
add( new JLabel( "Gain" ) );
add( new JLabel( "" ), "span 2" ); //filler
mAmplifier = new JToggleButton( "Amplifier" );
mAmplifier.setEnabled( false );
mAmplifier.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
mController.setAmplifierEnabled( mAmplifier.isSelected() );
save();
}
catch ( UsbException e )
{
mLog.error( "couldn't enable/disable amplifier", e );
JOptionPane.showMessageDialog( HackRFTunerEditor.this, "Couldn't change amplifier setting",
"Error changing amplifier setting", JOptionPane.ERROR_MESSAGE );
}
}
} );
add( mAmplifier );
mComboLNAGain = new JComboBox<HackRFTunerController.HackRFLNAGain>( HackRFTunerController.HackRFLNAGain.values() );
mComboLNAGain.setEnabled( false );
mComboLNAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
HackRFTunerController.HackRFLNAGain lnaGain = (HackRFTunerController.HackRFLNAGain)mComboLNAGain.getSelectedItem();
if ( lnaGain == null )
{
lnaGain = HackRFTunerController.HackRFLNAGain.GAIN_0;
}
mController.setLNAGain( lnaGain );
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( HackRFTunerEditor.this, "HackRF Tuner Controller"
+ " - couldn't apply the LNA gain setting - " + e.getLocalizedMessage() );
mLog.error( "HackRF Tuner Controller - couldn't apply LNA gain setting - ", e );
}
}
} );
mComboLNAGain.setToolTipText( "<html>LNA Gain. Adjust to set the IF gain</html>" );
add( new JLabel( "LNA:" ) );
add( mComboLNAGain );
mComboVGAGain = new JComboBox<HackRFTunerController.HackRFVGAGain>( HackRFTunerController.HackRFVGAGain.values() );
mComboVGAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
HackRFTunerController.HackRFVGAGain vgaGain = (HackRFTunerController.HackRFVGAGain)mComboVGAGain.getSelectedItem();
if( vgaGain == null )
{
vgaGain = HackRFTunerController.HackRFVGAGain.GAIN_4;
}
mController.setVGAGain( vgaGain );
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( HackRFTunerEditor.this, "HackRF Tuner Controller"
+ " - couldn't apply the VGA gain setting - " + e.getLocalizedMessage() );
mLog.error( "HackRF Tuner Controller - couldn't apply VGA gain setting", e );
}
}
} );
mComboVGAGain.setToolTipText( "<html>VGA Gain. Adjust to set the baseband gain</html>" );
add( new JLabel( "VGA:" ) );
add( mComboVGAGain );
}
/**
* Sets each of the tuner configuration controls to the enabled argument state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mComboSampleRate.isEnabled() != enabled )
{
mComboSampleRate.setEnabled( enabled );
}
if( mAmplifier.isEnabled() != enabled )
{
mAmplifier.setEnabled( enabled );
}
if( mComboLNAGain.isEnabled() != enabled )
{
mComboLNAGain.setEnabled( enabled );
}
if( mComboVGAGain.isEnabled() != enabled )
{
mComboVGAGain.setEnabled( enabled );
}
}
private String getTunerInfo()
{
HackRFTunerController.BoardID board = HackRFTunerController.BoardID.INVALID;
try
{
board = mController.getBoardID();
}
catch ( UsbException e )
{
mLog.error( "couldn't read HackRF board identifier", e );
}
StringBuilder sb = new StringBuilder();
sb.append( "<html><h3>HackRF Tuner</h3>" );
sb.append( "<b>Board: </b>" );
sb.append( board.getLabel() );
sb.append( "<br>" );
HackRFTunerController.Serial serial = null;
try
{
serial = mController.getSerial();
}
catch( Exception e )
{
mLog.error( "couldn't read HackRF serial number", e );
}
if( serial != null )
{
sb.append( "<b>Serial: </b>" );
sb.append( serial.getSerialNumber() );
sb.append( "<br>" );
sb.append( "<b>Part: </b>" );
sb.append( serial.getPartID() );
sb.append( "<br>" );
}
else
{
sb.append( "<b>Serial: Unknown</b><br>" );
sb.append( "<b>Part: Unknown</b><br>" );
}
String firmware = null;
try
{
firmware = mController.getFirmwareVersion();
}
catch( Exception e )
{
mLog.error( "couldn't read HackRF firmware version", e );
}
if( firmware != null )
{
sb.append( "<b>Firmware: </b>" );
sb.append( firmware );
sb.append( "<br>" );
}
else
{
sb.append( "<b>Firmware: Unknown</b><br>" );
}
return sb.toString();
}
@Override
public void setItem( TunerConfiguration tunerConfiguration )
{
super.setItem( tunerConfiguration );
//Toggle loading so that we don't fire a change event and schedule a settings file save
mLoading = true;
if( hasItem() )
{
HackRFTunerConfiguration config = getConfiguration();
if( tunerConfiguration.isAssigned() )
{
setControlsEnabled( true );
mConfigurationName.setText( config.getName() );
mComboSampleRate.setSelectedItem( config.getSampleRate() );
mFrequencyCorrection.setValue( config.getFrequencyCorrection() );
mAmplifier.setSelected( config.getAmplifierEnabled() );
mComboLNAGain.setSelectedItem( config.getLNAGain() );
mComboVGAGain.setSelectedItem( config.getVGAGain() );
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( config.getName() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( "" );
}
mLoading = false;
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
HackRFTunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
config.setSampleRate( (HackRFTunerController.HackRFSampleRate)mComboSampleRate.getSelectedItem() );
config.setAmplifierEnabled( mAmplifier.isSelected() );
config.setLNAGain( (HackRFTunerController.HackRFLNAGain)mComboLNAGain.getSelectedItem() );
config.setVGAGain( (HackRFTunerController.HackRFVGAGain)mComboVGAGain.getSelectedItem() );
getTunerConfigurationModel().broadcast(
new TunerConfigurationEvent( getConfiguration(), TunerConfigurationEvent.Event.CHANGE ) );
}
}
}

View File

@@ -0,0 +1,159 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.rtl;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerChannel;
import io.github.dsheirer.source.tuner.TunerChannelSource;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.TunerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import java.util.concurrent.RejectedExecutionException;
public class RTL2832Tuner extends Tuner
{
private final static Logger mLog =
LoggerFactory.getLogger( RTL2832Tuner.class );
private TunerClass mTunerClass;
public RTL2832Tuner( TunerClass tunerClass,
RTL2832TunerController controller )
throws SourceException
{
super( tunerClass.getVendorDeviceLabel() + "/" +
controller.getTunerType().getLabel() + " " +
controller.getUniqueID(), controller );
mTunerClass = tunerClass;
}
public void dispose()
{
//TODO: dispose of something here
}
public RTL2832TunerController getController()
{
return (RTL2832TunerController)getTunerController();
}
@Override
public String getUniqueID()
{
return getController().getUniqueID();
}
@Override
public TunerClass getTunerClass()
{
return mTunerClass;
}
@Override
public TunerType getTunerType()
{
return getController().getTunerType();
}
public void setSampleRate( RTL2832TunerController.SampleRate sampleRate ) throws SourceException
{
try
{
getController().setSampleRate( sampleRate );
}
catch( LibUsbException e )
{
throw new SourceException( "RTL2832 Tuner - error setting "
+ "sample rate", e );
}
}
@Override
public double getSampleSize()
{
//Note: although sample size is 8, we set it to 11 to align with the
//actual noise floor.
return 11.0;
}
public void setFrequency( int frequency ) throws SourceException
{
getController().setFrequency( frequency );
}
@Override
public TunerChannelSource getChannel(TunerChannel channel )
throws RejectedExecutionException,
SourceException
{
TunerChannelSource source = null;
try
{
source = getController().getChannel( this, channel );
if( source != null )
{
broadcast( new TunerEvent( this, TunerEvent.Event.CHANNEL_COUNT ) );
}
}
catch( Exception e )
{
mLog.error( "couldn't provide source channel", e );
}
return source;
}
/**
* Releases the tuned channel so that the tuner controller can tune to
* other frequencies as needed.
*/
@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 )
{
getController().releaseChannel( source );
}
}
@Override
public void addListener( Listener<ComplexBuffer> listener )
{
getController().addListener( listener );
}
@Override
public void removeListener( Listener<ComplexBuffer> listener )
{
getController().removeListener( listener );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.rtl.e4k;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KEnhanceGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KLNAGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KMixerGain;
import javax.xml.bind.annotation.XmlAttribute;
public class E4KTunerConfiguration extends TunerConfiguration
{
private E4KGain mMasterGain = E4KGain.MANUAL;
private E4KMixerGain mMixerGain = E4KMixerGain.GAIN_4;
private E4KLNAGain mLNAGain = E4KLNAGain.GAIN_PLUS_200;
private E4KEnhanceGain mEnhanceGain = E4KEnhanceGain.GAIN_3;
private double mFrequencyCorrection = 0.0d;
private RTL2832TunerController.SampleRate mSampleRate = RTL2832TunerController.SampleRate.RATE_2_400MHZ;
/**
* Default constructor for JAXB
*/
public E4KTunerConfiguration()
{
}
public E4KTunerConfiguration( String uniqueID, String name )
{
super( uniqueID, name );
}
@Override
public TunerType getTunerType()
{
return TunerType.ELONICS_E4000;
}
@XmlAttribute( name="master_gain" )
public E4KGain getMasterGain()
{
return mMasterGain;
}
public void setMasterGain( E4KGain gain )
{
mMasterGain = gain;
}
@XmlAttribute( name="mixer_gain" )
public E4KMixerGain getMixerGain()
{
return mMixerGain;
}
public void setMixerGain( E4KMixerGain mixerGain )
{
mMixerGain = mixerGain;
}
@XmlAttribute( name="lna_gain" )
public E4KLNAGain getLNAGain()
{
return mLNAGain;
}
public void setLNAGain( E4KLNAGain lnaGain )
{
mLNAGain = lnaGain;
}
@XmlAttribute( name="enhance_gain" )
public E4KEnhanceGain getEnhanceGain()
{
return mEnhanceGain;
}
public void setEnhanceGain( E4KEnhanceGain enhanceGain )
{
mEnhanceGain = enhanceGain;
}
@XmlAttribute( name="frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
@XmlAttribute( name="sample_rate" )
public RTL2832TunerController.SampleRate getSampleRate()
{
return mSampleRate;
}
public void setSampleRate( RTL2832TunerController.SampleRate sampleRate )
{
mSampleRate = sampleRate;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,486 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.rtl.e4k;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KEnhanceGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KLNAGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController.E4KMixerGain;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.usb.UsbException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
public class E4KTunerEditor extends TunerConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger( E4KTunerEditor.class );
private static final long serialVersionUID = 1L;
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JComboBox<RTL2832TunerController.SampleRate> mComboSampleRate;
private JSpinner mFrequencyCorrection;
private JComboBox<E4KGain> mComboMasterGain;
private JComboBox<E4KMixerGain> mComboMixerGain;
private JComboBox<E4KLNAGain> mComboLNAGain;
private JComboBox<E4KEnhanceGain> mComboEnhanceGain;
private E4KTunerController mController;
private boolean mLoading;
public E4KTunerEditor(TunerConfigurationModel tunerConfigurationModel, RTL2832Tuner tuner )
{
super( tunerConfigurationModel );
mController = (E4KTunerController)tuner.getController();
init();
}
private E4KTunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (E4KTunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][][][grow]" ) );
add( new JLabel( "E4000 Tuner Configuration" ), "span,align center" );
/**
* Tuner Configuration Name
*/
add( new JLabel( "Name:" ) );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
mComboSampleRate = new JComboBox<>( RTL2832TunerController.SampleRate.values() );
mComboSampleRate.setEnabled( false );
mComboSampleRate.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
RTL2832TunerController.SampleRate sampleRate = (RTL2832TunerController.SampleRate)mComboSampleRate.getSelectedItem();
try
{
mController.setSampleRate( sampleRate );
save();
}
catch ( SourceException | LibUsbException eSampleRate )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this,
"E4K Tuner Controller - couldn't apply the sample rate setting [" +
sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply sample rate setting [" +
sampleRate.getLabel() + "]", eSampleRate );
}
}
} );
add( new JLabel( "Sample Rate:" ) );
add( mComboSampleRate );
SpinnerModel model = new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
final double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
save();
}
catch ( SourceException e1 )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, "E4K Tuner Controller - "
+ "couldn't apply frequency correction value: " + value + e1.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply frequency correction value:"
+ value, e1 );
}
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
add( new JLabel( "Gain" ), "wrap" );
mComboMasterGain = new JComboBox<E4KGain>( E4KGain.values() );
mComboMasterGain.setEnabled( false );
mComboMasterGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
E4KGain gain = (E4KGain)mComboMasterGain.getSelectedItem();
mController.setGain( (E4KGain)mComboMasterGain.getSelectedItem(), true );
if( gain == E4KGain.MANUAL )
{
mComboMixerGain.setSelectedItem( mController.getMixerGain( true ) );
mComboMixerGain.setEnabled( true );
mComboLNAGain.setSelectedItem( mController.getLNAGain( true ) );
mComboLNAGain.setEnabled( true );
mComboEnhanceGain.setSelectedItem( mController.getEnhanceGain( true ) );
mComboEnhanceGain.setEnabled( true );
}
else
{
mComboMixerGain.setEnabled( false );
mComboMixerGain.setSelectedItem( gain.getMixerGain() );
mComboLNAGain.setEnabled( false );
mComboLNAGain.setSelectedItem( gain.getLNAGain() );
mComboEnhanceGain.setEnabled( false );
mComboEnhanceGain.setSelectedItem( gain.getEnhanceGain() );
}
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, "E4K Tuner Controller - "
+ "couldn't apply the gain setting - " + e.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply gain setting", e );
}
}
} );
mComboMasterGain.setToolTipText( "<html>Select <b>AUTOMATIC</b> for auto "
+ "gain, <b>MANUAL</b> to enable<br> independent control of "
+ "<i>Mixer</i>, <i>LNA</i> and <i>Enhance</i> gain<br>"
+ "settings, or one of the individual gain settings for<br>"
+ "semi-manual gain control</html>" );
add( new JLabel( "Master:" ) );
add( mComboMasterGain );
mComboMixerGain = new JComboBox<E4KMixerGain>( E4KMixerGain.values() );
mComboMixerGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
E4KMixerGain mixerGain = (E4KMixerGain)mComboMixerGain.getSelectedItem();
mController.setMixerGain( mixerGain, true );
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, "E4K Tuner Controller - "
+ "couldn't apply the mixer gain setting - " + e.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply mixer gain setting", e );
}
}
} );
mComboMixerGain.setToolTipText( "<html>Mixer Gain. Set master gain "
+ "to <b>MASTER</b> to enable adjustment</html>" );
mComboMixerGain.setEnabled( false );
add( new JLabel( "Mixer:" ) );
add( mComboMixerGain );
mComboLNAGain = new JComboBox<E4KLNAGain>( E4KLNAGain.values() );
mComboLNAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
E4KLNAGain lnaGain = (E4KLNAGain)mComboLNAGain.getSelectedItem();
mController.setLNAGain( lnaGain, true );
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, "E4K Tuner Controller - "
+ "couldn't apply the LNA gain setting - " + e.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply LNA gain setting - ", e );
}
}
} );
mComboLNAGain.setToolTipText( "<html>LNA Gain. Set master gain "
+ "to <b>MANUAL</b> to enable adjustment</html>" );
mComboLNAGain.setEnabled( false );
add( new JLabel( "LNA:" ) );
add( mComboLNAGain );
mComboEnhanceGain = new JComboBox<E4KEnhanceGain>( E4KEnhanceGain.values() );
mComboEnhanceGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
E4KEnhanceGain enhance = (E4KEnhanceGain)mComboEnhanceGain.getSelectedItem();
mController.setEnhanceGain( enhance, true );
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( E4KTunerEditor.this, "E4K Tuner Controller - "
+ "couldn't apply the enhance gain setting - " + e.getLocalizedMessage() );
mLog.error( "E4K Tuner Controller - couldn't apply enhance gain setting", e );
}
}
} );
mComboEnhanceGain.setToolTipText( "<html>Enhance Gain. Set master gain "
+ "to <b>MANUAL</b> to enable adjustment</html>" );
mComboEnhanceGain.setEnabled( false );
add( new JLabel( "Enhance:" ) );
add( mComboEnhanceGain );
}
/**
* Sets each of the tuner configuration controls to the enabled argument state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mComboSampleRate.isEnabled() != enabled )
{
mComboSampleRate.setEnabled( enabled );
}
if( mComboMasterGain.isEnabled() != enabled )
{
mComboMasterGain.setEnabled( enabled );
}
if( mComboMixerGain.isEnabled() != enabled )
{
mComboMixerGain.setEnabled( enabled );
}
if( mComboLNAGain.isEnabled() != enabled )
{
mComboLNAGain.setEnabled( enabled );
}
if( mComboEnhanceGain.isEnabled() != enabled )
{
mComboEnhanceGain.setEnabled( enabled );
}
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
RTL2832TunerController.Descriptor descriptor = mController.getDescriptor();
sb.append( "<html><h3>RTL-2832 with E4000 Tuner</h3>" );
if( descriptor == null )
{
sb.append( "No EEPROM Descriptor Available" );
}
else
{
sb.append( "<b>USB ID: </b>" );
sb.append( descriptor.getVendorID() );
sb.append( ":" );
sb.append( descriptor.getProductID() );
sb.append( "<br>" );
sb.append( "<b>Vendor: </b>" );
sb.append( descriptor.getVendorLabel() );
sb.append( "<br>" );
sb.append( "<b>Product: </b>" );
sb.append( descriptor.getProductLabel() );
sb.append( "<br>" );
sb.append( "<b>Serial: </b>" );
sb.append( descriptor.getSerial() );
sb.append( "<br>" );
sb.append( "<b>IR Enabled: </b>" );
sb.append( descriptor.irEnabled() );
sb.append( "<br>" );
sb.append( "<b>Remote Wake: </b>" );
sb.append( descriptor.remoteWakeupEnabled() );
sb.append( "<br>" );
}
return sb.toString();
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
E4KTunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
config.setSampleRate( (RTL2832TunerController.SampleRate)mComboSampleRate.getSelectedItem() );
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
config.setMasterGain( (E4KGain)mComboMasterGain.getSelectedItem() );
config.setMixerGain( (E4KMixerGain)mComboMixerGain.getSelectedItem() );
config.setLNAGain( (E4KLNAGain)mComboLNAGain.getSelectedItem() );
config.setEnhanceGain( (E4KEnhanceGain)mComboEnhanceGain.getSelectedItem() );
getTunerConfigurationModel().broadcast( new TunerConfigurationEvent(
getConfiguration(), TunerConfigurationEvent.Event.CHANGE ) );
}
}
@Override
public void setItem( TunerConfiguration tunerConfiguration )
{
super.setItem( tunerConfiguration );
//Toggle loading so that we don't fire a change event and schedule a settings file save
mLoading = true;
if( hasItem() )
{
E4KTunerConfiguration config = getConfiguration();
if( tunerConfiguration.isAssigned() )
{
setControlsEnabled( true );
mConfigurationName.setText( config.getName() );
mFrequencyCorrection.setValue( config.getFrequencyCorrection() );
mComboSampleRate.setSelectedItem( config.getSampleRate() );
mComboMasterGain.setSelectedItem( config.getMasterGain() );
mComboMixerGain.setSelectedItem( config.getMixerGain() );
mComboLNAGain.setSelectedItem( config.getLNAGain() );
mComboEnhanceGain.setSelectedItem( config.getEnhanceGain() );
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( config.getName() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( "" );
}
mLoading = false;
}
}

View File

@@ -0,0 +1,118 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.rtl.r820t;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import javax.xml.bind.annotation.XmlAttribute;
public class R820TTunerConfiguration extends TunerConfiguration
{
private R820TTunerController.R820TGain mMasterGain = R820TTunerController.R820TGain.GAIN_327;
private R820TTunerController.R820TMixerGain mMixerGain = R820TTunerController.R820TMixerGain.GAIN_105;
private R820TTunerController.R820TLNAGain mLNAGain = R820TTunerController.R820TLNAGain.GAIN_222;
private R820TTunerController.R820TVGAGain mVGAGain = R820TTunerController.R820TVGAGain.GAIN_210;
private double mFrequencyCorrection = 0.0d;
private RTL2832TunerController.SampleRate mSampleRate = RTL2832TunerController.SampleRate.RATE_2_400MHZ;
/**
* Default constructor for JAXB
*/
public R820TTunerConfiguration()
{
}
public R820TTunerConfiguration( String uniqueID, String name )
{
super( uniqueID, name );
}
@Override
public TunerType getTunerType()
{
return TunerType.RAFAELMICRO_R820T;
}
@XmlAttribute( name = "master_gain" )
public R820TTunerController.R820TGain getMasterGain()
{
return mMasterGain;
}
public void setMasterGain( R820TTunerController.R820TGain gain )
{
mMasterGain = gain;
}
@XmlAttribute( name = "mixer_gain" )
public R820TTunerController.R820TMixerGain getMixerGain()
{
return mMixerGain;
}
public void setMixerGain( R820TTunerController.R820TMixerGain mixerGain )
{
mMixerGain = mixerGain;
}
@XmlAttribute( name = "lna_gain" )
public R820TTunerController.R820TLNAGain getLNAGain()
{
return mLNAGain;
}
public void setLNAGain( R820TTunerController.R820TLNAGain lnaGain )
{
mLNAGain = lnaGain;
}
@XmlAttribute( name = "vga_gain" )
public R820TTunerController.R820TVGAGain getVGAGain()
{
return mVGAGain;
}
public void setVGAGain( R820TTunerController.R820TVGAGain vgaGain )
{
mVGAGain = vgaGain;
}
@XmlAttribute( name = "frequency_correction" )
public double getFrequencyCorrection()
{
return mFrequencyCorrection;
}
public void setFrequencyCorrection( double value )
{
mFrequencyCorrection = value;
}
@XmlAttribute( name = "sample_rate" )
public RTL2832TunerController.SampleRate getSampleRate()
{
return mSampleRate;
}
public void setSampleRate( RTL2832TunerController.SampleRate sampleRate )
{
mSampleRate = sampleRate;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,541 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 io.github.dsheirer.source.tuner.rtl.r820t;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEvent;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsbException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.usb.UsbException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;
public class R820TTunerEditor extends TunerConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger( R820TTunerEditor.class );
private static final long serialVersionUID = 1L;
private static final R820TTunerController.R820TGain DEFAULT_GAIN = R820TTunerController.R820TGain.GAIN_279;
private R820TTunerController mController;
private JTextField mConfigurationName;
private JButton mTunerInfo;
private JComboBox<RTL2832TunerController.SampleRate> mComboSampleRate;
private JSpinner mFrequencyCorrection;
private JComboBox<R820TTunerController.R820TGain> mComboMasterGain;
private JComboBox<R820TTunerController.R820TMixerGain> mComboMixerGain;
private JComboBox<R820TTunerController.R820TLNAGain> mComboLNAGain;
private JComboBox<R820TTunerController.R820TVGAGain> mComboVGAGain;
private boolean mLoading;
public R820TTunerEditor(TunerConfigurationModel tunerConfigurationModel, RTL2832Tuner tuner )
{
super( tunerConfigurationModel );
mController = (R820TTunerController)tuner.getController();
init();
}
private R820TTunerConfiguration getConfiguration()
{
if( hasItem() )
{
return (R820TTunerConfiguration)getItem();
}
return null;
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]",
"[][][][][][][][grow]" ) );
add( new JLabel( "R820T Tuner Configuration" ), "span,align center" );
mConfigurationName = new JTextField();
mConfigurationName.setEnabled( false );
mConfigurationName.addFocusListener( new FocusListener()
{
@Override
public void focusLost( FocusEvent e )
{
save();
}
@Override
public void focusGained( FocusEvent e ) {}
} );
add( new JLabel( "Name:" ) );
add( mConfigurationName, "span 2" );
mTunerInfo = new JButton( "Tuner Info" );
mTunerInfo.setEnabled( false );
mTunerInfo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
JOptionPane.showMessageDialog( R820TTunerEditor.this, getTunerInfo(),
"Tuner Info", JOptionPane.INFORMATION_MESSAGE );
}
} );
add( mTunerInfo );
mComboSampleRate = new JComboBox<>( RTL2832TunerController.SampleRate.values() );
mComboSampleRate.setEnabled( false );
mComboSampleRate.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
RTL2832TunerController.SampleRate sampleRate =
(RTL2832TunerController.SampleRate)mComboSampleRate.getSelectedItem();
try
{
mController.setSampleRate( sampleRate );
save();
}
catch ( SourceException | LibUsbException eSampleRate )
{
JOptionPane.showMessageDialog(
R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the sample "
+ "rate setting [" + sampleRate.getLabel() + "] " +
eSampleRate.getLocalizedMessage() );
mLog.error( "R820T Tuner Controller - couldn't apply sample "
+ "rate setting [" + sampleRate.getLabel() + "]",
eSampleRate );
}
}
} );
add( new JLabel( "Sample Rate:" ) );
add( mComboSampleRate );
/**
* Frequency Correction
*/
SpinnerModel model =
new SpinnerNumberModel( 0.0, //initial value
-1000.0, //min
1000.0, //max
0.1 ); //step
mFrequencyCorrection = new JSpinner( model );
mFrequencyCorrection.setEnabled( false );
JSpinner.NumberEditor editor =
(JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
DecimalFormat format = editor.getFormat();
format.setMinimumFractionDigits( 1 );
editor.getTextField().setHorizontalAlignment( SwingConstants.CENTER );
mFrequencyCorrection.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
final double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
try
{
mController.setFrequencyCorrection( value );
}
catch ( SourceException e1 )
{
mLog.error( "Error setting frequency correction value", e1 );
}
save();
}
} );
add( new JLabel( "PPM:" ) );
add( mFrequencyCorrection );
add( new JSeparator( JSeparator.HORIZONTAL ), "span,grow" );
/**
* Gain Controls
*/
add( new JLabel( "Gain" ), "wrap" );
/* Master Gain Control */
mComboMasterGain = new JComboBox<R820TTunerController.R820TGain>( R820TTunerController.R820TGain.values() );
mComboMasterGain.setEnabled( false );
mComboMasterGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
R820TTunerController.R820TGain gain = (R820TTunerController.R820TGain)mComboMasterGain.getSelectedItem();
mController.setGain( (R820TTunerController.R820TGain)mComboMasterGain.getSelectedItem(), true );
if( gain == R820TTunerController.R820TGain.MANUAL )
{
mComboMixerGain.setSelectedItem( gain.getMixerGain() );
mComboMixerGain.setEnabled( true );
mComboLNAGain.setSelectedItem( gain.getLNAGain() );
mComboLNAGain.setEnabled( true );
mComboVGAGain.setSelectedItem( gain.getVGAGain() );
mComboVGAGain.setEnabled( true );
}
else
{
mComboMixerGain.setEnabled( false );
mComboMixerGain.setSelectedItem( gain.getMixerGain() );
mComboLNAGain.setEnabled( false );
mComboLNAGain.setSelectedItem( gain.getLNAGain() );
mComboVGAGain.setEnabled( false );
mComboVGAGain.setSelectedItem( gain.getVGAGain() );
}
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog(
R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the gain "
+ "setting - " + e.getLocalizedMessage() );
mLog.error( "R820T Tuner Controller - couldn't apply "
+ "gain setting - ", e );
}
}
} );
mComboMasterGain.setToolTipText( "<html>Select <b>AUTOMATIC</b> for auto "
+ "gain, <b>MANUAL</b> to enable<br> independent control of "
+ "<i>Mixer</i>, <i>LNA</i> and <i>Enhance</i> gain<br>"
+ "settings, or one of the individual gain settings for<br>"
+ "semi-manual gain control</html>" );
add( new JLabel( "Master:" ) );
add( mComboMasterGain );
/* Mixer Gain Control */
mComboMixerGain = new JComboBox<R820TTunerController.R820TMixerGain>( R820TTunerController.R820TMixerGain.values() );
mComboMixerGain.setEnabled( false );
mComboMixerGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
R820TTunerController.R820TMixerGain mixerGain =
(R820TTunerController.R820TMixerGain)mComboMixerGain.getSelectedItem();
if( mixerGain == null )
{
mixerGain = DEFAULT_GAIN.getMixerGain();
}
if( mComboMixerGain.isEnabled() )
{
mController.setMixerGain( mixerGain, true );
}
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the mixer "
+ "gain setting - " + e.getLocalizedMessage() );
mLog.error( "R820T Tuner Controller - couldn't apply mixer "
+ "gain setting - ", e );
}
}
} );
mComboMixerGain.setToolTipText( "<html>Mixer Gain. Set master gain "
+ "to <b>MANUAL</b> to enable adjustment</html>" );
add( new JLabel( "Mixer:" ) );
add( mComboMixerGain );
/* LNA Gain Control */
mComboLNAGain = new JComboBox<R820TTunerController.R820TLNAGain>( R820TTunerController.R820TLNAGain.values() );
mComboLNAGain.setEnabled( false );
mComboLNAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
R820TTunerController.R820TLNAGain lnaGain =
(R820TTunerController.R820TLNAGain)mComboLNAGain.getSelectedItem();
if ( lnaGain == null )
{
lnaGain = DEFAULT_GAIN.getLNAGain();
}
if( mComboLNAGain.isEnabled() )
{
mController.setLNAGain( lnaGain, true );
}
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the LNA "
+ "gain setting - " + e.getLocalizedMessage() );
mLog.error( "R820T Tuner Controller - couldn't apply LNA "
+ "gain setting - ", e );
}
}
} );
mComboLNAGain.setToolTipText( "<html>LNA Gain. Set master gain "
+ "to <b>MANUAL</b> to enable adjustment</html>" );
add( new JLabel( "LNA:" ) );
add( mComboLNAGain );
/* VGA Gain Control */
mComboVGAGain = new JComboBox<R820TTunerController.R820TVGAGain>( R820TTunerController.R820TVGAGain.values() );
mComboVGAGain.setEnabled( false );
mComboVGAGain.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
R820TTunerController.R820TVGAGain vgaGain =
(R820TTunerController.R820TVGAGain)mComboVGAGain.getSelectedItem();
if( vgaGain == null )
{
vgaGain = DEFAULT_GAIN.getVGAGain();
}
if( mComboVGAGain.isEnabled() )
{
mController.setVGAGain( vgaGain, true );
}
save();
}
catch ( UsbException e )
{
JOptionPane.showMessageDialog( R820TTunerEditor.this,
"R820T Tuner Controller - couldn't apply the VGA "
+ "gain setting - " + e.getLocalizedMessage() );
mLog.error( "R820T Tuner Controller - couldn't apply VGA "
+ "gain setting", e );
}
}
} );
mComboVGAGain.setToolTipText( "<html>VGA Gain. Set master gain "
+ "to <b>MANUAL</b> to enable adjustment</html>" );
add( new JLabel( "VGA:" ) );
add( mComboVGAGain );
}
/**
* Sets each of the tuner configuration controls to the enabled argument state
*/
private void setControlsEnabled( boolean enabled )
{
if( mConfigurationName.isEnabled() != enabled )
{
mConfigurationName.setEnabled( enabled );
}
if( mTunerInfo.isEnabled() != enabled )
{
mTunerInfo.setEnabled( enabled );
}
if( mFrequencyCorrection.isEnabled() != enabled )
{
mFrequencyCorrection.setEnabled( enabled );
}
if( mComboSampleRate.isEnabled() != enabled )
{
mComboSampleRate.setEnabled( enabled );
}
if( mComboMasterGain.isEnabled() != enabled )
{
mComboMasterGain.setEnabled( enabled );
}
if( mComboMixerGain.isEnabled() != enabled )
{
mComboMixerGain.setEnabled( enabled );
}
if( mComboLNAGain.isEnabled() != enabled )
{
mComboLNAGain.setEnabled( enabled );
}
if( mComboVGAGain.isEnabled() != enabled )
{
mComboVGAGain.setEnabled( enabled );
}
}
private String getTunerInfo()
{
StringBuilder sb = new StringBuilder();
RTL2832TunerController.Descriptor descriptor = mController.getDescriptor();
sb.append( "<html><h3>RTL-2832 with R820T Tuner</h3>" );
if( descriptor == null )
{
sb.append( "No EEPROM Descriptor Available" );
}
else
{
sb.append( "<b>USB ID: </b>" );
sb.append( descriptor.getVendorID() );
sb.append( ":" );
sb.append( descriptor.getProductID() );
sb.append( "<br>" );
sb.append( "<b>Vendor: </b>" );
sb.append( descriptor.getVendorLabel() );
sb.append( "<br>" );
sb.append( "<b>Product: </b>" );
sb.append( descriptor.getProductLabel() );
sb.append( "<br>" );
sb.append( "<b>Serial: </b>" );
sb.append( descriptor.getSerial() );
sb.append( "<br>" );
sb.append( "<b>IR Enabled: </b>" );
sb.append( descriptor.irEnabled() );
sb.append( "<br>" );
sb.append( "<b>Remote Wake: </b>" );
sb.append( descriptor.remoteWakeupEnabled() );
sb.append( "<br>" );
}
return sb.toString();
}
@Override
public void save()
{
if( hasItem() && !mLoading )
{
R820TTunerConfiguration config = getConfiguration();
config.setName( mConfigurationName.getText() );
double value = ((SpinnerNumberModel)mFrequencyCorrection
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection( value );
config.setSampleRate( (RTL2832TunerController.SampleRate)mComboSampleRate.getSelectedItem() );
R820TTunerController.R820TGain gain = (R820TTunerController.R820TGain)mComboMasterGain.getSelectedItem();
config.setMasterGain( gain );
R820TTunerController.R820TMixerGain mixerGain = (R820TTunerController.R820TMixerGain)mComboMixerGain.getSelectedItem();
config.setMixerGain( mixerGain );
R820TTunerController.R820TLNAGain lnaGain = (R820TTunerController.R820TLNAGain)mComboLNAGain.getSelectedItem();
config.setLNAGain( lnaGain );
R820TTunerController.R820TVGAGain vgaGain = (R820TTunerController.R820TVGAGain)mComboVGAGain.getSelectedItem();
config.setVGAGain( vgaGain );
getTunerConfigurationModel().broadcast(
new TunerConfigurationEvent( getConfiguration(), TunerConfigurationEvent.Event.CHANGE ) );
}
}
@Override
public void setItem( TunerConfiguration tunerConfiguration )
{
super.setItem( tunerConfiguration );
//Toggle loading so that we don't fire a change event and schedule a settings file save
mLoading = true;
if( hasItem() )
{
R820TTunerConfiguration config = getConfiguration();
if( tunerConfiguration.isAssigned() )
{
setControlsEnabled( true );
mConfigurationName.setText( config.getName() );
mFrequencyCorrection.setValue( config.getFrequencyCorrection() );
mComboSampleRate.setSelectedItem( config.getSampleRate() );
mComboMasterGain.setSelectedItem( config.getMasterGain() );
mComboMixerGain.setSelectedItem( config.getMixerGain() );
mComboLNAGain.setSelectedItem( config.getLNAGain() );
mComboVGAGain.setSelectedItem( config.getVGAGain() );
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( config.getName() );
}
}
else
{
setControlsEnabled( false );
mConfigurationName.setText( "" );
}
mLoading = false;
}
}

View File

@@ -0,0 +1,440 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.source.tuner.usb;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.tuner.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class USBTransferProcessor implements TransferCallback
{
private final static Logger mLog = LoggerFactory.getLogger(USBTransferProcessor.class);
private static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
//Number of native byte buffers to allocate for transferring data from the USB device
private static final int TRANSFER_BUFFER_POOL_SIZE = 16;
//Maximum number of filled buffers for the blocking queue
private static final int FILLED_BUFFER_MAX_CAPACITY = 500;
//Threshold for resetting buffer overflow condition
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
private String mDeviceName;
//Handle to the USB bulk transfer device
private DeviceHandle mDeviceHandle;
//Tuner format-specific byte to IQ float sample converter
private ISampleAdapter mSampleAdapter;
//Byte array transfer buffers size in bytes
private int mBufferSize;
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private OverflowableTransferQueue<byte[]> mFilledBuffers;
private LinkedTransferQueue<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private ByteBuffer mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
private BufferDispatcher mBufferDispatcher = new BufferDispatcher();
private ScheduledFuture mBufferDispatcherFuture;
/**
* Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to
* any registered listeners.
*
* @param deviceName to use when logging information or errors
* @param deviceHandle to the USB bulk transfer device
* @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples
* @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
*/
public USBTransferProcessor(String deviceName,
DeviceHandle deviceHandle,
ISampleAdapter sampleAdapter,
int bufferSize)
{
mDeviceName = deviceName;
mDeviceHandle = deviceHandle;
mSampleAdapter = sampleAdapter;
mBufferSize = bufferSize;
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
mFilledBuffers.setOverflowListener(new IOverflowListener()
{
@Override
public void sourceOverflow(boolean overflow)
{
if(overflow)
{
mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up");
}
else
{
mLog.debug(mDeviceName + " - buffer overflow cleared - resuming normal processing");
}
}
});
}
/**
* Start USB transfer buffer processing. Subsequent calls to this method after started will be ignored.
*/
private void start()
{
if(mRunning.compareAndSet(false, true))
{
prepareDeviceStart();
prepareTransfers();
while(!mAvailableTransfers.isEmpty())
{
Transfer transfer = mAvailableTransfers.poll();
if(transfer != null)
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else if(result == LibUsb.ERROR_PIPE)
{
int resetResult = LibUsb.clearHalt(mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT);
if(resetResult == LibUsb.SUCCESS)
{
int resubmitResult = LibUsb.submitTransfer(transfer);
if(resubmitResult == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
mLog.error(mDeviceName + " - error resubmitting transfer after endpoint clear halt");
}
}
else
{
mLog.error(mDeviceName + " - unable to clear device endpoint halt");
}
}
else
{
//TODO: broadcast to each listener that this source has an error and is shutting down
mLog.error(mDeviceName + "- error submitting transfer [" + LibUsb.errorName(result) + "]");
}
}
}
//Start transferred buffer dispatcher
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
0, 11, TimeUnit.MILLISECONDS);
//Register with LibUSB processor so that it auto-starts LibUSB processing
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
}
}
/**
* Stop USB transfer buffer processing. Subsequent calls to this method after stopped will be ignored.
*/
private void stop()
{
if(mRunning.compareAndSet(true, false))
{
mBufferDispatcherFuture.cancel(true);
mFilledBuffers.clear();
//Cancel the lib usb process timer
for(Transfer transfer : mTransfersInProgress)
{
LibUsb.cancelTransfer(transfer);
}
//Unregister from LibUSB processor so that it auto-stops LibUSB processing
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this);
//Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed.
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS,
mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error(mDeviceName + " - error while cancelling transfer buffers during shutdown/pause - error code:" + result);
}
mLibUsbHandlerStatus.rewind();
executeDeviceStop();
}
}
/**
* Allows sub-class implementations to execute any device-specific operations to prepare for starting USB transfers
*/
protected void prepareDeviceStart()
{
}
/**
* Allows sub-class implementations to execute any device-specific operations after stopping USB transfers
*/
protected void executeDeviceStop()
{
}
/**
* Indicates if there are complex buffer listeners registered with this processor
*/
public boolean hasListeners()
{
return mComplexBufferBroadcaster.hasListeners();
}
public void addListener(Listener<ComplexBuffer> listener)
{
mComplexBufferBroadcaster.addListener(listener);
start();
}
public void removeListener(Listener<ComplexBuffer> listener)
{
mComplexBufferBroadcaster.removeListener(listener);
if(!hasListeners())
{
stop();
}
}
public void removeAllListeners()
{
mComplexBufferBroadcaster.clear();
stop();
}
/**
* Prepares (allocates) a set of transfer buffers for use in transferring data from the USB device via the bulk
* interface. Since we're using direct allocation (native), buffers are retained and reused across multiple
* start/stop cycles.
*/
private void prepareTransfers() throws LibUsbException
{
while(mAvailableTransfers.size() < TRANSFER_BUFFER_POOL_SIZE)
{
Transfer transfer = LibUsb.allocTransfer();
if(transfer == null)
{
throw new LibUsbException("Couldn't allocate USB transfer buffer", LibUsb.ERROR_NO_MEM);
}
final ByteBuffer buffer = ByteBuffer.allocateDirect(mBufferSize);
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this,
"Buffer", USB_TIMEOUT_MS);
mAvailableTransfers.add(transfer);
}
}
/**
* Process a filled transfer buffer received back from the USB device
*/
@Override
public void processTransfer(Transfer transfer)
{
mTransfersInProgress.remove(transfer);
switch(transfer.status())
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
case LibUsb.TRANSFER_TIMED_OUT:
if(transfer.actualLength() > 0)
{
ByteBuffer buffer = transfer.buffer();
byte[] data = new byte[transfer.actualLength()];
buffer.get(data);
buffer.rewind();
if(mRunning.get())
{
mFilledBuffers.offer(data);
}
}
break;
case LibUsb.TRANSFER_CANCELLED:
break;
default:
//Unexpected transfer error
mLog.error(mDeviceName + " - transfer error [" + getTransferStatus(transfer.status()) +
"] transferred actual: " + transfer.actualLength());
}
if(mRunning.get())
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else if(result == LibUsb.ERROR_PIPE)
{
int resetResult = LibUsb.clearHalt(mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT);
if(resetResult == LibUsb.SUCCESS)
{
int resubmitResult = LibUsb.submitTransfer(transfer);
if(resubmitResult == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
mLog.error(mDeviceName + " - error resubmitting transfer after endpoint clear halt");
}
}
else
{
mLog.error(mDeviceName + " - unable to clear device endpoint halt");
}
}
else
{
mAvailableTransfers.add(transfer);
mLog.error(mDeviceName + " - error submitting transfer [" + LibUsb.errorName(result) + "]");
if(mTransfersInProgress.isEmpty())
{
mLog.warn(mDeviceName + " - all transfer buffer processing is stopped");
//TODO: no transfers are in progress ... need to alert the user and the registered complex
//TODO: buffer listeners so that they can respond accordingly
}
}
}
else
{
//We're stopping - park the transfer buffers
if(!mAvailableTransfers.contains(transfer))
{
mAvailableTransfers.add(transfer);
}
}
}
/**
* Converts the USB transfer status number into a descriptive label
*/
public static String getTransferStatus(int status)
{
switch(status)
{
case 0:
return "TRANSFER COMPLETED (0)";
case 1:
return "TRANSFER ERROR (1)";
case 2:
return "TRANSFER TIMED OUT (2)";
case 3:
return "TRANSFER CANCELLED (3)";
case 4:
return "TRANSFER STALL (4)";
case 5:
return "TRANSFER NO DEVICE (5)";
case 6:
return "TRANSFER OVERFLOW (6)";
default:
return "UNKNOWN TRANSFER STATUS (" + status + ")";
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
private List<byte[]> mBuffersToDispatch = new ArrayList<>();
@Override
public void run()
{
mFilledBuffers.drainTo(mBuffersToDispatch, 50);
while(!mBuffersToDispatch.isEmpty())
{
try
{
for(byte[] buffer : mBuffersToDispatch)
{
float[] complexSamples = mSampleAdapter.convert(buffer);
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
}
}
catch(Exception e)
{
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
}
mBuffersToDispatch.clear();
mFilledBuffers.drainTo(mBuffersToDispatch, 50);
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.usb;
import io.github.dsheirer.source.tuner.TunerClass;
import javax.usb.UsbDevice;
public class USBTunerDevice
{
private UsbDevice mDevice;
private TunerClass mTunerClass;
public USBTunerDevice( UsbDevice device, TunerClass tunerClass )
{
mDevice = device;
mTunerClass = tunerClass;
}
public UsbDevice getDevice()
{
return mDevice;
}
public TunerClass getTunerClass()
{
return mTunerClass;
}
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* 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 io.github.dsheirer.source.tuner.usb;
import javax.usb.UsbConfiguration;
import javax.usb.UsbDevice;
import javax.usb.UsbDisconnectedException;
import javax.usb.UsbEndpoint;
import javax.usb.UsbException;
import javax.usb.UsbHostManager;
import javax.usb.UsbHub;
import javax.usb.UsbInterface;
import javax.usb.UsbServices;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class USBUtils
{
/**
* Returns a List of currently connected usb devices
*/
public static List<UsbDevice> getDevices()
throws SecurityException, UsbException, UnsupportedEncodingException
{
ArrayList<UsbDevice> devices = new ArrayList<UsbDevice>();
UsbServices services = UsbHostManager.getUsbServices();
UsbHub root = services.getRootUsbHub();
devices.addAll( getHubDevices( root ) );
return devices;
}
/**
* Returns a list of devices attached to the hub
*/
public static List<UsbDevice> getHubDevices( UsbHub hub )
throws UnsupportedEncodingException, UsbException
{
ArrayList<UsbDevice> devices = new ArrayList<UsbDevice>();
@SuppressWarnings( "unchecked" )
List<UsbDevice> children = hub.getAttachedUsbDevices();
Iterator<UsbDevice> it = children.iterator();
while( it.hasNext() )
{
UsbDevice child = it.next();
if( child.isUsbHub() )
{
devices.addAll( getHubDevices( (UsbHub)child ) );
}
else
{
devices.add( child );
}
}
return devices;
}
public static String getDeviceDetails( UsbDevice device )
throws UsbException, UnsupportedEncodingException, UsbDisconnectedException
{
StringBuilder sb = new StringBuilder();
sb.append( device.getUsbDeviceDescriptor().toString() + "\n\n" );
for( Object configObject: device.getUsbConfigurations() )
{
UsbConfiguration config = (UsbConfiguration)configObject;
sb.append( config.getUsbConfigurationDescriptor().toString() + "\n\n" );
for( Object interfaceObject: config.getUsbInterfaces() )
{
UsbInterface iface = (UsbInterface)interfaceObject;
sb.append( iface.getUsbInterfaceDescriptor().toString() + "\n\n" );
for( Object endpointObject: iface.getUsbEndpoints() )
{
UsbEndpoint endpoint = (UsbEndpoint)endpointObject;
sb.append( endpoint.getUsbEndpointDescriptor().toString() + "\n\n" );
}
}
}
return sb.toString();
}
}