mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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;
|
||||
|
||||
@@ -48,6 +45,7 @@ public class SourceEvent
|
||||
NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||
NOTIFICATION_SQUELCH_THRESHOLD,
|
||||
NOTIFICATION_STOP_SAMPLE_STREAM,
|
||||
NOTIFICATION_TUNER_SHUTDOWN,
|
||||
NOTIFICATION_ERROR_STATE,
|
||||
|
||||
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||
@@ -197,6 +195,15 @@ public class SourceEvent
|
||||
return new SourceEvent(Event.NOTIFICATION_ERROR_STATE, source, errorDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification that a tuner is shutting down and the source is therefore shutting down.
|
||||
* @param source that is shutting down
|
||||
*/
|
||||
public static SourceEvent tunerShutdown(Source source)
|
||||
{
|
||||
return new SourceEvent(Event.NOTIFICATION_TUNER_SHUTDOWN, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new locked state change event
|
||||
*/
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 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;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.mixer.MixerManager;
|
||||
import io.github.dsheirer.source.recording.RecordingSourceManager;
|
||||
import io.github.dsheirer.source.tuner.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.TunerModel;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.MultiFrequencyTunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
|
||||
public class SourceManager
|
||||
{
|
||||
private RecordingSourceManager mRecordingSourceManager;
|
||||
private TunerManager mTunerManager;
|
||||
private TunerModel mTunerModel;
|
||||
|
||||
public SourceManager(TunerModel tunerModel, SettingsManager settingsManager, UserPreferences userPreferences)
|
||||
{
|
||||
mTunerModel = tunerModel;
|
||||
mRecordingSourceManager = new RecordingSourceManager(settingsManager);
|
||||
mTunerManager = new TunerManager(tunerModel, userPreferences);
|
||||
|
||||
//TODO: change mixer & recording managers to be models and hand them
|
||||
//in via the constructor. Perform loading outside of this class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for shutdown and release all tuners
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
mTunerManager.releaseTuners();
|
||||
mTunerManager.dispose();
|
||||
}
|
||||
|
||||
public RecordingSourceManager getRecordingSourceManager()
|
||||
{
|
||||
return mRecordingSourceManager;
|
||||
}
|
||||
|
||||
public TunerManager getTunerManager()
|
||||
{
|
||||
return mTunerManager;
|
||||
}
|
||||
|
||||
public TunerModel getTunerModel()
|
||||
{
|
||||
return mTunerModel;
|
||||
}
|
||||
|
||||
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification) throws SourceException
|
||||
{
|
||||
Source retVal = null;
|
||||
|
||||
switch(config.getSourceType())
|
||||
{
|
||||
case MIXER:
|
||||
retVal = MixerManager.getSource(config);
|
||||
break;
|
||||
case TUNER:
|
||||
if(config instanceof SourceConfigTuner)
|
||||
{
|
||||
SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)config;
|
||||
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
|
||||
String preferredTuner = sourceConfigTuner.getPreferredTuner();
|
||||
retVal = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner);
|
||||
}
|
||||
break;
|
||||
case TUNER_MULTIPLE_FREQUENCIES:
|
||||
if(config instanceof SourceConfigTunerMultipleFrequency)
|
||||
{
|
||||
SourceConfigTunerMultipleFrequency sourceConfigTuner = (SourceConfigTunerMultipleFrequency)config;
|
||||
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
|
||||
String preferredTuner = sourceConfigTuner.getPreferredTuner();
|
||||
|
||||
Source source = mTunerModel.getSource(tunerChannel, channelSpecification, preferredTuner);
|
||||
|
||||
if(source instanceof TunerChannelSource)
|
||||
{
|
||||
retVal = new MultiFrequencyTunerChannelSource(getTunerModel(), (TunerChannelSource)source,
|
||||
sourceConfigTuner.getFrequencies(), channelSpecification, sourceConfigTuner.getPreferredTuner());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RECORDING:
|
||||
retVal = mRecordingSourceManager.getSource(config, channelSpecification);
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
@@ -61,9 +61,9 @@ public class ComplexMixer
|
||||
* Sets the buffer size in bytes per buffer for each read interval.
|
||||
* @param bufferSize in bytes
|
||||
*/
|
||||
public void setBufferSize(int bufferSize)
|
||||
public void setBufferSampleCount(int bufferSize)
|
||||
{
|
||||
mMixerReader.setBufferSize(bufferSize);
|
||||
mMixerReader.setBufferSampleSize(bufferSize);
|
||||
}
|
||||
|
||||
public void start()
|
||||
|
||||
@@ -22,16 +22,12 @@ import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.ISampleAdapter;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
@@ -39,23 +35,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered
|
||||
* buffer listener.
|
||||
*/
|
||||
public class MixerReader<T> implements Runnable
|
||||
public class MixerReader<T>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class);
|
||||
|
||||
private static final long BUFFER_PROCESSING_INTERVAL_MS = 50; //50 milliseconds, 20 reads per second
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private byte[] mBuffer;
|
||||
private int mBufferSize;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private ScheduledFuture mScheduledFuture;
|
||||
private int mBytesRead;
|
||||
private ISampleAdapter<T> mSampleAdapter;
|
||||
private Listener<T> mListener;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
private AudioFormat mAudioFormat;
|
||||
private HeartbeatManager mHeartbeatManager;
|
||||
private Thread mReaderThread;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param audioFormat for the target mixer
|
||||
* @param targetDataLine to read samples from
|
||||
* @param abstractSampleAdapter to convert samples to native buffers
|
||||
* @param heartbeatManager to ping before each buffer read for downstream consumers
|
||||
*/
|
||||
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine,
|
||||
ISampleAdapter<T> abstractSampleAdapter, HeartbeatManager heartbeatManager)
|
||||
{
|
||||
@@ -64,10 +63,22 @@ public class MixerReader<T> implements Runnable
|
||||
mSampleAdapter = abstractSampleAdapter;
|
||||
mHeartbeatManager = heartbeatManager;
|
||||
|
||||
/* Set buffer size to 1/10 second of samples */
|
||||
mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize();
|
||||
if(mAudioFormat.getSampleRate() <= 96000)
|
||||
{
|
||||
setBufferSampleSize(4096);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBufferSampleSize(8192);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param audioFormat for the target mixer
|
||||
* @param targetDataLine to read samples from
|
||||
* @param abstractSampleAdapter to convert samples to native buffers
|
||||
*/
|
||||
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, ISampleAdapter<T> abstractSampleAdapter)
|
||||
{
|
||||
this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager());
|
||||
@@ -82,54 +93,12 @@ public class MixerReader<T> implements Runnable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of buffers to use. This reader will read from target data line 20 times per second.
|
||||
* @param bytesPerBuffer to size for each buffer read.
|
||||
* Sets the number of samples per native buffer to produce.
|
||||
* @param samples is the number of samples
|
||||
*/
|
||||
public void setBufferSize(int bytesPerBuffer)
|
||||
public void setBufferSampleSize(int samples)
|
||||
{
|
||||
mBufferSize = bytesPerBuffer;
|
||||
mBuffer = new byte[mBufferSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the source mixer target data line
|
||||
*
|
||||
* @throws LineUnavailableException if the target data line is null or unavailable (from the OS).
|
||||
*/
|
||||
private void openTargetDataLine() throws LineUnavailableException
|
||||
{
|
||||
if(mTargetDataLine == null)
|
||||
{
|
||||
throw new LineUnavailableException("Source Mixer TargetDataLine is null");
|
||||
}
|
||||
|
||||
mBuffer = new byte[mBufferSize];
|
||||
|
||||
mTargetDataLine.open(getAudioFormat());
|
||||
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
|
||||
mLog.info("Format:" + mTargetDataLine.getFormat().toString());
|
||||
mTargetDataLine.start();
|
||||
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the mixer source target data line
|
||||
*/
|
||||
private void closeTargetDataLine()
|
||||
{
|
||||
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
||||
{
|
||||
if(mTargetDataLine.isRunning())
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
}
|
||||
|
||||
if(mTargetDataLine.isOpen())
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
mBufferSize = samples * mAudioFormat.getFrameSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +108,17 @@ public class MixerReader<T> implements Runnable
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
if(mTargetDataLine == null)
|
||||
{
|
||||
mRunning.set(false);
|
||||
mLog.error("Attempt to start failed - target data line is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
openTargetDataLine();
|
||||
mTargetDataLine.open(getAudioFormat());
|
||||
mTargetDataLine.start();
|
||||
}
|
||||
catch(LineUnavailableException e)
|
||||
{
|
||||
@@ -150,15 +127,9 @@ public class MixerReader<T> implements Runnable
|
||||
return;
|
||||
}
|
||||
|
||||
//Cancel the scheduled thread if it wasn't cancelled previously ... this shouldn't happen
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this,
|
||||
0, BUFFER_PROCESSING_INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||
mReaderThread = new Thread(new DataLineReader());
|
||||
mReaderThread.setName("sdrtrunk mixer sample reader");
|
||||
mReaderThread.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -173,66 +144,31 @@ public class MixerReader<T> implements Runnable
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
mScheduledFuture = null;
|
||||
if(mTargetDataLine.isRunning())
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
}
|
||||
|
||||
if(mTargetDataLine.isOpen())
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
|
||||
closeTargetDataLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Attempt to stop an already stopped BufferReader instance - this shouldn't happen");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample processing thread. This method runs each time the timer fires.
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(mRunning.get())
|
||||
{
|
||||
if(mHeartbeatManager != null)
|
||||
{
|
||||
mHeartbeatManager.broadcast();
|
||||
}
|
||||
mReaderThread.interrupt();
|
||||
|
||||
try
|
||||
{
|
||||
mBytesRead = 0;
|
||||
|
||||
//Blocking read - waits until the buffer fills
|
||||
mBytesRead = mTargetDataLine.read(mBuffer, 0, mBuffer.length);
|
||||
|
||||
if(mBytesRead == mBuffer.length)
|
||||
{
|
||||
//Sample adapter automatically sets initial listener count to one.
|
||||
T reusableBuffer = mSampleAdapter.convert(mBuffer);
|
||||
|
||||
if(reusableBuffer != null && mListener != null)
|
||||
{
|
||||
mListener.receive(reusableBuffer);
|
||||
}
|
||||
}
|
||||
else if(mBytesRead > 0)
|
||||
{
|
||||
//Sample adapter automatically sets initial listener count to one.
|
||||
T reusableBuffer = mSampleAdapter.convert(Arrays.copyOf(mBuffer, mBytesRead));
|
||||
|
||||
if(reusableBuffer != null && mListener != null)
|
||||
{
|
||||
mListener.receive(reusableBuffer);
|
||||
}
|
||||
}
|
||||
mReaderThread.join(500);
|
||||
}
|
||||
catch(Throwable t)
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
mLog.error("MixerSource - error while reading from the mixer target data line", t);
|
||||
stop();
|
||||
//No-op ... we're shutting down
|
||||
}
|
||||
|
||||
mReaderThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +232,49 @@ public class MixerReader<T> implements Runnable
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
stop();
|
||||
mHeartbeatManager = null;
|
||||
mListener = null;
|
||||
mSourceEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable reader to read samples from the target data line, convert them to native buffers, and dispatch.
|
||||
*/
|
||||
public class DataLineReader implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while(mRunning.get())
|
||||
{
|
||||
if(mHeartbeatManager != null)
|
||||
{
|
||||
mHeartbeatManager.broadcast();
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[mBufferSize];
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while(bytesRead < buffer.length)
|
||||
{
|
||||
bytesRead += mTargetDataLine.read(buffer, bytesRead, buffer.length);
|
||||
}
|
||||
}
|
||||
catch(ArrayIndexOutOfBoundsException aioobe)
|
||||
{
|
||||
//No-op ... this can happen during USB disconnect
|
||||
}
|
||||
|
||||
//We'll always read the correct number of bytes until the data line is closed or stopped, and we're
|
||||
//shutting down.
|
||||
if(bytesRead == buffer.length && mListener != null)
|
||||
{
|
||||
mListener.receive(mSampleAdapter.convert(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.recording;
|
||||
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Recording implements Comparable<Recording>, ISourceEventProcessor
|
||||
{
|
||||
private RecordingConfiguration mConfiguration;
|
||||
|
||||
private List<TunerChannelSource> mTunerChannels = new ArrayList<TunerChannelSource>();
|
||||
|
||||
private long mCenterFrequency;
|
||||
|
||||
private SettingsManager mSettingsManager;
|
||||
|
||||
public Recording( SettingsManager settingsManager,
|
||||
RecordingConfiguration configuration )
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
mConfiguration = configuration;
|
||||
mCenterFrequency = mConfiguration.getCenterFrequency();
|
||||
}
|
||||
|
||||
public void setAlias( String alias )
|
||||
{
|
||||
mConfiguration.setAlias( alias );
|
||||
// mSettingsManager.save();
|
||||
}
|
||||
|
||||
public void setRecordingFile( File file ) throws SourceException
|
||||
{
|
||||
if( hasChannels() )
|
||||
{
|
||||
throw new SourceException( "Recording source - can't change "
|
||||
+ "recording file while channels are enabled against the "
|
||||
+ "current recording." );
|
||||
}
|
||||
|
||||
mConfiguration.setFilePath( file.getAbsolutePath() );
|
||||
// mSettingsManager.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this recording is currently playing/providing samples to
|
||||
* tuner channel sources
|
||||
*/
|
||||
public boolean hasChannels()
|
||||
{
|
||||
return mTunerChannels.size() > 0;
|
||||
}
|
||||
|
||||
public TunerChannelSource getChannel( TunerChannel channel )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public RecordingConfiguration getRecordingConfiguration()
|
||||
{
|
||||
return mConfiguration;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mConfiguration.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event ) throws SourceException
|
||||
{
|
||||
if( event.getEvent() == SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE )
|
||||
{
|
||||
long frequency = event.getValue().longValue();
|
||||
|
||||
mConfiguration.setCenterFrequency( frequency );
|
||||
|
||||
// mSettingsManager.save();
|
||||
|
||||
mCenterFrequency = frequency;
|
||||
|
||||
for( TunerChannelSource channel: mTunerChannels )
|
||||
{
|
||||
channel.process( event );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( Recording other )
|
||||
{
|
||||
return getRecordingConfiguration().getAlias()
|
||||
.compareTo( other.getRecordingConfiguration().getAlias() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Recording)) return false;
|
||||
return compareTo((Recording) o) == 0;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.recording;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -7,6 +26,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import java.io.File;
|
||||
|
||||
@JacksonXmlRootElement(localName = "recording_configuration")
|
||||
@Deprecated //No longer used -- retained for backward compatibility with existing user configuration files.
|
||||
public class RecordingConfiguration
|
||||
{
|
||||
private File mRecording;
|
||||
@@ -24,24 +44,6 @@ public class RecordingConfiguration
|
||||
mFilePath = filePath;
|
||||
mAlias = alias;
|
||||
mCenterFrequency = centerFrequency;
|
||||
|
||||
updateFile();
|
||||
}
|
||||
|
||||
private void updateFile()
|
||||
{
|
||||
if(mFilePath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mRecording = new File(mFilePath);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new IllegalArgumentException("There was an error while "
|
||||
+ "accessing the recording source [" + mFilePath + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@@ -59,8 +61,6 @@ public class RecordingConfiguration
|
||||
public void setFilePath(String filePath)
|
||||
{
|
||||
mFilePath = filePath;
|
||||
|
||||
updateFile();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "alias")
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.recording;
|
||||
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.config.SourceConfigRecording;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RecordingSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RecordingSourceManager.class);
|
||||
|
||||
private ArrayList<Recording> mRecordings = new ArrayList<Recording>();
|
||||
|
||||
private SettingsManager mSettingsManager;
|
||||
|
||||
public RecordingSourceManager(SettingsManager settingsManager)
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
loadRecordings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates current recordings to get a tuner channel source for the frequency
|
||||
* specified in the channel config's source config object
|
||||
*/
|
||||
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification)
|
||||
throws SourceException
|
||||
{
|
||||
TunerChannelSource retVal = null;
|
||||
|
||||
if(config instanceof SourceConfigRecording)
|
||||
{
|
||||
SourceConfigRecording configRecording = (SourceConfigRecording)config;
|
||||
|
||||
TunerChannel tunerChannel = configRecording.getTunerChannel();
|
||||
|
||||
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
|
||||
|
||||
Recording recording = getRecordingFromAlias(
|
||||
configRecording.getRecordingAlias());
|
||||
|
||||
if(recording != null)
|
||||
{
|
||||
retVal = recording.getChannel(tunerChannel);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SourceException("Recording with alias name [" +
|
||||
configRecording.getRecordingAlias() + "] is not currently available");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Recording getRecordingFromAlias(String alias)
|
||||
{
|
||||
for(Recording recording : mRecordings)
|
||||
{
|
||||
if(recording.getRecordingConfiguration()
|
||||
.getAlias().contentEquals(alias))
|
||||
{
|
||||
return recording;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of loaded IQ files
|
||||
*/
|
||||
public List<Recording> getRecordings()
|
||||
{
|
||||
Collections.sort(mRecordings);
|
||||
|
||||
return mRecordings;
|
||||
}
|
||||
|
||||
public void addRecording(Recording recording)
|
||||
{
|
||||
mRecordings.add(recording);
|
||||
}
|
||||
|
||||
public Recording addRecording(RecordingConfiguration config)
|
||||
{
|
||||
Recording recording = new Recording(mSettingsManager, config);
|
||||
|
||||
mRecordings.add(recording);
|
||||
|
||||
return recording;
|
||||
}
|
||||
|
||||
public void removeRecording(Recording recording)
|
||||
{
|
||||
/* Remove the config from the settings manager */
|
||||
mSettingsManager.removeRecordingConfiguration(
|
||||
recording.getRecordingConfiguration());
|
||||
|
||||
mRecordings.remove(recording);
|
||||
}
|
||||
|
||||
private void loadRecordings()
|
||||
{
|
||||
List<RecordingConfiguration> recordingConfigurations =
|
||||
mSettingsManager.getRecordingConfigurations();
|
||||
|
||||
mLog.info("RecordingSourceManager - discovered [" +
|
||||
recordingConfigurations.size() + "] recording configurations");
|
||||
|
||||
for(RecordingConfiguration config : recordingConfigurations)
|
||||
{
|
||||
Recording recording = new Recording(mSettingsManager, config);
|
||||
|
||||
mRecordings.add(recording);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tuner error listener that logs any received tuner error messages
|
||||
*/
|
||||
public class LoggingTunerErrorListener implements ITunerErrorListener
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(LoggingTunerErrorListener.class);
|
||||
|
||||
@Override
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
mLog.error("Tuner Error: " + errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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;
|
||||
@@ -53,28 +54,28 @@ public enum MixerTunerType
|
||||
true, //Signed
|
||||
false ) ); //Little Endian )
|
||||
|
||||
private TunerType mTunerClass;
|
||||
private TunerType mTunerType;
|
||||
private String mMixerName;
|
||||
private String mPartialDescription;
|
||||
private String mDisplayString;
|
||||
private AudioFormat mAudioFormat;
|
||||
|
||||
MixerTunerType( TunerType tunerClass,
|
||||
MixerTunerType( TunerType tunerType,
|
||||
String mixerName,
|
||||
String partialDescription,
|
||||
String displayString,
|
||||
AudioFormat format )
|
||||
{
|
||||
mTunerClass = tunerClass;
|
||||
mTunerType = tunerType;
|
||||
mMixerName = mixerName;
|
||||
mPartialDescription = partialDescription;
|
||||
mDisplayString = displayString;
|
||||
mAudioFormat = format;
|
||||
}
|
||||
|
||||
public TunerType getTunerClass()
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return mTunerClass;
|
||||
return mTunerType;
|
||||
}
|
||||
|
||||
public String getMixerName()
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
|
||||
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
|
||||
@@ -46,31 +43,28 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
private ChannelSourceManager mChannelSourceManager;
|
||||
private TunerController mTunerController;
|
||||
private TunerFrequencyErrorMonitor mTunerFrequencyErrorMonitor;
|
||||
private String mName;
|
||||
private String mErrorMessage;
|
||||
private ITunerErrorListener mTunerErrorListener;
|
||||
|
||||
public Tuner(String name, TunerController tunerController)
|
||||
public Tuner(TunerController tunerController, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
mName = name;
|
||||
mTunerController = tunerController;
|
||||
mTunerErrorListener = tunerErrorListener;
|
||||
//Register to receive frequency and sample rate change notifications
|
||||
mTunerController.addListener(this::process);
|
||||
mTunerController.setTunerErrorListener(this);
|
||||
mTunerFrequencyErrorMonitor = new TunerFrequencyErrorMonitor(this);
|
||||
mTunerFrequencyErrorMonitor.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract tuner class.
|
||||
* @param name of the tuner
|
||||
* @param tunerController for the tuner
|
||||
* @param userPreferences to discover preferred channelizer type
|
||||
* @param tunerErrorListener to listen for tuner errors
|
||||
* @param channelizerType for the channelizer
|
||||
*/
|
||||
public Tuner(String name, TunerController tunerController, UserPreferences userPreferences)
|
||||
public Tuner(TunerController tunerController, ITunerErrorListener tunerErrorListener, ChannelizerType channelizerType)
|
||||
{
|
||||
this(name, tunerController);
|
||||
this(tunerController, tunerErrorListener);
|
||||
|
||||
ChannelizerType channelizerType = userPreferences.getTunerPreference().getChannelizerType();
|
||||
if(channelizerType == ChannelizerType.POLYPHASE)
|
||||
{
|
||||
setChannelSourceManager(new PolyphaseChannelSourceManager(mTunerController));
|
||||
@@ -85,6 +79,58 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform startup operations
|
||||
* @throws SourceException if there is an error that makes this tuner unusable
|
||||
*/
|
||||
public void start() throws SourceException
|
||||
{
|
||||
try
|
||||
{
|
||||
getTunerController().start();
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
//Rethrow the source exception
|
||||
throw se;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Wrap any other exceptions in a new source exception
|
||||
mLog.error("Error starting " + getTunerClass() + " tuner", e);
|
||||
throw new SourceException("Unable to start " + getTunerClass() + " tuner", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform shutdown and disposal operations.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(getChannelSourceManager() != null)
|
||||
{
|
||||
getChannelSourceManager().stopAllChannels();
|
||||
}
|
||||
|
||||
broadcast(new TunerEvent(this, Event.NOTIFICATION_SHUTTING_DOWN));
|
||||
getTunerController().stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an unrecoverable error state for this tuner and propagates the error to an external listener
|
||||
* @param errorMessage to set
|
||||
*/
|
||||
@Override
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
broadcast(new TunerEvent(this, Event.NOTIFICATION_ERROR_STATE));
|
||||
|
||||
if(mTunerErrorListener != null)
|
||||
{
|
||||
mTunerErrorListener.setErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel source manager
|
||||
* @param manager to use
|
||||
@@ -97,60 +143,32 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
mChannelSourceManager.addSourceEventListener(this::process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this tuner to an error state with the errorMessage description.
|
||||
* @param errorMessage describing the error state
|
||||
*/
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
mLog.info("[" + getName() + "] tuner is now disabled for error [" + errorMessage + "]");
|
||||
mErrorMessage = errorMessage;
|
||||
broadcast(new TunerEvent(this, Event.ERROR_STATE));
|
||||
getChannelSourceManager().setErrorMessage(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of bytes per second (MBps) produced by this tuner.
|
||||
* @return Bytes Per Second (Bps)
|
||||
*/
|
||||
public abstract int getMaximumUSBBitsPerSecond();
|
||||
|
||||
/**
|
||||
* Optional error message that describes an error state.
|
||||
*/
|
||||
public String getErrorMessage()
|
||||
{
|
||||
return mErrorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tuner has an error.
|
||||
*/
|
||||
public boolean hasError()
|
||||
{
|
||||
return mErrorMessage != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_COUNT_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.CHANNEL_COUNT));
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_CHANNEL_COUNT));
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY_UPDATED));
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_FREQUENCY));
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY_ERROR_UPDATED));
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_FREQUENCY_ERROR));
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE_UPDATED));
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_SAMPLE_RATE));
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.LOCK_STATE_CHANGE));
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_LOCK_STATE));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
mTunerFrequencyErrorMonitor.receive(event);
|
||||
@@ -159,7 +177,7 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
//ignore
|
||||
break;
|
||||
default:
|
||||
mLog.debug("Unrecognized Source Event: " + event.toString());
|
||||
mLog.debug("Unrecognized Source Event: " + event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -185,23 +203,7 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return mName + (hasError() ? mErrorMessage : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose and prepare for shutdown
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
getTunerController().dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name for this tuner
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
return getPreferredName();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,17 +222,17 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
/**
|
||||
* @return - tuner type enum entry
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return getTunerController().getTunerType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this tuner object
|
||||
* Preferred name for this. This should be unique and will be used to find this tuner (e.g. channel preferred tuner)
|
||||
*
|
||||
* @return - string name of this tuner object
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
public abstract String getPreferredName();
|
||||
|
||||
/**
|
||||
* Sample size in bits
|
||||
@@ -240,7 +242,7 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
/**
|
||||
* Registers the listener
|
||||
*/
|
||||
public void addTunerChangeListener(Listener<TunerEvent> listener)
|
||||
public void addTunerEventListener(Listener<TunerEvent> listener)
|
||||
{
|
||||
mTunerEventBroadcaster.addListener(listener);
|
||||
}
|
||||
@@ -248,7 +250,7 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
/**
|
||||
* Removes the registered listener
|
||||
*/
|
||||
public void removeTunerChangeListener(Listener<TunerEvent> listener)
|
||||
public void removeTunerEventListener(Listener<TunerEvent> listener)
|
||||
{
|
||||
mTunerEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@@ -1,288 +1,118 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Tuner Class enumeration
|
||||
*/
|
||||
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" ),
|
||||
HACKRF_JAWBREAKER( TunerType.HACKRF, "1D50", "604B", "Great Scott Gadgets", "HackRF Jawbreaker" ),
|
||||
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" ),
|
||||
TEST_TUNER(TunerType.TEST, "0", "0", "ABC Tuners Inc.", "Model XYZ"),
|
||||
RECORDING_TUNER(TunerType.RECORDING, "0", "0", "Recording Tuner", "Recording"),
|
||||
UNKNOWN( TunerType.UNKNOWN, "0", "0", "Unknown Manufacturer", "Unknown Device" );
|
||||
AIRSPY("Airspy"),
|
||||
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro" ),
|
||||
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
|
||||
HACKRF("HackRF" ),
|
||||
RTL2832("RTL-2832"),
|
||||
TEST_TUNER("Test"),
|
||||
RECORDING_TUNER("Recording"),
|
||||
UNKNOWN("Unknown" );
|
||||
|
||||
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 )
|
||||
private String mDescription;
|
||||
|
||||
/**
|
||||
* Constructs an entry
|
||||
* @param description of the entry
|
||||
*/
|
||||
TunerClass(String description)
|
||||
{
|
||||
mTunerType = tunerType;
|
||||
mVendorID = vendorID;
|
||||
mDeviceID = deviceID;
|
||||
mVendorDescription = vendorDescription;
|
||||
mDeviceDescription = deviceDescription;
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "USB" +
|
||||
" Tuner:" + mTunerType.toString() +
|
||||
" Vendor:" + mVendorDescription +
|
||||
" Device:" + mDeviceDescription +
|
||||
" Address:" + mVendorID + ":" + mDeviceID;
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
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 )
|
||||
public static final EnumSet<TunerClass> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, HACKRF, RTL2832,
|
||||
FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
||||
|
||||
/**
|
||||
* Indicates if this tuner class entry is a supported USB tuner class.
|
||||
*/
|
||||
public boolean isSupportedUsbTuner()
|
||||
{
|
||||
return SUPPORTED_USB_TUNERS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a USB tuner class from the USB vid and pid
|
||||
* @param vendor id
|
||||
* @param product id
|
||||
* @return tuner class or UNKNOWN
|
||||
*/
|
||||
public static TunerClass lookup(short vendor, short product)
|
||||
{
|
||||
//Combine vid & pid to make an integer that we can switch on
|
||||
int id = ((vendor & 0xFFFF) << 16) | (product & 0xFFFF);
|
||||
|
||||
switch(id)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else if( productID == 24651 ) //604B
|
||||
{
|
||||
retVal = HACKRF_JAWBREAKER;
|
||||
}
|
||||
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:
|
||||
case 0x04D8FB31:
|
||||
return FUNCUBE_DONGLE_PRO_PLUS;
|
||||
case 0x04D8FB56:
|
||||
return FUNCUBE_DONGLE_PRO;
|
||||
case 0x0BDA2832: //GENERIC RTL-2832
|
||||
case 0x0BDA2838: //GENERIC RTL-2832/2838
|
||||
case 0x185B0620: //COMPRO VIDEOMATE U620F
|
||||
case 0x185B0650: //COMPRO VIDEOMATE U650F
|
||||
case 0x185B0680: //COMPRO VIDEOMATE U680F
|
||||
case 0x1B80D393: //GIGABYTE GTU7300
|
||||
case 0x1B80D395: //PEAK 102569AGPK
|
||||
case 0x1B80D398: //ZAAPA ZTMINDVBZP
|
||||
case 0x1B80D39D: //SVEON_STV20
|
||||
case 0x1B80D3A4: //TWINTECH UT40
|
||||
case 0x1D191101: //DEXATEK LIGILINK VG002A
|
||||
case 0x1D191102: //DEXATEK DIGIVOX MINI II REV3
|
||||
case 0x1D191103: //DEXATEK 5217 DVBT
|
||||
case 0x1F4DB803: //GTEK T803
|
||||
case 0x1F4DC803: //LIFEVIEW LV5T DELUXE
|
||||
case 0x1F4DD286: //MIGICA TD312
|
||||
case 0x1F4DD803: //PROLECTRIX DV107669
|
||||
case 0x0CCD00A9: //TERRATEC CINERGY T REV1
|
||||
case 0x0CCD00B3: //TERRATEC NOXON REV1 B3
|
||||
case 0x0CCD00B4: //TERRATEC NOXON REV1 B4
|
||||
case 0x0CCD00B7: //TERRATEC NOXON REV1 B7
|
||||
case 0x0CCD00C6: //TERRATEC NOXON REV1 C6
|
||||
case 0x0CCD00D3: //TERRATEC CINERGY T REV3
|
||||
case 0x0CCD00D7: //TERRATEC T STICK PLUS
|
||||
case 0x0CCD00E0: //TERRATEC NOXON REV2
|
||||
return RTL2832;
|
||||
case 0x1D506089: //HACKRF ONE
|
||||
case 0x1D50CC15: //HACKRF ONE
|
||||
case 0x1D50604B: //HACKRF JAWBREAKER
|
||||
return HACKRF;
|
||||
case 0x1D5060A1:
|
||||
return AIRSPY;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public String getVendorID()
|
||||
{
|
||||
return mVendorID;
|
||||
}
|
||||
|
||||
public String getDeviceID()
|
||||
{
|
||||
return mDeviceID;
|
||||
}
|
||||
|
||||
public String getVendorDescription()
|
||||
{
|
||||
return mVendorDescription;
|
||||
}
|
||||
|
||||
public String getDeviceDescription()
|
||||
{
|
||||
return mDeviceDescription;
|
||||
return TunerClass.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.RecorderFactory;
|
||||
import io.github.dsheirer.record.wave.IRecordingStatusListener;
|
||||
import io.github.dsheirer.record.wave.NativeBufferWaveRecorder;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
@@ -34,9 +35,11 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
|
||||
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.SortedSet;
|
||||
|
||||
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
|
||||
@@ -51,31 +54,52 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
private int mMeasuredFrequencyError;
|
||||
private NativeBufferWaveRecorder mRecorder;
|
||||
private ITunerErrorListener mTunerErrorListener;
|
||||
public FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
|
||||
private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
|
||||
|
||||
/**
|
||||
* 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 middleUnusableHalfBandwidth 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
|
||||
* @param tunerErrorListener to monitor errors produced from this tuner controller
|
||||
*/
|
||||
public TunerController(long minimumFrequency, long maximumFrequency, int middleUnusableHalfBandwidth, double usableBandwidth)
|
||||
public TunerController(ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
|
||||
mMiddleUnusableHalfBandwidth = middleUnusableHalfBandwidth;
|
||||
mUsableBandwidthPercentage = usableBandwidth;
|
||||
mTunerErrorListener = tunerErrorListener;
|
||||
mFrequencyController = new FrequencyController(this);
|
||||
mSourceEventListener = new SourceEventListenerToProcessorAdapter(this);
|
||||
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this tuner controller and prepare for shutdown.
|
||||
* Frequency correction manager for this tuner controller.
|
||||
*/
|
||||
public abstract void dispose();
|
||||
public FrequencyErrorCorrectionManager getFrequencyErrorCorrectionManager()
|
||||
{
|
||||
return mFrequencyErrorCorrectionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform startup operations
|
||||
* @throws SourceException if there is an error that makes this tuner controller unusable
|
||||
*/
|
||||
public abstract void start() throws SourceException;
|
||||
|
||||
/**
|
||||
* Perform shutdown and disposal operations.
|
||||
*
|
||||
* Note: implementation should notify any native buffer listeners that we're shutting down.
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Tuner type for this controller
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Sets an unrecoverable error for this tuner controller that will be handled by the listener
|
||||
* @param errorMessage to set
|
||||
*/
|
||||
@Override
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
@@ -85,15 +109,6 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener for tuner error messages that originate outside of the tuner. This is normally the enclosing
|
||||
* tuner so that it can receive error signals from the USB processor(s)..
|
||||
*/
|
||||
public void setTunerErrorListener(ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
mTunerErrorListener = tunerErrorListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of samples contained in each complex buffer provided by this tuner.
|
||||
*
|
||||
@@ -121,7 +136,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
/**
|
||||
* Applies the settings in the tuner configuration
|
||||
*/
|
||||
public abstract void apply(TunerConfiguration config) throws SourceException;
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
setFrequency(config.getFrequency());
|
||||
setFrequencyCorrection(config.getFrequencyCorrection());
|
||||
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to requests to set the frequency
|
||||
@@ -230,16 +251,42 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
mFrequencyController.setFrequencyCorrection(correction);
|
||||
}
|
||||
|
||||
public long getMinFrequency()
|
||||
/**
|
||||
* Minimum tunable frequency
|
||||
* @return minimum in Hertz
|
||||
*/
|
||||
public long getMinimumFrequency()
|
||||
{
|
||||
return mFrequencyController.getMinimumFrequency();
|
||||
}
|
||||
|
||||
public long getMaxFrequency()
|
||||
/**
|
||||
* Sets the minimum tunable frequency
|
||||
* @param minimum frequency in Hertz
|
||||
*/
|
||||
public void setMinimumFrequency(long minimum)
|
||||
{
|
||||
mFrequencyController.setMinimumFrequency(minimum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum tunable frequency
|
||||
* @return maximum in Hertz
|
||||
*/
|
||||
public long getMaximumFrequency()
|
||||
{
|
||||
return mFrequencyController.getMaximumFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum tunable frequency
|
||||
* @param maximum in Hertz
|
||||
*/
|
||||
public void setMaximumFrequency(long maximum)
|
||||
{
|
||||
mFrequencyController.setMaximumFrequency(maximum);
|
||||
}
|
||||
|
||||
public long getMinTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
|
||||
@@ -251,14 +298,23 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
}
|
||||
|
||||
/**
|
||||
* Total bandwidth of the middle unusable bandwidth region. This value is used to avoid a central DC spike
|
||||
* present in some tuners.
|
||||
* Half of the total bandwidth of the middle unusable bandwidth region. This value is used to avoid a central DC
|
||||
* spike present in some tuners.
|
||||
*/
|
||||
public int getMiddleUnusableHalfBandwidth()
|
||||
{
|
||||
return mMiddleUnusableHalfBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the middle unusable half bandwidth value
|
||||
* @param halfBandwidth in Hertz
|
||||
*/
|
||||
public void setMiddleUnusableHalfBandwidth(int halfBandwidth)
|
||||
{
|
||||
mMiddleUnusableHalfBandwidth = halfBandwidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tuner controller has a middle unusable bandwidth region.
|
||||
*/
|
||||
@@ -302,6 +358,25 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
return mMeasuredFrequencyError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of measured frequency and PPM error
|
||||
*/
|
||||
public String getMeasuredErrorStatus()
|
||||
{
|
||||
if(hasMeasuredFrequencyError())
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Measured Error: ");
|
||||
sb.append(getMeasuredFrequencyError());
|
||||
sb.append("Hz (");
|
||||
sb.append(mFrequencyErrorPPMFormat.format(getPPMFrequencyError()));
|
||||
sb.append("ppm)");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tuner controller has a non-zero measured frequency error value.
|
||||
*/
|
||||
@@ -481,12 +556,14 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
/**
|
||||
* Records the complex I/Q buffers produced by the tuner
|
||||
* @param userPreferences to obtain a baseband recorder
|
||||
* @param statusListener to receive updates on recording file name and size
|
||||
* @param prefix for the recording file name (ie tuner class name)
|
||||
*/
|
||||
public void startRecorder(UserPreferences userPreferences)
|
||||
public void startRecorder(UserPreferences userPreferences, IRecordingStatusListener statusListener, String prefix)
|
||||
{
|
||||
if(!isRecording())
|
||||
{
|
||||
mRecorder = RecorderFactory.getTunerRecorder("TUNER_" + getFrequency(), userPreferences);
|
||||
mRecorder = RecorderFactory.getTunerRecorder(prefix + "_" + getFrequency(), userPreferences, statusListener);
|
||||
mRecorder.setSampleRate((float)getSampleRate());
|
||||
mRecorder.start();
|
||||
addBufferListener(mRecorder);
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationEditor;
|
||||
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.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.RowFilter;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.Dimension;
|
||||
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 JToggleButton mRecordButton;
|
||||
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();
|
||||
private UserPreferences mRecorderManager;
|
||||
|
||||
public TunerEditor(TunerConfigurationModel tunerConfigurationModel, UserPreferences userPreferences)
|
||||
{
|
||||
mTunerConfigurationModel = tunerConfigurationModel;
|
||||
mRecorderManager = userPreferences;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the lock state of the tuner so that the frequency control and sample rate controls
|
||||
* can be adjusted accordingly.
|
||||
*
|
||||
* @param locked true if the tuner is locked and the controls should be locked.
|
||||
*/
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
mFrequencyControl.setEnabled(!locked);
|
||||
|
||||
if(mEditor instanceof TunerConfigurationEditor)
|
||||
{
|
||||
((TunerConfigurationEditor)mEditor).setTunerLockState(locked);
|
||||
}
|
||||
}
|
||||
|
||||
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][grow,fill]", "[][][][grow," +
|
||||
"fill][]"));
|
||||
|
||||
listPanel.add(mSelectedTunerType, "span");
|
||||
|
||||
mFrequencyControl.setEnabled(false);
|
||||
listPanel.add(mFrequencyControl, "span 2");
|
||||
|
||||
mRecordButton = new JToggleButton("Record");
|
||||
mRecordButton.setEnabled(false);
|
||||
mRecordButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
if(mRecordButton.isSelected())
|
||||
{
|
||||
if(hasItem())
|
||||
{
|
||||
getItem().getTunerController().startRecorder(mRecorderManager);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(hasItem())
|
||||
{
|
||||
getItem().getTunerController().stopRecorder();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
listPanel.add(mRecordButton, "wrap");
|
||||
|
||||
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());
|
||||
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 and set enabled state
|
||||
mFrequencyControl.addListener(tuner.getTunerController());
|
||||
mFrequencyControl.setEnabled(!tuner.getTunerController().isLocked());
|
||||
|
||||
//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);
|
||||
|
||||
mRecordButton.setEnabled(true);
|
||||
mRecordButton.setSelected(getItem().getTunerController().isRecording());
|
||||
}
|
||||
else
|
||||
{
|
||||
mSelectedTunerType.setText("No Tuner Selected");
|
||||
mFrequencyControl.setEnabled(false);
|
||||
mTunerConfigurationTable.setEnabled(false);
|
||||
mNewConfigurationButton.setEnabled(false);
|
||||
mRowSorter.setRowFilter(null);
|
||||
mEditor = new EmptyEditor<TunerConfiguration>();
|
||||
mRecordButton.setEnabled(false);
|
||||
mRecordButton.setSelected(false);
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Tuner generated events
|
||||
*/
|
||||
public class TunerEvent
|
||||
{
|
||||
private Tuner mTuner;
|
||||
private Event mEvent;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tuner that is generating the event
|
||||
* @param event from the tuner
|
||||
*/
|
||||
public TunerEvent(Tuner tuner, Event event)
|
||||
{
|
||||
mTuner = tuner;
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner source for the event
|
||||
*/
|
||||
public Tuner getTuner()
|
||||
{
|
||||
return mTuner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tuner for this event is non-null
|
||||
*/
|
||||
public boolean hasTuner()
|
||||
{
|
||||
return mTuner != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event type
|
||||
*/
|
||||
public Event getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
@@ -45,15 +64,17 @@ public class TunerEvent
|
||||
|
||||
public enum Event
|
||||
{
|
||||
CHANNEL_COUNT,
|
||||
ERROR_STATE,
|
||||
FREQUENCY_UPDATED,
|
||||
FREQUENCY_ERROR_UPDATED,
|
||||
LOCK_STATE_CHANGE,
|
||||
MEASURED_FREQUENCY_ERROR_UPDATED,
|
||||
SAMPLE_RATE_UPDATED,
|
||||
UPDATE_CHANNEL_COUNT,
|
||||
UPDATE_FREQUENCY,
|
||||
UPDATE_FREQUENCY_ERROR,
|
||||
UPDATE_LOCK_STATE,
|
||||
UPDATE_MEASURED_FREQUENCY_ERROR,
|
||||
UPDATE_SAMPLE_RATE,
|
||||
|
||||
CLEAR_MAIN_SPECTRAL_DISPLAY,
|
||||
NOTIFICATION_ERROR_STATE,
|
||||
NOTIFICATION_SHUTTING_DOWN,
|
||||
|
||||
REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY,
|
||||
REQUEST_MAIN_SPECTRAL_DISPLAY,
|
||||
REQUEST_NEW_SPECTRAL_DISPLAY;
|
||||
}
|
||||
|
||||
107
src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java
Normal file
107
src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.preference.source.ChannelizerType;
|
||||
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.EmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
/**
|
||||
* Factory for creating tuners and tuner configurations
|
||||
*/
|
||||
public class TunerFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(TunerFactory.class);
|
||||
|
||||
/**
|
||||
* Create a USB tuner
|
||||
* @param tunerClass to instantiate
|
||||
* @param port usb
|
||||
* @param bus usb
|
||||
* @param tunerErrorListener to listen for errors from the tuner
|
||||
* @param channelizerType for the tuner
|
||||
* @return instantiated tuner
|
||||
* @throws SourceException if the tuner class is unrecognized
|
||||
*/
|
||||
public static Tuner getUsbTuner(TunerClass tunerClass, int port, int bus, ITunerErrorListener tunerErrorListener,
|
||||
ChannelizerType channelizerType) throws SourceException
|
||||
{
|
||||
switch(tunerClass)
|
||||
{
|
||||
case AIRSPY:
|
||||
return new AirspyTuner(new AirspyTunerController(bus, port, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
TargetDataLine tdl1 = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO);
|
||||
if(tdl1 != null)
|
||||
{
|
||||
FCD1TunerController controller = new FCD1TunerController(tdl1, bus, port, tunerErrorListener);
|
||||
return new FCDTuner(controller, tunerErrorListener);
|
||||
}
|
||||
throw new SourceException("Unable to find matching tuner sound card mixer");
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
TargetDataLine tdl2 = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS);
|
||||
if(tdl2 != null)
|
||||
{
|
||||
FCD2TunerController controller = new FCD2TunerController(tdl2, bus, port, tunerErrorListener);
|
||||
return new FCDTuner(controller, tunerErrorListener);
|
||||
}
|
||||
throw new SourceException("Unable to find matching tuner sound card mixer");
|
||||
case HACKRF:
|
||||
return new HackRFTuner(new HackRFTunerController(bus, port, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
case RTL2832:
|
||||
return new RTL2832Tuner(new RTL2832TunerController(bus, port, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
default:
|
||||
throw new SourceException("Unrecognized tuner class [" + tunerClass + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RTL-2832 embedded tuner instance.
|
||||
* @param tunerType to create
|
||||
* @param adapter exposing the RTL-2832 tuner controller functions
|
||||
* @return embedded tuner
|
||||
*/
|
||||
public static EmbeddedTuner getRtlEmbeddedTuner(TunerType tunerType,
|
||||
RTL2832TunerController.ControllerAdapter adapter) throws SourceException
|
||||
{
|
||||
return switch(tunerType)
|
||||
{
|
||||
case ELONICS_E4000 -> new E4KEmbeddedTuner(adapter);
|
||||
case RAFAELMICRO_R820T -> new R820TEmbeddedTuner(adapter);
|
||||
default -> throw new SourceException("Unsupported/Unrecognized Tuner Type");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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;
|
||||
@@ -94,7 +97,7 @@ public class TunerFrequencyErrorMonitor implements Listener<SourceEvent>
|
||||
private void broadcast(int averageError)
|
||||
{
|
||||
mTuner.getTunerController().setMeasuredFrequencyError(averageError);
|
||||
mTuner.broadcast(new TunerEvent(mTuner, TunerEvent.Event.MEASURED_FREQUENCY_ERROR_UPDATED));
|
||||
mTuner.broadcast(new TunerEvent(mTuner, TunerEvent.Event.UPDATE_MEASURED_FREQUENCY_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,562 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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.preference.UserPreferences;
|
||||
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 io.github.dsheirer.source.tuner.usb.USBMasterProcessor;
|
||||
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 javax.sound.sampled.TargetDataLine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class TunerManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerManager.class);
|
||||
private static final int MAXIMUM_USB_2_DATA_RATE = 480000000;
|
||||
|
||||
private TunerModel mTunerModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private Map<Integer,List<Tuner>> mUSBBusTunerMap = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* 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 USBMasterProcessor LIBUSB_TRANSFER_PROCESSOR;
|
||||
|
||||
static
|
||||
{
|
||||
LIBUSB_TRANSFER_PROCESSOR = new USBMasterProcessor();
|
||||
}
|
||||
|
||||
public TunerManager(TunerModel tunerModel, UserPreferences userPreferences)
|
||||
{
|
||||
mTunerModel = tunerModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
initTuners();
|
||||
validateUSBBusTransferRates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs cleanup of USB related issues
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mLog.info("Shutting down LibUsb timeout processor ...");
|
||||
|
||||
LIBUSB_TRANSFER_PROCESSOR.shutdown();
|
||||
|
||||
mLog.info("Shutting down LibUsb ...");
|
||||
try
|
||||
{
|
||||
LibUsb.exit(null);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error shutting down LibUsb", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
sb.append("USB Bus [").append(bus).append("]");
|
||||
sb.append(" Device [");
|
||||
sb.append(String.format("%04X", descriptor.idVendor()));
|
||||
sb.append(":");
|
||||
sb.append(String.format("%04X", descriptor.idProduct()));
|
||||
sb.append("]");
|
||||
|
||||
if(status.isLoaded())
|
||||
{
|
||||
Tuner tuner = status.getTuner();
|
||||
|
||||
try
|
||||
{
|
||||
mTunerModel.addTuner(tuner);
|
||||
sb.append(" LOADED: ");
|
||||
sb.append(tuner.toString());
|
||||
sb.append(" Max Rate:").append(tuner.getMaximumUSBBitsPerSecond()).append(" bps");
|
||||
updateUSBBusTunerMap(bus, tuner);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
sb.append(" NOT LOADED: ");
|
||||
sb.append(status.getInfo());
|
||||
sb.append(" Error:").append(e.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" ").append(getDeviceClass(descriptor.bDeviceClass()));
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
LibUsb.freeDeviceList(deviceList, true);
|
||||
}
|
||||
|
||||
private static String getDeviceClass(byte deviceClass)
|
||||
{
|
||||
switch(deviceClass)
|
||||
{
|
||||
case 0:
|
||||
return "Unknown Device - Class 0";
|
||||
case 2:
|
||||
return "Communications Device";
|
||||
case 3:
|
||||
return "HID Device";
|
||||
case 5:
|
||||
return "Physical Device";
|
||||
case 6:
|
||||
return "Still Imaging Device";
|
||||
case 7:
|
||||
return "Printer Device";
|
||||
case 8:
|
||||
return "Mass Storage Device";
|
||||
case 9:
|
||||
return "Hub Device";
|
||||
case 0xA:
|
||||
return "Communications Device";
|
||||
case 0xB:
|
||||
return "Smart Card Device";
|
||||
case 0xD:
|
||||
return "Content Security Device";
|
||||
case 0xE:
|
||||
return "Video Device";
|
||||
case 0xF:
|
||||
return "Personal Healthcare Device";
|
||||
case 0x10:
|
||||
return "Audio/Video Device";
|
||||
case (byte)0xDC:
|
||||
return "Diagnostic Device";
|
||||
case (byte)0xE0:
|
||||
return "Wireless Controller Device";
|
||||
case (byte)0xEF:
|
||||
return "Miscellaneous Device";
|
||||
default:
|
||||
return "Unknown Device - Class " + deviceClass;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUSBBusTunerMap(int bus, Tuner tuner)
|
||||
{
|
||||
if(mUSBBusTunerMap.containsKey(bus))
|
||||
{
|
||||
mUSBBusTunerMap.get(bus).add(tuner);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Tuner> tuners = new ArrayList<>();
|
||||
tuners.add(tuner);
|
||||
mUSBBusTunerMap.put(bus, tuners);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateUSBBusTransferRates()
|
||||
{
|
||||
mLog.info("-------------------------------------------------------------");
|
||||
mLog.info("USB Bus - Potential Maximum Data Rates");
|
||||
|
||||
boolean warning = false;
|
||||
|
||||
List<Integer> buses = new ArrayList(mUSBBusTunerMap.keySet());
|
||||
Collections.sort(buses);
|
||||
for(Integer bus: buses)
|
||||
{
|
||||
int totalBPS = 0;
|
||||
|
||||
for(Tuner tuner: mUSBBusTunerMap.get(bus))
|
||||
{
|
||||
totalBPS += tuner.getMaximumUSBBitsPerSecond();
|
||||
}
|
||||
|
||||
if(totalBPS > (MAXIMUM_USB_2_DATA_RATE * .7))
|
||||
{
|
||||
warning = true;
|
||||
}
|
||||
|
||||
mLog.info("USB Bus [" + bus + "] Rate [" + totalBPS + "] bits per second" + (warning ? " *** WARNING ***" : ""));
|
||||
}
|
||||
|
||||
if(warning)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(
|
||||
"\n\t\t******************************************************************************************************\n" +
|
||||
"\t\t* WARNING: the combined data rates of tuner(s) on USB bus(es) above can potentially saturate or exceed\n" +
|
||||
"\t\t* the maximum USB 2.0 data rate of 480 Mbps. This can cause data loss and severely impact decoding\n" +
|
||||
"\t\t* performance resulting in missed calls and stuttering audio. Recommend moving tuner(s) to other\n" +
|
||||
"\t\t* USB ports/buses to better balance data rates across available USB buses.\n" +
|
||||
"\t\t******************************************************************************************************");
|
||||
mLog.warn(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
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 HACKRF_JAWBREAKER:
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all tuners in preparation for shutdown
|
||||
*/
|
||||
public void releaseTuners()
|
||||
{
|
||||
mLog.info("Releasing [" + mTunerModel.getTuners().size() + "] tuner(s) ...");
|
||||
Tuner tuner = mTunerModel.getTuner(0);
|
||||
|
||||
while(tuner != null)
|
||||
{
|
||||
mTunerModel.removeTuner(tuner);
|
||||
tuner.dispose();
|
||||
tuner = mTunerModel.getTuner(0);
|
||||
}
|
||||
}
|
||||
|
||||
private TunerInitStatus initAirspyTuner(Device device, DeviceDescriptor descriptor)
|
||||
{
|
||||
try
|
||||
{
|
||||
AirspyTunerController airspyController = new AirspyTunerController(device);
|
||||
|
||||
airspyController.init();
|
||||
|
||||
AirspyTuner tuner = new AirspyTuner(airspyController, mUserPreferences);
|
||||
|
||||
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";
|
||||
|
||||
TargetDataLine tdl = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO);
|
||||
|
||||
if(tdl != null)
|
||||
{
|
||||
FCD1TunerController controller = new FCD1TunerController(tdl, device, descriptor);
|
||||
|
||||
try
|
||||
{
|
||||
controller.init();
|
||||
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
||||
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";
|
||||
|
||||
TargetDataLine dataline = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS);
|
||||
|
||||
if(dataline != null)
|
||||
{
|
||||
FCD2TunerController controller = new FCD2TunerController(dataline, device, descriptor);
|
||||
|
||||
try
|
||||
{
|
||||
controller.init();
|
||||
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
||||
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, mUserPreferences);
|
||||
|
||||
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, mUserPreferences);
|
||||
|
||||
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, mUserPreferences);
|
||||
|
||||
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() + "]";
|
||||
|
||||
mLog.info(reason);
|
||||
break;
|
||||
}
|
||||
|
||||
return new TunerInitStatus(null, reason);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTuner;
|
||||
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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 FREQUENCY_ERROR = 5;
|
||||
public static final int MEASURED_FREQUENCY_ERROR = 6;
|
||||
public static final int SPECTRAL_DISPLAY_NEW = 7;
|
||||
private static final int MAXIMUM_RECORDING_TUNERS_ALLOWED = 1;
|
||||
private static final String MHZ = " MHz";
|
||||
private static final String[] COLUMNS = {"Tuner", "ID", "Sample Rate", "Frequency", "Channels", "Current PPM",
|
||||
"Measured Error", "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 DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorted list of tuner names
|
||||
*/
|
||||
public List<String> getTunerNames()
|
||||
{
|
||||
List<String> names = new ArrayList<>();
|
||||
|
||||
for(Tuner tuner: mTuners)
|
||||
{
|
||||
names.add(tuner.getName());
|
||||
}
|
||||
|
||||
Collections.sort(names);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public Tuner getTuner(int index)
|
||||
{
|
||||
if(index < mTuners.size())
|
||||
{
|
||||
return mTuners.get(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tuner that matches the name argument
|
||||
*
|
||||
* @param name of the tuner
|
||||
* @return named tuner or null
|
||||
*/
|
||||
public Tuner getTuner(String name)
|
||||
{
|
||||
if(name != null)
|
||||
{
|
||||
for(Tuner tuner : mTuners)
|
||||
{
|
||||
if(!tuner.hasError() && tuner.getName().equalsIgnoreCase(name))
|
||||
{
|
||||
return tuner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a recording tuner can be added to the set of tuners. This is constrained by the maximum
|
||||
* allowed limit.
|
||||
* @return true if a recording tuner can be added.
|
||||
*/
|
||||
public boolean canAddRecordingTuner()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(Tuner tuner: mTuners)
|
||||
{
|
||||
if(tuner instanceof RecordingTuner)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count < MAXIMUM_RECORDING_TUNERS_ALLOWED;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
SystemProperties properties = SystemProperties.getInstance();
|
||||
boolean enabled = properties.get(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, true);
|
||||
|
||||
if(enabled && mTuners.size() > 0)
|
||||
{
|
||||
//Hack: the airspy tuner would lockup aperiodically and refuse to produce
|
||||
//transfer buffers ... delaying registering for buffers for 500 ms seems
|
||||
//to allow the airspy to stabilize before we start asking for samples.
|
||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
broadcast(new TunerEvent(mTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
|
||||
}
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast(new TunerEvent(null, Event.CLEAR_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_UPDATED:
|
||||
fireTableCellUpdated(index, FREQUENCY);
|
||||
mTunerConfigurationModel.tunerFrequencyChanged(event.getTuner());
|
||||
break;
|
||||
case FREQUENCY_ERROR_UPDATED:
|
||||
fireTableCellUpdated(index, FREQUENCY_ERROR);
|
||||
break;
|
||||
case MEASURED_FREQUENCY_ERROR_UPDATED:
|
||||
fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
|
||||
break;
|
||||
case SAMPLE_RATE_UPDATED:
|
||||
fireTableCellUpdated(index, SAMPLE_RATE);
|
||||
break;
|
||||
case REQUEST_MAIN_SPECTRAL_DISPLAY:
|
||||
fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
|
||||
break;
|
||||
case ERROR_STATE:
|
||||
fireTableCellUpdated(index, CHANNEL_COUNT);
|
||||
fireTableCellUpdated(index, FREQUENCY);
|
||||
fireTableCellUpdated(index, FREQUENCY_ERROR);
|
||||
fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
|
||||
fireTableCellUpdated(index, SAMPLE_RATE);
|
||||
break;
|
||||
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:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return tuner.getErrorMessage();
|
||||
}
|
||||
double sampleRate = tuner.getTunerController().getSampleRate();
|
||||
return mSampleRateFormat.format(sampleRate / 1E6D) + MHZ;
|
||||
case FREQUENCY:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return tuner.getErrorMessage();
|
||||
}
|
||||
try
|
||||
{
|
||||
long frequency = tuner.getTunerController().getFrequency();
|
||||
|
||||
return mFrequencyFormat.format(frequency / 1E6D) + MHZ;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
case CHANNEL_COUNT:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return tuner.getErrorMessage();
|
||||
}
|
||||
int channelCount = tuner.getChannelSourceManager().getTunerChannelCount();
|
||||
return channelCount + " (" + (tuner.getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
|
||||
case FREQUENCY_ERROR:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return tuner.getErrorMessage();
|
||||
}
|
||||
double ppm = tuner.getTunerController().getFrequencyCorrection();
|
||||
return mFrequencyErrorPPMFormat.format(ppm);
|
||||
case MEASURED_FREQUENCY_ERROR:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return tuner.getErrorMessage();
|
||||
}
|
||||
if(tuner.getTunerController().hasMeasuredFrequencyError())
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(tuner.getTunerController().getMeasuredFrequencyError());
|
||||
sb.append("Hz (");
|
||||
sb.append(mFrequencyErrorPPMFormat.format(tuner.getTunerController().getPPMFrequencyError()));
|
||||
sb.append("ppm)");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
case SPECTRAL_DISPLAY_NEW:
|
||||
if(tuner.hasError())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 specified frequency and bandwidth
|
||||
*
|
||||
* Returns null if no tuner can source the channel
|
||||
*/
|
||||
public Source getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification, String preferredTuner)
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
if(tunerChannel != null && channelSpecification != null)
|
||||
{
|
||||
Tuner tuner;
|
||||
|
||||
if(preferredTuner != null)
|
||||
{
|
||||
tuner = getTuner(preferredTuner);
|
||||
|
||||
if(tuner != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Fall through to logger below
|
||||
}
|
||||
}
|
||||
|
||||
mLog.info("Unable to source channel [" + tunerChannel.getFrequency() + "] from preferred tuner [" +
|
||||
preferredTuner + "] - searching for another tuner");
|
||||
}
|
||||
|
||||
Iterator<Tuner> it = mTuners.iterator();
|
||||
|
||||
while(it.hasNext() && source == null)
|
||||
{
|
||||
tuner = it.next();
|
||||
|
||||
if(!tuner.hasError())
|
||||
{
|
||||
try
|
||||
{
|
||||
source = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error obtaining channel from tuner [" + tuner.getName() + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.playlist.PlaylistManager;
|
||||
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.EventQueue;
|
||||
|
||||
public class TunerSpectralDisplayManager implements Listener<TunerEvent>
|
||||
{
|
||||
private SpectralDisplayPanel mSpectralDisplayPanel;
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private SettingsManager mSettingsManager;
|
||||
private TunerModel mTunerModel;
|
||||
|
||||
public TunerSpectralDisplayManager(SpectralDisplayPanel panel, PlaylistManager playlistManager,
|
||||
SettingsManager settingsManager, TunerModel tunerModel)
|
||||
{
|
||||
mSpectralDisplayPanel = panel;
|
||||
mPlaylistManager = playlistManager;
|
||||
mSettingsManager = settingsManager;
|
||||
mTunerModel = tunerModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(TunerEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case CLEAR_MAIN_SPECTRAL_DISPLAY:
|
||||
mSpectralDisplayPanel.clearTuner();
|
||||
break;
|
||||
case REQUEST_MAIN_SPECTRAL_DISPLAY:
|
||||
mSpectralDisplayPanel.showTuner(event.getTuner());
|
||||
break;
|
||||
case REQUEST_NEW_SPECTRAL_DISPLAY:
|
||||
final SpectrumFrame frame = new SpectrumFrame(mPlaylistManager, mSettingsManager, mTunerModel, event.getTuner());
|
||||
EventQueue.invokeLater(() -> frame.setVisible(true));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,95 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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 java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* List of known and optionally supported tuner types
|
||||
*/
|
||||
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" ),
|
||||
TEST("Test"),
|
||||
RECORDING("Recording"),
|
||||
UNKNOWN( "Unknown" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private TunerType( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
AIRSPY_R820T("R820T"),
|
||||
ELONICS_E4000("E4000"),
|
||||
FCI_FC2580("FC2580"),
|
||||
FITIPOWER_FC0012("FC0012"),
|
||||
FITIPOWER_FC0013("FC0013"),
|
||||
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro"),
|
||||
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro Plus"),
|
||||
HACKRF_ONE("ONE"),
|
||||
HACKRF_JAWBREAKER("Jawbreaker"),
|
||||
HACKRF_RAD1O("RAD1O"),
|
||||
RAFAELMICRO_R820T("R820T"),
|
||||
RAFAELMICRO_R828D("R828D"),
|
||||
TEST("Test"),
|
||||
RECORDING("Recording"),
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param label pretty
|
||||
*/
|
||||
TunerType(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Supported USB tuner types
|
||||
*/
|
||||
public static EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, ELONICS_E4000, FITIPOWER_FC0012,
|
||||
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, RAFAELMICRO_R820T);
|
||||
|
||||
/**
|
||||
* Supported sound card tuner types
|
||||
*/
|
||||
public static EnumSet<TunerType> SUPPORTED_SOUND_CARD_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
||||
|
||||
/**
|
||||
* Indicates if this tuner is supported as a USB tuner
|
||||
*/
|
||||
public boolean isSupportedUsbTuner()
|
||||
{
|
||||
return SUPPORTED_USB_TUNERS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tuner type is supported as a sound card tuner
|
||||
*/
|
||||
public boolean isSupportedSoundCardTuner()
|
||||
{
|
||||
return SUPPORTED_SOUND_CARD_TUNERS.contains(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.recording.AddRecordingTunerDialog;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTuner;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.RowSorter;
|
||||
import javax.swing.SortOrder;
|
||||
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.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
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 static final String TABLE_PREFERENCE_KEY = "tuner.view.panel";
|
||||
|
||||
private TunerModel mTunerModel;
|
||||
private JTable mTunerTable;
|
||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||
private TableRowSorter<TunerModel> mRowSorter;
|
||||
private JideSplitPane mSplitPane;
|
||||
private TunerEditor mTunerEditor;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public TunerViewPanel(TunerModel tunerModel, UserPreferences userPreferences)
|
||||
{
|
||||
mTunerModel = tunerModel;
|
||||
mTunerEditor = new TunerEditor(mTunerModel.getTunerConfigurationModel(), userPreferences);
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
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();
|
||||
|
||||
if(row >= 0)
|
||||
{
|
||||
int modelRow = mTunerTable.convertRowIndexToModel(row);
|
||||
mTunerEditor.setItem(mTunerModel.getTuner(modelRow));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mTunerModel.addListener(new Listener<TunerEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(TunerEvent tunerEvent)
|
||||
{
|
||||
switch(tunerEvent.getEvent())
|
||||
{
|
||||
case LOCK_STATE_CHANGE:
|
||||
if(tunerEvent.getTuner() != null)
|
||||
{
|
||||
int row = mTunerTable.getSelectedRow();
|
||||
|
||||
if(row >= 0)
|
||||
{
|
||||
int modelRow = mTunerTable.convertRowIndexToModel(row);
|
||||
Tuner selectedTuner = mTunerModel.getTuner(modelRow);
|
||||
|
||||
if(selectedTuner == tunerEvent.getTuner())
|
||||
{
|
||||
mTunerEditor.setTunerLockState(selectedTuner.getTunerController().isLocked());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mTunerTable.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
if(e.getButton() == MouseEvent.BUTTON1) //Left-Click
|
||||
{
|
||||
int column = mTunerTable.columnAtPoint(e.getPoint());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(e.getButton() == MouseEvent.BUTTON3) //Right-Click
|
||||
{
|
||||
int tableRow = mTunerTable.rowAtPoint(e.getPoint());
|
||||
int modelRow = mTunerTable.convertRowIndexToModel(tableRow);
|
||||
|
||||
final Tuner tuner = mTunerModel.getTuner(modelRow);
|
||||
|
||||
if(tuner instanceof RecordingTuner && tuner.getChannelSourceManager().getTunerChannelCount() == 0)
|
||||
{
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
JMenuItem removeTunerItem = new JMenuItem("Remove Recording Tuner");
|
||||
removeTunerItem.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
mTunerModel.removeTuner(tuner);
|
||||
}
|
||||
});
|
||||
popupMenu.add(removeTunerItem);
|
||||
popupMenu.show(mTunerTable, e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TableCellRenderer linkCellRenderer = new LinkCellRenderer();
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.SPECTRAL_DISPLAY_NEW).setCellRenderer(linkCellRenderer);
|
||||
|
||||
TableCellRenderer errorCellRenderer = new ErrorCellRenderer();
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.SAMPLE_RATE).setCellRenderer(errorCellRenderer);
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.FREQUENCY).setCellRenderer(errorCellRenderer);
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.CHANNEL_COUNT).setCellRenderer(errorCellRenderer);
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.FREQUENCY_ERROR).setCellRenderer(errorCellRenderer);
|
||||
mTunerTable.getColumnModel().getColumn(TunerModel.MEASURED_FREQUENCY_ERROR).setCellRenderer(errorCellRenderer);
|
||||
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTunerTable, TABLE_PREFERENCE_KEY);
|
||||
JScrollPane tunerTableScroller = new JScrollPane(mTunerTable);
|
||||
tunerTableScroller.setPreferredSize(new Dimension(400, 20));
|
||||
|
||||
JPanel tunerTablePanel = new JPanel();
|
||||
tunerTablePanel.setLayout(new MigLayout("insets 0 0 0 0", "[fill,grow][]", "[fill,grow][]"));
|
||||
tunerTablePanel.add(tunerTableScroller, "span");
|
||||
|
||||
tunerTablePanel.add(new JLabel("")); //Empty spacer
|
||||
JButton addRecordingTunerButton = new JButton("Add Recording Tuner");
|
||||
addRecordingTunerButton.setEnabled(mTunerModel.canAddRecordingTuner());
|
||||
addRecordingTunerButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
AddRecordingTunerDialog dialog = new AddRecordingTunerDialog(mUserPreferences, mTunerModel);
|
||||
dialog.setLocationRelativeTo(TunerViewPanel.this);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
tunerTablePanel.add(addRecordingTunerButton);
|
||||
|
||||
tunerTablePanel.add(addRecordingTunerButton);
|
||||
|
||||
JScrollPane editorScroller = new JScrollPane(mTunerEditor);
|
||||
editorScroller.setPreferredSize(new Dimension(400, 80));
|
||||
|
||||
mSplitPane = new JideSplitPane();
|
||||
mSplitPane.setOrientation(JideSplitPane.VERTICAL_SPLIT);
|
||||
mSplitPane.add(tunerTablePanel);
|
||||
mSplitPane.add(editorScroller);
|
||||
|
||||
add(mSplitPane);
|
||||
}
|
||||
|
||||
public class ErrorCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
Tuner tuner = mTunerModel.getTuner(mTunerTable.convertRowIndexToModel(row));
|
||||
|
||||
if(isSelected)
|
||||
{
|
||||
component.setBackground(table.getSelectionBackground());
|
||||
}
|
||||
else if(tuner.hasError())
|
||||
{
|
||||
component.setBackground(Color.RED);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.setBackground(table.getBackground());
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
label.setToolTipText("Show this tuner in a new spectral display");
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,59 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.airspy.AirspyTunerController.BoardID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Airspy Tuner
|
||||
*/
|
||||
public class AirspyTuner extends Tuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AirspyTuner.class);
|
||||
|
||||
public AirspyTuner(AirspyTunerController controller, UserPreferences userPreferences)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller for the airspy
|
||||
* @param tunerErrorListener to listen for errors from this tuner
|
||||
* @param channelizerType for the channelizer
|
||||
*/
|
||||
public AirspyTuner(AirspyTunerController controller, ITunerErrorListener tunerErrorListener,
|
||||
ChannelizerType channelizerType)
|
||||
{
|
||||
super("Airspy " + controller.getDeviceInfo().getSerialNumber(), controller, userPreferences);
|
||||
super(controller, tunerErrorListener, channelizerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
return "Airspy " + getController().getDeviceInfo().getSerialNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Airspy tuner controller
|
||||
*/
|
||||
public AirspyTunerController getController()
|
||||
{
|
||||
return (AirspyTunerController)getTunerController();
|
||||
@@ -64,12 +80,6 @@ public class AirspyTuner extends Tuner
|
||||
return TunerClass.AIRSPY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
/*
|
||||
* *********************************************************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
* *********************************************************************************************************************
|
||||
* 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;
|
||||
|
||||
@@ -23,120 +25,107 @@ import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
|
||||
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;
|
||||
private Gain mGain = AirspyTunerController.LINEARITY_GAIN_DEFAULT;
|
||||
private int mSampleRate = AirspyTunerController.DEFAULT_SAMPLE_RATE.getRate();
|
||||
private int mIFGain = AirspyTunerController.IF_GAIN_DEFAULT;
|
||||
private int mMixerGain = AirspyTunerController.MIXER_GAIN_DEFAULT;
|
||||
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
|
||||
private boolean mMixerAGC = false;
|
||||
private boolean mLNAAGC = false;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
*/
|
||||
public AirspyTunerConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
public AirspyTunerConfiguration( String uniqueID, String name )
|
||||
{
|
||||
super( uniqueID, name );
|
||||
}
|
||||
|
||||
@Override
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
public TunerType getTunerType()
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
*/
|
||||
public AirspyTunerConfiguration()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate( int sampleRate )
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
@Override
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
||||
public Gain getGain()
|
||||
{
|
||||
return mGain;
|
||||
}
|
||||
|
||||
public void setGain( Gain gain )
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
public AirspyTunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "if_gain")
|
||||
public int getIFGain()
|
||||
{
|
||||
return mIFGain;
|
||||
}
|
||||
|
||||
public void setIFGain( int gain )
|
||||
{
|
||||
mIFGain = gain;
|
||||
}
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mixer_gain")
|
||||
public int getMixerGain()
|
||||
{
|
||||
return mMixerGain;
|
||||
}
|
||||
|
||||
public void setMixerGain( int gain )
|
||||
{
|
||||
mMixerGain = gain;
|
||||
}
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
|
||||
public int getLNAGain()
|
||||
{
|
||||
return mLNAGain;
|
||||
}
|
||||
|
||||
public void setLNAGain( int gain )
|
||||
{
|
||||
mLNAGain = gain;
|
||||
}
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
||||
public Gain getGain()
|
||||
{
|
||||
return mGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mixer_agc")
|
||||
public boolean isMixerAGC()
|
||||
{
|
||||
return mMixerAGC;
|
||||
}
|
||||
|
||||
public void setMixerAGC( boolean enabled )
|
||||
{
|
||||
mMixerAGC = enabled;
|
||||
}
|
||||
public void setGain(Gain gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_agc")
|
||||
public boolean isLNAAGC()
|
||||
{
|
||||
return mLNAAGC;
|
||||
}
|
||||
|
||||
public void setLNAAGC( boolean enabled )
|
||||
{
|
||||
mLNAAGC = enabled;
|
||||
}
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "if_gain")
|
||||
public int getIFGain()
|
||||
{
|
||||
return mIFGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection( double value )
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
public void setIFGain(int gain)
|
||||
{
|
||||
mIFGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mixer_gain")
|
||||
public int getMixerGain()
|
||||
{
|
||||
return mMixerGain;
|
||||
}
|
||||
|
||||
public void setMixerGain(int gain)
|
||||
{
|
||||
mMixerGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
|
||||
public int getLNAGain()
|
||||
{
|
||||
return mLNAGain;
|
||||
}
|
||||
|
||||
public void setLNAGain(int gain)
|
||||
{
|
||||
mLNAGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mixer_agc")
|
||||
public boolean isMixerAGC()
|
||||
{
|
||||
return mMixerAGC;
|
||||
}
|
||||
|
||||
public void setMixerAGC(boolean enabled)
|
||||
{
|
||||
mMixerAGC = enabled;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_agc")
|
||||
public boolean isLNAAGC()
|
||||
{
|
||||
return mLNAAGC;
|
||||
}
|
||||
|
||||
public void setLNAAGC(boolean enabled)
|
||||
{
|
||||
mLNAAGC = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
|
||||
package io.github.dsheirer.source.tuner.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.buffer.airspy.AirspyNativeBufferFactory;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
|
||||
import io.github.dsheirer.source.tuner.usb.USBTunerController;
|
||||
import org.apache.commons.io.EndianUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
@@ -39,10 +39,13 @@ import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Airspy tuner controller
|
||||
*/
|
||||
public class AirspyTunerController extends USBTunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
|
||||
public static final Gain LINEARITY_GAIN_DEFAULT = Gain.LINEARITY_14;
|
||||
public static final Gain SENSITIVITY_GAIN_DEFAULT = Gain.SENSITIVITY_10;
|
||||
public static final int GAIN_MIN = 1;
|
||||
@@ -60,32 +63,44 @@ public class AirspyTunerController extends USBTunerController
|
||||
public static final long FREQUENCY_MAX = 1800000000l;
|
||||
public static final long FREQUENCY_DEFAULT = 101100000;
|
||||
public static final double USABLE_BANDWIDTH_PERCENT = 0.90;
|
||||
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate(0, 10000000, "10.00 MHz");
|
||||
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
private static final byte USB_INTERFACE = (byte) 0x0;
|
||||
private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
|
||||
private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
|
||||
|
||||
private Device mDevice;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
|
||||
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate(0, 10000000, "10.00 MHz");
|
||||
private AirspyNativeBufferFactory mNativeBufferFactory = new AirspyNativeBufferFactory();
|
||||
private AirspyDeviceInformation mDeviceInfo;
|
||||
private List<AirspySampleRate> mSampleRates = new ArrayList<>();
|
||||
private int mSampleRate = 0;
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
private USBTransferProcessor mUSBTransferProcessor;
|
||||
|
||||
public AirspyTunerController(Device device) throws SourceException
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
* @param tunerErrorListener to receive error notifications from this controller
|
||||
*/
|
||||
public AirspyTunerController(int bus, int port, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(FREQUENCY_MIN, FREQUENCY_MAX, 0, USABLE_BANDWIDTH_PERCENT);
|
||||
super(bus, port, FREQUENCY_MIN, FREQUENCY_MAX, 0, USABLE_BANDWIDTH_PERCENT, tunerErrorListener);
|
||||
}
|
||||
|
||||
mDevice = device;
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.AIRSPY_R820T;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected INativeBufferFactory getNativeBufferFactory()
|
||||
{
|
||||
return mNativeBufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTransferBufferSize()
|
||||
{
|
||||
return USB_TRANSFER_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,35 +110,9 @@ public class AirspyTunerController extends USBTunerController
|
||||
return USB_TRANSFER_BUFFER_SIZE / 4;
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
@Override
|
||||
protected void deviceStart() throws SourceException
|
||||
{
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
|
||||
int result = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
if(result == LibUsb.ERROR_ACCESS)
|
||||
{
|
||||
mLog.error("Unable to access Airspy - insufficient permissions."
|
||||
+ " If you are running a Linux OS, have you installed the "
|
||||
+ "airspy rules file in \\etc\\udev\\rules.d ??");
|
||||
}
|
||||
|
||||
throw new SourceException("Couldn't open airspy device - " + LibUsb.strError(result));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
claimInterface();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("Airspy Tuner Controller - error while "
|
||||
+ "setting USB configuration, claiming USB interface or "
|
||||
+ "reset the kernel mode driver", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
setSamplePacking(false);
|
||||
@@ -161,122 +150,30 @@ public class AirspyTunerController extends USBTunerController
|
||||
{
|
||||
mLog.error("Setting sample rate is not supported by firmware", e);
|
||||
}
|
||||
|
||||
String deviceName = "Airspy " + getDeviceInfo().getSerialNumber();
|
||||
|
||||
mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferFactory,
|
||||
USB_TRANSFER_BUFFER_SIZE, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
protected void deviceStop()
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
try
|
||||
{
|
||||
mLog.info("Releasing Airspy Tuner - " + getDeviceInfo().getSerialNumber());
|
||||
try
|
||||
{
|
||||
LibUsb.close(mDeviceHandle);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error while closing device handle", e);
|
||||
}
|
||||
|
||||
mDeviceHandle = null;
|
||||
setReceiverMode(false);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't enable airspy receiver mode", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected USBTransferProcessor getUSBTransferProcessor()
|
||||
public void apply(TunerConfiguration tunerConfiguration) throws SourceException
|
||||
{
|
||||
return mUSBTransferProcessor;
|
||||
}
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(tunerConfiguration);
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
if(tunerConfiguration instanceof AirspyTunerConfiguration config)
|
||||
{
|
||||
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.setConfiguration(mDeviceHandle, 1);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new SourceException("couldn't set USB configuration 1 [" +
|
||||
LibUsb.errorName(result) + "]");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
if(config instanceof AirspyTunerConfiguration)
|
||||
{
|
||||
AirspyTunerConfiguration airspy = (AirspyTunerConfiguration) config;
|
||||
|
||||
int sampleRate = airspy.getSampleRate();
|
||||
|
||||
int sampleRate = config.getSampleRate();
|
||||
AirspySampleRate rate = getSampleRate(sampleRate);
|
||||
|
||||
if(rate == null)
|
||||
@@ -302,39 +199,28 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
try
|
||||
{
|
||||
setIFGain(airspy.getIFGain());
|
||||
setMixerGain(airspy.getMixerGain());
|
||||
setLNAGain(airspy.getLNAGain());
|
||||
setFrequency(config.getFrequency());
|
||||
setFrequencyCorrection(config.getFrequencyCorrection());
|
||||
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
|
||||
|
||||
setMixerAGC(airspy.isMixerAGC());
|
||||
setLNAAGC(airspy.isLNAAGC());
|
||||
setIFGain(config.getIFGain());
|
||||
setMixerGain(config.getMixerGain());
|
||||
setLNAGain(config.getLNAGain());
|
||||
setMixerAGC(config.isMixerAGC());
|
||||
setLNAAGC(config.isLNAAGC());
|
||||
|
||||
//Set the gain mode last, so custom values are already set, and
|
||||
//linearity and sensitivity modes will automatically override
|
||||
//the custom values.
|
||||
setGain(airspy.getGain());
|
||||
|
||||
setFrequencyCorrection(airspy.getFrequencyCorrection());
|
||||
//Set the gain mode last, so custom values are already set, and linearity and sensitivity modes will
|
||||
//automatically override the custom values.
|
||||
setGain(config.getGain());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("Couldn't apply gain settings from airspy config", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(airspy.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
//Do nothing, we couldn't set the frequency
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid tuner config:" +
|
||||
config.getClass());
|
||||
throw new IllegalArgumentException("Invalid tuner config:" + tunerConfiguration.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,37 +231,28 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunedFrequency(long frequency) throws SourceException
|
||||
public synchronized void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX)
|
||||
{
|
||||
if(FREQUENCY_MIN <= frequency && frequency <= FREQUENCY_MAX)
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putInt((int) frequency);
|
||||
buffer.rewind();
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putInt((int) frequency);
|
||||
buffer.rewind();
|
||||
|
||||
try
|
||||
{
|
||||
write(Command.SET_FREQUENCY, 0, 0, buffer);
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
mLog.error("error setting frequency [" + frequency + "]", e);
|
||||
throw new SourceException("error setting frequency [" + frequency + "]", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
throw new SourceException("Frequency [" + frequency + "] outside " + "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
|
||||
write(Command.SET_FREQUENCY, 0, 0, buffer);
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
mLog.error("error setting frequency [" + frequency + "]", e);
|
||||
throw new SourceException("error setting frequency [" + frequency + "]", e);
|
||||
}
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new SourceException("Frequency [" + frequency + "] outside " + "of tunable range " + FREQUENCY_MIN + "-" + FREQUENCY_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,13 +263,11 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate to the rate specified by the index value in the
|
||||
* available sample rates map
|
||||
* Sets the sample rate to the rate specified by the index value in the available sample rates map
|
||||
*
|
||||
* @param rate to a sample rate in the available samples rates map.
|
||||
* @throws IllegalArgumentException if index is not a valid rate index
|
||||
* @throws LibUsbException if there was a read error or if this operation
|
||||
* is not supported by the current firmware
|
||||
* @throws LibUsbException if there was a read error or if this operation is not supported by the current firmware
|
||||
* @throws UsbException if there was a USB error
|
||||
*/
|
||||
public void setSampleRate(AirspySampleRate rate) throws LibUsbException, UsbException, SourceException
|
||||
@@ -468,15 +343,13 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/Disables sample packing to allow two 12-bit samples to be packed
|
||||
* into 3 bytes (enabled) or 4 bytes (disabled).
|
||||
* Enables/Disables sample packing to allow two 12-bit samples to be packed into 3 bytes (enabled) or 4 bytes (disabled).
|
||||
*
|
||||
* @param enabled
|
||||
* @throws UsbException if sample packing is not supported by the current
|
||||
* device firmware or if there were usb communication issues
|
||||
* @param enabled true or false
|
||||
* @throws UsbException if sample packing is not supported by the current device firmware or if there were usb
|
||||
* communication issues
|
||||
*/
|
||||
public void setSamplePacking(boolean enabled)
|
||||
throws LibUsbException, UsbException
|
||||
public void setSamplePacking(boolean enabled) throws LibUsbException, UsbException
|
||||
{
|
||||
int result = readByte(Command.SET_PACKING, 0, (enabled ? 1 : 0), true);
|
||||
|
||||
@@ -497,22 +370,13 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @throws LibUsbException on unsuccessful read operation
|
||||
* @throws UsbException on USB error
|
||||
*/
|
||||
public void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
|
||||
public synchronized void setMixerAGC(boolean enabled) throws LibUsbException, UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true);
|
||||
|
||||
try
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
int result = readByte(Command.SET_MIXER_AGC, 0, (enabled ? 1 : 0), true);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new UsbException("Couldnt set mixer AGC enabled: " + enabled);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();;
|
||||
throw new UsbException("Couldnt set mixer AGC enabled: " + enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,22 +387,13 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @throws LibUsbException on unsuccessful read operation
|
||||
* @throws UsbException on USB error
|
||||
*/
|
||||
public void setLNAAGC(boolean enabled) throws LibUsbException, UsbException
|
||||
public synchronized void setLNAAGC(boolean enabled) throws LibUsbException, UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true);
|
||||
|
||||
try
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
int result = readByte(Command.SET_LNA_AGC, 0, (enabled ? 1 : 0), true);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new UsbException("Couldnt set LNA AGC enabled: " + enabled);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new UsbException("Couldn't set LNA AGC enabled: " + enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,30 +417,21 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @throws UsbException on error in USB transfer
|
||||
* @throws IllegalArgumentException if gain value is invalid
|
||||
*/
|
||||
public void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
|
||||
public synchronized void setLNAGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX)
|
||||
{
|
||||
if(LNA_GAIN_MIN <= gain && gain <= LNA_GAIN_MAX)
|
||||
{
|
||||
int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
|
||||
int result = readByte(Command.SET_LNA_GAIN, 0, gain, true);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new UsbException("Couldnt set LNA gain to: " + gain);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new IllegalArgumentException("LNA gain value [" + gain +
|
||||
"] is outside value range: " + LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
|
||||
throw new UsbException("Couldnt set LNA gain to: " + gain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new IllegalArgumentException("LNA gain value [" + gain + "] is outside value range: " +
|
||||
LNA_GAIN_MIN + "-" + LNA_GAIN_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,28 +445,19 @@ public class AirspyTunerController extends USBTunerController
|
||||
*/
|
||||
public void setMixerGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX)
|
||||
{
|
||||
if(MIXER_GAIN_MIN <= gain && gain <= MIXER_GAIN_MAX)
|
||||
{
|
||||
int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
|
||||
int result = readByte(Command.SET_MIXER_GAIN, 0, gain, true);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new UsbException("Couldnt set mixer gain to: " + gain);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new IllegalArgumentException("Mixer gain value [" + gain +
|
||||
"] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
|
||||
throw new UsbException("Couldnt set mixer gain to: " + gain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new IllegalArgumentException("Mixer gain value [" + gain +
|
||||
"] is outside value range: " + MIXER_GAIN_MIN + "-" + MIXER_GAIN_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,28 +471,19 @@ public class AirspyTunerController extends USBTunerController
|
||||
*/
|
||||
public void setIFGain(int gain) throws LibUsbException, UsbException, IllegalArgumentException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX)
|
||||
{
|
||||
if(IF_GAIN_MIN <= gain && gain <= IF_GAIN_MAX)
|
||||
{
|
||||
int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
|
||||
int result = readByte(Command.SET_VGA_GAIN, 0, gain, true);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new UsbException("Couldnt set VGA gain to: " + gain);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new IllegalArgumentException("VGA gain value [" + gain +
|
||||
"] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
|
||||
throw new UsbException("Couldnt set VGA gain to: " + gain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new IllegalArgumentException("VGA gain value [" + gain +
|
||||
"] is outside value range: " + IF_GAIN_MIN + "-" + IF_GAIN_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +491,6 @@ public class AirspyTunerController extends USBTunerController
|
||||
{
|
||||
//Empty buffer to throw away
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(0);
|
||||
|
||||
write(Command.RECEIVER_MODE, (enabled ? 1 : 0), 0, buffer);
|
||||
}
|
||||
|
||||
@@ -743,7 +570,6 @@ public class AirspyTunerController extends USBTunerController
|
||||
try
|
||||
{
|
||||
int boardID = readByte(Command.BOARD_ID_READ, 0, 0, true);
|
||||
|
||||
mDeviceInfo.setBoardID(boardID);
|
||||
}
|
||||
catch(LibUsbException | UsbException e)
|
||||
@@ -754,10 +580,9 @@ public class AirspyTunerController extends USBTunerController
|
||||
/* Version String */
|
||||
try
|
||||
{
|
||||
//NOTE: libairspy is internally reading 127 bytes, however airspy_info
|
||||
//script is telling it to read 255 bytes ... things that make you go hmmmm
|
||||
//NOTE: libairspy is internally reading 127 bytes, however airspy_info script is telling it to read
|
||||
//255 bytes ... things that make you go hmmmm
|
||||
byte[] version = readArray(Command.VERSION_STRING_READ, 0, 0, 127);
|
||||
|
||||
mDeviceInfo.setVersion(version);
|
||||
}
|
||||
catch(LibUsbException | UsbException e)
|
||||
@@ -769,9 +594,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
try
|
||||
{
|
||||
//Read 6 x 32-bit integers = 24 bytes
|
||||
byte[] serial = readArray(
|
||||
Command.BOARD_PART_ID_SERIAL_NUMBER_READ, 0, 0, 24);
|
||||
|
||||
byte[] serial = readArray(Command.BOARD_PART_ID_SERIAL_NUMBER_READ, 0, 0, 24);
|
||||
mDeviceInfo.setPartAndSerialNumber(serial);
|
||||
}
|
||||
catch(LibUsbException | UsbException e)
|
||||
@@ -790,20 +613,14 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @throws LibUsbException if the operation is unsuccesful
|
||||
* @throws UsbException on any usb errors
|
||||
*/
|
||||
private int readByte(Command command, int value, int index, boolean signed)
|
||||
throws LibUsbException, UsbException
|
||||
private int readByte(Command command, int value, int index, boolean signed) throws LibUsbException, UsbException
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
if(isRunning())
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(1);
|
||||
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_IN,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(),
|
||||
(short) value, (short) index, buffer, USB_TIMEOUT_MS);
|
||||
|
||||
if(transferred < 0)
|
||||
{
|
||||
@@ -821,11 +638,8 @@ public class AirspyTunerController extends USBTunerController
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LibUsbException("device handle is null",
|
||||
LibUsb.ERROR_NO_DEVICE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -836,24 +650,17 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @param index - usb packet index
|
||||
* @param length - number of bytes to read
|
||||
* @return - bytes read from the device
|
||||
* @throws LibUsbException if quantity of bytes read doesn't equal the
|
||||
* requested number of bytes
|
||||
* @throws LibUsbException if quantity of bytes read doesn't equal the requested number of bytes
|
||||
* @throws UsbException on error communicating with the device
|
||||
*/
|
||||
private byte[] readArray(Command command, int value, int index, int length)
|
||||
throws LibUsbException, UsbException
|
||||
private byte[] readArray(Command command, int value, int index, int length) throws LibUsbException, UsbException
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
if(isRunning())
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(length);
|
||||
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_IN,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_IN, command.getValue(), (short) value,
|
||||
(short) index, buffer, USB_TIMEOUT_MS);
|
||||
|
||||
if(transferred < 0)
|
||||
{
|
||||
@@ -861,16 +668,11 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
|
||||
byte[] results = new byte[transferred];
|
||||
|
||||
buffer.get(results);
|
||||
|
||||
return results;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LibUsbException("device handle is null",
|
||||
LibUsb.ERROR_NO_DEVICE);
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -882,36 +684,23 @@ public class AirspyTunerController extends USBTunerController
|
||||
* @param buffer - data to write to the device
|
||||
* @throws UsbException on error
|
||||
*/
|
||||
public void write(Command command, int value, int index, ByteBuffer buffer)
|
||||
throws UsbException
|
||||
public void write(Command command, int value, int index, ByteBuffer buffer) throws UsbException
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
if(isRunning())
|
||||
{
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
USB_REQUEST_OUT,
|
||||
command.getValue(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
buffer,
|
||||
USB_TIMEOUT_MS);
|
||||
int transferred = LibUsb.controlTransfer(getDeviceHandle(), USB_REQUEST_OUT, command.getValue(),
|
||||
(short) value, (short) index, buffer, USB_TIMEOUT_MS);
|
||||
|
||||
if(transferred < 0)
|
||||
{
|
||||
throw new LibUsbException("error writing byte buffer",
|
||||
transferred);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public enum GainMode
|
||||
@@ -1032,17 +821,13 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
}
|
||||
|
||||
public static GainMode getGainMode(Gain gain)
|
||||
public GainMode getGainMode()
|
||||
{
|
||||
if(gain == CUSTOM)
|
||||
{
|
||||
return GainMode.CUSTOM;
|
||||
}
|
||||
else if(getLinearityGains().contains(gain))
|
||||
if(getLinearityGains().contains(this))
|
||||
{
|
||||
return GainMode.LINEARITY;
|
||||
}
|
||||
else if(getSensitivityGains().contains(gain))
|
||||
else if(getSensitivityGains().contains(this))
|
||||
{
|
||||
return GainMode.SENSITIVITY;
|
||||
}
|
||||
@@ -1061,7 +846,6 @@ public class AirspyTunerController extends USBTunerController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Airspy Board Identifier
|
||||
*/
|
||||
@@ -1135,7 +919,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
private int mValue;
|
||||
|
||||
private Command(int value)
|
||||
Command(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
@@ -1241,7 +1025,7 @@ public class AirspyTunerController extends USBTunerController
|
||||
|
||||
private int mValue;
|
||||
|
||||
private GPIOPin(int value)
|
||||
GPIOPin(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,8 +26,8 @@ import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||
import io.github.dsheirer.source.tuner.TunerModel;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -46,7 +46,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MultiFrequencyTunerChannelSource.class);
|
||||
|
||||
private TunerModel mTunerModel;
|
||||
private TunerManager mTunerManager;
|
||||
private TunerChannelSource mTunerChannelSource;
|
||||
private List<Long> mFrequencies;
|
||||
private List<Long> mLockedFrequencies = new ArrayList<>();
|
||||
@@ -59,12 +59,12 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
||||
private boolean mStarted;
|
||||
private ConsumerSourceEventAdapter mConsumerSourceEventAdapter = new ConsumerSourceEventAdapter();
|
||||
|
||||
public MultiFrequencyTunerChannelSource(TunerModel tunerModel, TunerChannelSource tunerChannelSource,
|
||||
public MultiFrequencyTunerChannelSource(TunerManager tunerManager, TunerChannelSource tunerChannelSource,
|
||||
List<Long> frequencies, ChannelSpecification channelSpecification,
|
||||
String preferredTuner)
|
||||
{
|
||||
super(null, tunerChannelSource.getTunerChannel());
|
||||
mTunerModel = tunerModel;
|
||||
mTunerManager = tunerManager;
|
||||
mTunerChannelSource = tunerChannelSource;
|
||||
mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter);
|
||||
mFrequencies = frequencies;
|
||||
@@ -115,7 +115,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
||||
{
|
||||
if(mStarted)
|
||||
{
|
||||
Source source = mTunerModel.getSource(nextChannel, mChannelSpecification, mPreferredTuner);
|
||||
Source source = mTunerManager.getSource(nextChannel, mChannelSpecification, mPreferredTuner);
|
||||
|
||||
if(source instanceof TunerChannelSource)
|
||||
{
|
||||
|
||||
@@ -172,6 +172,10 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
sourceEvent.setSource(this);
|
||||
broadcastProducerSourceEvent(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_TUNER_SHUTDOWN:
|
||||
//Rebroadcast this event so the channel processing manager receives it and shuts down the channel
|
||||
broadcastConsumerSourceEvent(sourceEvent);
|
||||
break;
|
||||
//Request events from the consumer
|
||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
|
||||
/**
|
||||
* Record to track disabled USB tuners
|
||||
*/
|
||||
@JsonSerialize
|
||||
public record DisabledTuner(TunerClass tunerClass, String id)
|
||||
{
|
||||
/**
|
||||
* Indicates if this disabled tuner matches the discovered tuner argument
|
||||
* @param other discovered tuner to compare
|
||||
* @return true if they match
|
||||
*/
|
||||
public boolean matches(DiscoveredTuner other)
|
||||
{
|
||||
return this.tunerClass().equals(other.getTunerClass()) && this.id().equals(other.getId());
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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;
|
||||
|
||||
@@ -36,56 +33,50 @@ import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
|
||||
|
||||
/**
|
||||
* Abstract class to hold a named configuration for a specific type of tuner
|
||||
* Abstract class to hold a configuration for a specific type of tuner
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY, property="type")
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value=AirspyTunerConfiguration.class, name="airspyTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value=E4KTunerConfiguration.class, name="e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value=FCD1TunerConfiguration.class, name="fcd1TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value=FCD2TunerConfiguration.class, name="fcd2TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value=HackRFTunerConfiguration.class, name="hackRFTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value= RecordingTunerConfiguration.class, name="recordingTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value=R820TTunerConfiguration.class, name="r820TTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = AirspyTunerConfiguration.class, name = "airspyTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
||||
})
|
||||
@JacksonXmlRootElement( localName = "tuner_configuration" )
|
||||
@JacksonXmlRootElement(localName = "tuner_configuration")
|
||||
public abstract class TunerConfiguration
|
||||
{
|
||||
protected String mName;
|
||||
protected String mUniqueID;
|
||||
protected boolean mAssigned;
|
||||
protected long mFrequency;
|
||||
private String mUniqueID;
|
||||
private long mFrequency = 101100000;
|
||||
private double mFrequencyCorrection = 0.0d;
|
||||
private boolean mAutoPPMCorrection = true;
|
||||
|
||||
/**
|
||||
* Default constructor to support JAXB
|
||||
* Default constructor to support Jackson
|
||||
*/
|
||||
public TunerConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the tuner type for this configuration
|
||||
*/
|
||||
@JsonIgnore
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Normal constructor
|
||||
*/
|
||||
public TunerConfiguration(String uniqueID, String name)
|
||||
public TunerConfiguration(String uniqueID)
|
||||
{
|
||||
mUniqueID = uniqueID;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "name")
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
return getTunerType() + " " + getUniqueID();
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "unique_id")
|
||||
@@ -97,18 +88,6 @@ public abstract class TunerConfiguration
|
||||
public void setUniqueID(String id)
|
||||
{
|
||||
mUniqueID = id;
|
||||
;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "assigned")
|
||||
public boolean isAssigned()
|
||||
{
|
||||
return mAssigned;
|
||||
}
|
||||
|
||||
public void setAssigned(boolean assigned)
|
||||
{
|
||||
mAssigned = assigned;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency")
|
||||
@@ -122,12 +101,35 @@ public abstract class TunerConfiguration
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public abstract TunerType getTunerType();
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
//Below is maintained for compatibility with javax.xml.bind serialization of the settings file.
|
||||
//This can be removed if/once there is a new version of the settings.
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "tuner_type")
|
||||
public String getLegacyAttribute(){ return null; }
|
||||
public void setLegacyAttribute(String bonkers) { }
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if automatic correction of PPM from measured frequency error is enabled/disabled.
|
||||
*
|
||||
* @return true if auto-correction is enabled.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "auto_ppm_correction_enabled")
|
||||
public boolean getAutoPPMCorrectionEnabled()
|
||||
{
|
||||
return mAutoPPMCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state for auto-correction of PPM from measured frequency error values.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setAutoPPMCorrectionEnabled(boolean enabled)
|
||||
{
|
||||
mAutoPPMCorrection = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lock state for the tuner so that the frequency and sample rate controls can be
|
||||
* enabled/disabled.
|
||||
*
|
||||
* @param locked true if the tuner is locked.
|
||||
*/
|
||||
public abstract void setTunerLockState(boolean locked);
|
||||
}
|
||||
@@ -1,105 +1,112 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
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.recording.RecordingTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfigurationEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
|
||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832UnknownTunerEditor;
|
||||
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;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
|
||||
/**
|
||||
* Factory methods to support tuner configurations and configuration editors.
|
||||
*/
|
||||
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)
|
||||
public static TunerConfiguration getTunerConfiguration(TunerType type, String uniqueID)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case AIRSPY_R820T:
|
||||
return new AirspyTunerConfiguration(uniqueID, name);
|
||||
return new AirspyTunerConfiguration(uniqueID);
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerConfiguration(uniqueID, name);
|
||||
return new E4KTunerConfiguration(uniqueID);
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
return new FCD1TunerConfiguration(uniqueID, name);
|
||||
return new FCD1TunerConfiguration(uniqueID);
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
return new FCD2TunerConfiguration(uniqueID, name);
|
||||
case HACKRF:
|
||||
return new HackRFTunerConfiguration(uniqueID, name);
|
||||
return new FCD2TunerConfiguration(uniqueID);
|
||||
case HACKRF_JAWBREAKER:
|
||||
case HACKRF_ONE:
|
||||
case HACKRF_RAD1O:
|
||||
return new HackRFTunerConfiguration(uniqueID);
|
||||
case RAFAELMICRO_R820T:
|
||||
return new R820TTunerConfiguration(uniqueID, name);
|
||||
return new R820TTunerConfiguration(uniqueID);
|
||||
case RECORDING:
|
||||
return new RecordingTunerConfiguration(uniqueID, name);
|
||||
return new RecordingTunerConfiguration(uniqueID);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized tuner type ["
|
||||
+ type.name() + "] - can't create named [" + name + "] tuner"
|
||||
+ " configuration");
|
||||
throw new IllegalArgumentException("Unrecognized tuner type [" + type.name() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tuner editor gui for the specified tuner
|
||||
*/
|
||||
public static Editor<TunerConfiguration> getEditor(Tuner tuner, TunerConfigurationModel model)
|
||||
public static TunerEditor getEditor(UserPreferences userPreferences, DiscoveredTuner discoveredTuner,
|
||||
TunerManager tunerManager)
|
||||
{
|
||||
switch(tuner.getTunerType())
|
||||
switch(discoveredTuner.getTunerClass())
|
||||
{
|
||||
case AIRSPY_R820T:
|
||||
return new AirspyTunerEditor(model, (AirspyTuner)tuner);
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerEditor(model, (RTL2832Tuner)tuner);
|
||||
case AIRSPY:
|
||||
return new AirspyTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
return new FCD1TunerEditor(model, (FCDTuner)tuner);
|
||||
return new FCD1TunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
return new FCD2TunerEditor(model, (FCDTuner)tuner);
|
||||
return new FCD2TunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case HACKRF:
|
||||
return new HackRFTunerEditor(model, (HackRFTuner)tuner);
|
||||
case RAFAELMICRO_R820T:
|
||||
return new R820TTunerEditor(model, (RTL2832Tuner)tuner);
|
||||
case RECORDING:
|
||||
return new RecordingTunerConfigurationEditor(model, (RecordingTuner)tuner);
|
||||
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case RTL2832:
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
switch(discoveredTuner.getTuner().getTunerType())
|
||||
{
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case RAFAELMICRO_R820T:
|
||||
return new R820TTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
}
|
||||
}
|
||||
return new RTL2832UnknownTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case RECORDING_TUNER:
|
||||
return new RecordingTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case TEST_TUNER:
|
||||
case UNKNOWN:
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized Tuner: " + tuner.getName());
|
||||
throw new IllegalArgumentException("Unsupported tuner class: " + discoveredTuner.getTunerClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Manages settings and configurations for all tuner types.
|
||||
*/
|
||||
public class TunerConfigurationManager implements IDiscoveredTunerStatusListener
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(TunerConfigurationManager.class);
|
||||
private static final String SETTINGS_FILE_NAME = "tuner_configuration.json";
|
||||
private UserPreferences mUserPreferences;
|
||||
private List<DisabledTuner> mDisabledTunerList = new ArrayList<>();
|
||||
private List<TunerConfiguration> mTunerConfigurations = new ArrayList<>();
|
||||
private AtomicBoolean mSavePending = new AtomicBoolean();
|
||||
private Lock mLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Constructs an instance and loads the save configuration state.
|
||||
*
|
||||
* @param userPreferences to determine directories for accessing files
|
||||
*/
|
||||
public TunerConfigurationManager(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads settings from the persisted tuner state file
|
||||
*/
|
||||
private void load()
|
||||
{
|
||||
Path configPath = getConfigurationFilePath();
|
||||
|
||||
if(Files.exists(configPath))
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
try
|
||||
{
|
||||
TunerConfigurationState state = objectMapper.readValue(configPath.toFile(), TunerConfigurationState.class);
|
||||
mDisabledTunerList.addAll(state.getDisabledTuners());
|
||||
mTunerConfigurations.addAll(state.getTunerConfigurations());
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error loading tuner configuration file", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner configuration state file (.json).
|
||||
*/
|
||||
private Path getConfigurationFilePath()
|
||||
{
|
||||
return mUserPreferences.getDirectoryPreference().getDirectoryConfiguration().resolve(SETTINGS_FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current tuner configuration state to disk.
|
||||
*/
|
||||
private void save()
|
||||
{
|
||||
TunerConfigurationState state = new TunerConfigurationState();
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
state.setDisabledTuners(new ArrayList<>(mDisabledTunerList));
|
||||
state.setTunerConfigurations(new ArrayList<>(mTunerConfigurations));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
try
|
||||
{
|
||||
objectMapper.writeValue(getConfigurationFilePath().toFile(), state);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error writing tuner configuration state to file [" + getConfigurationFilePath() + "]", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a configurations save task. Subsequent calls to this method will be ignored until the
|
||||
* save event occurs, thus limiting repetitive saving to a minimum.
|
||||
*/
|
||||
public void saveConfigurations()
|
||||
{
|
||||
if(mSavePending.compareAndSet(false, true))
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(new ConfigurationSaveTask(), 2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
if(current == TunerStatus.DISABLED)
|
||||
{
|
||||
mLog.info("Setting Tuner to Disabled: " + discoveredTuner.getId());
|
||||
addDisabledTuner(discoveredTuner);
|
||||
}
|
||||
else if(current == TunerStatus.ENABLED)
|
||||
{
|
||||
mLog.info("Setting Tuner to Enabled: " + discoveredTuner.getId());
|
||||
removeDisabledTuner(discoveredTuner);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the discovered tuner to the list of disabled tuners
|
||||
*/
|
||||
private void addDisabledTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
if(!isDisabled(discoveredTuner))
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
mDisabledTunerList.add(new DisabledTuner(discoveredTuner.getTunerClass(), discoveredTuner.getId()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
saveConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tuner from the disabled tuners list
|
||||
* @param discoveredTuner to remove
|
||||
*/
|
||||
private void removeDisabledTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
mDisabledTunerList.removeIf(tuner -> tuner.matches(discoveredTuner));
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
saveConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the discovered tuner is disabled. This method should only be used to determine the disabled state
|
||||
* of a tuner when it is added to the system for use, such as tuner discovery at application startup, or for
|
||||
* USB tuner hotplug device add notifications.
|
||||
*
|
||||
* @param discoveredTuner to check for disabled status.
|
||||
* @return true if the tuner is supposed to be disabled.
|
||||
*/
|
||||
public boolean isDisabled(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
return findDisabledTuner(discoveredTuner) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the disabled tuner that matches the discovered tuner.
|
||||
* @param discoveredTuner to search for
|
||||
* @return disabled tuner instance or null if the discovered tuner is not currently disabled.
|
||||
*/
|
||||
private DisabledTuner findDisabledTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
DisabledTuner found = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
for(DisabledTuner disabledTuner: mDisabledTunerList)
|
||||
{
|
||||
if(disabledTuner.matches(discoveredTuner))
|
||||
{
|
||||
found = disabledTuner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the tuner configuration if one doesn't exist that matches the tuner type and unique id.
|
||||
* @param tunerConfiguration to add
|
||||
*/
|
||||
public void addTunerConfiguration(TunerConfiguration tunerConfiguration)
|
||||
{
|
||||
mLog.info("Adding tuner config: " + tunerConfiguration);
|
||||
if(!mTunerConfigurations.stream().filter(config -> config.getTunerType().equals(tunerConfiguration.getTunerType()) &&
|
||||
config.getUniqueID().equalsIgnoreCase(tunerConfiguration.getUniqueID())).findFirst().isPresent())
|
||||
{
|
||||
mTunerConfigurations.add(tunerConfiguration);
|
||||
saveConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tuner configuration from this manager.
|
||||
* @param tunerConfiguration to remove
|
||||
*/
|
||||
public void removeTunerConfiguration(TunerConfiguration tunerConfiguration)
|
||||
{
|
||||
mTunerConfigurations.remove(tunerConfiguration);
|
||||
saveConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an existing or creates a new tuner configuration for the specified tuner type and unique ID value.
|
||||
*
|
||||
* Note: this method is not thread-safe.
|
||||
*/
|
||||
public TunerConfiguration getTunerConfiguration(TunerType type, String uniqueID )
|
||||
{
|
||||
Optional<TunerConfiguration> optional = mTunerConfigurations.stream().filter(config -> config.getTunerType().equals(type) &&
|
||||
config.getUniqueID().equalsIgnoreCase(uniqueID)).findFirst();
|
||||
|
||||
if(optional.isPresent())
|
||||
{
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
TunerConfiguration config = TunerConfigurationFactory.getTunerConfiguration(type, uniqueID);
|
||||
addTunerConfiguration(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current tuner configuration state and resets the save pending flag
|
||||
*/
|
||||
public class ConfigurationSaveTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error saving tuner configurations", e);
|
||||
}
|
||||
|
||||
mSavePending.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Persistable instance of tuner configurations and disabled tuners
|
||||
*/
|
||||
@JsonSerialize
|
||||
public class TunerConfigurationState
|
||||
{
|
||||
private List<DisabledTuner> mDisabledTuners = new ArrayList();
|
||||
private List<TunerConfiguration> mTunerConfigurations = new ArrayList<>();
|
||||
|
||||
public TunerConfigurationState()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* List of currently disabled tuners
|
||||
*/
|
||||
public List<DisabledTuner> getDisabledTuners()
|
||||
{
|
||||
return mDisabledTuners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of disabled tuners
|
||||
*/
|
||||
public void setDisabledTuners(List<DisabledTuner> disabledTuners)
|
||||
{
|
||||
mDisabledTuners = disabledTuners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of tuner configurations
|
||||
*/
|
||||
public List<TunerConfiguration> getTunerConfigurations()
|
||||
{
|
||||
return mTunerConfigurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of tuner configurations
|
||||
*/
|
||||
public void setTunerConfigurations(List<TunerConfiguration> tunerConfigurations)
|
||||
{
|
||||
mTunerConfigurations = tunerConfigurations;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,48 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
|
||||
/**
|
||||
* Funcube Dongle Tuner base class
|
||||
*/
|
||||
public class FCDTuner extends Tuner
|
||||
{
|
||||
public FCDTuner(FCDTunerController controller, UserPreferences userPreferences)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller for the tuner
|
||||
* @param tunerErrorListener to receive error notifications
|
||||
*/
|
||||
public FCDTuner(FCDTunerController controller, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(controller.getConfiguration().toString(), controller, userPreferences);
|
||||
super(controller, tunerErrorListener, ChannelizerType.HETERODYNE);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
getController().dispose();
|
||||
return getTunerType() + " " + getUniqueID();
|
||||
}
|
||||
|
||||
public FCDTunerController getController()
|
||||
|
||||
@@ -23,16 +23,18 @@ import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.ComplexShortAdapter;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.mixer.ComplexMixer;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Context;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.DeviceList;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
@@ -47,60 +49,74 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
public abstract class FCDTunerController extends TunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
|
||||
private static final double USABLE_BANDWIDTH_PERCENT = 1.0;
|
||||
private static final int DC_SPIKE_AVOID_HALF_BANDWIDTH = 5000;
|
||||
private final static byte FCD_INTERFACE = (byte) 0x2;
|
||||
private final static byte FCD_ENDPOINT_IN = (byte) 0x82;
|
||||
private final static byte FCD_ENDPOINT_OUT = (byte) 0x2;
|
||||
|
||||
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 int mBus;
|
||||
private int mPort;
|
||||
private Context mDeviceContext = new Context();
|
||||
private Device mDevice;
|
||||
private DeviceDescriptor mDeviceDescriptor;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
private DeviceDescriptor mDeviceDescriptor = new DeviceDescriptor();
|
||||
private DeviceHandle mDeviceHandle = new DeviceHandle();
|
||||
|
||||
private FCDConfiguration mConfiguration = new FCDConfiguration();
|
||||
protected ComplexMixer mComplexMixer;
|
||||
protected ReentrantLock mLock = new ReentrantLock();
|
||||
|
||||
private ReentrantLock mListenerLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Generic FCD tuner controller - contains functionality common across both
|
||||
* funcube dongle tuners
|
||||
*
|
||||
* @param device
|
||||
* @param descriptor
|
||||
* @param minTunableFrequency
|
||||
* @param maxTunableFrequency
|
||||
* @param tunerType of FCD
|
||||
* @param mixerTDL for the sound card mixer interface
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
* @param minTunableFrequency of the tuner
|
||||
* @param maxTunableFrequency of the tuner
|
||||
* @param tunerErrorListener to receive errors from this tuner
|
||||
*/
|
||||
public FCDTunerController(MixerTunerType tunerType, TargetDataLine mixerTDL, Device device,
|
||||
DeviceDescriptor descriptor, int minTunableFrequency, int maxTunableFrequency)
|
||||
public FCDTunerController(MixerTunerType tunerType, TargetDataLine mixerTDL, int bus, int port,
|
||||
int minTunableFrequency, int maxTunableFrequency, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
|
||||
mDevice = device;
|
||||
mDeviceDescriptor = descriptor;
|
||||
super(tunerErrorListener);
|
||||
mBus = bus;
|
||||
mPort = port;
|
||||
setMinimumFrequency(minTunableFrequency);
|
||||
setMaximumFrequency(maxTunableFrequency);
|
||||
setMiddleUnusableHalfBandwidth(DC_SPIKE_AVOID_HALF_BANDWIDTH);
|
||||
setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENT);
|
||||
|
||||
try
|
||||
{
|
||||
mFrequencyController.setSampleRate((int)tunerType.getAudioFormat().getSampleRate());
|
||||
mFrequencyController.setSampleRate((int) tunerType.getAudioFormat().getSampleRate());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error setting sample rate to [" + tunerType.getAudioFormat().getSampleRate() + "]", se);
|
||||
}
|
||||
|
||||
mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(),
|
||||
new ComplexShortAdapter());
|
||||
|
||||
mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount());
|
||||
|
||||
mComplexMixer = new ComplexMixer(mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(),
|
||||
new ComplexShortAdapter());
|
||||
mComplexMixer.setBufferSampleCount(getBufferSampleCount());
|
||||
mComplexMixer.setBufferListener(mNativeBufferBroadcaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return (int)(mComplexMixer.getAudioFormat().getSampleRate() * 0.1);
|
||||
if(getTunerType() == TunerType.FUNCUBE_DONGLE_PRO)
|
||||
{
|
||||
return 4096;
|
||||
}
|
||||
else if(getTunerType() == TunerType.FUNCUBE_DONGLE_PRO_PLUS)
|
||||
{
|
||||
return 8192;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unrecognized tuner type: " + getTunerType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,13 +125,20 @@ public abstract class FCDTunerController extends TunerController
|
||||
@Override
|
||||
public void addBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
boolean hasExistingListeners = hasBufferListeners();
|
||||
mListenerLock.lock();
|
||||
|
||||
super.addBufferListener(listener);
|
||||
|
||||
if(!hasExistingListeners)
|
||||
try
|
||||
{
|
||||
mComplexMixer.start();
|
||||
boolean hasExistingListeners = hasBufferListeners();
|
||||
super.addBufferListener(listener);
|
||||
if(!hasExistingListeners)
|
||||
{
|
||||
mComplexMixer.start();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mListenerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,44 +149,156 @@ public abstract class FCDTunerController extends TunerController
|
||||
@Override
|
||||
public void removeBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
super.removeBufferListener(listener);
|
||||
mListenerLock.lock();
|
||||
|
||||
if(!hasBufferListeners())
|
||||
try
|
||||
{
|
||||
mComplexMixer.stop();
|
||||
super.removeBufferListener(listener);
|
||||
if(!hasBufferListeners())
|
||||
{
|
||||
ComplexMixer complexMixer = mComplexMixer;
|
||||
|
||||
if(complexMixer != null)
|
||||
{
|
||||
complexMixer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mListenerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Finds the USB device for this tuner at the specified USB bus and port.
|
||||
* @return discovered USB device
|
||||
* @throws SourceException if there is an error or the device is not discovered.
|
||||
*/
|
||||
public void init() throws SourceException
|
||||
private Device findDevice() throws SourceException
|
||||
{
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
DeviceList deviceList = new DeviceList();
|
||||
int count = LibUsb.getDeviceList(mDeviceContext, deviceList);
|
||||
|
||||
int result = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
if(count >= 0)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
for(Device device: deviceList)
|
||||
{
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
int port = LibUsb.getPortNumber(device);
|
||||
|
||||
throw new SourceException("libusb couldn't open funcube usb device [" + LibUsb.errorName(result) + "]");
|
||||
if(mBus == bus && mPort == port)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
claimInterface();
|
||||
throw new SourceException("LibUsb couldn't discover USB device [" + mBus + ":" + mPort +
|
||||
"] from device list" + (count < 0 ? " - error: " + LibUsb.errorName(count) : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 when it cannot open and claim the USB device
|
||||
*/
|
||||
public void start() throws SourceException
|
||||
{
|
||||
if(mDeviceContext == null)
|
||||
{
|
||||
throw new SourceException("Device cannot be reused once it has been shutdown");
|
||||
}
|
||||
|
||||
int status = LibUsb.init(mDeviceContext);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDevice = findDevice();
|
||||
mDeviceDescriptor = new DeviceDescriptor();
|
||||
status = LibUsb.getDeviceDescriptor(mDevice, mDeviceDescriptor);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
status = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
|
||||
mLog.error("Can't open USB tuner [" + getTunerType() + "] - check driver or Linux udev rules");
|
||||
throw new SourceException("Can't open USB tuner - check driver or Linux udev rules - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Remove a kernel driver if active
|
||||
status = LibUsb.kernelDriverActive(mDeviceHandle, FCD_INTERFACE);
|
||||
|
||||
if(status == 1)
|
||||
{
|
||||
status = LibUsb.detachKernelDriver(mDeviceHandle, FCD_INTERFACE);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPort);
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't detach kernel driver");
|
||||
}
|
||||
}
|
||||
|
||||
//Claim the interface
|
||||
status = LibUsb.claimInterface(mDeviceHandle, FCD_INTERFACE);
|
||||
|
||||
if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("USB tuner is in-use by another application");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
deviceStart();
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Startup actions for the sub-class implementation
|
||||
* @throws SourceException if there is an error
|
||||
*/
|
||||
protected abstract void deviceStart() throws SourceException;
|
||||
|
||||
/**
|
||||
* Disposes of resources. Closes the USB device and interface.
|
||||
*/
|
||||
public void dispose()
|
||||
public void stop()
|
||||
{
|
||||
if(mComplexMixer != null)
|
||||
{
|
||||
mComplexMixer.stop();
|
||||
mComplexMixer = null;
|
||||
}
|
||||
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
try
|
||||
@@ -174,12 +309,13 @@ public abstract class FCDTunerController extends TunerController
|
||||
{
|
||||
mLog.error("error while closing device handle", e);
|
||||
}
|
||||
|
||||
mDeviceHandle = null;
|
||||
}
|
||||
|
||||
mDeviceDescriptor = null;
|
||||
mDeviceHandle = null;
|
||||
mDevice = null;
|
||||
LibUsb.exit(mDeviceContext);
|
||||
mDeviceContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,11 +333,6 @@ public abstract class FCDTunerController extends TunerController
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Applies the settings in the tuner configuration
|
||||
*/
|
||||
public abstract void apply(TunerConfiguration config) throws SourceException;
|
||||
|
||||
/**
|
||||
* USB address (bus/port)
|
||||
*/
|
||||
@@ -234,9 +365,9 @@ public abstract class FCDTunerController extends TunerController
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(String.format("%04X", (int)(mDeviceDescriptor.idVendor() & 0xFFFF)));
|
||||
sb.append(String.format("%04X", (int) (mDeviceDescriptor.idVendor() & 0xFFFF)));
|
||||
sb.append(":");
|
||||
sb.append(String.format("%04X", (int)(mDeviceDescriptor.idProduct() & 0xFFFF)));
|
||||
sb.append(String.format("%04X", (int) (mDeviceDescriptor.idProduct() & 0xFFFF)));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -304,7 +435,7 @@ public abstract class FCDTunerController extends TunerController
|
||||
*/
|
||||
public void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
mListenerLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -313,11 +444,11 @@ public abstract class FCDTunerController extends TunerController
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("Couldn't set FCD Local " +
|
||||
"Oscillator Frequency [" + frequency + "]", e);
|
||||
"Oscillator Frequency [" + frequency + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
mListenerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,15 +460,12 @@ public abstract class FCDTunerController extends TunerController
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_FREQUENCY_HZ);
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
return (int)(buffer.getInt(2) & 0xFFFFFFFF);
|
||||
return (int) (buffer.getInt(2) & 0xFFFFFFFF);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("FCDTunerController - "
|
||||
+ "couldn't get LO frequency", e);
|
||||
throw new SourceException("FCDTunerController - couldn't get LO frequency", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,39 +477,6 @@ public abstract class FCDTunerController extends TunerController
|
||||
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.
|
||||
*
|
||||
@@ -393,7 +488,6 @@ public abstract class FCDTunerController extends TunerController
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
IntBuffer transferred = IntBuffer.allocate(1);
|
||||
|
||||
int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_OUT, buffer, transferred, 500l);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
@@ -416,37 +510,11 @@ public abstract class FCDTunerController extends TunerController
|
||||
private void write(FCDCommand command) throws LibUsbException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
|
||||
|
||||
buffer.put(0, command.getCommand());
|
||||
buffer.put(1, (byte)0x00);
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -457,13 +525,10 @@ public abstract class FCDTunerController extends TunerController
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -478,10 +543,7 @@ public abstract class FCDTunerController extends TunerController
|
||||
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)
|
||||
@@ -490,8 +552,8 @@ public abstract class FCDTunerController extends TunerController
|
||||
}
|
||||
else if(transferred.get(0) != buffer.capacity())
|
||||
{
|
||||
throw new LibUsbException("received bytes [" + transferred.get(0) +
|
||||
"] didn't match expected length [" + buffer.capacity() + "]", result);
|
||||
throw new LibUsbException("received bytes [" + transferred.get(0) + "] didn't match expected length [" +
|
||||
buffer.capacity() + "]", result);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
@@ -508,7 +570,7 @@ public abstract class FCDTunerController extends TunerController
|
||||
* @param argument - command argument to send
|
||||
* @throws LibUsbException - on error
|
||||
*/
|
||||
protected void send(FCDCommand command, long argument) throws LibUsbException
|
||||
protected synchronized void send(FCDCommand command, long argument) throws LibUsbException
|
||||
{
|
||||
write(command, argument);
|
||||
read();
|
||||
@@ -555,7 +617,6 @@ public abstract class FCDTunerController extends TunerController
|
||||
if(buffer.capacity() == 64)
|
||||
{
|
||||
byte[] data = new byte[64];
|
||||
|
||||
for(int x = 0; x < 64; x++)
|
||||
{
|
||||
data[x] = buffer.get(x);
|
||||
@@ -680,7 +741,7 @@ public abstract class FCDTunerController extends TunerController
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private Block(String label)
|
||||
Block(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
@@ -26,12 +28,10 @@ import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.MixerGain;
|
||||
|
||||
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;
|
||||
@@ -43,12 +43,12 @@ public class FCD1TunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
public FCD1TunerConfiguration(String uniqueID, String name)
|
||||
public FCD1TunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID, name);
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
@@ -88,17 +88,6 @@ public class FCD1TunerConfiguration extends TunerConfiguration
|
||||
mMixerGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "inphase_dc_correction")
|
||||
public double getInphaseDCCorrection()
|
||||
{
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
@@ -26,8 +28,6 @@ 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.sound.sampled.TargetDataLine;
|
||||
import javax.usb.UsbClaimException;
|
||||
@@ -35,14 +35,15 @@ import javax.usb.UsbException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Funcube Dongle Pro tuner controller. Combines USB HID control with Audio Mixer data streaming interface.
|
||||
*/
|
||||
public class FCD1TunerController extends FCDTunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FCD1TunerController.class);
|
||||
|
||||
public static final int MINIMUM_TUNABLE_FREQUENCY = 64000000;
|
||||
public static final int MAXIMUM_TUNABLE_FREQUENCY = 1700000000;
|
||||
public static final int SAMPLE_RATE = 96000;
|
||||
|
||||
private double mDCCorrectionInPhase = 0.0;
|
||||
private double mDCCorrectionQuadrature = 0.0;
|
||||
private double mPhaseCorrection = 0.0;
|
||||
@@ -50,19 +51,22 @@ public class FCD1TunerController extends FCDTunerController
|
||||
private LNAGain mLNAGain;
|
||||
private LNAEnhance mLNAEnhance;
|
||||
private MixerGain mMixerGain;
|
||||
private FCD1TunerConfiguration mTunerConfiguration;
|
||||
private FCD1TunerEditor mEditor;
|
||||
|
||||
public FCD1TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param mixerTDL for the audio/data interface
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
* @param tunerErrorListener to receive tuner errors
|
||||
*/
|
||||
public FCD1TunerController(TargetDataLine mixerTDL, int bus, int port, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(MixerTunerType.FUNCUBE_DONGLE_PRO, mixerTDL, device, descriptor,
|
||||
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
|
||||
super(MixerTunerType.FUNCUBE_DONGLE_PRO, mixerTDL, bus, port, MINIMUM_TUNABLE_FREQUENCY,
|
||||
MAXIMUM_TUNABLE_FREQUENCY, tunerErrorListener);
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
protected void deviceStart() throws SourceException
|
||||
{
|
||||
super.init();
|
||||
|
||||
mFrequencyController.setSampleRate(SAMPLE_RATE);
|
||||
|
||||
try
|
||||
@@ -73,7 +77,6 @@ public class FCD1TunerController extends FCDTunerController
|
||||
getLNAGainSetting();
|
||||
getLNAEnhanceSetting();
|
||||
getMixerGainSetting();
|
||||
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, 1l);
|
||||
}
|
||||
catch(Exception e)
|
||||
@@ -99,73 +102,45 @@ public class FCD1TunerController extends FCDTunerController
|
||||
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
|
||||
public void apply(TunerConfiguration tunerConfig) throws SourceException
|
||||
{
|
||||
FCD1TunerConfiguration config = (FCD1TunerConfiguration)tunerConfig;
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(tunerConfig);
|
||||
|
||||
try
|
||||
if(tunerConfig instanceof FCD1TunerConfiguration fcd1)
|
||||
{
|
||||
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;
|
||||
try
|
||||
{
|
||||
setLNAGain(fcd1.getLNAGain());
|
||||
setLNAEnhance(fcd1.getLNAEnhance());
|
||||
setMixerGain(fcd1.getMixerGain());
|
||||
setDCCorrectionInPhase(fcd1.getInphaseDCCorrection());
|
||||
setDCCorrectionQuadrature(fcd1.getQuadratureDCCorrection());
|
||||
setGainCorrection(fcd1.getGainCorrection());
|
||||
setPhaseCorrection(fcd1.getPhaseCorrection());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Re-cast any usb exceptions as source exceptions
|
||||
throw new SourceException("FCD 1 Controller error applying tuner config - " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
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
|
||||
public LNAGain getLNAGainSetting() throws UsbClaimException, UsbException
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_LNA_GAIN);
|
||||
if(mLNAGain == null)
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_LNA_GAIN);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
int gain = buffer.getInt(2);
|
||||
mLNAGain = LNAGain.valueOf(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;
|
||||
}
|
||||
|
||||
@@ -178,16 +153,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*/
|
||||
public void setLNAGain(LNAGain gain) throws UsbException, UsbClaimException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting());
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,13 +172,11 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* @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
|
||||
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 )");
|
||||
throw new IllegalArgumentException("Phase correction value [" + value + "] outside limits ( -1.0 <> 1.0 )");
|
||||
}
|
||||
|
||||
mPhaseCorrection = value;
|
||||
@@ -238,8 +202,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
throw new IllegalArgumentException("Gain correction value [" +
|
||||
value + "] outside limits ( -1.0 <> 1.0 )");
|
||||
throw new IllegalArgumentException("Gain correction value [" + value + "] outside limits ( -1.0 <> 1.0 )");
|
||||
}
|
||||
|
||||
mGainCorrection = value;
|
||||
@@ -249,73 +212,47 @@ public class FCD1TunerController extends FCDTunerController
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
* 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
|
||||
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;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_IQ_CORRECTION, correction);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
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.
|
||||
* 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
|
||||
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.
|
||||
* 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;
|
||||
|
||||
/**
|
||||
@@ -323,7 +260,6 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* value
|
||||
*/
|
||||
short phase = (short)(correction & 0x0000FFFF);
|
||||
|
||||
mPhaseCorrection = ((double)phase / (double)Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@@ -336,16 +272,14 @@ public class FCD1TunerController extends FCDTunerController
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets DC correction for the I (inphase) component and sends the new value
|
||||
* to the FCD controller
|
||||
* 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
|
||||
public void setDCCorrectionInPhase(double value) throws UsbClaimException, UsbException
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -366,16 +300,14 @@ public class FCD1TunerController extends FCDTunerController
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DC correction for the Q (quadrature) component and send the
|
||||
* new value to the FCD controller
|
||||
* 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
|
||||
public void setDCCorrectionQuadrature(double value) throws UsbClaimException, UsbException
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -390,56 +322,37 @@ public class FCD1TunerController extends FCDTunerController
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, correction);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, correction);
|
||||
}
|
||||
|
||||
private void getDCIQCorrection() throws UsbClaimException,
|
||||
UsbException
|
||||
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
|
||||
@@ -447,9 +360,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*/
|
||||
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.
|
||||
@@ -457,23 +368,13 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -484,26 +385,14 @@ public class FCD1TunerController extends FCDTunerController
|
||||
|
||||
public void setLNAEnhance(LNAEnhance enhance) throws UsbClaimException, UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting());
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -514,16 +403,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
|
||||
public void setMixerGain(MixerGain gain) throws UsbClaimException, UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting());
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting());
|
||||
}
|
||||
|
||||
public enum Block
|
||||
@@ -677,9 +557,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
case 7:
|
||||
return LNA_ENHANCE_3;
|
||||
default:
|
||||
throw new IllegalArgumentException("FCD 1.0 Tuner "
|
||||
+ "Controller - unrecognized LNA enhance setting "
|
||||
+ "value [" + setting + "]");
|
||||
throw new IllegalArgumentException("FCD 1.0 Tuner Controller - unrecognized LNA enhance setting " +
|
||||
"value [" + setting + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,8 +604,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
case 1:
|
||||
return MIXER_GAIN_PLUS_12_0;
|
||||
default:
|
||||
throw new IllegalArgumentException("FCD 1.0 Tuner "
|
||||
+ "Controller - unrecognized mixer gain setting "
|
||||
throw new IllegalArgumentException("FCD 1.0 Tuner Controller - unrecognized mixer gain setting "
|
||||
+ "value [" + setting + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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.TunerConfigurationEvent.Event;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
|
||||
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 io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,332 +36,296 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
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
|
||||
/**
|
||||
* Funcube Dongle Pro tuner editor
|
||||
*/
|
||||
public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration>
|
||||
{
|
||||
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<LNAGain> mComboLNAGain;
|
||||
private JComboBox<LNAEnhance> mComboLNAEnhance;
|
||||
private JComboBox<MixerGain> mComboMixerGain;
|
||||
private JButton mTunerInfoButton;
|
||||
private JComboBox<LNAGain> mLnaGainCombo;
|
||||
private JComboBox<LNAEnhance> mLnaEnhanceCombo;
|
||||
private JComboBox<MixerGain> mMixerGainCombo;
|
||||
private CorrectionSpinner mCorrectionDCI;
|
||||
private CorrectionSpinner mCorrectionDCQ;
|
||||
private CorrectionSpinner mCorrectionGain;
|
||||
private CorrectionSpinner mCorrectionPhase;
|
||||
private boolean mLoading;
|
||||
private CorrectionSpinner mGainCorrectionSpinner;
|
||||
private CorrectionSpinner mPhaseCorrectionSpinner;
|
||||
|
||||
private FCD1TunerController mController;
|
||||
|
||||
public FCD1TunerEditor(TunerConfigurationModel tunerConfigurationModel, FCDTuner tuner)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager to save configuration
|
||||
* @param discoveredTuner to control
|
||||
*/
|
||||
public FCD1TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
mController = (FCD1TunerController)tuner.getController();
|
||||
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private FCD1TunerConfiguration getConfiguration()
|
||||
private FCD1TunerController getController()
|
||||
{
|
||||
if(hasItem())
|
||||
if(hasTuner())
|
||||
{
|
||||
return (FCD1TunerConfiguration)getItem();
|
||||
return (FCD1TunerController)getTuner().getController();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
getTunerInfoButton().setEnabled(hasTuner());
|
||||
|
||||
if(hasTuner() && hasConfiguration())
|
||||
{
|
||||
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
|
||||
getLnaEnhanceCombo().setSelectedItem(getConfiguration().getLNAEnhance());
|
||||
getLnaGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
||||
getDcCorrectionSpinnerI().setValue(getConfiguration().getInphaseDCCorrection());
|
||||
getDcCorrectionSpinnerQ().setValue(getConfiguration().getQuadratureDCCorrection());
|
||||
getPhaseCorrectionSpinner().setValue(getConfiguration().getGainCorrection());
|
||||
getGainCorrectionSpinner().setValue(getConfiguration().getPhaseCorrection());
|
||||
}
|
||||
|
||||
getMixerGainCombo().setEnabled(hasTuner());
|
||||
getLnaEnhanceCombo().setEnabled(hasTuner());
|
||||
getLnaGainCombo().setEnabled(hasTuner());
|
||||
getDcCorrectionSpinnerI().setEnabled(hasTuner());
|
||||
getDcCorrectionSpinnerQ().setEnabled(hasTuner());
|
||||
getPhaseCorrectionSpinner().setEnabled(hasTuner());
|
||||
getGainCorrectionSpinner().setEnabled(hasTuner());
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][][][][][][][grow]"));
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("FCD Pro Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
mConfigurationName = new JTextField();
|
||||
mConfigurationName.setEnabled(false);
|
||||
mConfigurationName.addFocusListener(new FocusListener()
|
||||
{
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
add(mConfigurationName, "span 2");
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "wrap");
|
||||
|
||||
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 JSeparator(), "span,growx,push");
|
||||
add(new JLabel("Gain"), "span,align center");
|
||||
|
||||
mComboLNAGain = new JComboBox<LNAGain>(LNAGain.values());
|
||||
mComboLNAGain.setEnabled(false);
|
||||
mComboLNAGain.setToolTipText("Adjust the low noise amplifier gain setting.");
|
||||
mComboLNAGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
LNAGain gain = (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<LNAEnhance>(LNAEnhance.values());
|
||||
mComboLNAEnhance.setToolTipText("Adjust the LNA enhance setting. Default value is OFF");
|
||||
|
||||
mComboLNAEnhance.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
LNAEnhance enhance = (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(getLnaGainCombo(), "wrap");
|
||||
add(new JLabel("Enhance:"));
|
||||
add(mComboLNAEnhance);
|
||||
|
||||
/**
|
||||
* Mixer Gain
|
||||
*/
|
||||
mComboMixerGain = new JComboBox<MixerGain>(MixerGain.values());
|
||||
mComboMixerGain.setToolTipText("Adjust mixer gain setting");
|
||||
|
||||
|
||||
mComboMixerGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
MixerGain gain = (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(getLnaEnhanceCombo(), "wrap");
|
||||
add(new JLabel("Mixer:"));
|
||||
add(mComboMixerGain, "wrap");
|
||||
add(getMixerGainCombo(), "wrap");
|
||||
|
||||
add(new JSeparator(JSeparator.HORIZONTAL), "span,grow");
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
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(getDcCorrectionSpinnerI(), "wrap");
|
||||
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(getDcCorrectionSpinnerQ(), "wrap");
|
||||
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(getGainCorrectionSpinner(), "wrap");
|
||||
add(new JLabel("Phase:"));
|
||||
add(mCorrectionPhase);
|
||||
add(getPhaseCorrectionSpinner(), "wrap");
|
||||
}
|
||||
|
||||
public JComboBox getLnaGainCombo()
|
||||
{
|
||||
if(mLnaGainCombo == null)
|
||||
{
|
||||
mLnaGainCombo = new JComboBox<>(LNAGain.values());
|
||||
mLnaGainCombo.setEnabled(false);
|
||||
mLnaGainCombo.setToolTipText("Adjust the low noise amplifier gain setting.");
|
||||
mLnaGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
LNAGain gain = (LNAGain) mLnaGainCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getController().setLNAGain(gain);
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FCD1TunerEditor.this, "FCD Pro Tuner " +
|
||||
"Controller - error setting LNA gain [" + gain + "]");
|
||||
|
||||
mLog.error("FuncubeDonglePro Controller - error setting gain [" + gain + "]", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mLnaGainCombo;
|
||||
}
|
||||
|
||||
public JComboBox getLnaEnhanceCombo()
|
||||
{
|
||||
if(mLnaEnhanceCombo == null)
|
||||
{
|
||||
mLnaEnhanceCombo = new JComboBox<>(LNAEnhance.values());
|
||||
mLnaEnhanceCombo.setEnabled(false);
|
||||
mLnaEnhanceCombo.setToolTipText("Adjust the LNA enhance setting. Default value is OFF");
|
||||
mLnaEnhanceCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
LNAEnhance enhance = (LNAEnhance) mLnaEnhanceCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getController().setLNAEnhance(enhance);
|
||||
save();
|
||||
}
|
||||
catch(Exception e1)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FCD1TunerEditor.this, "FCD Pro Tuner"
|
||||
+ " error setting LNA enhance gain [" + enhance + "]");
|
||||
|
||||
mLog.error("FCDPro - error setting LNA enhance [" + enhance + "]", e1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mLnaEnhanceCombo;
|
||||
}
|
||||
|
||||
public JComboBox getMixerGainCombo()
|
||||
{
|
||||
if(mMixerGainCombo == null)
|
||||
{
|
||||
mMixerGainCombo = new JComboBox<>(MixerGain.values());
|
||||
mMixerGainCombo.setEnabled(false);
|
||||
mMixerGainCombo.setToolTipText("Adjust mixer gain setting");
|
||||
mMixerGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
MixerGain gain = (MixerGain) mMixerGainCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getController().setMixerGain(gain);
|
||||
save();
|
||||
}
|
||||
catch(Exception e1)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FCD1TunerEditor.this, "FCDPro - error setting"
|
||||
+ " mixer gain [" + gain + "]");
|
||||
|
||||
mLog.error("FCDPro - error setting mixer gain [" + gain + "]", e1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mMixerGainCombo;
|
||||
}
|
||||
|
||||
public CorrectionSpinner getDcCorrectionSpinnerQ()
|
||||
{
|
||||
if(mCorrectionDCQ == null)
|
||||
{
|
||||
mCorrectionDCQ = new CorrectionSpinner(Correction.DC_QUADRATURE, 0.0, 0.00001, 5);
|
||||
mCorrectionDCQ.setEnabled(false);
|
||||
mCorrectionDCQ.setToolTipText("DC Bias Correction/Quadrature Component: valid values are -1.0 to 1.0 (default: 0.0)");
|
||||
}
|
||||
|
||||
return mCorrectionDCQ;
|
||||
}
|
||||
|
||||
public CorrectionSpinner getDcCorrectionSpinnerI()
|
||||
{
|
||||
if(mCorrectionDCI == null)
|
||||
{
|
||||
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)");
|
||||
}
|
||||
|
||||
return mCorrectionDCI;
|
||||
}
|
||||
|
||||
public CorrectionSpinner getGainCorrectionSpinner()
|
||||
{
|
||||
if(mGainCorrectionSpinner == null)
|
||||
{
|
||||
mGainCorrectionSpinner = new CorrectionSpinner(Correction.GAIN, 0.0, 0.00001, 5);
|
||||
mGainCorrectionSpinner.setEnabled(false);
|
||||
mGainCorrectionSpinner.setToolTipText("Gain Correction: valid values are -1.0 to 1.0 (default: 0.0)");
|
||||
}
|
||||
|
||||
return mGainCorrectionSpinner;
|
||||
}
|
||||
|
||||
public CorrectionSpinner getPhaseCorrectionSpinner()
|
||||
{
|
||||
if(mPhaseCorrectionSpinner == null)
|
||||
{
|
||||
mPhaseCorrectionSpinner = new CorrectionSpinner(Correction.PHASE, 0.0, 0.00001, 5);
|
||||
mPhaseCorrectionSpinner.setEnabled(false);
|
||||
mPhaseCorrectionSpinner.setToolTipText("Phase Correction: valid values are -1.0 to 1.0 (default: 0.0)");
|
||||
}
|
||||
|
||||
return mPhaseCorrectionSpinner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
//no-op
|
||||
super.setTunerLockState(locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets each of the tuner configuration controls to the enabled argument state
|
||||
*/
|
||||
private void setControlsEnabled(boolean enabled)
|
||||
public JButton getTunerInfoButton()
|
||||
{
|
||||
if(mConfigurationName.isEnabled() != enabled)
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
mConfigurationName.setEnabled(enabled);
|
||||
mTunerInfoButton = new JButton("Tuner Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(FCD1TunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
@@ -371,25 +334,28 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
|
||||
|
||||
sb.append("<html><h3>Funcube Dongle Pro Tuner</h3>");
|
||||
|
||||
sb.append("<b>USB ID: </b>");
|
||||
sb.append(mController.getUSBID());
|
||||
sb.append("<br>");
|
||||
if(hasTuner())
|
||||
{
|
||||
sb.append("<b>USB ID: </b>");
|
||||
sb.append(getController().getUSBID());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>USB Address: </b>");
|
||||
sb.append(mController.getUSBAddress());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>USB Address: </b>");
|
||||
sb.append(getController().getUSBAddress());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>USB Speed: </b>");
|
||||
sb.append(mController.getUSBSpeed());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>USB Speed: </b>");
|
||||
sb.append(getController().getUSBSpeed());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Cellular Band: </b>");
|
||||
sb.append(mController.getConfiguration().getBandBlocking());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>Cellular Band: </b>");
|
||||
sb.append(getController().getConfiguration().getBandBlocking());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Firmware: </b>");
|
||||
sb.append(mController.getConfiguration().getFirmware());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>Firmware: </b>");
|
||||
sb.append(getController().getConfiguration().getFirmware());
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -397,81 +363,28 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
FCD1TunerConfiguration config = getConfiguration();
|
||||
|
||||
config.setName(mConfigurationName.getText());
|
||||
|
||||
double value = ((SpinnerNumberModel)mFrequencyCorrection
|
||||
.getModel()).getNumber().doubleValue();
|
||||
config.setFrequencyCorrection(value);
|
||||
config.setLNAEnhance((LNAEnhance)mComboLNAEnhance.getSelectedItem());
|
||||
config.setLNAGain((LNAGain)mComboLNAGain.getSelectedItem());
|
||||
config.setMixerGain((MixerGain)mComboMixerGain.getSelectedItem());
|
||||
|
||||
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
getConfiguration().setFrequencyCorrection(value);
|
||||
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
getConfiguration().setLNAEnhance((LNAEnhance) mLnaEnhanceCombo.getSelectedItem());
|
||||
getConfiguration().setLNAGain((LNAGain) mLnaGainCombo.getSelectedItem());
|
||||
getConfiguration().setMixerGain((MixerGain) mMixerGainCombo.getSelectedItem());
|
||||
double dci = ((SpinnerNumberModel)mCorrectionDCI.getModel()).getNumber().doubleValue();
|
||||
config.setInphaseDCCorrection(dci);
|
||||
|
||||
getConfiguration().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(), Event.CHANGE));
|
||||
getConfiguration().setQuadratureDCCorrection(dcq);
|
||||
double gain = ((SpinnerNumberModel) mGainCorrectionSpinner.getModel()).getNumber().doubleValue();
|
||||
getConfiguration().setGainCorrection(gain);
|
||||
double phase = ((SpinnerNumberModel) mPhaseCorrectionSpinner.getModel()).getNumber().doubleValue();
|
||||
getConfiguration().setPhaseCorrection(phase);
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@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 enum Correction {GAIN, PHASE, DC_INPHASE, DC_QUADRATURE};
|
||||
|
||||
public class CorrectionSpinner extends JSpinner
|
||||
{
|
||||
@@ -495,29 +408,27 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
|
||||
|
||||
editor.getTextField().setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
addChangeListener(new ChangeListener()
|
||||
addChangeListener(e ->
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
if(!isLoading())
|
||||
{
|
||||
double value =
|
||||
((SpinnerNumberModel)getModel()).getNumber().doubleValue();
|
||||
double value = ((SpinnerNumberModel)getModel()).getNumber().doubleValue();
|
||||
|
||||
try
|
||||
{
|
||||
switch(mCorrectionComponent)
|
||||
{
|
||||
case DC_INPHASE:
|
||||
mController.setDCCorrectionInPhase(value);
|
||||
getController().setDCCorrectionInPhase(value);
|
||||
break;
|
||||
case DC_QUADRATURE:
|
||||
mController.setDCCorrectionQuadrature(value);
|
||||
getController().setDCCorrectionQuadrature(value);
|
||||
break;
|
||||
case GAIN:
|
||||
mController.setGainCorrection(value);
|
||||
getController().setGainCorrection(value);
|
||||
break;
|
||||
case PHASE:
|
||||
mController.setPhaseCorrection(value);
|
||||
getController().setPhaseCorrection(value);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -526,11 +437,10 @@ public class FCD1TunerEditor extends TunerConfigurationEditor
|
||||
catch(Exception e1)
|
||||
{
|
||||
JOptionPane.showMessageDialog(FCD1TunerEditor.this, "FCDPro - error "
|
||||
+ "applying " + mCorrectionComponent.toString() + " correction value ["
|
||||
+ value + "]");
|
||||
+ "applying " + mCorrectionComponent.toString() + " correction value [" + value + "]");
|
||||
|
||||
mLog.error("FCDPro - error applying " + mCorrectionComponent.toString()
|
||||
+ " correction value [" + value + "]", e1);
|
||||
mLog.error("FCDPro - error applying " + mCorrectionComponent.toString() + " correction value [" +
|
||||
value + "]", e1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
@@ -25,7 +27,6 @@ public class FCD2TunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
private boolean mGainLNA = true;
|
||||
private boolean mGainMixer = true;
|
||||
private double mFrequencyCorrection = -2.2d;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
@@ -34,18 +35,18 @@ public class FCD2TunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
public FCD2TunerConfiguration(String uniqueID, String name)
|
||||
{
|
||||
super(uniqueID, name);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
|
||||
}
|
||||
|
||||
public FCD2TunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
|
||||
public boolean getGainLNA()
|
||||
{
|
||||
@@ -67,15 +68,4 @@ public class FCD2TunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
mGainMixer = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -14,11 +14,12 @@
|
||||
*
|
||||
* 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.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
@@ -27,13 +28,14 @@ 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.sound.sampled.TargetDataLine;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Funcube Dongle Pro Plus tuner controller. Combines USB HID control with Audio Mixer for data streaming.
|
||||
*/
|
||||
public class FCD2TunerController extends FCDTunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerController.class);
|
||||
@@ -42,16 +44,21 @@ public class FCD2TunerController extends FCDTunerController
|
||||
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
|
||||
public static final int SAMPLE_RATE = 192000;
|
||||
|
||||
public FCD2TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param mixerTDL for the sample stream
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
* @param tunerErrorListener to receive errors from this tuner
|
||||
*/
|
||||
public FCD2TunerController(TargetDataLine mixerTDL, int bus, int port, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS, mixerTDL, device, descriptor,
|
||||
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
|
||||
super(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS, mixerTDL, bus, port, MINIMUM_TUNABLE_FREQUENCY,
|
||||
MAXIMUM_TUNABLE_FREQUENCY, tunerErrorListener);
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
protected void deviceStart() throws SourceException
|
||||
{
|
||||
super.init();
|
||||
|
||||
mFrequencyController.setSampleRate(SAMPLE_RATE);
|
||||
|
||||
try
|
||||
@@ -83,69 +90,24 @@ public class FCD2TunerController extends FCDTunerController
|
||||
|
||||
public void setLNAGain(boolean enabled) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("error while setting LNA Gain", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public void setMixerGain(boolean enabled) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("error while setting Mixer Gain", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
if(config instanceof FCD2TunerConfiguration)
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(config);
|
||||
|
||||
if(config instanceof FCD2TunerConfiguration fcd2)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,9 +118,7 @@ public class FCD2TunerController extends FCDTunerController
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
return buffer.getInt(2);
|
||||
}
|
||||
catch(Exception e)
|
||||
@@ -171,20 +131,7 @@ public class FCD2TunerController extends FCDTunerController
|
||||
|
||||
public void setDCCorrection(int value)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, value);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error setting dc correction to [" + value + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, value);
|
||||
}
|
||||
|
||||
public int getIQCorrection()
|
||||
@@ -194,9 +141,7 @@ public class FCD2TunerController extends FCDTunerController
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
return buffer.getInt(2);
|
||||
}
|
||||
catch(Exception e)
|
||||
@@ -209,19 +154,6 @@ public class FCD2TunerController extends FCDTunerController
|
||||
|
||||
public void setIQCorrection(int value)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error setting IQ correction to [" + value + "]", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.preference.UserPreferences;
|
||||
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 io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,217 +33,169 @@ import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
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
|
||||
/**
|
||||
* Funcube Dongle Pro Plus tuner editor
|
||||
*/
|
||||
public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
private JButton mTunerInfoButton;
|
||||
private JCheckBox mLnaGainCheckBox;
|
||||
private JCheckBox mMixerGainCheckBox;
|
||||
|
||||
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)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager to save configuration
|
||||
* @param discoveredTuner to control
|
||||
*/
|
||||
public FCD2TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
mController = (FCD2TunerController)tuner.getController();
|
||||
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private FCD2TunerConfiguration getConfiguration()
|
||||
private FCD2TunerController getController()
|
||||
{
|
||||
if(hasItem())
|
||||
if(hasTuner())
|
||||
{
|
||||
return (FCD2TunerConfiguration)getItem();
|
||||
return (FCD2TunerController)getTuner().getController();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
getTunerInfoButton().setEnabled(hasTuner());
|
||||
getLnaGainCheckBox().setEnabled(hasTuner());
|
||||
getMixerGainCheckBox().setEnabled(hasTuner());
|
||||
|
||||
if(hasTuner() && hasConfiguration())
|
||||
{
|
||||
getLnaGainCheckBox().setSelected(getConfiguration().getGainLNA());
|
||||
getMixerGainCheckBox().setSelected(getConfiguration().getGainMixer());
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][grow]"));
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("FCD Pro+ Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
mConfigurationName = new JTextField();
|
||||
mConfigurationName.setEnabled(false);
|
||||
mConfigurationName.addFocusListener(new FocusListener()
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "wrap");
|
||||
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
add(getLnaGainCheckBox());
|
||||
add(getMixerGainCheckBox());
|
||||
}
|
||||
|
||||
public JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
mTunerInfoButton = new JButton("Tuner Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(FCD2TunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
add(mConfigurationName, "span 2");
|
||||
|
||||
mTunerInfo = new JButton("Tuner Info");
|
||||
mTunerInfo.setEnabled(false);
|
||||
mTunerInfo.addActionListener(new ActionListener()
|
||||
public JCheckBox getLnaGainCheckBox()
|
||||
{
|
||||
if(mLnaGainCheckBox == null)
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
mLnaGainCheckBox = new JCheckBox("LNA Gain");
|
||||
mLnaGainCheckBox.setEnabled(false);
|
||||
mLnaGainCheckBox.addActionListener(event ->
|
||||
{
|
||||
JOptionPane.showMessageDialog(FCD2TunerEditor.this, getTunerInfo(),
|
||||
"Tuner Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(mTunerInfo);
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getController().setLNAGain(getLnaGainCheckBox().isSelected());
|
||||
save();
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
mLog.error("Couldn't set LNA gain for FCD2", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SpinnerModel model = new SpinnerNumberModel(0.0, //initial value
|
||||
-1000.0, //min
|
||||
1000.0, //max
|
||||
0.1); //step
|
||||
return mLnaGainCheckBox;
|
||||
}
|
||||
|
||||
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()
|
||||
public JCheckBox getMixerGainCheckBox()
|
||||
{
|
||||
if(mMixerGainCheckBox == null)
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
mMixerGainCheckBox = new JCheckBox("Mixer Gain");
|
||||
mMixerGainCheckBox.setEnabled(false);
|
||||
mMixerGainCheckBox.addActionListener(event ->
|
||||
{
|
||||
double value = ((SpinnerNumberModel)mFrequencyCorrection
|
||||
.getModel()).getNumber().doubleValue();
|
||||
|
||||
try
|
||||
if(!isLoading())
|
||||
{
|
||||
mController.setFrequencyCorrection(value);
|
||||
save();
|
||||
try
|
||||
{
|
||||
getController().setMixerGain(getMixerGainCheckBox().isSelected());
|
||||
save();
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
mLog.error("Couldn't set mixer gain for FCD2", e);
|
||||
}
|
||||
}
|
||||
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);
|
||||
return mMixerGainCheckBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
super.setTunerLockState(locked);
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
@@ -250,25 +204,28 @@ public class FCD2TunerEditor extends TunerConfigurationEditor
|
||||
|
||||
sb.append("<html><h3>Funcube Dongle Pro Plus Tuner</h3>");
|
||||
|
||||
sb.append("<b>USB ID: </b>");
|
||||
sb.append(mController.getUSBID());
|
||||
sb.append("<br>");
|
||||
if(hasTuner())
|
||||
{
|
||||
sb.append("<b>USB ID: </b>");
|
||||
sb.append(getController().getUSBID());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>USB Address: </b>");
|
||||
sb.append(mController.getUSBAddress());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>USB Address: </b>");
|
||||
sb.append(getController().getUSBAddress());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>USB Speed: </b>");
|
||||
sb.append(mController.getUSBSpeed());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>USB Speed: </b>");
|
||||
sb.append(getController().getUSBSpeed());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Cellular Band: </b>");
|
||||
sb.append(mController.getConfiguration().getBandBlocking());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>Cellular Band: </b>");
|
||||
sb.append(getController().getConfiguration().getBandBlocking());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Firmware: </b>");
|
||||
sb.append(mController.getConfiguration().getFirmware());
|
||||
sb.append("<br>");
|
||||
sb.append("<b>Firmware: </b>");
|
||||
sb.append(getController().getConfiguration().getFirmware());
|
||||
sb.append("<br>");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -276,57 +233,15 @@ public class FCD2TunerEditor extends TunerConfigurationEditor
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
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));
|
||||
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
getConfiguration().setFrequencyCorrection(value);
|
||||
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
getConfiguration().setGainLNA(getLnaGainCheckBox().isSelected());
|
||||
getConfiguration().setGainMixer(getMixerGainCheckBox().isSelected());
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
@@ -36,20 +35,21 @@ public class FrequencyController
|
||||
private Tunable mTunable;
|
||||
private long mFrequency = 101100000;
|
||||
private long mTunedFrequency = 101100000;
|
||||
private long mMinimumFrequency;
|
||||
private long mMaximumFrequency;
|
||||
private long mMinimumFrequency = 0;
|
||||
private long mMaximumFrequency = 0;
|
||||
private double mFrequencyCorrection = 0.0d;
|
||||
private double mSampleRate = 0.0d;
|
||||
private boolean mLocked = false;
|
||||
|
||||
private List<ISourceEventProcessor> mProcessors = new CopyOnWriteArrayList<>();
|
||||
|
||||
public FrequencyController(Tunable tunable, long minFrequency, long maxFrequency, double frequencyCorrection)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tunable that can be controlled by this frequency controller.
|
||||
*/
|
||||
public FrequencyController(Tunable tunable)
|
||||
{
|
||||
mTunable = tunable;
|
||||
mMinimumFrequency = minFrequency;
|
||||
mMaximumFrequency = maxFrequency;
|
||||
mFrequencyCorrection = frequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,16 +193,42 @@ public class FrequencyController
|
||||
return mTunedFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum tunable frequency
|
||||
* @return minimum in Hertz
|
||||
*/
|
||||
public long getMinimumFrequency()
|
||||
{
|
||||
return mMinimumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum tunable frequency
|
||||
* @param minimum in Hertz
|
||||
*/
|
||||
public void setMinimumFrequency(long minimum)
|
||||
{
|
||||
mMaximumFrequency = minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum tunable frequency
|
||||
* @return maximum frequency in Hertz
|
||||
*/
|
||||
public long getMaximumFrequency()
|
||||
{
|
||||
return mMaximumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum tunable frequency
|
||||
* @param maximum in Hertz
|
||||
*/
|
||||
public void setMaximumFrequency(long maximum)
|
||||
{
|
||||
mMaximumFrequency = maximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the tuned frequency by adding frequency correction to the
|
||||
* corrected frequency.
|
||||
@@ -304,21 +330,21 @@ public class FrequencyController
|
||||
/**
|
||||
* Gets the tuned frequency of the device
|
||||
*/
|
||||
public long getTunedFrequency() throws SourceException;
|
||||
long getTunedFrequency() throws SourceException;
|
||||
|
||||
/**
|
||||
* Sets the tuned frequency of the device
|
||||
*/
|
||||
public void setTunedFrequency(long frequency) throws SourceException;
|
||||
void setTunedFrequency(long frequency) throws SourceException;
|
||||
|
||||
/**
|
||||
* Gets the current bandwidth setting of the device
|
||||
*/
|
||||
public double getCurrentSampleRate() throws SourceException;
|
||||
double getCurrentSampleRate() throws SourceException;
|
||||
|
||||
/**
|
||||
* Indicates if this tunable can tune the frequency
|
||||
*/
|
||||
public boolean canTune(long frequency);
|
||||
boolean canTune(long frequency);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,64 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.BoardID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* HackRF Tuner
|
||||
*/
|
||||
public class HackRFTuner extends Tuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HackRFTuner.class);
|
||||
|
||||
public HackRFTuner(HackRFTunerController controller, UserPreferences userPreferences) throws SourceException
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller for the HackRF
|
||||
* @param tunerErrorListener to listen for errors from this tuner
|
||||
* @param channelizerType for the channelizer
|
||||
*/
|
||||
public HackRFTuner(HackRFTunerController controller, ITunerErrorListener tunerErrorListener,
|
||||
ChannelizerType channelizerType)
|
||||
{
|
||||
super("HackRF", controller, userPreferences);
|
||||
super(controller, tunerErrorListener, channelizerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
try
|
||||
{
|
||||
HackRFTunerController.Serial serial = getController().getSerial();
|
||||
return "HackRF " + getController().getTunerType() + " " + serial.getSerialNumber();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//No - op
|
||||
}
|
||||
|
||||
return "HackRF " + getController().getTunerType();
|
||||
}
|
||||
|
||||
public HackRFTunerController getController()
|
||||
@@ -47,13 +69,7 @@ public class HackRFTuner extends Tuner
|
||||
@Override
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return TunerClass.HACKRF_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.HACKRF;
|
||||
return TunerClass.HACKRF;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,7 +83,7 @@ public class HackRFTuner extends Tuner
|
||||
{
|
||||
try
|
||||
{
|
||||
return getController().getSerial().getSerialNumber();
|
||||
return getTunerClass().toString() + " " + getController().getSerial().getSerialNumber();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
@@ -31,7 +33,6 @@ public class HackRFTunerConfiguration extends TunerConfiguration
|
||||
private HackRFLNAGain mLNAGain = HackRFLNAGain.GAIN_16; // We can see some signal at this gain
|
||||
private HackRFVGAGain mVGAGain = HackRFVGAGain.GAIN_16; // We can see some signal at this gain
|
||||
private boolean mAmplifierEnabled = false; //Probably should start off disabled
|
||||
private double mFrequencyCorrection = 0.0d;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
@@ -40,16 +41,16 @@ public class HackRFTunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
public HackRFTunerConfiguration(String uniqueID, String name)
|
||||
public HackRFTunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID, name);
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.HACKRF;
|
||||
return TunerType.HACKRF_ONE;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "amplifier_enabled")
|
||||
@@ -85,17 +86,6 @@ public class HackRFTunerConfiguration extends TunerConfiguration
|
||||
mVGAGain = vgaGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public HackRFSampleRate getSampleRate()
|
||||
{
|
||||
|
||||
@@ -18,17 +18,16 @@
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.hackrf;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.buffer.SignedByteNativeBufferFactory;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
|
||||
import io.github.dsheirer.source.tuner.usb.USBTunerController;
|
||||
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;
|
||||
|
||||
@@ -37,43 +36,79 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class HackRFTunerController extends USBTunerController
|
||||
{
|
||||
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 MINIMUM_TUNABLE_FREQUENCY = 10000000l;
|
||||
public static final long MAXIMUM_TUNABLE_FREQUENCY = 6000000000l;
|
||||
public static final long DEFAULT_FREQUENCY = 101100000;
|
||||
public static final double USABLE_BANDWIDTH_PERCENT = 0.90;
|
||||
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
||||
|
||||
private SignedByteNativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory();
|
||||
private USBTransferProcessor mUSBTransferProcessor;
|
||||
public static final double USABLE_BANDWIDTH = 0.90;
|
||||
public static final int DC_HALF_BANDWIDTH = 5000;
|
||||
|
||||
private INativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory();
|
||||
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE_5_0;
|
||||
private boolean mAmplifierEnabled = false;
|
||||
|
||||
private Device mDevice;
|
||||
private DeviceDescriptor mDeviceDescriptor;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
|
||||
public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
*/
|
||||
public HackRFTunerController(int bus, int port, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
|
||||
super(bus, port, MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, DC_HALF_BANDWIDTH, USABLE_BANDWIDTH,
|
||||
tunerErrorListener);
|
||||
}
|
||||
|
||||
mDevice = device;
|
||||
mDeviceDescriptor = descriptor;
|
||||
/**
|
||||
* Tuner type for this HackRf
|
||||
*/
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
if(isRunning())
|
||||
{
|
||||
try
|
||||
{
|
||||
BoardID boardID = getBoardID();
|
||||
|
||||
switch(boardID)
|
||||
{
|
||||
case RAD1O:
|
||||
return TunerType.HACKRF_RAD1O;
|
||||
case JAWBREAKER:
|
||||
return TunerType.HACKRF_JAWBREAKER;
|
||||
case HACKRF_ONE:
|
||||
return TunerType.HACKRF_ONE;
|
||||
};
|
||||
}
|
||||
catch(UsbException ue)
|
||||
{
|
||||
mLog.error("Couldn't read HackRF board ID - " + ue.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return TunerType.HACKRF_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected INativeBufferFactory getNativeBufferFactory()
|
||||
{
|
||||
return mNativeBufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTransferBufferSize()
|
||||
{
|
||||
return USB_TRANSFER_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,118 +117,37 @@ public class HackRFTunerController extends USBTunerController
|
||||
return USB_TRANSFER_BUFFER_SIZE / 2;
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
/**
|
||||
* Device startup actions invoked by the parent start() method.
|
||||
* @throws SourceException if there is an error performing these operations
|
||||
*/
|
||||
protected void deviceStart() 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);
|
||||
mLog.error("Error on Hack RF startup", 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, mNativeBufferFactory,
|
||||
USB_TRANSFER_BUFFER_SIZE, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
mLog.info("Releasing HackRF Tuner");
|
||||
try
|
||||
{
|
||||
LibUsb.close(mDeviceHandle);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error while closing device handle", e);
|
||||
}
|
||||
|
||||
mDeviceHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected USBTransferProcessor getUSBTransferProcessor()
|
||||
{
|
||||
return mUSBTransferProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Devices shutdown actions invoked by the parent stop() method
|
||||
*/
|
||||
private void claimInterface() throws SourceException
|
||||
@Override
|
||||
protected void deviceStop()
|
||||
{
|
||||
if(mDeviceHandle != null)
|
||||
try
|
||||
{
|
||||
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) + "]");
|
||||
}
|
||||
setMode(Mode.OFF);
|
||||
}
|
||||
else
|
||||
catch(UsbException ue)
|
||||
{
|
||||
throw new SourceException("couldn't claim usb interface - no "
|
||||
+ "device handle");
|
||||
mLog.error("Error while setting HackRF mode to OFF", ue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +157,6 @@ public class HackRFTunerController extends USBTunerController
|
||||
public BoardID getBoardID() throws UsbException
|
||||
{
|
||||
int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false);
|
||||
|
||||
return BoardID.lookup(id);
|
||||
}
|
||||
|
||||
@@ -213,11 +166,8 @@ public class HackRFTunerController extends USBTunerController
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -227,7 +177,6 @@ public class HackRFTunerController extends USBTunerController
|
||||
public Serial getSerial() throws UsbException
|
||||
{
|
||||
ByteBuffer buffer = readArray(Request.BOARD_PARTID_SERIALNO_READ, 0, 0, 24);
|
||||
|
||||
return new Serial(buffer);
|
||||
}
|
||||
|
||||
@@ -242,18 +191,9 @@ public class HackRFTunerController extends USBTunerController
|
||||
/**
|
||||
* Sets the HackRF baseband filter
|
||||
*/
|
||||
public void setBasebandFilter(BasebandFilter filter) throws UsbException
|
||||
public void setBasebandFilter(BasebandFilter filter) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
write(Request.BASEBAND_FILTER_BANDWIDTH_SET, filter.getLowValue(), filter.getHighValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
write(Request.BASEBAND_FILTER_BANDWIDTH_SET, filter.getLowValue(), filter.getHighValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,17 +201,8 @@ public class HackRFTunerController extends USBTunerController
|
||||
*/
|
||||
public void setAmplifierEnabled(boolean enabled) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0);
|
||||
mAmplifierEnabled = enabled;
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();;
|
||||
}
|
||||
write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0);
|
||||
mAmplifierEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean getAmplifier()
|
||||
@@ -284,20 +215,11 @@ public class HackRFTunerController extends USBTunerController
|
||||
*/
|
||||
public void setLNAGain(HackRFLNAGain gain) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true);
|
||||
|
||||
try
|
||||
if(result != 1)
|
||||
{
|
||||
int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true);
|
||||
|
||||
if(result != 1)
|
||||
{
|
||||
throw new UsbException("couldn't set lna gain to " + gain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new UsbException("couldn't set lna gain to " + gain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,20 +228,11 @@ public class HackRFTunerController extends USBTunerController
|
||||
*/
|
||||
public void setVGAGain(HackRFVGAGain gain) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true);
|
||||
|
||||
try
|
||||
if(result != 1)
|
||||
{
|
||||
int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true);
|
||||
|
||||
if(result != 1)
|
||||
{
|
||||
throw new UsbException("couldn't set vga gain to " + gain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
throw new UsbException("couldn't set vga gain to " + gain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,34 +247,25 @@ public class HackRFTunerController extends USBTunerController
|
||||
@Override
|
||||
public void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
write(Request.SET_FREQUENCY, 0, 0, buffer);
|
||||
}
|
||||
finally
|
||||
catch(UsbException e)
|
||||
{
|
||||
mLock.unlock();
|
||||
mLog.error("error setting frequency [" + frequency + "]", e);
|
||||
throw new SourceException("error setting frequency [" + frequency + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,39 +278,30 @@ public class HackRFTunerController extends USBTunerController
|
||||
@Override
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
if(config instanceof HackRFTunerConfiguration)
|
||||
{
|
||||
HackRFTunerConfiguration hackRFConfig = (HackRFTunerConfiguration)config;
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(config);
|
||||
|
||||
if(config instanceof HackRFTunerConfiguration hackRFConfig)
|
||||
{
|
||||
//Convert legacy sample rate setting to new sample rates
|
||||
if(!hackRFConfig.getSampleRate().isValidSampleRate())
|
||||
{
|
||||
mLog.warn("Changing legacy HackRF Sample Rates Setting [" + hackRFConfig.getSampleRate().name() + "] to current valid setting");
|
||||
mLog.warn("Changing legacy HackRF Sample Rates Setting [" + hackRFConfig.getSampleRate().name() +
|
||||
"] to current valid setting");
|
||||
hackRFConfig.setSampleRate(HackRFSampleRate.RATE_5_0);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
@@ -414,39 +309,22 @@ public class HackRFTunerController extends USBTunerController
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer readArray(Request request,
|
||||
int value,
|
||||
int index,
|
||||
int length) throws UsbException
|
||||
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(getDeviceHandle(), REQUEST_TYPE_IN, request.getRequestNumber(),
|
||||
(short)value, (short)index, buffer, USB_TIMEOUT_US);
|
||||
|
||||
if(transferred < 0)
|
||||
{
|
||||
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);
|
||||
throw new LibUsbException("read error", transferred);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int read(Request request, int value, int index, int length)
|
||||
throws UsbException
|
||||
public int read(Request request, int value, int index, int length) throws UsbException
|
||||
{
|
||||
if(!(length == 1 || length == 2 || length == 4))
|
||||
{
|
||||
@@ -474,61 +352,36 @@ public class HackRFTunerController extends USBTunerController
|
||||
}
|
||||
}
|
||||
|
||||
public int readByte(Request request, int value, int index, boolean signed)
|
||||
throws UsbException
|
||||
public synchronized 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());
|
||||
return buffer.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)(buffer.get() & 0xFF);
|
||||
return (buffer.get() & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(Request request,
|
||||
int value,
|
||||
int index,
|
||||
ByteBuffer buffer) throws UsbException
|
||||
public synchronized 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);
|
||||
int status = LibUsb.controlTransfer(getDeviceHandle(), 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
|
||||
if(status < LibUsb.SUCCESS)
|
||||
{
|
||||
throw new LibUsbException("device handle is null",
|
||||
LibUsb.ERROR_NO_DEVICE);
|
||||
throw new LibUsbException("error writing control transfer for request [" + request.name() +
|
||||
"] value [" + value + "]", status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request that doesn't have a data payload
|
||||
*/
|
||||
public void write(Request request,
|
||||
int value,
|
||||
int index) throws UsbException
|
||||
public void write(Request request, int value, int index) throws UsbException
|
||||
{
|
||||
write(request, value, index, ByteBuffer.allocateDirect(0));
|
||||
}
|
||||
@@ -548,24 +401,17 @@ public class HackRFTunerController extends USBTunerController
|
||||
}
|
||||
|
||||
setSampleRateManual(rate.getRate(), 1);
|
||||
|
||||
mFrequencyController.setSampleRate(rate.getRate());
|
||||
|
||||
setBasebandFilter(rate.getFilter());
|
||||
|
||||
mSampleRate = rate;
|
||||
}
|
||||
|
||||
public void setSampleRateManual(int frequency, int divider)
|
||||
throws UsbException
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -601,7 +447,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
private byte mRequestNumber;
|
||||
|
||||
private Request(int number)
|
||||
Request(int number)
|
||||
{
|
||||
mRequestNumber = (byte)number;
|
||||
}
|
||||
@@ -655,7 +501,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
private String mLabel;
|
||||
private BasebandFilter mFilter;
|
||||
|
||||
private HackRFSampleRate(int rate, String label, BasebandFilter filter)
|
||||
HackRFSampleRate(int rate, String label, BasebandFilter filter)
|
||||
{
|
||||
mRate = rate;
|
||||
mLabel = label;
|
||||
@@ -713,7 +559,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
private int mBandwidth;
|
||||
private String mLabel;
|
||||
|
||||
private BasebandFilter(int bandwidth, String label)
|
||||
BasebandFilter(int bandwidth, String label)
|
||||
{
|
||||
mBandwidth = bandwidth;
|
||||
mLabel = label;
|
||||
@@ -745,12 +591,13 @@ public class HackRFTunerController extends USBTunerController
|
||||
JELLYBEAN(0x00, "HackRF Jelly Bean"),
|
||||
JAWBREAKER(0x01, "HackRF Jaw Breaker"),
|
||||
HACKRF_ONE(0x02, "HackRF One"),
|
||||
RAD1O(0x03, "RAD1O"),
|
||||
INVALID(0xFF, "HackRF Unknown Board");
|
||||
|
||||
private byte mIDNumber;
|
||||
private String mLabel;
|
||||
|
||||
private BoardID(int number, String label)
|
||||
BoardID(int number, String label)
|
||||
{
|
||||
mIDNumber = (byte)number;
|
||||
mLabel = label;
|
||||
@@ -781,6 +628,8 @@ public class HackRFTunerController extends USBTunerController
|
||||
return JAWBREAKER;
|
||||
case 2:
|
||||
return HACKRF_ONE;
|
||||
case 3:
|
||||
return RAD1O;
|
||||
default:
|
||||
return INVALID;
|
||||
}
|
||||
@@ -797,7 +646,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
private byte mNumber;
|
||||
private String mLabel;
|
||||
|
||||
private Mode(int number, String label)
|
||||
Mode(int number, String label)
|
||||
{
|
||||
mNumber = (byte)number;
|
||||
mLabel = label;
|
||||
@@ -830,7 +679,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
private int mValue;
|
||||
|
||||
private HackRFLNAGain(int value)
|
||||
HackRFLNAGain(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
@@ -842,7 +691,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return String.valueOf(mValue) + " dB";
|
||||
return mValue + " dB";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -886,7 +735,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
private int mValue;
|
||||
|
||||
private HackRFVGAGain(int value)
|
||||
HackRFVGAGain(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
@@ -898,7 +747,7 @@ public class HackRFTunerController extends USBTunerController
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return String.valueOf(mValue) + " dB";
|
||||
return mValue + " dB";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.preference.UserPreferences;
|
||||
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.hackrf.HackRFTunerController.HackRFLNAGain;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFSampleRate;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFVGAGain;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -32,259 +35,254 @@ import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
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.Collections;
|
||||
|
||||
public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
/**
|
||||
* HackRF Tuner Editor
|
||||
*/
|
||||
public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfiguration>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerEditor.class);
|
||||
|
||||
private JTextField mConfigurationName;
|
||||
private JButton mTunerInfo;
|
||||
private JComboBox<HackRFSampleRate> mComboSampleRate;
|
||||
private JSpinner mFrequencyCorrection;
|
||||
private JComboBox<HackRFSampleRate> mSampleRateCombo;
|
||||
private JToggleButton mAmplifier;
|
||||
private JComboBox<HackRFLNAGain> mComboLNAGain;
|
||||
private JComboBox<HackRFVGAGain> mComboVGAGain;
|
||||
private boolean mLoading;
|
||||
private JComboBox<HackRFLNAGain> mLnaGainCombo;
|
||||
private JComboBox<HackRFVGAGain> mVgaGainCombo;
|
||||
|
||||
private HackRFTunerController mController;
|
||||
|
||||
public HackRFTunerEditor(TunerConfigurationModel tunerConfigurationModel, HackRFTuner tuner)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager to save configuration
|
||||
* @param discoveredTuner to control
|
||||
*/
|
||||
public HackRFTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
|
||||
mController = tuner.getController();
|
||||
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
}
|
||||
|
||||
private HackRFTunerConfiguration getConfiguration()
|
||||
{
|
||||
if(hasItem())
|
||||
{
|
||||
return (HackRFTunerConfiguration)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][][grow]"));
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("HackRF Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
mConfigurationName = new JTextField();
|
||||
mConfigurationName.setEnabled(false);
|
||||
mConfigurationName.addFocusListener(new FocusListener()
|
||||
{
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
add(mConfigurationName, "span 2");
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
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);
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "wrap");
|
||||
|
||||
|
||||
HackRFSampleRate[] validRates = HackRFSampleRate.VALID_SAMPLE_RATES
|
||||
.toArray(new HackRFSampleRate[0]);
|
||||
mComboSampleRate = new JComboBox<>(validRates);
|
||||
mComboSampleRate.setEnabled(false);
|
||||
mComboSampleRate.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
HackRFSampleRate sampleRate = (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);
|
||||
add(getSampleRateCombo(), "wrap");
|
||||
|
||||
SpinnerModel model = new SpinnerNumberModel(0.0, //initial value
|
||||
-1000.0, //min
|
||||
1000.0, //max
|
||||
0.1); //step
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
mFrequencyCorrection = new JSpinner(model);
|
||||
mFrequencyCorrection.setEnabled(false);
|
||||
JSpinner.NumberEditor editor = (JSpinner.NumberEditor)mFrequencyCorrection.getEditor();
|
||||
add(new JLabel("Gain Control"));
|
||||
add(getAmplifierToggle(), "wrap");
|
||||
|
||||
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<HackRFLNAGain>(HackRFLNAGain.values());
|
||||
mComboLNAGain.setEnabled(false);
|
||||
mComboLNAGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
HackRFLNAGain lnaGain = (HackRFLNAGain)mComboLNAGain.getSelectedItem();
|
||||
|
||||
if(lnaGain == null)
|
||||
{
|
||||
lnaGain = HackRFLNAGain.GAIN_16;
|
||||
}
|
||||
|
||||
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);
|
||||
add(getLnaGainCombo(), "wrap");
|
||||
|
||||
mComboVGAGain = new JComboBox<HackRFVGAGain>(HackRFVGAGain.values());
|
||||
mComboVGAGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
HackRFVGAGain vgaGain = (HackRFVGAGain)mComboVGAGain.getSelectedItem();
|
||||
|
||||
if(vgaGain == null)
|
||||
{
|
||||
vgaGain = HackRFVGAGain.GAIN_16;
|
||||
}
|
||||
|
||||
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);
|
||||
add(getVgaGainCombo(), "wrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked());
|
||||
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
|
||||
updateSampleRateToolTip();
|
||||
getTunerInfoButton().setEnabled(hasTuner());
|
||||
|
||||
getAmplifierToggle().setEnabled(hasTuner());
|
||||
getAmplifierToggle().setSelected(getConfiguration().getAmplifierEnabled());
|
||||
getLnaGainCombo().setEnabled(hasTuner());
|
||||
getLnaGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
||||
getVgaGainCombo().setEnabled(hasTuner());
|
||||
getVgaGainCombo().setSelectedItem(getConfiguration().getVGAGain());
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
private JComboBox getVgaGainCombo()
|
||||
{
|
||||
if(mVgaGainCombo == null)
|
||||
{
|
||||
mVgaGainCombo = new JComboBox<HackRFVGAGain>(HackRFVGAGain.values());
|
||||
mVgaGainCombo.setToolTipText("<html>VGA Gain. Adjust to set the baseband gain</html>");
|
||||
mVgaGainCombo.setEnabled(false);
|
||||
mVgaGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
HackRFVGAGain vgaGain = (HackRFVGAGain) mVgaGainCombo.getSelectedItem();
|
||||
|
||||
if(vgaGain == null)
|
||||
{
|
||||
vgaGain = HackRFVGAGain.GAIN_16;
|
||||
}
|
||||
|
||||
getTuner().getController().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mVgaGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getLnaGainCombo()
|
||||
{
|
||||
if(mLnaGainCombo == null)
|
||||
{
|
||||
mLnaGainCombo = new JComboBox<HackRFLNAGain>(HackRFLNAGain.values());
|
||||
mLnaGainCombo.setToolTipText("<html>LNA Gain. Adjust to set the IF gain</html>");
|
||||
mLnaGainCombo.setEnabled(false);
|
||||
mLnaGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
HackRFLNAGain lnaGain = (HackRFLNAGain) mLnaGainCombo.getSelectedItem();
|
||||
|
||||
if(lnaGain == null)
|
||||
{
|
||||
lnaGain = HackRFLNAGain.GAIN_16;
|
||||
}
|
||||
|
||||
getTuner().getController().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mLnaGainCombo;
|
||||
}
|
||||
|
||||
private JToggleButton getAmplifierToggle()
|
||||
{
|
||||
if(mAmplifier == null)
|
||||
{
|
||||
mAmplifier = new JToggleButton("Amplifier");
|
||||
mAmplifier.setToolTipText("Enable or disable the gain amplifier");
|
||||
mAmplifier.setEnabled(false);
|
||||
mAmplifier.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTuner().getController().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mAmplifier;
|
||||
}
|
||||
|
||||
private JComboBox getSampleRateCombo()
|
||||
{
|
||||
if(mSampleRateCombo == null)
|
||||
{
|
||||
HackRFSampleRate[] validRates = HackRFSampleRate.VALID_SAMPLE_RATES.toArray(new HackRFSampleRate[0]);
|
||||
mSampleRateCombo = new JComboBox<>(validRates);
|
||||
mSampleRateCombo.setEnabled(false);
|
||||
mSampleRateCombo.addActionListener(e ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
HackRFSampleRate sampleRate = (HackRFSampleRate)getSampleRateCombo().getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSampleRateCombo;
|
||||
}
|
||||
|
||||
private JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfo == null)
|
||||
{
|
||||
mTunerInfo = new JButton("Tuner Info");
|
||||
mTunerInfo.setToolTipText("Provides details and information about the tuner");
|
||||
mTunerInfo.setEnabled(false);
|
||||
mTunerInfo.addActionListener(e -> JOptionPane.showMessageDialog(HackRFTunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
return mTunerInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,67 +290,21 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
*/
|
||||
private void updateSampleRateToolTip()
|
||||
{
|
||||
if(mController.isLocked())
|
||||
if(hasTuner() && getTuner().getController().isLocked())
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Select a sample rate for the tuner");
|
||||
mSampleRateCombo.setToolTipText("Select a sample rate for the tuner");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
mComboSampleRate.setEnabled(!locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
if(mController.isLocked())
|
||||
{
|
||||
mComboSampleRate.setEnabled(false);
|
||||
}
|
||||
else 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);
|
||||
}
|
||||
super.setTunerLockState(locked);
|
||||
mSampleRateCombo.setEnabled(!locked);
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
@@ -361,7 +313,10 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
board = mController.getBoardID();
|
||||
if(hasTuner())
|
||||
{
|
||||
board = getTuner().getController().getBoardID();
|
||||
}
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
@@ -371,7 +326,6 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("<html><h3>HackRF Tuner</h3>");
|
||||
|
||||
sb.append("<b>Board: </b>");
|
||||
sb.append(board.getLabel());
|
||||
sb.append("<br>");
|
||||
@@ -380,7 +334,10 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
serial = mController.getSerial();
|
||||
if(hasTuner())
|
||||
{
|
||||
serial = getTuner().getController().getSerial();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -407,7 +364,10 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
try
|
||||
{
|
||||
firmware = mController.getFirmwareVersion();
|
||||
if(hasTuner())
|
||||
{
|
||||
firmware = getTuner().getController().getFirmwareVersion();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -428,66 +388,20 @@ public class HackRFTunerEditor extends TunerConfigurationEditor
|
||||
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());
|
||||
|
||||
//Update enabled state to reflect when frequency and sample rate controls are locked
|
||||
mComboSampleRate.setEnabled(!mController.isLocked());
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText(config.getName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText("");
|
||||
}
|
||||
|
||||
mLoading = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
HackRFTunerConfiguration config = getConfiguration();
|
||||
|
||||
config.setName(mConfigurationName.getText());
|
||||
|
||||
double value = ((SpinnerNumberModel)mFrequencyCorrection
|
||||
.getModel()).getNumber().doubleValue();
|
||||
config.setFrequencyCorrection(value);
|
||||
config.setSampleRate((HackRFSampleRate)mComboSampleRate.getSelectedItem());
|
||||
config.setAmplifierEnabled(mAmplifier.isSelected());
|
||||
config.setLNAGain((HackRFLNAGain)mComboLNAGain.getSelectedItem());
|
||||
config.setVGAGain((HackRFVGAGain)mComboVGAGain.getSelectedItem());
|
||||
|
||||
getTunerConfigurationModel().broadcast(
|
||||
new TunerConfigurationEvent(getConfiguration(), TunerConfigurationEvent.Event.CHANGE));
|
||||
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
getConfiguration().setFrequencyCorrection(value);
|
||||
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
getConfiguration().setSampleRate((HackRFSampleRate)getSampleRateCombo().getSelectedItem());
|
||||
getConfiguration().setAmplifierEnabled(getAmplifierToggle().isSelected());
|
||||
getConfiguration().setLNAGain((HackRFLNAGain)getLnaGainCombo().getSelectedItem());
|
||||
getConfiguration().setVGAGain((HackRFVGAGain)getVgaGainCombo().getSelectedItem());
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.manager;
|
||||
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
@@ -133,7 +136,7 @@ public class CenterFrequencyCalculator
|
||||
public static boolean canTune(TunerChannel channel, TunerController tunerController, SortedSet<TunerChannel> channels)
|
||||
{
|
||||
//Make sure we're within the tunable frequency range of this tuner
|
||||
if(tunerController.getMinFrequency() < channel.getMinFrequency() && tunerController.getMaxFrequency() > channel.getMaxFrequency())
|
||||
if(tunerController.getMinimumFrequency() < channel.getMinFrequency() && tunerController.getMaximumFrequency() > channel.getMaxFrequency())
|
||||
{
|
||||
//If this is the first lock, then we're good
|
||||
if(channels.isEmpty())
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.manager;
|
||||
|
||||
@@ -50,6 +47,11 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
|
||||
*/
|
||||
public abstract int getTunerChannelCount();
|
||||
|
||||
/**
|
||||
* Tells this channel source manager to shutdown all tuner channel sources to prepare for tuner shutdown.
|
||||
*/
|
||||
public abstract void stopAllChannels();
|
||||
|
||||
/**
|
||||
* Obtains a source for the tuner channel or returns null if the channel cannot be sourced by this tuner.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.manager;
|
||||
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* A discovered tuner that may be accessible for use.
|
||||
*/
|
||||
public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(DiscoveredTuner.class);
|
||||
private TunerStatus mTunerStatus = TunerStatus.ENABLED;
|
||||
private boolean mEnabled = true;
|
||||
private String mErrorMessage;
|
||||
private List<IDiscoveredTunerStatusListener> mListeners = new CopyOnWriteArrayList();
|
||||
protected Tuner mTuner;
|
||||
protected TunerConfiguration mTunerConfiguration;
|
||||
|
||||
/**
|
||||
* Tuner Class
|
||||
*/
|
||||
public abstract TunerClass getTunerClass();
|
||||
|
||||
/**
|
||||
* Current status of the discovered tuner
|
||||
*/
|
||||
public TunerStatus getTunerStatus()
|
||||
{
|
||||
return mTunerStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of the discovered tuner and notifies registered listeners of the status change.
|
||||
* @param tunerStatus to set
|
||||
*/
|
||||
protected void setTunerStatus(TunerStatus tunerStatus)
|
||||
{
|
||||
TunerStatus previous = mTunerStatus;
|
||||
mTunerStatus = tunerStatus;
|
||||
broadcast(this, previous, mTunerStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this discovered tuner is enabled and usable.
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state of this discovered tuner
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
//If there was a change in state
|
||||
if(mEnabled ^ enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
|
||||
if(mEnabled)
|
||||
{
|
||||
setTunerStatus(TunerStatus.ENABLED);
|
||||
start();
|
||||
}
|
||||
else
|
||||
{
|
||||
stop();
|
||||
setTunerStatus(TunerStatus.DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this discovered tuner is available and usable. Use this method to check if a discovered tuner is
|
||||
* available prior to access the tuner directly via the getTuner() method.
|
||||
*/
|
||||
public boolean isAvailable()
|
||||
{
|
||||
return getTunerStatus().isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier for this discovered tuner where this identifier when combined with the discovered tuner type
|
||||
* is a globally unique value.
|
||||
*
|
||||
* Note: this identifier will be used to persist the enabled/disabled state of each discoverable tuner. Therefore,
|
||||
* this identifier must be consistent across application run cycles in order to correctly manage disabled tuners.
|
||||
* @return globally unique string identifier for the discovered tuner.
|
||||
*/
|
||||
public abstract String getId();
|
||||
|
||||
/**
|
||||
* Access a started and initialized.
|
||||
*
|
||||
* Use the isAvailable() method to check if this tuner is available prior to invoking this method to avoid a
|
||||
* null tuner instance.
|
||||
*
|
||||
* @return started and initialized tuner
|
||||
*/
|
||||
public Tuner getTuner()
|
||||
{
|
||||
return mTuner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this discovered tuner is started and has a fully constructed tuner instance
|
||||
*/
|
||||
public boolean hasTuner()
|
||||
{
|
||||
return getTuner() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner configuration for this tuner
|
||||
*/
|
||||
public TunerConfiguration getTunerConfiguration()
|
||||
{
|
||||
return mTunerConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tuner configuration for this tuner.
|
||||
*/
|
||||
public void setTunerConfiguration(TunerConfiguration tunerConfiguration)
|
||||
{
|
||||
mTunerConfiguration = tunerConfiguration;
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTuner().getTunerController().apply(mTunerConfiguration);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error applying tuner configuration [" + mTunerConfiguration.getClass() +
|
||||
"] to discovered tuner [" + getId() + "}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this discovered tuner has a tuner configuration
|
||||
*/
|
||||
public boolean hasTunerConfiguration()
|
||||
{
|
||||
return mTunerConfiguration != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tuner status change listener to monitor this discovered tuner for changes in status.
|
||||
*/
|
||||
public void addTunerStatusListener(IDiscoveredTunerStatusListener listener)
|
||||
{
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tuner status change listener from monitoring this discovered tuner for changes in status.
|
||||
*/
|
||||
public void removeTunerStatusListener(IDiscoveredTunerStatusListener listener)
|
||||
{
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the tuner status change to all registered listeners.
|
||||
* @param tuner that was changed
|
||||
* @param previous tuner status
|
||||
* @param current tuner status
|
||||
*/
|
||||
private void broadcast(DiscoveredTuner tuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
for(IDiscoveredTunerStatusListener listener: mListeners)
|
||||
{
|
||||
listener.tunerStatusUpdated(tuner, previous, current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this tuner to an error state and applies the error message
|
||||
* @param errorMessage to set
|
||||
*/
|
||||
@Override
|
||||
public void setErrorMessage(String errorMessage)
|
||||
{
|
||||
mErrorMessage = errorMessage;
|
||||
setTunerStatus(TunerStatus.ERROR);
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tuner has an error message.
|
||||
*/
|
||||
public boolean hasErrorMessage()
|
||||
{
|
||||
return mErrorMessage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional error message.
|
||||
* @return error message if there is one, or null if there is not.
|
||||
*/
|
||||
public String getErrorMessage()
|
||||
{
|
||||
return mErrorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully instantiate and start this discovered tuner to make it usable within the application. Implementations
|
||||
* should attempt to instantiate the tuner and assign it to mTuner variable. If there is an error, invoke the
|
||||
* setErrorMessage() to signal the tuner is unusable.
|
||||
*/
|
||||
public abstract void start();
|
||||
|
||||
/**
|
||||
* Attempts to restart a tuner that's currently in an error state
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
if(getTunerStatus() == TunerStatus.ERROR)
|
||||
{
|
||||
mErrorMessage = null;
|
||||
|
||||
if(isEnabled())
|
||||
{
|
||||
setTunerStatus(TunerStatus.ENABLED);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTunerStatus(TunerStatus.DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop this discovered tuner, notify registered listeners/consumers and release any resources that it is using.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
mLog.info("Stopping Tuner: " + getId());
|
||||
getTuner().stop();
|
||||
mTuner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.manager;
|
||||
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A USB tuner that is discovered during the USB device traversal process that has a VID and PID that match a known
|
||||
* USB tuner type that is currently supported by the software.
|
||||
*/
|
||||
public class DiscoveredUSBTuner extends DiscoveredTuner
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(DiscoveredUSBTuner.class);
|
||||
private TunerClass mTunerClass;
|
||||
private int mBus;
|
||||
private int mPort;
|
||||
private ChannelizerType mChannelizerType;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus (USB) number
|
||||
* @param port (USB) number
|
||||
* @param channelizerType to use with the tuner
|
||||
*/
|
||||
public DiscoveredUSBTuner(TunerClass tunerClass, int bus, int port, ChannelizerType channelizerType)
|
||||
{
|
||||
mTunerClass = tunerClass;
|
||||
mBus = bus;
|
||||
mPort = port;
|
||||
mChannelizerType = channelizerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this USB tuner is plugged into the specified bus and port.
|
||||
* @param bus number
|
||||
* @param port number
|
||||
* @return true if there is a match
|
||||
*/
|
||||
public boolean isAt(int bus, int port)
|
||||
{
|
||||
return mBus == bus && mPort == port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner class as determined from the USB VID and PID values
|
||||
*/
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return mTunerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* USB bus number
|
||||
*/
|
||||
public int getBus()
|
||||
{
|
||||
return mBus;
|
||||
}
|
||||
|
||||
/**
|
||||
* USB port number
|
||||
*/
|
||||
public int getPort()
|
||||
{
|
||||
return mPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getTunerClass());
|
||||
sb.append(" USB Bus:").append(getBus());
|
||||
sb.append(" Port:").append(getPort());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
if(isAvailable() && !hasTuner())
|
||||
{
|
||||
try
|
||||
{
|
||||
mTuner = TunerFactory.getUsbTuner(getTunerClass(), getPort(), getBus(), this, mChannelizerType);
|
||||
mTuner.start();
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
|
||||
//Set error message to flag this tuner with error status and invoke stop()
|
||||
setErrorMessage(se.getMessage());
|
||||
|
||||
mLog.error("Unable to start tuner [" + getTunerClass() + "] - error: " + getErrorMessage());
|
||||
|
||||
try
|
||||
{
|
||||
stop();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//No-op on attempted cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "USB Tuner - " + getId();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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;
|
||||
* 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.manager;
|
||||
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -54,6 +58,14 @@ public class FrequencyErrorCorrectionManager
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if Auto-PPM is enabled
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets monitoring
|
||||
*/
|
||||
@@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -45,12 +46,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
|
||||
private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000;
|
||||
|
||||
// private List<CICTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private List<HalfBandTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
|
||||
private TunerController mTunerController;
|
||||
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
|
||||
private NativeSampleDelayBuffer mSampleDelayBuffer;
|
||||
private boolean mRunning = true;
|
||||
|
||||
public HeterodyneChannelSourceManager(TunerController tunerController)
|
||||
{
|
||||
@@ -58,6 +59,26 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
mTunerController.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAllChannels()
|
||||
{
|
||||
mRunning = false;
|
||||
|
||||
List<TunerChannelSource> toStop = new ArrayList<>(mChannelSources);
|
||||
|
||||
for(TunerChannelSource tunerChannelSource: toStop)
|
||||
{
|
||||
try
|
||||
{
|
||||
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error stopping tuner channel source for tuner shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<TunerChannel> getTunerChannels()
|
||||
{
|
||||
@@ -73,14 +94,16 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
@Override
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
||||
{
|
||||
if(!mRunning)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
|
||||
{
|
||||
try
|
||||
{
|
||||
//Attempt to create the channel source first, in case we get a filter design exception
|
||||
// CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor,
|
||||
// tunerChannel, mTunerController.getSampleRate(), channelSpecification);
|
||||
|
||||
HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor,
|
||||
tunerChannel, mTunerController.getSampleRate(), channelSpecification);
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.manager;
|
||||
|
||||
/**
|
||||
* Discovered tuner status change listener interface
|
||||
*/
|
||||
public interface IDiscoveredTunerStatusListener
|
||||
{
|
||||
/**
|
||||
* Notification that a discovered tuner's status has changed.
|
||||
* @param discoveredTuner that has a status change.
|
||||
* @param previous tuner status
|
||||
* @param current tuner status
|
||||
*/
|
||||
void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current);
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.manager;
|
||||
@@ -33,6 +30,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -48,12 +46,33 @@ public class PassThroughSourceManager extends ChannelSourceManager
|
||||
private TunerController mTunerController;
|
||||
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
|
||||
private List<TunerChannelSource> mTunerChannelSources = new CopyOnWriteArrayList<>();
|
||||
private boolean mRunning = true;
|
||||
|
||||
public PassThroughSourceManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAllChannels()
|
||||
{
|
||||
mRunning = false;
|
||||
|
||||
List<TunerChannelSource> toStop = new ArrayList<>(mTunerChannelSources);
|
||||
|
||||
for(TunerChannelSource tunerChannelSource: toStop)
|
||||
{
|
||||
try
|
||||
{
|
||||
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error stopping tuner channel source for tuner shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<TunerChannel> getTunerChannels()
|
||||
{
|
||||
@@ -69,6 +88,11 @@ public class PassThroughSourceManager extends ChannelSourceManager
|
||||
@Override
|
||||
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
|
||||
{
|
||||
if(!mRunning)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PassThroughChannelSource channelSource = new PassThroughChannelSource(new SourceEventProxy(),
|
||||
mTunerController, tunerChannel);
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to
|
||||
* polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
|
||||
* be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source.
|
||||
*/
|
||||
public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSourceManager.class);
|
||||
@@ -38,10 +43,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
private TunerController mTunerController;
|
||||
|
||||
/**
|
||||
* PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to
|
||||
* polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
|
||||
* be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source.
|
||||
*
|
||||
* Constructs an instance
|
||||
* @param tunerController with a center tuned frequency that will be managed by this instance
|
||||
*/
|
||||
public PolyphaseChannelSourceManager(TunerController tunerController)
|
||||
@@ -54,6 +56,12 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
mTunerController.addListener(mPolyphaseChannelManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAllChannels()
|
||||
{
|
||||
mPolyphaseChannelManager.stopAllChannels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
|
||||
*
|
||||
|
||||
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.manager;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.mixer.MixerManager;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.MultiFrequencyTunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationManager;
|
||||
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Context;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceList;
|
||||
import org.usb4java.HotplugCallback;
|
||||
import org.usb4java.HotplugCallbackHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Tuner manager provides access to tuners using USB, recording, sound-card and system-daemon accessible devices. This
|
||||
* manager also supports hot-plug detection and black-listing of discovered tuners so that they can be used with other
|
||||
* software applications.
|
||||
*/
|
||||
public class TunerManager implements IDiscoveredTunerStatusListener
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(TunerManager.class);
|
||||
private static final int MAXIMUM_USB_2_DATA_RATE = 480000000;
|
||||
|
||||
private UserPreferences mUserPreferences;
|
||||
private DiscoveredTunerModel mDiscoveredTunerModel = new DiscoveredTunerModel();
|
||||
private TunerConfigurationManager mTunerConfigurationManager;
|
||||
private HotplugEventSupport mHotplugEventSupport = new HotplugEventSupport();
|
||||
private Context mLibUsbApplicationContext = new Context();
|
||||
private boolean mLibUsbInitialized = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public TunerManager(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mTunerConfigurationManager = new TunerConfigurationManager(userPreferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovered tuner model
|
||||
*/
|
||||
public DiscoveredTunerModel getDiscoveredTunerModel()
|
||||
{
|
||||
return mDiscoveredTunerModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the tuner manager and loads all available tuners
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
mLog.info("LibUSB API Version: " + LibUsb.getApiVersion());
|
||||
mLog.info("LibUSB Version: " + LibUsb.getVersion());
|
||||
|
||||
int status = LibUsb.init(mLibUsbApplicationContext);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
mLibUsbInitialized = true;
|
||||
// LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_DEBUG);
|
||||
mHotplugEventSupport.start();
|
||||
|
||||
discoverUSBTuners();
|
||||
}
|
||||
|
||||
discoverSdrPlayTuners();
|
||||
discoverSoundCardTuners();
|
||||
discoverRecordingTuners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops or shuts down the tuner manager and releases all devices
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
//Stop all tuners
|
||||
mDiscoveredTunerModel.releaseDiscoveredTuners();
|
||||
|
||||
//Shutdown LibUsb
|
||||
if(mLibUsbInitialized)
|
||||
{
|
||||
mHotplugEventSupport.stop();
|
||||
LibUsb.exit(mLibUsbApplicationContext);
|
||||
mLibUsbInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests to save the current state of the tuner configurations
|
||||
*/
|
||||
public void saveConfigurations()
|
||||
{
|
||||
mTunerConfigurationManager.saveConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover or rediscovers USB tuners
|
||||
*/
|
||||
private void discoverUSBTuners()
|
||||
{
|
||||
if(mLibUsbInitialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
DeviceList deviceList = new DeviceList();
|
||||
int deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
|
||||
|
||||
if(deviceCount >= 0)
|
||||
{
|
||||
for(Device device: deviceList)
|
||||
{
|
||||
addUsbTuner(device);
|
||||
}
|
||||
}
|
||||
|
||||
LibUsb.freeDeviceList(deviceList, true);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("LibUsb - error during USB device discovery", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the USB device is a supported tuner and add if it has not already been added/discovered.
|
||||
* @param device to inspect and optionally add as a discovered tuner
|
||||
*/
|
||||
private void addUsbTuner(Device device)
|
||||
{
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
int port = LibUsb.getPortNumber(device);
|
||||
|
||||
DeviceDescriptor deviceDescriptor = new DeviceDescriptor();
|
||||
int status = LibUsb.getDeviceDescriptor(device, deviceDescriptor);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
TunerClass tunerClass = TunerClass.lookup(deviceDescriptor.idVendor(), deviceDescriptor.idProduct());
|
||||
|
||||
if(tunerClass.isSupportedUsbTuner() && !mDiscoveredTunerModel.hasUsbTuner(bus, port))
|
||||
{
|
||||
mLog.info("Adding USB Bus [" + bus + "] Port [" + port + "] ID [" +
|
||||
String.format("%04X", deviceDescriptor.idVendor()) + ":" +
|
||||
String.format("%04X", deviceDescriptor.idProduct()) + "] Tuner Class [" + tunerClass + "]");
|
||||
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
|
||||
DiscoveredUSBTuner discoveredUSBTuner = new DiscoveredUSBTuner(tunerClass, bus, port, channelizerType);
|
||||
discoveredUSBTuner.addTunerStatusListener(this);
|
||||
|
||||
//Set the tuner to disabled if the user has previously blacklisted the tuner
|
||||
if(mTunerConfigurationManager.isDisabled(discoveredUSBTuner))
|
||||
{
|
||||
discoveredUSBTuner.setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Attempt to start the discovered tuner so we can determine the tuner type
|
||||
discoveredUSBTuner.start();
|
||||
|
||||
if(discoveredUSBTuner.hasTuner())
|
||||
{
|
||||
TunerType tunerType = discoveredUSBTuner.getTuner().getTunerType();
|
||||
|
||||
TunerConfiguration tunerConfiguration = mTunerConfigurationManager
|
||||
.getTunerConfiguration(tunerType, discoveredUSBTuner.getId());
|
||||
|
||||
if(tunerConfiguration != null)
|
||||
{
|
||||
mLog.info("Applying tuner configuration to " + discoveredUSBTuner.getId());
|
||||
discoveredUSBTuner.setTunerConfiguration(tunerConfiguration);
|
||||
mTunerConfigurationManager.saveConfigurations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mDiscoveredTunerModel.addDiscoveredTuner(discoveredUSBTuner);
|
||||
mLog.info("Tuner Added: " + discoveredUSBTuner);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("LibUsb - unable to get device descriptor for device on bus [" + bus +
|
||||
"] port [" + port + "] - status [" + status + "] - " + LibUsb.errorName(status));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and removes the USB tuner if it is currently discovered.
|
||||
* @param bus usb for the tuner
|
||||
* @param port usb for the tuner
|
||||
*/
|
||||
private void removeUsbTuner(int bus, int port)
|
||||
{
|
||||
mDiscoveredTunerModel.removeUsbTuner(bus, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover sound card tuners like the Funcube Dongle (Pro)
|
||||
*/
|
||||
private void discoverSoundCardTuners()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover SDRPlay RSP tuners
|
||||
*/
|
||||
private void discoverSdrPlayTuners()
|
||||
{
|
||||
//placeholder ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover recording based tuners
|
||||
*/
|
||||
private void discoverRecordingTuners()
|
||||
{
|
||||
//placeholder
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles tuner status change events. Events are sent to the tuner configuration manager so that it can save
|
||||
* configuration updates and events are also monitored to detect when a user changes the tuner state of a tuner
|
||||
* so that we can auto-start the tuner and apply a tuner configuration.
|
||||
*
|
||||
* @param discoveredTuner that has a status change.
|
||||
* @param previous tuner status
|
||||
* @param current tuner status
|
||||
*/
|
||||
@Override
|
||||
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
mTunerConfigurationManager.tunerStatusUpdated(discoveredTuner, previous, current);
|
||||
|
||||
if(previous != TunerStatus.ENABLED && current == TunerStatus.ENABLED)
|
||||
{
|
||||
discoveredTuner.start();
|
||||
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
TunerType tunerType = discoveredTuner.getTuner().getTunerType();
|
||||
|
||||
TunerConfiguration tunerConfiguration = mTunerConfigurationManager
|
||||
.getTunerConfiguration(tunerType, discoveredTuner.getId());
|
||||
|
||||
if(tunerConfiguration != null)
|
||||
{
|
||||
discoveredTuner.setTunerConfiguration(tunerConfiguration);
|
||||
mTunerConfigurationManager.saveConfigurations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a tuner that matches the name argument
|
||||
*
|
||||
* @param preferredTunerName of the tuner
|
||||
* @return named tuner or null
|
||||
*/
|
||||
public DiscoveredTuner getDiscoveredTuner(String preferredTunerName)
|
||||
{
|
||||
if(preferredTunerName != null)
|
||||
{
|
||||
for(DiscoveredTuner discoveredTuner : getDiscoveredTunerModel().getAvailableTuners())
|
||||
{
|
||||
if(discoveredTuner.isAvailable() &&
|
||||
discoveredTuner.getTuner().getPreferredName().equalsIgnoreCase(preferredTunerName))
|
||||
{
|
||||
return discoveredTuner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorted list of preferred tuner names
|
||||
*/
|
||||
public List<String> getPreferredTunerNames()
|
||||
{
|
||||
List<String> preferredNames = new ArrayList<>();
|
||||
List<DiscoveredTuner> availableTuners = getAvailableTuners();
|
||||
|
||||
for(DiscoveredTuner discoveredTuner: availableTuners)
|
||||
{
|
||||
String preferredName = discoveredTuner.getTuner().getPreferredName();
|
||||
|
||||
if(!preferredNames.contains(preferredName))
|
||||
{
|
||||
preferredNames.add(preferredName);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(preferredNames);
|
||||
|
||||
return preferredNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuners currently available for use
|
||||
*/
|
||||
public List<DiscoveredTuner> getAvailableTuners()
|
||||
{
|
||||
return mDiscoveredTunerModel.getAvailableTuners();
|
||||
}
|
||||
|
||||
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification) throws SourceException
|
||||
{
|
||||
Source retVal = null;
|
||||
|
||||
switch(config.getSourceType())
|
||||
{
|
||||
case MIXER:
|
||||
retVal = MixerManager.getSource(config);
|
||||
break;
|
||||
case TUNER:
|
||||
if(config instanceof SourceConfigTuner)
|
||||
{
|
||||
SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)config;
|
||||
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
|
||||
String preferredTuner = sourceConfigTuner.getPreferredTuner();
|
||||
retVal = getSource(tunerChannel, channelSpecification, preferredTuner);
|
||||
}
|
||||
break;
|
||||
case TUNER_MULTIPLE_FREQUENCIES:
|
||||
if(config instanceof SourceConfigTunerMultipleFrequency)
|
||||
{
|
||||
SourceConfigTunerMultipleFrequency sourceConfigTuner = (SourceConfigTunerMultipleFrequency)config;
|
||||
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
|
||||
String preferredTuner = sourceConfigTuner.getPreferredTuner();
|
||||
|
||||
Source source = getSource(tunerChannel, channelSpecification, preferredTuner);
|
||||
|
||||
if(source instanceof TunerChannelSource)
|
||||
{
|
||||
retVal = new MultiFrequencyTunerChannelSource(this, (TunerChannelSource)source,
|
||||
sourceConfigTuner.getFrequencies(), channelSpecification, sourceConfigTuner.getPreferredTuner());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates current available tuners to get a tuner channel source for the specified frequency and bandwidth
|
||||
*
|
||||
* Returns null if no tuner can source the channel
|
||||
*/
|
||||
public Source getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification, String preferredTuner)
|
||||
{
|
||||
TunerChannelSource source = null;
|
||||
|
||||
if(tunerChannel != null && channelSpecification != null)
|
||||
{
|
||||
DiscoveredTuner discoveredTuner;
|
||||
|
||||
if(preferredTuner != null)
|
||||
{
|
||||
discoveredTuner = getDiscoveredTuner(preferredTuner);
|
||||
|
||||
if(discoveredTuner != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = discoveredTuner.getTuner().getChannelSourceManager().getSource(tunerChannel, channelSpecification);
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Fall through to logger below
|
||||
}
|
||||
}
|
||||
|
||||
mLog.info("Unable to source channel [" + tunerChannel.getFrequency() + "] from preferred tuner [" +
|
||||
preferredTuner + "] - searching for another tuner");
|
||||
}
|
||||
|
||||
Iterator<DiscoveredTuner> it = mDiscoveredTunerModel.getAvailableTuners().iterator();
|
||||
|
||||
while(it.hasNext() && source == null)
|
||||
{
|
||||
discoveredTuner = it.next();
|
||||
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
try
|
||||
{
|
||||
source = discoveredTuner.getTuner().getChannelSourceManager().getSource(tunerChannel, channelSpecification);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error obtaining channel from tuner [" + discoveredTuner.getTuner().getPreferredName() + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* USB hotplug event listener to register, unregister and detect when USB devices are added or removed.
|
||||
*
|
||||
* Note: hotplug is not supported on all platforms.
|
||||
*/
|
||||
public class HotplugEventSupport implements HotplugCallback
|
||||
{
|
||||
private static final int HOTPLUG_CONTINUE_EVENT_SUPPORT = 0;
|
||||
private HotplugCallbackHandle mHotplugCallbackHandle;
|
||||
private ScheduledFuture<?> mEventProcessorFuture;
|
||||
|
||||
/**
|
||||
* LibUsb hotplug event notification
|
||||
* @param context used for hotplug event registration
|
||||
* @param device that arrived or left
|
||||
* @param event value, arrived event or left event
|
||||
* @param userData not used
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int processEvent(Context context, Device device, int event, Object userData)
|
||||
{
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
int port = LibUsb.getPortNumber(device);
|
||||
|
||||
switch(event)
|
||||
{
|
||||
case LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED:
|
||||
addUsbTuner(device);
|
||||
break;
|
||||
case LibUsb.HOTPLUG_EVENT_DEVICE_LEFT:
|
||||
removeUsbTuner(bus, port);
|
||||
break;
|
||||
}
|
||||
|
||||
return HOTPLUG_CONTINUE_EVENT_SUPPORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for hotplug event notifications, if the capability is supported on this platform and starts an
|
||||
* event processing timer to process hotplug events.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(LibUsb.hasCapability(LibUsb.CAP_HAS_HOTPLUG))
|
||||
{
|
||||
mHotplugCallbackHandle = new HotplugCallbackHandle();
|
||||
int events = LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED | LibUsb.HOTPLUG_EVENT_DEVICE_LEFT;
|
||||
int flags = LibUsb.HOTPLUG_ENUMERATE;
|
||||
int vendorId = LibUsb.HOTPLUG_MATCH_ANY;
|
||||
int productId = LibUsb.HOTPLUG_MATCH_ANY;
|
||||
int deviceClass = LibUsb.HOTPLUG_MATCH_ANY;
|
||||
|
||||
int status = LibUsb.hotplugRegisterCallback(mLibUsbApplicationContext, events, flags, vendorId,
|
||||
productId, deviceClass, this, "sdrtrunk hotplug support", mHotplugCallbackHandle);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
Runnable eventHandler = () -> LibUsb.handleEvents(mLibUsbApplicationContext);
|
||||
mEventProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(eventHandler,
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
mHotplugCallbackHandle = null;
|
||||
mLog.info("LibUsb - unable to register device hotplug listener - " + LibUsb.errorName(status));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("LibUsb Hotplug event notification Is Not Supported on this platform.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters from hotplug event notifications and stops the event processing timer
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mHotplugCallbackHandle != null)
|
||||
{
|
||||
LibUsb.hotplugDeregisterCallback(mLibUsbApplicationContext, mHotplugCallbackHandle);
|
||||
mHotplugCallbackHandle = null;
|
||||
}
|
||||
|
||||
if(mEventProcessorFuture != null)
|
||||
{
|
||||
mEventProcessorFuture.cancel(true);
|
||||
mEventProcessorFuture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
UserPreferences userPreferences = new UserPreferences();
|
||||
TunerManager tunerManager = new TunerManager(userPreferences);
|
||||
tunerManager.start();
|
||||
|
||||
while(true);
|
||||
|
||||
// mLog.info("Finished!");
|
||||
}
|
||||
}
|
||||
@@ -17,35 +17,50 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.source.tuner.usb.converter;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.List;
|
||||
package io.github.dsheirer.source.tuner.manager;
|
||||
|
||||
/**
|
||||
* Base class for native samples byte buffer. This class holds a copy of the byte array of samples
|
||||
* that was copied from a native byte buffer into the JVM heap space.
|
||||
*
|
||||
* Consumers of this sample buffer must make a copy of the contents in either interleaved or non-interleaved
|
||||
* sample array buffers.
|
||||
* Current status of a discovered tuner.
|
||||
*/
|
||||
public abstract class NativeSampleBuffer
|
||||
public enum TunerStatus
|
||||
{
|
||||
private byte[] mSamples;
|
||||
/**
|
||||
* A tuner that is enabled and usable, but not currently in-use.
|
||||
*/
|
||||
ENABLED("Enabled"),
|
||||
|
||||
public NativeSampleBuffer(byte[] samples)
|
||||
/**
|
||||
* A tuner that has been disabled or black-listed for use
|
||||
*/
|
||||
DISABLED("Disabled"),
|
||||
|
||||
/**
|
||||
* A tuner that has an error state and is unusable
|
||||
*/
|
||||
ERROR("Error");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param label
|
||||
*/
|
||||
TunerStatus(String label)
|
||||
{
|
||||
mSamples = samples;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
protected byte[] getSamples()
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mSamples;
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public abstract List<ComplexSamples> convert();
|
||||
|
||||
public abstract List<InterleavedComplexSamples> convertInterleaved();
|
||||
/**
|
||||
* Indicates if this tuner is available and can be started
|
||||
*/
|
||||
public boolean isAvailable()
|
||||
{
|
||||
return this == ENABLED;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,27 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.recording;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.source.tuner.TunerModel;
|
||||
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
@@ -54,7 +51,7 @@ public class AddRecordingTunerDialog extends JFrame
|
||||
private static final String SELECT_A_FILE = "Please select a recording file";
|
||||
private static final String LAST_FILE_BROWSE_LOCATION_KEY = "AddRecordingTunerDialog.lastBrowseLocation";
|
||||
private UserPreferences mUserPreferences;
|
||||
private TunerModel mTunerModel;
|
||||
private DiscoveredTunerModel mDiscoveredTunerModel;
|
||||
private JButton mSelectFileButton;
|
||||
private File mSelectedRecording;
|
||||
private JLabel mRecordingFileLabel;
|
||||
@@ -64,21 +61,12 @@ public class AddRecordingTunerDialog extends JFrame
|
||||
private JButton mCancelButton;
|
||||
private static final Pattern TUNER_RECORDING_PATTERN = Pattern.compile("TUNER_(\\d*)_baseband_\\d{8}_\\d{6}\\.wav");
|
||||
|
||||
public AddRecordingTunerDialog(UserPreferences userPreferences, TunerModel tunerModel)
|
||||
public AddRecordingTunerDialog(UserPreferences userPreferences, DiscoveredTunerModel discoveredTunerModel)
|
||||
{
|
||||
Validate.notNull(userPreferences, "UserPreferences cannot be null");
|
||||
Validate.notNull(tunerModel, "TunerModel cannot be null");
|
||||
Validate.notNull(discoveredTunerModel, "TunerModel cannot be null");
|
||||
|
||||
if(!tunerModel.canAddRecordingTuner())
|
||||
{
|
||||
JOptionPane.showMessageDialog(AddRecordingTunerDialog.this,
|
||||
"Cannot add a recording tuner. Only one recording tuner can be loaded at any time",
|
||||
"Cannot Add Recording Tuner",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
mTunerModel = tunerModel;
|
||||
mDiscoveredTunerModel = discoveredTunerModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
@@ -206,13 +194,13 @@ public class AddRecordingTunerDialog extends JFrame
|
||||
|
||||
try
|
||||
{
|
||||
RecordingTuner tuner = new RecordingTuner(mUserPreferences);
|
||||
mTunerModel.addTuner(tuner);
|
||||
RecordingTunerController controller = tuner.getTunerController();
|
||||
RecordingTunerConfiguration config = new RecordingTunerConfiguration();
|
||||
config.setFrequency(frequency);
|
||||
config.setPath(mSelectedRecording.getAbsolutePath());
|
||||
controller.apply(config);
|
||||
// RecordingTuner tuner = new RecordingTuner(mUserPreferences);
|
||||
// mDiscoveredTunerModel.addDiscoveredTuner(tuner);
|
||||
// RecordingTunerController controller = tuner.getTunerController();
|
||||
// RecordingTunerConfiguration config = new RecordingTunerConfiguration();
|
||||
// config.setFrequency(frequency);
|
||||
// config.setPath(mSelectedRecording.getAbsolutePath());
|
||||
// controller.apply(config);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.recording;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
@@ -43,9 +41,9 @@ public class RecordingTuner extends Tuner
|
||||
private final int mInstanceID = mInstanceCounter++;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public RecordingTuner(UserPreferences userPreferences)
|
||||
public RecordingTuner(UserPreferences userPreferences, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super("Recording Tuner", new RecordingTunerController());
|
||||
super(new RecordingTunerController(tunerErrorListener), tunerErrorListener);
|
||||
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
@@ -72,6 +70,12 @@ public class RecordingTuner extends Tuner
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
return "Recording Tuner-" + mInstanceID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tuner controller cast as a test tuner controller.
|
||||
*/
|
||||
@@ -83,7 +87,7 @@ public class RecordingTuner extends Tuner
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
return getName() + "-" + mInstanceID;
|
||||
return getPreferredName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,7 +99,7 @@ public class RecordingTuner extends Tuner
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerClass.RECORDING_TUNER.getTunerType();
|
||||
return TunerType.RECORDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.recording;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
@@ -38,18 +36,18 @@ public class RecordingTunerConfiguration extends TunerConfiguration
|
||||
//Empty jackson constructor
|
||||
}
|
||||
|
||||
public RecordingTunerConfiguration(String uniqueId, String name)
|
||||
{
|
||||
super(uniqueId, name);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.RECORDING;
|
||||
}
|
||||
|
||||
public RecordingTunerConfiguration(String uniqueId)
|
||||
{
|
||||
super(uniqueId);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "path")
|
||||
public String getPath()
|
||||
{
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 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.recording;
|
||||
|
||||
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.JLabel;
|
||||
import javax.swing.JTextField;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
|
||||
public class RecordingTunerConfigurationEditor extends TunerConfigurationEditor
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RecordingTunerConfigurationEditor.class);
|
||||
|
||||
private JTextField mConfigurationName;
|
||||
private JLabel mRecordingPath;
|
||||
private boolean mLoading;
|
||||
|
||||
private RecordingTunerController mController;
|
||||
|
||||
public RecordingTunerConfigurationEditor(TunerConfigurationModel tunerConfigurationModel, RecordingTuner tuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
|
||||
mController = tuner.getTunerController();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
setControlsEnabled(!locked);
|
||||
}
|
||||
|
||||
private RecordingTunerConfiguration getConfiguration()
|
||||
{
|
||||
if(hasItem())
|
||||
{
|
||||
return (RecordingTunerConfiguration)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("Recording 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, wrap");
|
||||
|
||||
add(new JLabel("File:"));
|
||||
mRecordingPath = new JLabel();
|
||||
add(mRecordingPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets each of the tuner configuration controls to the enabled argument state
|
||||
*/
|
||||
private void setControlsEnabled(boolean enabled)
|
||||
{
|
||||
if(mConfigurationName.isEnabled() != enabled)
|
||||
{
|
||||
mConfigurationName.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@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())
|
||||
{
|
||||
RecordingTunerConfiguration config = getConfiguration();
|
||||
|
||||
setControlsEnabled(tunerConfiguration.isAssigned());
|
||||
mConfigurationName.setText(config.getName());
|
||||
mRecordingPath.setText(config.getPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText("");
|
||||
mRecordingPath.setText("");
|
||||
}
|
||||
|
||||
mLoading = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
{
|
||||
RecordingTunerConfiguration config = getConfiguration();
|
||||
|
||||
config.setName(mConfigurationName.getText());
|
||||
|
||||
String path = mRecordingPath.getText();
|
||||
|
||||
if(path != null && !path.isEmpty())
|
||||
{
|
||||
config.setPath(path);
|
||||
|
||||
mLog.debug("Saving and setting controller with path: " + path);
|
||||
try
|
||||
{
|
||||
mController.apply(config);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error while applying recording tuner configuration", se);
|
||||
}
|
||||
}
|
||||
|
||||
getTunerConfigurationModel().broadcast(
|
||||
new TunerConfigurationEvent(getConfiguration(), TunerConfigurationEvent.Event.CHANGE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
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.wave.ComplexWaveSource;
|
||||
import org.slf4j.Logger;
|
||||
@@ -44,10 +46,33 @@ public class RecordingTunerController extends TunerController
|
||||
|
||||
/**
|
||||
* Tuner controller testing implementation.
|
||||
* @param tunerErrorListener to receive errors from this controller
|
||||
*/
|
||||
public RecordingTunerController()
|
||||
public RecordingTunerController(ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(1000000l, 3000000000l, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH_PERCENTAGE);
|
||||
super(tunerErrorListener);
|
||||
setMinimumFrequency(1000000l);
|
||||
setMaximumFrequency(3000000000l);
|
||||
setMiddleUnusableHalfBandwidth(DC_NOISE_BANDWIDTH);
|
||||
setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENTAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws SourceException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.RECORDING;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,14 +140,6 @@ public class RecordingTunerController extends TunerController
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
@@ -154,10 +171,11 @@ public class RecordingTunerController extends TunerController
|
||||
@Override
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
if(config instanceof RecordingTunerConfiguration)
|
||||
{
|
||||
RecordingTunerConfiguration rtc = (RecordingTunerConfiguration)config;
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(config);
|
||||
|
||||
if(config instanceof RecordingTunerConfiguration rtc)
|
||||
{
|
||||
mCenterFrequency = rtc.getFrequency();
|
||||
|
||||
try
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.recording;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
/**
|
||||
* Recording tuner configuration editor
|
||||
*/
|
||||
public class RecordingTunerEditor extends TunerEditor<RecordingTuner,RecordingTunerConfiguration>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RecordingTunerEditor.class);
|
||||
private JLabel mRecordingPath;
|
||||
private boolean mLoading;
|
||||
|
||||
private RecordingTunerController mController;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager for saving configurations
|
||||
* @param discoveredTuner for this configuration
|
||||
*/
|
||||
public RecordingTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
// mController = discoveredTuner.getTunerController();
|
||||
// init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
super.setTunerLockState(locked);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill]",
|
||||
"[][][grow]"));
|
||||
add(new JLabel("Recording Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("File:"));
|
||||
mRecordingPath = new JLabel();
|
||||
add(mRecordingPath);
|
||||
}
|
||||
|
||||
// @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())
|
||||
// {
|
||||
// RecordingTunerConfiguration config = getConfiguration();
|
||||
//
|
||||
// mRecordingPath.setText(config.getPath());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mRecordingPath.setText("");
|
||||
// }
|
||||
//
|
||||
// mLoading = false;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasConfiguration() && !mLoading)
|
||||
{
|
||||
RecordingTunerConfiguration config = getConfiguration();
|
||||
String path = mRecordingPath.getText();
|
||||
|
||||
if(path != null && !path.isEmpty())
|
||||
{
|
||||
config.setPath(path);
|
||||
|
||||
try
|
||||
{
|
||||
mController.apply(config);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error while applying recording tuner configuration", se);
|
||||
}
|
||||
}
|
||||
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import javax.usb.UsbException;
|
||||
|
||||
/**
|
||||
* Tuner embedded in an RTL-2832 USB tuner.
|
||||
*
|
||||
* Note: since the RTL2832TunerController instance is constructed without knowing the embedded tuner type, we construct
|
||||
* an instance of the actual tuner once the type is discovered and we use an adapter to access the non-public USB
|
||||
* controls and interfaces needed by the embedded tuner. This access is through the RtlTunerControllerAdapter.
|
||||
*/
|
||||
public abstract class EmbeddedTuner
|
||||
{
|
||||
private RTL2832TunerController.ControllerAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param adapter exposing the interfaces of the parent RTL2832TunerController
|
||||
*/
|
||||
public EmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
|
||||
{
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the interfaces of the parent RTL tuner controller via the adapter
|
||||
* @return
|
||||
*/
|
||||
protected RTL2832TunerController.ControllerAdapter getAdapter()
|
||||
{
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this embedded tuner. The calling method should ensure that the I2C repeater is enabled before
|
||||
* calling this method.
|
||||
*/
|
||||
protected abstract void initTuner() throws UsbException;
|
||||
|
||||
/**
|
||||
* Type of embedded tuner
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Apply the tuner configuration settings to this embedded tuner
|
||||
* @param tunerConfig containing settings
|
||||
* @throws SourceException if there is an error in applying the settings
|
||||
*/
|
||||
public abstract void apply(TunerConfiguration tunerConfig) throws SourceException;
|
||||
|
||||
/**
|
||||
* Minimum frequency supported by this tuner
|
||||
*/
|
||||
public abstract long getMinimumFrequencySupported();
|
||||
|
||||
/**
|
||||
* Maximum frequency supported by this tuner
|
||||
*/
|
||||
public abstract long getMaximumFrequencySupported();
|
||||
|
||||
/**
|
||||
* Bandwidth of the DC spike that is unusable for this tuner
|
||||
*/
|
||||
//TODO: can we get rid of this now?
|
||||
public abstract int getDcSpikeHalfBandwidth();
|
||||
|
||||
/**
|
||||
* Percentage of the bandwidth that is usable for this tuner
|
||||
*/
|
||||
public abstract double getUsableBandwidthPercent();
|
||||
|
||||
/**
|
||||
* Sets the center tuned frequency
|
||||
* @param frequency in Hertz
|
||||
* @throws SourceException if there is an error
|
||||
*/
|
||||
public abstract void setTunedFrequency(long frequency) throws SourceException;
|
||||
|
||||
/**
|
||||
* Sets the sample mode for the tuner
|
||||
* @param mode to set
|
||||
* @throws LibUsbException
|
||||
*/
|
||||
public abstract void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException;
|
||||
|
||||
/**
|
||||
* Sets the filters according to the specified sample rate
|
||||
* @param sampleRate for filter setup
|
||||
* @throws SourceException if there is an error
|
||||
*/
|
||||
public abstract void setSampleRateFilters(int sampleRate) throws SourceException;
|
||||
}
|
||||
@@ -1,47 +1,49 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* RTL-2832 Tuner
|
||||
*/
|
||||
public class RTL2832Tuner extends Tuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RTL2832Tuner.class);
|
||||
|
||||
private TunerClass mTunerClass;
|
||||
|
||||
public RTL2832Tuner(TunerClass tunerClass, RTL2832TunerController controller, UserPreferences userPreferences)
|
||||
throws SourceException
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller tuner
|
||||
* @param channelizerType for the channelizer
|
||||
* @param tunerErrorListener to listen for errors from this tuner
|
||||
* @throws SourceException if there is an issue
|
||||
*/
|
||||
public RTL2832Tuner(RTL2832TunerController controller, ITunerErrorListener tunerErrorListener,
|
||||
ChannelizerType channelizerType)
|
||||
{
|
||||
super(tunerClass.getVendorDeviceLabel() + "/" + controller.getTunerType().getLabel() + " " +
|
||||
controller.getUniqueID(), controller, userPreferences);
|
||||
|
||||
mTunerClass = tunerClass;
|
||||
super(controller, tunerErrorListener, channelizerType);
|
||||
}
|
||||
|
||||
public RTL2832TunerController getController()
|
||||
@@ -49,6 +51,12 @@ public class RTL2832Tuner extends Tuner
|
||||
return (RTL2832TunerController)getTunerController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
return getUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
@@ -58,13 +66,7 @@ public class RTL2832Tuner extends Tuner
|
||||
@Override
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return mTunerClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return getController().getTunerType();
|
||||
return TunerClass.RTL2832;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
|
||||
|
||||
/**
|
||||
* RTL2832 tuner configuration
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
||||
})
|
||||
@JacksonXmlRootElement(localName = "tuner_configuration")
|
||||
public abstract class RTL2832TunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
private RTL2832TunerController.SampleRate mSampleRate = RTL2832TunerController.SampleRate.RATE_2_400MHZ;
|
||||
|
||||
/**
|
||||
* Default constructor to support Jackson
|
||||
*/
|
||||
public RTL2832TunerConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
public RTL2832TunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "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
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JSeparator;
|
||||
|
||||
/**
|
||||
* Tuner editor for RTL2832 tuner that has not been started, or for an unknown tuner type
|
||||
*/
|
||||
public class RTL2832UnknownTunerEditor extends TunerEditor<RTL2832Tuner, RTL2832TunerConfiguration>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param userPreferences for starting wide-band recorders
|
||||
* @param tunerManager for requesting configuration saves.
|
||||
* @param discoveredTuner that is not started, or that doesn't have a recognized tuner type
|
||||
*/
|
||||
public RTL2832UnknownTunerEditor(UserPreferences userPreferences, TunerManager tunerManager,
|
||||
DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel(), "wrap");
|
||||
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
// add(new JLabel("Frequency (MHz):"));
|
||||
// add(getFrequencyPanel(), "wrap");
|
||||
//
|
||||
// add(new JLabel("Sample Rate:"));
|
||||
//// add(getSampleRateCombo(), "wrap");
|
||||
//
|
||||
// add(new JSeparator(), "span,growx,push");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void save()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId() + (hasTuner() ? " ID:" + getTuner().getUniqueID() : ""));
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
super.setTunerLockState(locked);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +1,41 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
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 io.github.dsheirer.source.tuner.rtl.RTL2832TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KLNAGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.IFGain;
|
||||
|
||||
public class E4KTunerConfiguration extends TunerConfiguration
|
||||
/**
|
||||
* RTL-2832 with embedded E4000 tuner configuration
|
||||
*/
|
||||
public class E4KTunerConfiguration extends RTL2832TunerConfiguration
|
||||
{
|
||||
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;
|
||||
private IFGain mIFGain = IFGain.LINEARITY_8;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
@@ -42,18 +44,23 @@ public class E4KTunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
public E4KTunerConfiguration(String uniqueID, String name)
|
||||
{
|
||||
super(uniqueID, name);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.ELONICS_E4000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param uniqueID for the tuner
|
||||
*/
|
||||
public E4KTunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "master_gain")
|
||||
public E4KGain getMasterGain()
|
||||
{
|
||||
@@ -87,37 +94,14 @@ public class E4KTunerConfiguration extends TunerConfiguration
|
||||
mLNAGain = lnaGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "enhance_gain")
|
||||
public E4KEnhanceGain getEnhanceGain()
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "if_gain")
|
||||
public IFGain getIFGain()
|
||||
{
|
||||
return mEnhanceGain;
|
||||
return mIFGain;
|
||||
}
|
||||
|
||||
public void setEnhanceGain(E4KEnhanceGain enhanceGain)
|
||||
public void setIFGain(IFGain ifGain)
|
||||
{
|
||||
mEnhanceGain = enhanceGain;
|
||||
mIFGain = ifGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public RTL2832TunerController.SampleRate getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(RTL2832TunerController.SampleRate sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.preference.UserPreferences;
|
||||
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.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||
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 io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KLNAGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,51 +39,47 @@ import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
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
|
||||
/**
|
||||
* E4000 tuner editor
|
||||
*/
|
||||
public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(E4KTunerEditor.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private JButton mTunerInfoButton;
|
||||
private JComboBox<SampleRate> mSampleRateCombo;
|
||||
private JComboBox<E4KGain> mMasterGainCombo;
|
||||
private JComboBox<E4KMixerGain> mMixerGainCombo;
|
||||
private JComboBox<E4KLNAGain> mLNAGainCombo;
|
||||
private JComboBox<E4KEmbeddedTuner.IFGain> mIfGainCombo;
|
||||
|
||||
private JTextField mConfigurationName;
|
||||
private JButton mTunerInfo;
|
||||
private JComboBox<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)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager to save configuration
|
||||
* @param discoveredTuner to control
|
||||
*/
|
||||
public E4KTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
mController = (E4KTunerController)tuner.getController();
|
||||
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private E4KTunerConfiguration getConfiguration()
|
||||
/**
|
||||
* Access to the E4000 embedded tuner
|
||||
* @return E4000 tuner if there is a tuner, or null otherwise
|
||||
*/
|
||||
public E4KEmbeddedTuner getEmbeddedTuner()
|
||||
{
|
||||
if(hasItem())
|
||||
if(hasTuner())
|
||||
{
|
||||
return (E4KTunerConfiguration)getItem();
|
||||
return (E4KEmbeddedTuner)getTuner().getController().getEmbeddedTuner();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -89,336 +87,328 @@ public class E4KTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][][][grow]"));
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("E4000 Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
/**
|
||||
* Tuner Configuration Name
|
||||
*/
|
||||
add(new JLabel("Name:"));
|
||||
mConfigurationName = new JTextField();
|
||||
mConfigurationName.setEnabled(false);
|
||||
mConfigurationName.addFocusListener(new FocusListener()
|
||||
{
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(mConfigurationName, "span 2");
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
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);
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "span 2");
|
||||
|
||||
mComboSampleRate = new JComboBox<>(SampleRate.values());
|
||||
mComboSampleRate.setEnabled(false);
|
||||
mComboSampleRate.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
SampleRate sampleRate = (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);
|
||||
add(getSampleRateCombo(), "wrap");
|
||||
|
||||
SpinnerModel model = new SpinnerNumberModel(0.0, //initial value
|
||||
-1000.0, //min
|
||||
1000.0, //max
|
||||
0.1); //step
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
add(new JLabel("Mixer/LNA Gain Control"), "wrap");
|
||||
|
||||
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);
|
||||
add(getMasterGainCombo(), "wrap");
|
||||
|
||||
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);
|
||||
add(getMixerGainCombo(), "wrap");
|
||||
|
||||
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);
|
||||
add(getLNAGainCombo(), "wrap");
|
||||
|
||||
mComboEnhanceGain = new JComboBox<E4KEnhanceGain>(E4KEnhanceGain.values());
|
||||
mComboEnhanceGain.addActionListener(new ActionListener()
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
add(new JLabel("IF Gain:"));
|
||||
add(getIfGainCombo(), "wrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
@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());
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
getTunerStatusLabel().setText(getDiscoveredTuner().getTunerStatus().toString());
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
|
||||
mLog.error("E4K Tuner Controller - couldn't apply enhance gain setting", e);
|
||||
}
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerInfoButton().setEnabled(true);
|
||||
getSampleRateCombo().setEnabled(true);
|
||||
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
|
||||
getMasterGainCombo().setEnabled(true);
|
||||
getIfGainCombo().setEnabled(true);
|
||||
getIfGainCombo().setSelectedItem(getConfiguration().getIFGain());
|
||||
|
||||
E4KGain gain = getConfiguration().getMasterGain();
|
||||
getMasterGainCombo().setEnabled(true);
|
||||
getMasterGainCombo().setSelectedItem(gain);
|
||||
|
||||
if(gain == E4KGain.MANUAL)
|
||||
{
|
||||
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
|
||||
getMixerGainCombo().setEnabled(true);
|
||||
|
||||
getLNAGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
||||
getLNAGainCombo().setEnabled(true);
|
||||
}
|
||||
});
|
||||
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);
|
||||
else
|
||||
{
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
|
||||
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setSelectedItem(gain.getLNAGain());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerInfoButton().setEnabled(false);
|
||||
getSampleRateCombo().setEnabled(false);
|
||||
getMasterGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getIfGainCombo().setEnabled(false);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
private JComboBox getIfGainCombo()
|
||||
{
|
||||
if(mIfGainCombo == null)
|
||||
{
|
||||
mIfGainCombo = new JComboBox<>(E4KEmbeddedTuner.IFGain.values());
|
||||
mIfGainCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
E4KEmbeddedTuner.IFGain selected = (E4KEmbeddedTuner.IFGain) getIfGainCombo().getSelectedItem();
|
||||
getEmbeddedTuner().setIFGain(selected, true);
|
||||
save();
|
||||
}
|
||||
catch(LibUsbException lue)
|
||||
{
|
||||
JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - "
|
||||
+ "couldn't apply the IF setting - " + lue.getLocalizedMessage());
|
||||
mLog.error("E4000 Tuner Controller - couldn't apply IF gain setting", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
mIfGainCombo.setToolTipText("Linear IF Gain");
|
||||
mIfGainCombo.setEnabled(true);
|
||||
}
|
||||
|
||||
return mIfGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getLNAGainCombo()
|
||||
{
|
||||
if(mLNAGainCombo == null)
|
||||
{
|
||||
mLNAGainCombo = new JComboBox<>(E4KLNAGain.values());
|
||||
mLNAGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
E4KLNAGain lnaGain = (E4KLNAGain) mLNAGainCombo.getSelectedItem();
|
||||
getEmbeddedTuner().setLNAGain(lnaGain, true);
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - "
|
||||
+ "couldn't apply the LNA gain setting - " + e.getLocalizedMessage());
|
||||
mLog.error("E4000 Tuner Controller - couldn't apply LNA gain setting - ", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
mLNAGainCombo.setToolTipText("<html>LNA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
|
||||
mLNAGainCombo.setEnabled(false);
|
||||
}
|
||||
|
||||
return mLNAGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getMixerGainCombo()
|
||||
{
|
||||
if(mMixerGainCombo == null)
|
||||
{
|
||||
mMixerGainCombo = new JComboBox<>(E4KMixerGain.values());
|
||||
mMixerGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
E4KMixerGain mixerGain = (E4KMixerGain) mMixerGainCombo.getSelectedItem();
|
||||
getEmbeddedTuner().setMixerGain(mixerGain, true);
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - "
|
||||
+ "couldn't apply the mixer gain setting - " + e.getLocalizedMessage());
|
||||
mLog.error("E4000 Tuner Controller - couldn't apply mixer gain setting", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
mMixerGainCombo.setToolTipText("<html>Mixer Gain. Set master gain to <b>MASTER</b> to enable adjustment</html>");
|
||||
mMixerGainCombo.setEnabled(false);
|
||||
}
|
||||
|
||||
return mMixerGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getMasterGainCombo()
|
||||
{
|
||||
if(mMasterGainCombo == null)
|
||||
{
|
||||
mMasterGainCombo = new JComboBox<>(E4KGain.values());
|
||||
mMasterGainCombo.setEnabled(false);
|
||||
mMasterGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
E4KGain gain = (E4KGain) mMasterGainCombo.getSelectedItem();
|
||||
getEmbeddedTuner().setGain((E4KGain) mMasterGainCombo.getSelectedItem(), true);
|
||||
if(gain == E4KGain.MANUAL)
|
||||
{
|
||||
getMixerGainCombo().setSelectedItem(getEmbeddedTuner().getMixerGain(true));
|
||||
getMixerGainCombo().setEnabled(true);
|
||||
getLNAGainCombo().setSelectedItem(getEmbeddedTuner().getLNAGain(true));
|
||||
getLNAGainCombo().setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setSelectedItem(gain.getLNAGain());
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(E4KTunerEditor.this, "E4000 Tuner Controller - "
|
||||
+ "couldn't apply the gain setting - " + e.getLocalizedMessage());
|
||||
mLog.error("E4000 Tuner Controller - couldn't apply gain setting", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mMasterGainCombo.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>");
|
||||
}
|
||||
|
||||
return mMasterGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getSampleRateCombo()
|
||||
{
|
||||
if(mSampleRateCombo == null)
|
||||
{
|
||||
mSampleRateCombo = new JComboBox<>(SampleRate.values());
|
||||
mSampleRateCombo.setEnabled(false);
|
||||
mSampleRateCombo.addActionListener(e ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
SampleRate sampleRate = (SampleRate) mSampleRateCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().setSampleRate(sampleRate);
|
||||
save();
|
||||
}
|
||||
catch(SourceException | LibUsbException eSampleRate)
|
||||
{
|
||||
JOptionPane.showMessageDialog(E4KTunerEditor.this,
|
||||
"E4000 Tuner Controller - couldn't apply the sample rate setting [" +
|
||||
sampleRate.getLabel() + "] " + eSampleRate.getLocalizedMessage());
|
||||
|
||||
mLog.error("E4000 Tuner Controller - couldn't apply sample rate setting [" +
|
||||
sampleRate.getLabel() + "]", eSampleRate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSampleRateCombo;
|
||||
}
|
||||
|
||||
private JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
mTunerInfoButton = new JButton("Tuner Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(E4KTunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sample rate tooltip according to the tuner controller's lock state.
|
||||
*/
|
||||
/**
|
||||
* Updates the sample rate tooltip according to the tuner controller's lock state.
|
||||
*/
|
||||
private void updateSampleRateToolTip()
|
||||
{
|
||||
if(mController.isLocked())
|
||||
if(hasTuner() && getTuner().getTunerController().isLocked())
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
}
|
||||
else if(hasTuner())
|
||||
{
|
||||
getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Select a sample rate for the tuner");
|
||||
getSampleRateCombo().setToolTipText("No tuner available");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
mComboSampleRate.setEnabled(!locked);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
if(mController.isLocked())
|
||||
{
|
||||
mComboSampleRate.setEnabled(false);
|
||||
}
|
||||
else 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);
|
||||
}
|
||||
super.setTunerLockState(locked);
|
||||
mSampleRateCombo.setEnabled(!locked);
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
RTL2832TunerController.Descriptor descriptor = mController.getDescriptor();
|
||||
RTL2832TunerController.Descriptor descriptor = getTuner().getController().getDescriptor();
|
||||
|
||||
sb.append("<html><h3>RTL-2832 with E4000 Tuner</h3>");
|
||||
|
||||
@@ -461,68 +451,20 @@ public class E4KTunerEditor extends TunerConfigurationEditor
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
E4KTunerConfiguration config = getConfiguration();
|
||||
|
||||
config.setName(mConfigurationName.getText());
|
||||
|
||||
config.setSampleRate((SampleRate)mComboSampleRate.getSelectedItem());
|
||||
|
||||
double value = ((SpinnerNumberModel)mFrequencyCorrection
|
||||
.getModel()).getNumber().doubleValue();
|
||||
config.setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
config.setFrequencyCorrection(value);
|
||||
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
|
||||
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));
|
||||
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
|
||||
config.setMasterGain((E4KGain)getMasterGainCombo().getSelectedItem());
|
||||
config.setMixerGain((E4KMixerGain)getMixerGainCombo().getSelectedItem());
|
||||
config.setLNAGain((E4KLNAGain)getLNAGainCombo().getSelectedItem());
|
||||
config.setIFGain((E4KEmbeddedTuner.IFGain)getIfGainCombo().getSelectedItem());
|
||||
saveConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
//Update enabled state to reflect when frequency and sample rate controls are locked
|
||||
mComboSampleRate.setEnabled(!mController.isLocked());
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText(config.getName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText("");
|
||||
}
|
||||
|
||||
mLoading = false;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* Java port of librtlsdr <https://github.com/steve-m/librtlsdr>
|
||||
* 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.
|
||||
*
|
||||
* Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com>
|
||||
* Copyright (C) 2013 Steve Markgraf <steve@steve-m.de>
|
||||
* 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.
|
||||
*
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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.FrequencyErrorCorrectionManager;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.rtl.EmbeddedTuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import javax.usb.UsbException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class R820TTunerController extends RTL2832TunerController
|
||||
public class R820TEmbeddedTuner extends EmbeddedTuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(R820TTunerController.class);
|
||||
|
||||
public static final long MIN_FREQUENCY = 3180000;
|
||||
public static final long MAX_FREQUENCY = 1782030000;
|
||||
public static final double USABLE_BANDWIDTH_PERCENT = 0.98;
|
||||
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
||||
|
||||
public static final int R820T_IF_FREQUENCY = 3570000;
|
||||
public static final byte VERSION = (byte) 49;
|
||||
|
||||
public static final byte[] BIT_REV_LOOKUP_TABLE =
|
||||
{(byte) 0x0, (byte) 0x8, (byte) 0x4, (byte) 0xC,
|
||||
(byte) 0x2, (byte) 0xA, (byte) 0x6, (byte) 0xE,
|
||||
(byte) 0x1, (byte) 0x9, (byte) 0x5, (byte) 0xD,
|
||||
(byte) 0x3, (byte) 0xB, (byte) 0x7, (byte) 0xF};
|
||||
|
||||
/* R820T I2C address */
|
||||
private byte mI2CAddress = (byte) 0x34;
|
||||
|
||||
/* Shadow register is used to keep a cached (in-memory) copy of all
|
||||
* registers, so that we don't have to read a full byte from a register in
|
||||
* order to apply a masked value and then re-write the full byte. With the
|
||||
* shadow register, we can apply the masked value to the cached value, and
|
||||
* then just write the masked byte, skipping the need to read the byte
|
||||
* first. */
|
||||
private int[] mShadowRegister =
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x32, 0x75,
|
||||
0xC0, 0x40, 0xD6, 0x6C, 0xF5, 0x63, 0x75, 0x68,
|
||||
0x6C, 0x83, 0x80, 0x00, 0x0F, 0x00, 0xC0, 0x30,
|
||||
0x48, 0xCC, 0x60, 0x00, 0x54, 0xAE, 0x4A, 0xC0};
|
||||
|
||||
public FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
|
||||
|
||||
public R820TTunerController(Device device, DeviceDescriptor deviceDescriptor) throws SourceException
|
||||
{
|
||||
super(device, deviceDescriptor, MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER,
|
||||
USABLE_BANDWIDTH_PERCENT);
|
||||
|
||||
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this);
|
||||
}
|
||||
private final static Logger mLog = LoggerFactory.getLogger(R820TEmbeddedTuner.class);
|
||||
private static final long MINIMUM_SUPPORTED_FREQUENCY = 3180000;
|
||||
private static final long MAXIMUM_SUPPORTED_FREQUENCY = 1782030000;
|
||||
private static final double USABLE_BANDWIDTH_PERCENT = 0.98;
|
||||
private static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
||||
private static final int R820T_IF_FREQUENCY = 3570000;
|
||||
private static final byte VERSION = (byte) 49;
|
||||
private static final byte I2C_ADDRESS = (byte) 0x34;
|
||||
public static final byte[] BIT_REV_LOOKUP_TABLE = {(byte) 0x0, (byte) 0x8, (byte) 0x4, (byte) 0xC, (byte) 0x2,
|
||||
(byte) 0xA, (byte) 0x6, (byte) 0xE, (byte) 0x1, (byte) 0x9, (byte) 0x5, (byte) 0xD, (byte) 0x3, (byte) 0xB,
|
||||
(byte) 0x7, (byte) 0xF};
|
||||
|
||||
/**
|
||||
* Manager for applying automatic frequency error PPM adjustments to the tuner controller based on
|
||||
* frequency error measurements received from certain downstream decoders (e.g. P25).
|
||||
* @return manager
|
||||
* Shadow register is used to keep a cached (in-memory) copy of all registers, so that we don't have to read a
|
||||
* full byte from a register in order to apply a masked value and then re-write the full byte. With the shadow
|
||||
* register, we can apply the masked value to the cached value, and then just write the masked byte, skipping the
|
||||
* need to read the byte first.
|
||||
*/
|
||||
public FrequencyErrorCorrectionManager getFrequencyErrorCorrectionManager()
|
||||
{
|
||||
return mFrequencyErrorCorrectionManager;
|
||||
}
|
||||
private int[] mShadowRegister = {0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x32, 0x75, 0xC0, 0x40, 0xD6, 0x6C, 0xF5, 0x63,
|
||||
0x75, 0x68, 0x6C, 0x83, 0x80, 0x00, 0x0F, 0x00, 0xC0, 0x30, 0x48, 0xCC, 0x60, 0x00, 0x54, 0xAE, 0x4A, 0xC0};
|
||||
|
||||
|
||||
/**
|
||||
* Overrides updates for measured frequency error so that the updates can also be applied to the
|
||||
* frequency error correction manager for automatic PPM updating.
|
||||
* @param measuredFrequencyError in hertz averaged over a 5 second interval.
|
||||
* Constructs an instance
|
||||
* @param adapter for accessing RTL2832USBController interfaces
|
||||
*/
|
||||
@Override
|
||||
public void setMeasuredFrequencyError(int measuredFrequencyError)
|
||||
public R820TEmbeddedTuner(RTL2832TunerController.ControllerAdapter adapter)
|
||||
{
|
||||
super.setMeasuredFrequencyError(measuredFrequencyError);
|
||||
getFrequencyErrorCorrectionManager().updatePPM(getPPMFrequencyError());
|
||||
super(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,102 +70,92 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRateFilters(int sampleRate) throws LibUsbException
|
||||
public long getMinimumFrequencySupported()
|
||||
{
|
||||
//TODO: why is this being forced as an abstract method on sub classes?
|
||||
return MINIMUM_SUPPORTED_FREQUENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximumFrequencySupported()
|
||||
{
|
||||
return MAXIMUM_SUPPORTED_FREQUENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDcSpikeHalfBandwidth()
|
||||
{
|
||||
return DC_SPIKE_AVOID_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getUsableBandwidthPercent()
|
||||
{
|
||||
return USABLE_BANDWIDTH_PERCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRateFilters(int sampleRate) throws SourceException
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the tuner configuration values to this embedded tuner
|
||||
* @param tunerConfig containing settings to apply
|
||||
* @throws SourceException if there is an error
|
||||
*/
|
||||
@Override
|
||||
public void apply(TunerConfiguration tunerConfig) throws SourceException
|
||||
{
|
||||
if(tunerConfig != null &&
|
||||
tunerConfig instanceof R820TTunerConfiguration)
|
||||
{
|
||||
R820TTunerConfiguration config = (R820TTunerConfiguration) tunerConfig;
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
|
||||
if(tunerConfig instanceof R820TTunerConfiguration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
SampleRate sampleRate = config.getSampleRate();
|
||||
setSampleRate(sampleRate);
|
||||
|
||||
double correction = config.getFrequencyCorrection();
|
||||
setFrequencyCorrection(correction);
|
||||
|
||||
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
|
||||
|
||||
R820TGain masterGain = config.getMasterGain();
|
||||
setGain(masterGain, true);
|
||||
|
||||
if(masterGain == R820TGain.MANUAL)
|
||||
{
|
||||
R820TMixerGain mixerGain = config.getMixerGain();
|
||||
setMixerGain(mixerGain, true);
|
||||
|
||||
R820TLNAGain lnaGain = config.getLNAGain();
|
||||
setLNAGain(lnaGain, true);
|
||||
|
||||
R820TVGAGain vgaGain = config.getVGAGain();
|
||||
setVGAGain(vgaGain, true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(config.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
//Do nothing, we couldn't set the frequency
|
||||
setLNAGain(config.getLNAGain(), true);
|
||||
setMixerGain(config.getMixerGain(), true);
|
||||
setVGAGain(config.getVGAGain(), true);
|
||||
}
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
throw new SourceException("R820TTunerController - usb error "
|
||||
+ "while applying tuner config", e);
|
||||
throw new SourceException("R820TTunerController - usb error while applying tuner config", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented.
|
||||
*/
|
||||
@Override
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the center frequency. Setting the frequency is a two-part process
|
||||
* of setting the multiplexer and then setting the Oscillator (PLL).
|
||||
*/
|
||||
@Override
|
||||
public void setTunedFrequency(long frequency) throws SourceException
|
||||
public synchronized void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
mLock.lock();
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
enableI2CRepeater(mDeviceHandle, true);
|
||||
|
||||
getAdapter().enableI2CRepeater();
|
||||
boolean controlI2C = false;
|
||||
|
||||
long offsetFrequency = frequency + R820T_IF_FREQUENCY;
|
||||
|
||||
setMux(offsetFrequency, controlI2C);
|
||||
|
||||
setPLL(offsetFrequency, controlI2C);
|
||||
|
||||
enableI2CRepeater(mDeviceHandle, false);
|
||||
getAdapter().disableI2CRepeater();
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
throw new SourceException("R820TTunerController - exception "
|
||||
+ "while setting frequency [" + frequency + "] - " +
|
||||
e.getLocalizedMessage());
|
||||
throw new SourceException("R820TTunerController - exception while setting frequency [" + frequency + "] - " +
|
||||
e.getLocalizedMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,31 +163,18 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
* Overrides the same method from the RTL2832 tuner controller to apply
|
||||
* settings specific to the R820T tuner.
|
||||
*/
|
||||
public void setSamplingMode(SampleMode mode) throws LibUsbException
|
||||
public void setSamplingMode(RTL2832TunerController.SampleMode mode) throws LibUsbException
|
||||
{
|
||||
switch(mode)
|
||||
if(mode == RTL2832TunerController.SampleMode.QUADRATURE)
|
||||
{
|
||||
case QUADRATURE:
|
||||
/* Set intermediate frequency to R820T IF frequency */
|
||||
setIFFrequency(R820T_IF_FREQUENCY);
|
||||
/* Set intermediate frequency to R820T IF frequency */
|
||||
getAdapter().setIFFrequency(R820T_IF_FREQUENCY);
|
||||
|
||||
/* Enable spectrum inversion */
|
||||
writeDemodRegister(mDeviceHandle,
|
||||
Page.ONE,
|
||||
(short) 0x15,
|
||||
(short) 0x01,
|
||||
1);
|
||||
|
||||
/* Set default i/q path */
|
||||
writeDemodRegister(mDeviceHandle,
|
||||
Page.ZERO,
|
||||
(short) 0x06,
|
||||
(short) 0x80,
|
||||
1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
/* Enable spectrum inversion */
|
||||
getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0x15, (short) 0x01, 1);
|
||||
|
||||
/* Set default i/q path */
|
||||
getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x06, (short) 0x80, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,8 +195,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
writeR820TRegister(Register.TF_BAND, range.getTFC(), controlI2C);
|
||||
|
||||
/* XTAL CAP & Drive */
|
||||
writeR820TRegister(Register.PLL_XTAL_CAPACITOR_AND_DRIVE,
|
||||
range.getXTALHighCap0P(), controlI2C);
|
||||
writeR820TRegister(Register.PLL_XTAL_CAPACITOR_AND_DRIVE, range.getXTALHighCap0P(), controlI2C);
|
||||
|
||||
/* Register 8 - what is it? */
|
||||
writeR820TRegister(Register.UNKNOWN_REGISTER_8, (byte) 0x00, controlI2C);
|
||||
@@ -267,86 +204,25 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
writeR820TRegister(Register.UNKNOWN_REGISTER_9, (byte) 0x00, controlI2C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the tuner for use.
|
||||
*/
|
||||
public void init() throws SourceException
|
||||
{
|
||||
/* Initialize the super class to open and claim the usb interface*/
|
||||
super.init();
|
||||
|
||||
try
|
||||
{
|
||||
initBaseband(mDeviceHandle);
|
||||
|
||||
enableI2CRepeater(mDeviceHandle, true);
|
||||
|
||||
boolean i2CRepeaterControl = false;
|
||||
|
||||
initTuner(i2CRepeaterControl);
|
||||
|
||||
enableI2CRepeater(mDeviceHandle, false);
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
throw new SourceException("error during init()", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mLog.info("Releasing R820T Tuner");
|
||||
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LibUsb.close(mDeviceHandle);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error while closing device handle", e);
|
||||
}
|
||||
|
||||
mDeviceHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the tuner section.
|
||||
*/
|
||||
public void initTuner(boolean controlI2C) throws UsbException
|
||||
protected void initTuner() throws UsbException
|
||||
{
|
||||
/* Disable zero IF mode */
|
||||
writeDemodRegister(mDeviceHandle,
|
||||
Page.ONE,
|
||||
(short) 0xB1,
|
||||
(short) 0x1A,
|
||||
1);
|
||||
getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0xB1, (short) 0x1A, 1);
|
||||
|
||||
/* Only enable in-phase ADC input */
|
||||
writeDemodRegister(mDeviceHandle,
|
||||
Page.ZERO,
|
||||
(short) 0x08,
|
||||
(short) 0x4D,
|
||||
1);
|
||||
getAdapter().writeDemodRegister(RTL2832TunerController.Page.ZERO, (short) 0x08, (short) 0x4D, 1);
|
||||
|
||||
/* Set intermediate frequency to R820T IF frequency (3.57 MHz) */
|
||||
setIFFrequency(R820T_IF_FREQUENCY);
|
||||
getAdapter().setIFFrequency(R820T_IF_FREQUENCY);
|
||||
|
||||
/* Enable spectrum inversion */
|
||||
writeDemodRegister(mDeviceHandle,
|
||||
Page.ONE,
|
||||
(short) 0x15,
|
||||
(short) 0x01,
|
||||
1);
|
||||
|
||||
initializeRegisters(controlI2C);
|
||||
|
||||
setTVStandard(controlI2C);
|
||||
|
||||
systemFrequencySelect(0, controlI2C);
|
||||
getAdapter().writeDemodRegister(RTL2832TunerController.Page.ONE, (short) 0x15, (short) 0x01, 1);
|
||||
initializeRegisters(false);
|
||||
setTVStandard(false);
|
||||
systemFrequencySelect(0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,8 +268,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
|
||||
if(!calibrationSuccessful(calibrationCode))
|
||||
{
|
||||
mLog.error("Calibration NOT successful - code: " +
|
||||
calibrationCode);
|
||||
mLog.error("Calibration NOT successful - code: " + calibrationCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,8 +328,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
/**
|
||||
* Sets the system IF frequency
|
||||
*/
|
||||
private void systemFrequencySelect(long frequency, boolean controlI2C)
|
||||
throws UsbException
|
||||
private void systemFrequencySelect(long frequency, boolean controlI2C) throws UsbException
|
||||
{
|
||||
/* LNA top? */
|
||||
writeR820TRegister(Register.LNA_TOP2, (byte) 0xE5, controlI2C);
|
||||
@@ -463,9 +337,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
byte cp_cur;
|
||||
byte div_buf_cur;
|
||||
|
||||
if(frequency == 506000000 ||
|
||||
frequency == 666000000 ||
|
||||
frequency == 818000000)
|
||||
if(frequency == 506000000 || frequency == 666000000 || frequency == 818000000)
|
||||
{
|
||||
mixer_top = (byte) 0x14;
|
||||
cp_cur = (byte) 0x28;
|
||||
@@ -479,46 +351,29 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
}
|
||||
|
||||
writeR820TRegister(Register.MIXER_TOP, mixer_top, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.LNA_VTH_L, (byte) 0x53, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.MIXER_VTH_L, (byte) 0x75, controlI2C);
|
||||
|
||||
/* Air-In only for Astrometa */
|
||||
writeR820TRegister(Register.AIR_CABLE1_INPUT_SELECTOR, (byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.CABLE2_INPUT_SELECTOR, (byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.CP_CUR, cp_cur, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.DIVIDER_BUFFER_CURRENT, div_buf_cur, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.FILTER_CURRENT, (byte) 0x40, controlI2C);
|
||||
|
||||
/* if( type != TUNER_ANALOG_TV ) ... */
|
||||
|
||||
writeR820TRegister(Register.LNA_TOP, (byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.MIXER_TOP2, (byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.PRE_DETECT, (byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.AGC_CLOCK, (byte) 0x30, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.LNA_TOP, (byte) 0x18, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.MIXER_TOP2, mixer_top, controlI2C);
|
||||
|
||||
/* LNA discharge current */
|
||||
writeR820TRegister(Register.LNA_DISCHARGE_CURRENT, (byte) 0x14, controlI2C);
|
||||
|
||||
/* AGC clock 1 khz, external det1 cap 1u */
|
||||
writeR820TRegister(Register.AGC_CLOCK, (byte) 0x20, controlI2C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tuner's Phase-Locked-Loop (PLL) oscillator used for frequency
|
||||
* (tuning) control
|
||||
* Sets the tuner's Phase-Locked-Loop (PLL) oscillator used for frequency (tuning) control
|
||||
*
|
||||
* @param frequency - desired center frequency
|
||||
* @param controlI2C - control the I2C repeater locally
|
||||
@@ -528,49 +383,32 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
{
|
||||
/* Set reference divider to 0 */
|
||||
writeR820TRegister(Register.REFERENCE_DIVIDER_2, (byte) 0x00, controlI2C);
|
||||
|
||||
/* Set PLL autotune to 128kHz */
|
||||
writeR820TRegister(Register.PLL_AUTOTUNE, (byte) 0x00, controlI2C);
|
||||
|
||||
/* Set VCO current to 100 */
|
||||
writeR820TRegister(Register.VCO_CURRENT, (byte) 0x80, controlI2C);
|
||||
|
||||
/* Set the frequency divider - adjust for vco_fine_tune status */
|
||||
FrequencyDivider divider = FrequencyDivider.fromFrequency(frequency);
|
||||
|
||||
int statusRegister4 = getStatusRegister(4, controlI2C);
|
||||
|
||||
int vco_fine_tune = (statusRegister4 & 0x30) >> 4;
|
||||
|
||||
int div_num = divider.getDividerNumber(vco_fine_tune);
|
||||
|
||||
writeR820TRegister(Register.DIVIDER, (byte) (div_num << 5), controlI2C);
|
||||
|
||||
/* Get the integral number for this divider and frequency */
|
||||
Integral integral = divider.getIntegral(frequency);
|
||||
|
||||
writeR820TRegister(Register.PLL, integral.getRegisterValue(), controlI2C);
|
||||
|
||||
/* Calculate the sigma-delta modulator fractional setting. If it's
|
||||
* non-zero, power up the sdm and apply the fractional setting,
|
||||
* otherwise turn it off */
|
||||
int sdm = divider.getSDM(integral, frequency);
|
||||
|
||||
if(sdm != 0)
|
||||
{
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_POWER,
|
||||
(byte) 0x00, controlI2C);
|
||||
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_MSB,
|
||||
(byte) ((sdm >> 8) & 0xFF), controlI2C);
|
||||
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_LSB,
|
||||
(byte) (sdm & 0xFF), controlI2C);
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_POWER, (byte) 0x00, controlI2C);
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_MSB, (byte) ((sdm >> 8) & 0xFF), controlI2C);
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_LSB, (byte) (sdm & 0xFF), controlI2C);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_POWER,
|
||||
(byte) 0x08, controlI2C);
|
||||
writeR820TRegister(Register.SIGMA_DELTA_MODULATOR_POWER, (byte) 0x08, controlI2C);
|
||||
}
|
||||
|
||||
/* Check to see if the PLL locked with these divider, integral and sdm
|
||||
@@ -582,8 +420,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
|
||||
if(!isPLLLocked(controlI2C))
|
||||
{
|
||||
throw new UsbException("R820T Tuner Controller - couldn't "
|
||||
+ "achieve PLL lock on frequency [" + frequency + "]");
|
||||
throw new UsbException("R820T Tuner Controller - couldn't achieve PLL lock on frequency [" + frequency + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,7 +436,6 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private boolean isPLLLocked(boolean controlI2C) throws UsbException
|
||||
{
|
||||
int register = getStatusRegister(2, controlI2C);
|
||||
|
||||
return (register & 0x40) == 0x40;
|
||||
}
|
||||
|
||||
@@ -614,11 +450,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
{
|
||||
for(int x = 5; x < mShadowRegister.length; x++)
|
||||
{
|
||||
writeI2CRegister(mDeviceHandle,
|
||||
mI2CAddress,
|
||||
(byte) x,
|
||||
(byte) mShadowRegister[x],
|
||||
controlI2C);
|
||||
getAdapter().writeI2CRegister(I2C_ADDRESS, (byte) x, (byte) mShadowRegister[x], controlI2C);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,9 +460,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private int getStatusRegister(int register, boolean controlI2C) throws UsbException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(5);
|
||||
|
||||
read(mDeviceHandle, mI2CAddress, Block.I2C, buffer);
|
||||
|
||||
getAdapter().read(I2C_ADDRESS, RTL2832TunerController.Block.I2C, buffer);
|
||||
return bitReverse(buffer.get(register) & 0xFF);
|
||||
}
|
||||
|
||||
@@ -639,48 +469,31 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
*/
|
||||
private static int bitReverse(int value)
|
||||
{
|
||||
return BIT_REV_LOOKUP_TABLE[value & 0x0F] << 4 |
|
||||
BIT_REV_LOOKUP_TABLE[(value & 0xF0) >> 4];
|
||||
return BIT_REV_LOOKUP_TABLE[value & 0x0F] << 4 | BIT_REV_LOOKUP_TABLE[(value & 0xF0) >> 4];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the byte value to the specified register, optionally controlling
|
||||
* the I2C repeater as needed.
|
||||
*/
|
||||
public void writeR820TRegister(Register register,
|
||||
byte value,
|
||||
boolean controlI2C) throws UsbException
|
||||
public synchronized void writeR820TRegister(Register register, byte value, boolean controlI2C) throws UsbException
|
||||
{
|
||||
if(register.isMasked())
|
||||
{
|
||||
int current = mShadowRegister[register.getRegister()];
|
||||
|
||||
value = (byte) ((current & ~register.getMask()) |
|
||||
(value & register.getMask()));
|
||||
value = (byte) ((current & ~register.getMask()) | (value & register.getMask()));
|
||||
}
|
||||
|
||||
writeI2CRegister(mDeviceHandle, mI2CAddress,
|
||||
(byte) register.getRegister(), value, controlI2C);
|
||||
|
||||
getAdapter().writeI2CRegister(I2C_ADDRESS, (byte) register.getRegister(), value, controlI2C);
|
||||
mShadowRegister[register.getRegister()] = value;
|
||||
|
||||
// Log.info( "R820T writing register " +
|
||||
// String.format( "%02X", register.getRegister() ) + " value " +
|
||||
// String.format( "%02X", value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified register, optionally controlling the I2C repeater
|
||||
*/
|
||||
public int readR820TRegister(Register register, boolean controlI2C)
|
||||
throws UsbException
|
||||
public int readR820TRegister(Register register, boolean controlI2C) throws UsbException
|
||||
{
|
||||
int value = readI2CRegister(mDeviceHandle,
|
||||
mI2CAddress,
|
||||
(byte) register.getRegister(),
|
||||
controlI2C);
|
||||
|
||||
return value;
|
||||
return getAdapter().readI2CRegister(I2C_ADDRESS, (byte) register.getRegister(), controlI2C);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -699,7 +512,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
*/
|
||||
public void setLNAGain(R820TLNAGain gain, boolean controlI2C) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -707,7 +520,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,7 +529,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
*/
|
||||
public void setMixerGain(R820TMixerGain gain, boolean controlI2C) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -724,7 +537,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +546,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
*/
|
||||
public void setVGAGain(R820TVGAGain gain, boolean controlI2C) throws UsbException
|
||||
{
|
||||
mLock.lock();
|
||||
getAdapter().getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -741,7 +554,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
getAdapter().getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +583,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private String mLabel;
|
||||
private int mSetting;
|
||||
|
||||
private R820TVGAGain(String label, int setting)
|
||||
R820TVGAGain(String label, int setting)
|
||||
{
|
||||
mLabel = label;
|
||||
mSetting = setting;
|
||||
@@ -813,7 +626,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private String mLabel;
|
||||
private int mSetting;
|
||||
|
||||
private R820TLNAGain(String label, int setting)
|
||||
R820TLNAGain(String label, int setting)
|
||||
{
|
||||
mLabel = label;
|
||||
mSetting = setting;
|
||||
@@ -859,7 +672,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private String mLabel;
|
||||
private int mSetting;
|
||||
|
||||
private R820TMixerGain(String label, int setting)
|
||||
R820TMixerGain(String label, int setting)
|
||||
{
|
||||
mLabel = label;
|
||||
mSetting = setting;
|
||||
@@ -919,7 +732,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private R820TLNAGain mLNAGain;
|
||||
private R820TMixerGain mMixerGain;
|
||||
|
||||
private R820TGain(String label, R820TVGAGain vga, R820TLNAGain lna, R820TMixerGain mixer)
|
||||
R820TGain(String label, R820TVGAGain vga, R820TLNAGain lna, R820TMixerGain mixer)
|
||||
{
|
||||
mLabel = label;
|
||||
mVGAGain = vga;
|
||||
@@ -1006,7 +819,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private int mRegister;
|
||||
private int mMask;
|
||||
|
||||
private Register(int register, int mask)
|
||||
Register(int register, int mask)
|
||||
{
|
||||
mRegister = register;
|
||||
mMask = mask;
|
||||
@@ -1061,13 +874,8 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private int mXtalCap20p;
|
||||
private int mXtalCap10p;
|
||||
|
||||
private FrequencyRange(long minFrequency,
|
||||
long maxFrequency,
|
||||
int openDrain,
|
||||
int rfMuxPloy,
|
||||
int tf_c,
|
||||
int xtalCap20p,
|
||||
int xtalCap10p)
|
||||
FrequencyRange(long minFrequency, long maxFrequency, int openDrain, int rfMuxPloy, int tf_c, int xtalCap20p,
|
||||
int xtalCap10p)
|
||||
{
|
||||
mMinFrequency = minFrequency;
|
||||
mMaxFrequency = maxFrequency;
|
||||
@@ -1078,11 +886,19 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
mXtalCap10p = xtalCap10p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the frequency is contained by the frequency range of this band
|
||||
*/
|
||||
public boolean contains(long frequency)
|
||||
{
|
||||
return mMinFrequency <= frequency && frequency <= mMaxFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the correct frequency range that contains the frequency
|
||||
* @param frequency to lookup
|
||||
* @return frequency range
|
||||
*/
|
||||
public static FrequencyRange getRangeForFrequency(long frequency)
|
||||
{
|
||||
for(FrequencyRange range : values())
|
||||
@@ -1175,12 +991,8 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private int mIntegralValue;
|
||||
private static final int mVCOPowerReference = 2;
|
||||
|
||||
private FrequencyDivider(int dividerNumber,
|
||||
int mixerDivider,
|
||||
long minimumFrequency,
|
||||
long maximumFrequency,
|
||||
int registerSetting,
|
||||
int integralValue)
|
||||
FrequencyDivider(int dividerNumber, int mixerDivider, long minimumFrequency, long maximumFrequency,
|
||||
int registerSetting, int integralValue)
|
||||
{
|
||||
mDividerNumber = dividerNumber;
|
||||
mMixerDivider = mixerDivider;
|
||||
@@ -1230,8 +1042,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
|
||||
public boolean contains(long frequency)
|
||||
{
|
||||
return mMinimumFrequency <= frequency &&
|
||||
frequency <= mMaximumFrequency;
|
||||
return mMinimumFrequency <= frequency && frequency <= mMaximumFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1269,27 +1080,22 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
return Integral.fromValue(integral);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("PLL frequency [" + frequency +
|
||||
"] is not valid for this frequency divider " + this.toString());
|
||||
throw new IllegalArgumentException("PLL frequency [" + frequency + "] is not valid for this frequency " +
|
||||
"divider " + this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the 16-bit value of the sigma-delta modulator setting
|
||||
* which represents the fractional portion of the requested frequency
|
||||
* that is left over after subtracting the divider minimum frequency
|
||||
* and the integral frequency units. That residual value is divided
|
||||
* by the integral unit value to derive a 16-bit fractional value,
|
||||
* returned as an integer
|
||||
* Calculates the 16-bit value of the sigma-delta modulator setting which represents the fractional portion of
|
||||
* the requested frequency that is left over after subtracting the divider minimum frequency and the integral
|
||||
* frequency units. That residual value is divided by the integral unit value to derive a 16-bit fractional
|
||||
* value, returned as an integer
|
||||
*/
|
||||
public int getSDM(Integral integral, long frequency)
|
||||
{
|
||||
if(contains(frequency))
|
||||
{
|
||||
int delta = (int) (frequency - mMinimumFrequency -
|
||||
(integral.getNumber() * mIntegralValue));
|
||||
|
||||
int delta = (int) (frequency - mMinimumFrequency - (integral.getNumber() * mIntegralValue));
|
||||
double fractional = (double) delta / (double) mIntegralValue;
|
||||
|
||||
//Left shift the double value 16 bits and truncate to an integer
|
||||
return (int) (fractional * 0x10000) & 0xFFFF;
|
||||
}
|
||||
@@ -1348,7 +1154,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
private int mNumber;
|
||||
private int mRegister;
|
||||
|
||||
private Integral(int number, int register)
|
||||
Integral(int number, int register)
|
||||
{
|
||||
mNumber = number;
|
||||
mRegister = register;
|
||||
@@ -1371,8 +1177,7 @@ public class R820TTunerController extends RTL2832TunerController
|
||||
return Integral.values()[value];
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("PLL integral value [" + value +
|
||||
"] must be in the range 0 - 31");
|
||||
throw new IllegalArgumentException("PLL integral value [" + value + "] must be in the range 0 - 31");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
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.RTL2832TunerConfiguration;
|
||||
|
||||
public class R820TTunerConfiguration extends TunerConfiguration
|
||||
/**
|
||||
* RTL-2832 with embedded R820T tuner configuration
|
||||
*/
|
||||
public class R820TTunerConfiguration extends RTL2832TunerConfiguration
|
||||
{
|
||||
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;
|
||||
private boolean mAutoPPMCorrection = true;
|
||||
private R820TEmbeddedTuner.R820TGain mMasterGain = R820TEmbeddedTuner.R820TGain.GAIN_327;
|
||||
private R820TEmbeddedTuner.R820TMixerGain mMixerGain = R820TEmbeddedTuner.R820TMixerGain.GAIN_105;
|
||||
private R820TEmbeddedTuner.R820TLNAGain mLNAGain = R820TEmbeddedTuner.R820TLNAGain.GAIN_222;
|
||||
private R820TEmbeddedTuner.R820TVGAGain mVGAGain = R820TEmbeddedTuner.R820TVGAGain.GAIN_210;
|
||||
|
||||
/**
|
||||
* Default constructor for JAXB
|
||||
@@ -39,100 +40,64 @@ public class R820TTunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
}
|
||||
|
||||
public R820TTunerConfiguration(String uniqueID, String name)
|
||||
{
|
||||
super(uniqueID, name);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.RAFAELMICRO_R820T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param uniqueID for the tuner
|
||||
*/
|
||||
public R820TTunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "master_gain")
|
||||
public R820TTunerController.R820TGain getMasterGain()
|
||||
public R820TEmbeddedTuner.R820TGain getMasterGain()
|
||||
{
|
||||
return mMasterGain;
|
||||
}
|
||||
|
||||
public void setMasterGain(R820TTunerController.R820TGain gain)
|
||||
public void setMasterGain(R820TEmbeddedTuner.R820TGain gain)
|
||||
{
|
||||
mMasterGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "mixer_gain")
|
||||
public R820TTunerController.R820TMixerGain getMixerGain()
|
||||
public R820TEmbeddedTuner.R820TMixerGain getMixerGain()
|
||||
{
|
||||
return mMixerGain;
|
||||
}
|
||||
|
||||
public void setMixerGain(R820TTunerController.R820TMixerGain mixerGain)
|
||||
public void setMixerGain(R820TEmbeddedTuner.R820TMixerGain mixerGain)
|
||||
{
|
||||
mMixerGain = mixerGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna_gain")
|
||||
public R820TTunerController.R820TLNAGain getLNAGain()
|
||||
public R820TEmbeddedTuner.R820TLNAGain getLNAGain()
|
||||
{
|
||||
return mLNAGain;
|
||||
}
|
||||
|
||||
public void setLNAGain(R820TTunerController.R820TLNAGain lnaGain)
|
||||
public void setLNAGain(R820TEmbeddedTuner.R820TLNAGain lnaGain)
|
||||
{
|
||||
mLNAGain = lnaGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "vga_gain")
|
||||
public R820TTunerController.R820TVGAGain getVGAGain()
|
||||
public R820TEmbeddedTuner.R820TVGAGain getVGAGain()
|
||||
{
|
||||
return mVGAGain;
|
||||
}
|
||||
|
||||
public void setVGAGain(R820TTunerController.R820TVGAGain vgaGain)
|
||||
public void setVGAGain(R820TEmbeddedTuner.R820TVGAGain vgaGain)
|
||||
{
|
||||
mVGAGain = vgaGain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "frequency_correction")
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
return mFrequencyCorrection;
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double value)
|
||||
{
|
||||
mFrequencyCorrection = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public RTL2832TunerController.SampleRate getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(RTL2832TunerController.SampleRate sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if automatic correction of PPM from measured frequency error is enabled/disabled.
|
||||
* @return true if auto-correction is enabled.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "auto_ppm_correction_enabled")
|
||||
public boolean getAutoPPMCorrectionEnabled()
|
||||
{
|
||||
return mAutoPPMCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state for auto-correction of PPM from measured frequency error values.
|
||||
* @param enabled
|
||||
*/
|
||||
public void setAutoPPMCorrectionEnabled(boolean enabled)
|
||||
{
|
||||
mAutoPPMCorrection = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.preference.UserPreferences;
|
||||
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.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController;
|
||||
import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController.R820TGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController.R820TLNAGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController.R820TMixerGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController.R820TVGAGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TLNAGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TMixerGain;
|
||||
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner.R820TVGAGain;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
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
|
||||
public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(R820TTunerEditor.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final R820TGain DEFAULT_GAIN = R820TGain.GAIN_279;
|
||||
private JButton mTunerInfoButton;
|
||||
private JComboBox<SampleRate> mSampleRateCombo;
|
||||
private JComboBox<R820TGain> mMasterGainCombo;
|
||||
private JComboBox<R820TMixerGain> mMixerGainCombo;
|
||||
private JComboBox<R820TLNAGain> mLNAGainCombo;
|
||||
private JComboBox<R820TVGAGain> mVGAGainCombo;
|
||||
|
||||
private R820TTunerController mController;
|
||||
|
||||
private JTextField mConfigurationName;
|
||||
private JButton mTunerInfo;
|
||||
private JComboBox<SampleRate> mComboSampleRate;
|
||||
private JSpinner mFrequencyCorrection;
|
||||
private JCheckBox mAutoPPMEnabled;
|
||||
private JComboBox<R820TGain> mComboMasterGain;
|
||||
private JComboBox<R820TMixerGain> mComboMixerGain;
|
||||
private JComboBox<R820TLNAGain> mComboLNAGain;
|
||||
private JComboBox<R820TVGAGain> mComboVGAGain;
|
||||
private boolean mLoading;
|
||||
|
||||
public R820TTunerEditor(TunerConfigurationModel tunerConfigurationModel, RTL2832Tuner tuner)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager for saving configurations
|
||||
* @param discoveredTuner to edit
|
||||
*/
|
||||
public R820TTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(tunerConfigurationModel);
|
||||
mController = (R820TTunerController)tuner.getController();
|
||||
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
private R820TTunerConfiguration getConfiguration()
|
||||
/**
|
||||
* Access the R820T embedded tuner
|
||||
* @return R820T tuner if there is a tuner, or null otherwise
|
||||
*/
|
||||
private R820TEmbeddedTuner getEmbeddedTuner()
|
||||
{
|
||||
if(hasItem())
|
||||
if(hasTuner())
|
||||
{
|
||||
return (R820TTunerConfiguration)getItem();
|
||||
return (R820TEmbeddedTuner)getTuner().getController().getEmbeddedTuner();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -92,324 +84,331 @@ public class R820TTunerEditor extends TunerConfigurationEditor
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]",
|
||||
"[][][][][][][][grow]"));
|
||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
|
||||
"[][][][][][][][][][][][][][][][grow]"));
|
||||
|
||||
add(new JLabel("R820T Tuner Configuration"), "span,align center");
|
||||
add(new JLabel("Tuner:"));
|
||||
add(getTunerIdLabel());
|
||||
add(getTunerInfoButton());
|
||||
|
||||
mConfigurationName = new JTextField();
|
||||
mConfigurationName.setEnabled(false);
|
||||
mConfigurationName.addFocusListener(new FocusListener()
|
||||
{
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
save();
|
||||
}
|
||||
add(new JLabel("Status:"));
|
||||
add(getTunerStatusLabel(), "wrap");
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
}
|
||||
});
|
||||
add(getButtonPanel(), "span,align left");
|
||||
|
||||
add(new JLabel("Name:"));
|
||||
add(mConfigurationName, "span 2");
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
|
||||
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);
|
||||
add(new JLabel("Frequency (MHz):"));
|
||||
add(getFrequencyPanel(), "wrap");
|
||||
|
||||
mComboSampleRate = new JComboBox<>(SampleRate.values());
|
||||
mComboSampleRate.setEnabled(false);
|
||||
mComboSampleRate.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
SampleRate sampleRate =
|
||||
(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);
|
||||
add(getSampleRateCombo(), "wrap");
|
||||
|
||||
/**
|
||||
* Frequency Correction
|
||||
*/
|
||||
SpinnerModel model =
|
||||
new SpinnerNumberModel(0.0, //initial value
|
||||
-1000.0, //min
|
||||
1000.0, //max
|
||||
0.1); //step
|
||||
add(new JSeparator(), "span,growx,push");
|
||||
add(new JLabel("Gain Control"), "wrap");
|
||||
|
||||
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 JLabel("")); //Space filler
|
||||
add(new JLabel("")); //Space filler
|
||||
add(new JLabel("")); //Space filler
|
||||
mAutoPPMEnabled = new JCheckBox("PPM Auto-Correction");
|
||||
mAutoPPMEnabled.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
boolean enabled = mAutoPPMEnabled.isSelected();
|
||||
mController.getFrequencyErrorCorrectionManager().setEnabled(enabled);
|
||||
save();
|
||||
}
|
||||
});
|
||||
add(mAutoPPMEnabled);
|
||||
|
||||
add(new JSeparator(JSeparator.HORIZONTAL), "span,grow");
|
||||
|
||||
/**
|
||||
* Gain Controls
|
||||
*/
|
||||
add(new JLabel("Gain"), "wrap");
|
||||
|
||||
/* Master Gain Control */
|
||||
mComboMasterGain = new JComboBox<R820TGain>(R820TGain.values());
|
||||
mComboMasterGain.setEnabled(false);
|
||||
mComboMasterGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TGain gain = (R820TGain)mComboMasterGain.getSelectedItem();
|
||||
|
||||
mController.setGain((R820TGain)mComboMasterGain.getSelectedItem(), true);
|
||||
|
||||
if(gain == 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<R820TMixerGain>(R820TMixerGain.values());
|
||||
mComboMixerGain.setEnabled(false);
|
||||
mComboMixerGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TMixerGain mixerGain =
|
||||
(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(getMasterGainCombo(), "wrap");
|
||||
|
||||
add(new JLabel("Mixer:"));
|
||||
add(mComboMixerGain);
|
||||
|
||||
/* LNA Gain Control */
|
||||
mComboLNAGain = new JComboBox<R820TLNAGain>(R820TLNAGain.values());
|
||||
mComboLNAGain.setEnabled(false);
|
||||
mComboLNAGain.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TLNAGain lnaGain =
|
||||
(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(getMixerGainCombo(), "wrap");
|
||||
|
||||
add(new JLabel("LNA:"));
|
||||
add(mComboLNAGain);
|
||||
add(getLNAGainCombo(), "wrap");
|
||||
|
||||
/* VGA Gain Control */
|
||||
mComboVGAGain = new JComboBox<R820TVGAGain>(R820TVGAGain.values());
|
||||
mComboVGAGain.setEnabled(false);
|
||||
mComboVGAGain.addActionListener(new ActionListener()
|
||||
add(new JLabel("VGA:"));
|
||||
add(getVGAGainCombo(), "wrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
getTunerIdLabel().setText(getTuner().getPreferredName());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerIdLabel().setText(getDiscoveredTuner().getId());
|
||||
}
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
getTunerInfoButton().setEnabled(true);
|
||||
getSampleRateCombo().setEnabled(true);
|
||||
getSampleRateCombo().setSelectedItem(getConfiguration().getSampleRate());
|
||||
getMasterGainCombo().setEnabled(true);
|
||||
R820TGain gain = getConfiguration().getMasterGain();
|
||||
getMasterGainCombo().setEnabled(true);
|
||||
getMasterGainCombo().setSelectedItem(gain);
|
||||
|
||||
if(gain == R820TGain.MANUAL)
|
||||
{
|
||||
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
|
||||
getMixerGainCombo().setEnabled(true);
|
||||
|
||||
getLNAGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
||||
getLNAGainCombo().setEnabled(true);
|
||||
|
||||
getVGAGainCombo().setSelectedItem(getConfiguration().getVGAGain());
|
||||
getVGAGainCombo().setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
|
||||
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setSelectedItem(gain.getLNAGain());
|
||||
|
||||
getVGAGainCombo().setEnabled(false);
|
||||
getVGAGainCombo().setSelectedItem(gain.getVGAGain());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getTunerInfoButton().setEnabled(false);
|
||||
getSampleRateCombo().setEnabled(false);
|
||||
getMasterGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getVGAGainCombo().setEnabled(false);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hyperlink button that provides tuner information
|
||||
*/
|
||||
private JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
mTunerInfoButton = new JButton("Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(R820TTunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
private JComboBox getVGAGainCombo()
|
||||
{
|
||||
if(mVGAGainCombo == null)
|
||||
{
|
||||
mVGAGainCombo = new JComboBox<>(R820TVGAGain.values());
|
||||
mVGAGainCombo.setEnabled(false);
|
||||
mVGAGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TVGAGain vgaGain =
|
||||
(R820TVGAGain)mComboVGAGain.getSelectedItem();
|
||||
R820TVGAGain vgaGain = (R820TVGAGain) mVGAGainCombo.getSelectedItem();
|
||||
|
||||
if(vgaGain == null)
|
||||
{
|
||||
vgaGain = DEFAULT_GAIN.getVGAGain();
|
||||
}
|
||||
|
||||
if(mComboVGAGain.isEnabled())
|
||||
if(mVGAGainCombo.isEnabled())
|
||||
{
|
||||
mController.setVGAGain(vgaGain, true);
|
||||
getEmbeddedTuner().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);
|
||||
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);
|
||||
});
|
||||
mVGAGainCombo.setToolTipText("<html>VGA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
|
||||
}
|
||||
|
||||
return mVGAGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getLNAGainCombo()
|
||||
{
|
||||
if(mLNAGainCombo == null)
|
||||
{
|
||||
mLNAGainCombo = new JComboBox<>(R820TLNAGain.values());
|
||||
mLNAGainCombo.setEnabled(false);
|
||||
mLNAGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TLNAGain lnaGain = (R820TLNAGain) mLNAGainCombo.getSelectedItem();
|
||||
|
||||
if(lnaGain == null)
|
||||
{
|
||||
lnaGain = DEFAULT_GAIN.getLNAGain();
|
||||
}
|
||||
|
||||
if(mLNAGainCombo.isEnabled())
|
||||
{
|
||||
getEmbeddedTuner().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);
|
||||
}
|
||||
});
|
||||
mLNAGainCombo.setToolTipText("<html>LNA Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
|
||||
}
|
||||
|
||||
return mLNAGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getSampleRateCombo()
|
||||
{
|
||||
if(mSampleRateCombo == null)
|
||||
{
|
||||
mSampleRateCombo = new JComboBox<>(SampleRate.values());
|
||||
mSampleRateCombo.setEnabled(false);
|
||||
mSampleRateCombo.addActionListener(e ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
SampleRate sampleRate = (SampleRate) mSampleRateCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSampleRateCombo;
|
||||
}
|
||||
|
||||
private JComboBox getMixerGainCombo()
|
||||
{
|
||||
if(mMixerGainCombo == null)
|
||||
{
|
||||
mMixerGainCombo = new JComboBox<>(R820TMixerGain.values());
|
||||
mMixerGainCombo.setEnabled(false);
|
||||
mMixerGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TMixerGain mixerGain = (R820TMixerGain) mMixerGainCombo.getSelectedItem();
|
||||
|
||||
if(mixerGain == null)
|
||||
{
|
||||
mixerGain = DEFAULT_GAIN.getMixerGain();
|
||||
}
|
||||
|
||||
if(mMixerGainCombo.isEnabled())
|
||||
{
|
||||
getEmbeddedTuner().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
mMixerGainCombo.setToolTipText("<html>Mixer Gain. Set master gain to <b>MANUAL</b> to enable adjustment</html>");
|
||||
}
|
||||
|
||||
return mMixerGainCombo;
|
||||
}
|
||||
|
||||
private JComboBox getMasterGainCombo()
|
||||
{
|
||||
if(mMasterGainCombo == null)
|
||||
{
|
||||
mMasterGainCombo = new JComboBox<>(R820TGain.values());
|
||||
mMasterGainCombo.setEnabled(false);
|
||||
mMasterGainCombo.addActionListener(arg0 ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
R820TGain gain = (R820TGain)getMasterGainCombo().getSelectedItem();
|
||||
getEmbeddedTuner().setGain((R820TGain)getMasterGainCombo().getSelectedItem(), true);
|
||||
|
||||
if(gain == R820TGain.MANUAL)
|
||||
{
|
||||
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
|
||||
getMixerGainCombo().setEnabled(true);
|
||||
|
||||
getLNAGainCombo().setSelectedItem(gain.getLNAGain());
|
||||
getLNAGainCombo().setEnabled(true);
|
||||
|
||||
getVGAGainCombo().setSelectedItem(gain.getVGAGain());
|
||||
getVGAGainCombo().setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getMixerGainCombo().setEnabled(false);
|
||||
getMixerGainCombo().setSelectedItem(gain.getMixerGain());
|
||||
|
||||
getLNAGainCombo().setEnabled(false);
|
||||
getLNAGainCombo().setSelectedItem(gain.getLNAGain());
|
||||
|
||||
getVGAGainCombo().setEnabled(false);
|
||||
getVGAGainCombo().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);
|
||||
}
|
||||
}
|
||||
});
|
||||
mMasterGainCombo.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>");
|
||||
}
|
||||
|
||||
return mMasterGainCombo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,85 +416,31 @@ public class R820TTunerEditor extends TunerConfigurationEditor
|
||||
*/
|
||||
private void updateSampleRateToolTip()
|
||||
{
|
||||
if(mController.isLocked())
|
||||
if(hasTuner() && getTuner().getTunerController().isLocked())
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
|
||||
}
|
||||
else if(hasTuner())
|
||||
{
|
||||
getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboSampleRate.setToolTipText("Select a sample rate for the tuner");
|
||||
getSampleRateCombo().setToolTipText("No tuner available");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
mComboSampleRate.setEnabled(!locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(mAutoPPMEnabled.isEnabled() != enabled)
|
||||
{
|
||||
mAutoPPMEnabled.setEnabled(enabled);
|
||||
}
|
||||
|
||||
updateSampleRateToolTip();
|
||||
|
||||
if(mController.isLocked())
|
||||
{
|
||||
mComboSampleRate.setEnabled(false);
|
||||
}
|
||||
else 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);
|
||||
}
|
||||
super.setTunerLockState(locked);
|
||||
mSampleRateCombo.setEnabled(!locked);
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
RTL2832TunerController.Descriptor descriptor = mController.getDescriptor();
|
||||
|
||||
RTL2832TunerController.Descriptor descriptor = getTuner().getController().getDescriptor();
|
||||
sb.append("<html><h3>RTL-2832 with R820T Tuner</h3>");
|
||||
|
||||
if(descriptor == null)
|
||||
@@ -537,76 +482,25 @@ public class R820TTunerEditor extends TunerConfigurationEditor
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && !mLoading)
|
||||
if(hasConfiguration() && !isLoading())
|
||||
{
|
||||
R820TTunerConfiguration config = getConfiguration();
|
||||
|
||||
config.setName(mConfigurationName.getText());
|
||||
|
||||
double value = ((SpinnerNumberModel)mFrequencyCorrection
|
||||
.getModel()).getNumber().doubleValue();
|
||||
config.setFrequency(getFrequencyControl().getFrequency());
|
||||
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
config.setFrequencyCorrection(value);
|
||||
config.setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||
|
||||
config.setAutoPPMCorrectionEnabled(mAutoPPMEnabled.isSelected());
|
||||
|
||||
config.setSampleRate((SampleRate)mComboSampleRate.getSelectedItem());
|
||||
|
||||
R820TGain gain = (R820TGain)mComboMasterGain.getSelectedItem();
|
||||
config.setSampleRate((SampleRate)getSampleRateCombo().getSelectedItem());
|
||||
R820TGain gain = (R820TGain)getMasterGainCombo().getSelectedItem();
|
||||
config.setMasterGain(gain);
|
||||
|
||||
R820TMixerGain mixerGain = (R820TMixerGain)mComboMixerGain.getSelectedItem();
|
||||
R820TMixerGain mixerGain = (R820TMixerGain)getMixerGainCombo().getSelectedItem();
|
||||
config.setMixerGain(mixerGain);
|
||||
|
||||
R820TLNAGain lnaGain = (R820TLNAGain)mComboLNAGain.getSelectedItem();
|
||||
R820TLNAGain lnaGain = (R820TLNAGain)getLNAGainCombo().getSelectedItem();
|
||||
config.setLNAGain(lnaGain);
|
||||
|
||||
R820TVGAGain vgaGain = (R820TVGAGain)mComboVGAGain.getSelectedItem();
|
||||
R820TVGAGain vgaGain = (R820TVGAGain)getVGAGainCombo().getSelectedItem();
|
||||
config.setVGAGain(vgaGain);
|
||||
|
||||
getTunerConfigurationModel().broadcast(
|
||||
new TunerConfigurationEvent(getConfiguration(), TunerConfigurationEvent.Event.CHANGE));
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
mAutoPPMEnabled.setSelected(config.getAutoPPMCorrectionEnabled());
|
||||
mComboSampleRate.setSelectedItem(config.getSampleRate());
|
||||
mComboMasterGain.setSelectedItem(config.getMasterGain());
|
||||
mComboMixerGain.setSelectedItem(config.getMixerGain());
|
||||
mComboLNAGain.setSelectedItem(config.getLNAGain());
|
||||
mComboVGAGain.setSelectedItem(config.getVGAGain());
|
||||
|
||||
//Update enabled state to reflect when frequency and sample rate controls are locked
|
||||
mComboSampleRate.setEnabled(!mController.isLocked());
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText(config.getName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mConfigurationName.setText("");
|
||||
}
|
||||
|
||||
mLoading = false;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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/>
|
||||
* * *****************************************************************************
|
||||
* 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.test;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
@@ -38,9 +35,9 @@ public class TestTuner extends Tuner
|
||||
private static int mInstanceCounter = 1;
|
||||
private final int mInstanceID = mInstanceCounter++;
|
||||
|
||||
public TestTuner(UserPreferences userPreferences)
|
||||
public TestTuner(ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super("Test Tuner", new TestTunerController(), userPreferences);
|
||||
super(new TestTunerController(), tunerErrorListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,10 +48,16 @@ public class TestTuner extends Tuner
|
||||
return (TestTunerController)super.getTunerController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
return "Test Tuner-" + mInstanceID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
return getName() + "-" + mInstanceID;
|
||||
return getPreferredName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +69,7 @@ public class TestTuner extends Tuner
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerClass.TEST_TUNER.getTunerType();
|
||||
return TunerType.TEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,7 +21,9 @@ package io.github.dsheirer.source.tuner.test;
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.LoggingTunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -47,7 +49,12 @@ public class TestTunerController extends TunerController
|
||||
*/
|
||||
public TestTunerController()
|
||||
{
|
||||
super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH_PERCENTAGE);
|
||||
super(new LoggingTunerErrorListener());
|
||||
|
||||
setMinimumFrequency(MINIMUM_FREQUENCY);
|
||||
setMaximumFrequency(MAXIMUM_FREQUENCY);
|
||||
setMiddleUnusableHalfBandwidth(DC_NOISE_BANDWIDTH);
|
||||
setUsableBandwidthPercentage(USABLE_BANDWIDTH_PERCENTAGE);
|
||||
|
||||
int sweepRate = 0; //Hz per interval
|
||||
// long initialToneFrequency = SAMPLE_RATE / 2 + 100;
|
||||
@@ -72,9 +79,21 @@ public class TestTunerController extends TunerController
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
public void start() throws SourceException
|
||||
{
|
||||
//no-op
|
||||
//No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.TEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationFactory;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
/**
|
||||
* Editor for Discovered Tuners that displays custom tuner editor for the selected discovered tuner
|
||||
*/
|
||||
public class DiscoveredTunerEditor extends Editor<DiscoveredTuner> implements IDiscoveredTunerStatusListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DiscoveredTunerEditor.class);
|
||||
private UserPreferences mUserPreferences;
|
||||
private TunerManager mTunerManager;
|
||||
private JPanel mEmptyEditor = new EmptyTunerEditor();
|
||||
private JPanel mEditor = mEmptyEditor;
|
||||
private JScrollPane mEditorScroller;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences to use for starting wideband recordings sourced by a tuner
|
||||
* @param tunerManager for creating tuner editors
|
||||
*/
|
||||
public DiscoveredTunerEditor(UserPreferences userPreferences, TunerManager tunerManager)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mTunerManager = tunerManager;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the lock state of the tuner editor so that lockable controls are updated
|
||||
* @param locked true if the tuner is locked and the controls should be locked.
|
||||
*/
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
if(mEditor instanceof TunerEditor tunerEditor)
|
||||
{
|
||||
tunerEditor.setTunerLockState(locked);
|
||||
}
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mEditorScroller = new JScrollPane(mEditor);
|
||||
add(mEditorScroller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
//Unused
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(DiscoveredTuner tuner)
|
||||
{
|
||||
if(hasItem())
|
||||
{
|
||||
getItem().removeTunerStatusListener(this);
|
||||
}
|
||||
|
||||
//Cleanup previously selected tuner
|
||||
if(hasItem() && mEditor instanceof TunerEditor tunerEditor)
|
||||
{
|
||||
tunerEditor.dispose();
|
||||
}
|
||||
|
||||
super.setItem(tuner);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
mEditor = TunerConfigurationFactory.getEditor(mUserPreferences, getItem(), mTunerManager);
|
||||
getItem().addTunerStatusListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEditor = mEmptyEditor;
|
||||
}
|
||||
|
||||
remove(mEditorScroller);
|
||||
mEditorScroller = new JScrollPane(mEditor);
|
||||
add(mEditorScroller);
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
//If this is the currently displayed tuner, set it again to re-render the editor
|
||||
if(hasItem() && getItem().equals(discoveredTuner))
|
||||
{
|
||||
setItem(discoveredTuner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredUSBTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Model for discovered tuners
|
||||
*/
|
||||
public class DiscoveredTunerModel extends AbstractTableModel implements Listener<TunerEvent>,
|
||||
IDiscoveredTunerStatusListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DiscoveredTunerModel.class);
|
||||
|
||||
//Model columns
|
||||
public static final int COLUMN_TUNER_STATUS = 0;
|
||||
public static final int COLUMN_TUNER_CLASS = 1;
|
||||
public static final int COLUMN_TUNER_TYPE = 2;
|
||||
public static final int COLUMN_FREQUENCY = 3;
|
||||
public static final int COLUMN_CHANNEL_COUNT = 4;
|
||||
private static final String MHZ = " MHz";
|
||||
private static final String[] COLUMN_HEADERS = {"Status","Class", "Type", "Frequency", "Channels"};
|
||||
|
||||
private List<DiscoveredTuner> mDiscoveredTuners = new CopyOnWriteArrayList<>();
|
||||
private List<Listener<TunerEvent>> mTunerEventListeners = new ArrayList<>();
|
||||
private DecimalFormat mFrequencyFormat = new DecimalFormat("0.00000");
|
||||
private Lock mLock = new ReentrantLock();
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DiscoveredTunerModel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* List of currently enabled and available tuners
|
||||
*/
|
||||
public List<DiscoveredTuner> getAvailableTuners()
|
||||
{
|
||||
return mDiscoveredTuners.stream().filter(discoveredTuner -> discoveredTuner.hasTuner()).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the discovered tuner that matches the instantiated tuner
|
||||
* @param tuner to match
|
||||
* @return matching discovered tuner
|
||||
*/
|
||||
private DiscoveredTuner getDiscoveredTuner(Tuner tuner)
|
||||
{
|
||||
DiscoveredTuner match = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
for(DiscoveredTuner discoveredTuner: mDiscoveredTuners)
|
||||
{
|
||||
if(discoveredTuner.hasTuner() && discoveredTuner.getTuner().equals(tuner))
|
||||
{
|
||||
match = discoveredTuner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the discovered tuner at the specified row index
|
||||
* @param index to lookup
|
||||
* @return discovered tuner or null
|
||||
*/
|
||||
public DiscoveredTuner getDiscoveredTuner(int index)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(index < mDiscoveredTuners.size())
|
||||
{
|
||||
return mDiscoveredTuners.get(index);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Tuner to this model
|
||||
*/
|
||||
public void addDiscoveredTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(!mDiscoveredTuners.contains(discoveredTuner))
|
||||
{
|
||||
mDiscoveredTuners.add(discoveredTuner);
|
||||
int index = mDiscoveredTuners.indexOf(discoveredTuner);
|
||||
EventQueue.invokeLater(() -> fireTableRowsInserted(index, index));
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
discoveredTuner.getTuner().addTunerEventListener(this);
|
||||
}
|
||||
discoveredTuner.addTunerStatusListener(this);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and removes all discovered tuners, in preparation for shutdown.
|
||||
*/
|
||||
public void releaseDiscoveredTuners()
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
List<DiscoveredTuner> discoveredTuners = new ArrayList<>(mDiscoveredTuners);
|
||||
|
||||
for(DiscoveredTuner discoveredTuner: discoveredTuners)
|
||||
{
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
discoveredTuner.getTuner().removeTunerEventListener(this);
|
||||
}
|
||||
|
||||
removeDiscoveredTuner(discoveredTuner);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Tuner from this model
|
||||
*/
|
||||
public void removeDiscoveredTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mDiscoveredTuners.contains(discoveredTuner))
|
||||
{
|
||||
int index = mDiscoveredTuners.indexOf(discoveredTuner);
|
||||
mDiscoveredTuners.remove(discoveredTuner);
|
||||
EventQueue.invokeLater(() -> fireTableRowsDeleted(index, index));
|
||||
discoveredTuner.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the discovered tuner is still being managed by this tuner model and hasn't been removed.
|
||||
*/
|
||||
public boolean hasTuner(DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
return mDiscoveredTuners.contains(discoveredTuner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this model currently has the discovered tuner that is plugged into the specified bus and port.
|
||||
* @param bus number
|
||||
* @param port number
|
||||
* @return true if it already has the discovered device
|
||||
*/
|
||||
public boolean hasUsbTuner(int bus, int port)
|
||||
{
|
||||
boolean hasDevice;
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
hasDevice = mDiscoveredTuners.stream()
|
||||
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner && usbTuner.isAt(bus, port))
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return hasDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the USB tuner at bus and port number
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
*/
|
||||
public void removeUsbTuner(int bus, int port)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
DiscoveredTuner discoveredTuner = mDiscoveredTuners.stream()
|
||||
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner &&
|
||||
usbTuner.isAt(bus, port)).findFirst().get();
|
||||
|
||||
if(discoveredTuner != null)
|
||||
{
|
||||
removeDiscoveredTuner(discoveredTuner);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tuner event listener
|
||||
*/
|
||||
public void addListener(Listener<TunerEvent> listener)
|
||||
{
|
||||
mTunerEventListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tuner event 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()
|
||||
{
|
||||
//TODO: move this out of the model ... is not part of the scope of this model
|
||||
// SystemProperties properties = SystemProperties.getInstance();
|
||||
// boolean enabled = properties.get(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, true);
|
||||
//
|
||||
// if(enabled && mDiscoveredTuners.size() > 0)
|
||||
// {
|
||||
// //Hack: the airspy tuner would lockup aperiodically and refuse to produce
|
||||
// //transfer buffers ... delaying registering for buffers for 500 ms seems
|
||||
// //to allow the airspy to stabilize before we start asking for samples.
|
||||
// ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
// {
|
||||
// @Override
|
||||
// public void run()
|
||||
// {
|
||||
// broadcast(new TunerEvent(mDiscoveredTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
|
||||
// }
|
||||
// }, 500, TimeUnit.MILLISECONDS);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// broadcast(new TunerEvent(null, Event.CLEAR_MAIN_SPECTRAL_DISPLAY));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
if(current == TunerStatus.ENABLED && discoveredTuner.hasTuner())
|
||||
{
|
||||
discoveredTuner.getTuner().addTunerEventListener(this);
|
||||
}
|
||||
|
||||
int row = mDiscoveredTuners.indexOf(discoveredTuner);
|
||||
EventQueue.invokeLater(() -> fireTableRowsUpdated(row, row));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(TunerEvent event)
|
||||
{
|
||||
if(event.hasTuner())
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
DiscoveredTuner matching = getDiscoveredTuner(event.getTuner());
|
||||
int index = mDiscoveredTuners.indexOf(matching);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case UPDATE_CHANNEL_COUNT:
|
||||
EventQueue.invokeLater(() -> fireTableCellUpdated(index, COLUMN_CHANNEL_COUNT));
|
||||
break;
|
||||
case UPDATE_FREQUENCY:
|
||||
EventQueue.invokeLater(() -> fireTableCellUpdated(index, COLUMN_FREQUENCY));
|
||||
break;
|
||||
case NOTIFICATION_ERROR_STATE:
|
||||
EventQueue.invokeLater(() -> fireTableRowsUpdated(index, index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Got a tuner event without a tuner - " + event);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mDiscoveredTuners.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return COLUMN_HEADERS.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if(rowIndex < mDiscoveredTuners.size())
|
||||
{
|
||||
DiscoveredTuner discoveredTuner = mDiscoveredTuners.get(rowIndex);
|
||||
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_TUNER_STATUS:
|
||||
return discoveredTuner.getTunerStatus();
|
||||
case COLUMN_TUNER_CLASS:
|
||||
return discoveredTuner.getTunerClass().toString();
|
||||
case COLUMN_TUNER_TYPE:
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
return discoveredTuner.getTuner().getTunerType().getLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// case COLUMN_TUNER_ID:
|
||||
// if(discoveredTuner.hasTuner())
|
||||
// {
|
||||
// return discoveredTuner.getTuner().getUniqueID();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return discoveredTuner.getId();
|
||||
// }
|
||||
// case COLUMN_SAMPLE_RATE:
|
||||
// if(discoveredTuner.hasTuner())
|
||||
// {
|
||||
// double sampleRate = discoveredTuner.getTuner().getTunerController().getSampleRate();
|
||||
// return mSampleRateFormat.format(sampleRate / 1E6D) + MHZ;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return "";
|
||||
// }
|
||||
case COLUMN_FREQUENCY:
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
long frequency = discoveredTuner.getTuner().getTunerController().getFrequency();
|
||||
return mFrequencyFormat.format(frequency / 1E6D) + MHZ;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
case COLUMN_CHANNEL_COUNT:
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
int channelCount = discoveredTuner.getTuner().getChannelSourceManager().getTunerChannelCount();
|
||||
return channelCount + " (" + (discoveredTuner.getTuner().getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)");
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// case COLUMN_FREQUENCY_ERROR:
|
||||
// if(discoveredTuner.hasTuner())
|
||||
// {
|
||||
// double ppm = discoveredTuner.getTuner().getTunerController().getFrequencyCorrection();
|
||||
// return mFrequencyErrorPPMFormat.format(ppm);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return "";
|
||||
// }
|
||||
// case COLUMN_MEASURED_FREQUENCY_ERROR:
|
||||
// if(discoveredTuner.hasTuner())
|
||||
// {
|
||||
// if(discoveredTuner.getTuner().getTunerController().hasMeasuredFrequencyError())
|
||||
// {
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.append(discoveredTuner.getTuner().getTunerController().getMeasuredFrequencyError());
|
||||
// sb.append("Hz (");
|
||||
// sb.append(mFrequencyErrorPPMFormat.format(discoveredTuner.getTuner().getTunerController().getPPMFrequencyError()));
|
||||
// sb.append("ppm)");
|
||||
// return sb.toString();
|
||||
// }
|
||||
// }
|
||||
// return "";
|
||||
// case COLUMN_ERROR_OR_SPECTRAL_DISPLAY_NEW:
|
||||
// if(discoveredTuner.hasErrorMessage())
|
||||
// {
|
||||
// return discoveredTuner.getErrorMessage();
|
||||
// }
|
||||
// else if(discoveredTuner.hasTuner())
|
||||
// {
|
||||
// return "New";
|
||||
// }
|
||||
// return "";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex)
|
||||
{
|
||||
return COLUMN_HEADERS[columnIndex];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Empty tuner editor panel
|
||||
*/
|
||||
public class EmptyTunerEditor extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public EmptyTunerEditor()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[center,grow]", "[center,grow]"));
|
||||
add(new JLabel("No tuner selected"), "wrap");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import io.github.dsheirer.gui.control.JFrequencyControl;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.wave.IRecordingStatusListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||
import io.github.dsheirer.util.SwingUtils;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerModel;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.CharacterIterator;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.StringCharacterIterator;
|
||||
|
||||
/**
|
||||
* Base tuner configuration editor.
|
||||
*/
|
||||
public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration> extends JPanel
|
||||
implements IDiscoveredTunerStatusListener, Listener<TunerEvent>
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(TunerEditor.class);
|
||||
private static final String BUTTON_STATUS_ENABLE = "Enable";
|
||||
private static final String BUTTON_STATUS_DISABLE = "Disable";
|
||||
private static final String BUTTON_STATUS_RESET = "Reset Error";
|
||||
private static final long serialVersionUID = 1L;
|
||||
private UserPreferences mUserPreferences;
|
||||
private TunerManager mTunerManager;
|
||||
private DiscoveredTuner mDiscoveredTuner;
|
||||
private C mTunerConfiguration;
|
||||
private FrequencyAndCorrectionChangeListener mFrequencyAndCorrectionChangeListener = new FrequencyAndCorrectionChangeListener();
|
||||
private JFrequencyControl mFrequencyControl;
|
||||
private JSpinner mFrequencyCorrectionSpinner;
|
||||
private JButton mChangeStatusButton;
|
||||
private JButton mViewSpectrumButton;
|
||||
private JButton mNewSpectrumButton;
|
||||
private JToggleButton mRecordButton;
|
||||
private ButtonPanel mButtonsPanel;
|
||||
private FrequencyPanel mFrequencyPanel;
|
||||
private JLabel mTunerIdLabel;
|
||||
private JCheckBox mAutoPPMCheckBox;
|
||||
private JLabel mMeasuredPPMLabel;
|
||||
private JLabel mRecordingStatusLabel;
|
||||
private JLabel mTunerStatusLabel;
|
||||
private JLabel mTunerLockedStatusLabel;
|
||||
private boolean mLoading = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tunerManager for requesting configuration saves.
|
||||
*/
|
||||
public TunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mTunerManager = tunerManager;
|
||||
mDiscoveredTuner = discoveredTuner;
|
||||
|
||||
if(mDiscoveredTuner != null && mDiscoveredTuner.hasTunerConfiguration())
|
||||
{
|
||||
mTunerConfiguration = (C)mDiscoveredTuner.getTunerConfiguration();
|
||||
}
|
||||
|
||||
if(mDiscoveredTuner != null)
|
||||
{
|
||||
mDiscoveredTuner.addTunerStatusListener(this);
|
||||
|
||||
if(mDiscoveredTuner.hasTuner())
|
||||
{
|
||||
mDiscoveredTuner.getTuner().addTunerEventListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the controls are currently being loaded with values.
|
||||
*/
|
||||
protected boolean isLoading()
|
||||
{
|
||||
return mLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the loading status
|
||||
*/
|
||||
protected void setLoading(boolean loading)
|
||||
{
|
||||
mLoading = loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measured PPM value received from any decoders that may be running.
|
||||
*/
|
||||
protected JLabel getMeasuredPPMLabel()
|
||||
{
|
||||
if(mMeasuredPPMLabel == null)
|
||||
{
|
||||
mMeasuredPPMLabel = new JLabel("");
|
||||
mMeasuredPPMLabel.setToolTipText("Displays the measured frequency error and PPM when provided by compatible channel decoders");
|
||||
}
|
||||
|
||||
return mMeasuredPPMLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner locked status label that can be turned on/off depending on tuner lock state.
|
||||
*/
|
||||
protected JLabel getTunerLockedStatusLabel()
|
||||
{
|
||||
if(mTunerLockedStatusLabel == null)
|
||||
{
|
||||
mTunerLockedStatusLabel = new JLabel("Channel(s) active - frequency and sample rate controls are locked");
|
||||
mTunerLockedStatusLabel.setToolTipText("Indicates that the tuner is providing channel(s) and you can't " +
|
||||
"change the tuner frequency or sample rate");
|
||||
mTunerLockedStatusLabel.setVisible(false);
|
||||
}
|
||||
|
||||
return mTunerLockedStatusLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner status label
|
||||
*/
|
||||
protected JLabel getTunerStatusLabel()
|
||||
{
|
||||
if(mTunerStatusLabel == null)
|
||||
{
|
||||
mTunerStatusLabel = new JLabel(" ");
|
||||
}
|
||||
|
||||
return mTunerStatusLabel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Label to display current file size for a wide-band recording
|
||||
*/
|
||||
protected JLabel getRecordingStatusLabel()
|
||||
{
|
||||
if(mRecordingStatusLabel == null)
|
||||
{
|
||||
mRecordingStatusLabel = new JLabel(" ");
|
||||
mRecordingStatusLabel.setToolTipText("Shows the status of the latest baseband recording when active");
|
||||
mRecordingStatusLabel.setVisible(false);
|
||||
}
|
||||
|
||||
return mRecordingStatusLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check box for enable/disable automatic PPM adjustment from decoder(s) frequency error feedback.
|
||||
*/
|
||||
protected JCheckBox getAutoPPMCheckBox()
|
||||
{
|
||||
if(mAutoPPMCheckBox == null)
|
||||
{
|
||||
mAutoPPMCheckBox = new JCheckBox("Enable decoder(s) to auto-adjust PPM");
|
||||
mAutoPPMCheckBox.setToolTipText("Allow decoders to measure channel frequency error and correct tuner PPM");
|
||||
mAutoPPMCheckBox.addActionListener(e ->
|
||||
{
|
||||
if(!isLoading())
|
||||
{
|
||||
Tuner tuner = getTuner();
|
||||
if(tuner != null)
|
||||
{
|
||||
boolean enabled = getAutoPPMCheckBox().isSelected();
|
||||
getTuner().getTunerController().getFrequencyErrorCorrectionManager().setEnabled(enabled);
|
||||
save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mAutoPPMCheckBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label for displaying the tuner ID
|
||||
*/
|
||||
protected JLabel getTunerIdLabel()
|
||||
{
|
||||
if(mTunerIdLabel == null)
|
||||
{
|
||||
mTunerIdLabel = new JLabel(" ");
|
||||
}
|
||||
|
||||
return mTunerIdLabel;
|
||||
}
|
||||
|
||||
protected FrequencyPanel getFrequencyPanel()
|
||||
{
|
||||
if(mFrequencyPanel == null)
|
||||
{
|
||||
mFrequencyPanel = new FrequencyPanel();
|
||||
mFrequencyPanel.setToolTipText("Tuner frequency and PPM controls");
|
||||
}
|
||||
|
||||
return mFrequencyPanel;
|
||||
}
|
||||
|
||||
protected JSpinner getFrequencyCorrectionSpinner()
|
||||
{
|
||||
if(mFrequencyCorrectionSpinner == null)
|
||||
{
|
||||
SpinnerModel model = new SpinnerNumberModel(0.0, -1000.0, 1000.0, 0.1);
|
||||
mFrequencyCorrectionSpinner = new JSpinner(model);
|
||||
mFrequencyCorrectionSpinner.setToolTipText("Adjust the PPM value to compensate for tuner frequency error");
|
||||
mFrequencyCorrectionSpinner.setEnabled(false);
|
||||
JSpinner.NumberEditor editor = (JSpinner.NumberEditor) mFrequencyCorrectionSpinner.getEditor();
|
||||
DecimalFormat format = editor.getFormat();
|
||||
format.setMinimumFractionDigits(1);
|
||||
editor.getTextField().setHorizontalAlignment(SwingConstants.CENTER);
|
||||
mFrequencyCorrectionSpinner.addChangeListener(mFrequencyAndCorrectionChangeListener);
|
||||
}
|
||||
|
||||
return mFrequencyCorrectionSpinner;
|
||||
}
|
||||
|
||||
protected ButtonPanel getButtonPanel()
|
||||
{
|
||||
if(mButtonsPanel == null)
|
||||
{
|
||||
mButtonsPanel = new ButtonPanel();
|
||||
mButtonsPanel.setToolTipText("Button controls for the selected tuner");
|
||||
}
|
||||
|
||||
return mButtonsPanel;
|
||||
}
|
||||
|
||||
protected JFrequencyControl getFrequencyControl()
|
||||
{
|
||||
if(mFrequencyControl == null)
|
||||
{
|
||||
mFrequencyControl = new JFrequencyControl();
|
||||
}
|
||||
|
||||
return mFrequencyControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button requesting to show tuner in new spectral display
|
||||
*/
|
||||
protected JButton getNewSpectrumButton()
|
||||
{
|
||||
if(mNewSpectrumButton == null)
|
||||
{
|
||||
mNewSpectrumButton = new JButton("New Spectrum Display");
|
||||
mNewSpectrumButton.setToolTipText("Show this tuner in a new (separate) spectral display window");
|
||||
mNewSpectrumButton.addActionListener(e ->
|
||||
{
|
||||
Tuner tuner = getTuner();
|
||||
|
||||
if(tuner != null)
|
||||
{
|
||||
mTunerManager.getDiscoveredTunerModel().broadcast(new TunerEvent(tuner,
|
||||
TunerEvent.Event.REQUEST_NEW_SPECTRAL_DISPLAY));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mNewSpectrumButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button requesting to show tuner in main spectral display
|
||||
*/
|
||||
protected JButton getViewSpectrumButton()
|
||||
{
|
||||
if(mViewSpectrumButton == null)
|
||||
{
|
||||
mViewSpectrumButton = new JButton("View Spectrum");
|
||||
mViewSpectrumButton.setToolTipText("Show this tuner in the spectral display");
|
||||
mViewSpectrumButton.addActionListener(e ->
|
||||
{
|
||||
Tuner tuner = getTuner();
|
||||
|
||||
if(tuner != null)
|
||||
{
|
||||
mTunerManager.getDiscoveredTunerModel().broadcast(new TunerEvent(tuner,
|
||||
TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mViewSpectrumButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button to change enable, disable, or error restart status.
|
||||
*/
|
||||
protected JButton getChangeStatusButton()
|
||||
{
|
||||
if(mChangeStatusButton == null)
|
||||
{
|
||||
mChangeStatusButton = new JButton(BUTTON_STATUS_ENABLE);
|
||||
mChangeStatusButton.setToolTipText("Enable or disable the tuner for use by sdrtrunk, or attempt to reset a tuner error");
|
||||
mChangeStatusButton.addActionListener(e ->
|
||||
{
|
||||
switch(getChangeStatusButton().getText())
|
||||
{
|
||||
case BUTTON_STATUS_DISABLE:
|
||||
mLog.info("Disabling " + getDiscoveredTuner().getTunerClass() + " tuner");
|
||||
getDiscoveredTuner().setEnabled(false);
|
||||
break;
|
||||
case BUTTON_STATUS_ENABLE:
|
||||
mLog.info("Enabling " + getDiscoveredTuner().getTunerClass() + " tuner");
|
||||
getDiscoveredTuner().setEnabled(true);
|
||||
break;
|
||||
case BUTTON_STATUS_RESET:
|
||||
mLog.info("Resetting " + getDiscoveredTuner().getTunerClass() + " tuner");
|
||||
getDiscoveredTuner().reset();
|
||||
break;
|
||||
default:
|
||||
mLog.info("None matched");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mChangeStatusButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off the recorder, if it's currently recording.
|
||||
*/
|
||||
protected void turnOffRecorder()
|
||||
{
|
||||
if(getRecordButton().isSelected())
|
||||
{
|
||||
getRecordButton().setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the tuner's wide-band sample stream.
|
||||
*/
|
||||
protected JToggleButton getRecordButton()
|
||||
{
|
||||
if(mRecordButton == null)
|
||||
{
|
||||
mRecordButton = new JToggleButton("Record");
|
||||
mRecordButton.setToolTipText("Create a baseband recording for this tuner");
|
||||
mRecordButton.setEnabled(false);
|
||||
mRecordButton.addActionListener(e ->
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
if(getRecordButton().isSelected())
|
||||
{
|
||||
getRecordingStatusLabel().setVisible(true);
|
||||
getRecordingStatusLabel().setText(" ");
|
||||
getTuner().getTunerController().startRecorder(mUserPreferences, new RecordingStatusListener(),
|
||||
getDiscoveredTuner().getTunerClass().name());
|
||||
}
|
||||
else
|
||||
{
|
||||
getTuner().getTunerController().stopRecorder();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mRecordButton;
|
||||
}
|
||||
|
||||
protected abstract void save();
|
||||
|
||||
/**
|
||||
* Access the tuner configuration
|
||||
*/
|
||||
protected C getConfiguration()
|
||||
{
|
||||
return mTunerConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this editor has a tuner configuration
|
||||
*/
|
||||
protected boolean hasConfiguration()
|
||||
{
|
||||
return mTunerConfiguration != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovered tuner for this editor
|
||||
*/
|
||||
protected DiscoveredTuner getDiscoveredTuner()
|
||||
{
|
||||
return mDiscoveredTuner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification that the status of the discovered tuner has changed and that the editor implementation must
|
||||
* refresh the UI controls with the current tuner state.
|
||||
*/
|
||||
protected abstract void tunerStatusUpdated();
|
||||
|
||||
/**
|
||||
* Implements the tuner status listener interface to send notification to editor implementations that the
|
||||
* status of the discovered tuner has changed.
|
||||
* @param discoveredTuner that has a status change.
|
||||
* @param previous tuner status
|
||||
* @param current tuner status
|
||||
*/
|
||||
@Override
|
||||
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
|
||||
{
|
||||
SwingUtils.run(() -> tunerStatusUpdated());
|
||||
|
||||
if(current == TunerStatus.ENABLED)
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
getTuner().addTunerEventListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(TunerEvent tunerEvent)
|
||||
{
|
||||
if(tunerEvent.getEvent().equals(TunerEvent.Event.UPDATE_MEASURED_FREQUENCY_ERROR))
|
||||
{
|
||||
SwingUtils.run(() -> getFrequencyPanel().updateFrequencyError());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare this editor for disposal
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
if(mDiscoveredTuner != null)
|
||||
{
|
||||
mDiscoveredTuner.removeTunerStatusListener(this);
|
||||
|
||||
if(mDiscoveredTuner.hasTuner())
|
||||
{
|
||||
mDiscoveredTuner.getTuner().removeTunerEventListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
turnOffRecorder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the discovered tuner has a usable tuner
|
||||
*/
|
||||
public boolean hasTuner()
|
||||
{
|
||||
return mDiscoveredTuner != null && mDiscoveredTuner.hasTuner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the usable tuner from the discovered tuner
|
||||
* Note: use hasTuner() to check before invoking this method.
|
||||
* @return tuner or null.
|
||||
*/
|
||||
public T getTuner()
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
return (T)mDiscoveredTuner.getTuner();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to save the state of this configuration.
|
||||
*/
|
||||
protected void saveConfiguration()
|
||||
{
|
||||
mTunerManager.saveConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lock state for the tuner so that the frequency and sample rate controls can be enabled/disabled.
|
||||
*
|
||||
* @param locked true if the tuner is locked.
|
||||
*/
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
getFrequencyPanel().updateControls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner buttons panel
|
||||
*/
|
||||
public class ButtonPanel extends JPanel
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public ButtonPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0,fill", "[][][][][grow,fill]", ""));
|
||||
add(getChangeStatusButton());
|
||||
add(getRecordButton());
|
||||
add(getViewSpectrumButton());
|
||||
add(getNewSpectrumButton(), "wrap");
|
||||
// add(new JLabel(""), "wrap"); //Spacer to allow recording status label to grow and not affect buttons
|
||||
add(getRecordingStatusLabel(), "span");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state and text of the buttons based on the tuner status.
|
||||
*/
|
||||
public void updateControls()
|
||||
{
|
||||
TunerStatus tunerStatus = getDiscoveredTuner().getTunerStatus();
|
||||
|
||||
getRecordButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
|
||||
getRecordingStatusLabel().setText(" ");
|
||||
getViewSpectrumButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
|
||||
getNewSpectrumButton().setEnabled(tunerStatus.isAvailable() && getDiscoveredTuner().hasTuner());
|
||||
|
||||
switch(tunerStatus)
|
||||
{
|
||||
case ENABLED:
|
||||
getChangeStatusButton().setText(BUTTON_STATUS_DISABLE);
|
||||
break;
|
||||
case DISABLED:
|
||||
getChangeStatusButton().setText(BUTTON_STATUS_ENABLE);
|
||||
break;
|
||||
case ERROR:
|
||||
getChangeStatusButton().setText(BUTTON_STATUS_RESET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub panel that displays frequency control, ppm spinner and control, and tuner locked status label.
|
||||
*/
|
||||
public class FrequencyPanel extends JPanel
|
||||
{
|
||||
public FrequencyPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0,fill", "[][][][][grow,fill]", ""));
|
||||
add(getFrequencyControl(), "spany 2");
|
||||
add(new JLabel("PPM:"));
|
||||
add(getFrequencyCorrectionSpinner());
|
||||
add(getMeasuredPPMLabel(), "wrap");
|
||||
add(getAutoPPMCheckBox(), "span");
|
||||
add(getTunerLockedStatusLabel(), "span");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the frequency panel controls
|
||||
*/
|
||||
public void updateControls()
|
||||
{
|
||||
getFrequencyControl().clearListeners();
|
||||
getFrequencyControl().addListener(mFrequencyAndCorrectionChangeListener);
|
||||
getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked());
|
||||
getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLocked());
|
||||
getFrequencyCorrectionSpinner().setEnabled(hasTuner());
|
||||
getAutoPPMCheckBox().setEnabled(hasTuner());
|
||||
|
||||
Tuner tuner = getTuner();
|
||||
|
||||
if(tuner != null)
|
||||
{
|
||||
getFrequencyControl().setFrequency(tuner.getTunerController().getFrequency(), false);
|
||||
getFrequencyCorrectionSpinner().setValue(tuner.getTunerController().getFrequencyCorrection());
|
||||
getAutoPPMCheckBox().setSelected(tuner.getTunerController().getFrequencyErrorCorrectionManager().isEnabled());
|
||||
getFrequencyControl().addListener(getTuner().getTunerController());
|
||||
getTuner().getTunerController().addListener(getFrequencyControl());
|
||||
getMeasuredPPMLabel().setText(tuner.getTunerController().getMeasuredErrorStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
getFrequencyControl().setFrequency(0, false);
|
||||
getFrequencyCorrectionSpinner().setValue(0);
|
||||
getAutoPPMCheckBox().setSelected(false);
|
||||
getMeasuredPPMLabel().setText("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the measured frequency error label
|
||||
*/
|
||||
public void updateFrequencyError()
|
||||
{
|
||||
SwingUtils.run(() ->
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
getMeasuredPPMLabel().setText(getTuner().getTunerController().getMeasuredErrorStatus());
|
||||
}
|
||||
else
|
||||
{
|
||||
getMeasuredPPMLabel().setText("");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements status listener to receive updates for wide-band recordings
|
||||
*/
|
||||
public class RecordingStatusListener implements IRecordingStatusListener
|
||||
{
|
||||
private DecimalFormat mSizeFormat = new DecimalFormat("0.0");
|
||||
|
||||
@Override
|
||||
public void update(int fileCount, String file, long size)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Recording Size: ").append(humanReadableByteCount(size));
|
||||
sb.append(" File #").append(fileCount).append(": ").append(file);
|
||||
final String status = sb.toString();
|
||||
EventQueue.invokeLater(() -> getRecordingStatusLabel().setText(status));
|
||||
}
|
||||
|
||||
public static String humanReadableByteCount(long bytes) {
|
||||
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
|
||||
if (absB < 1024) {
|
||||
return bytes + " B";
|
||||
}
|
||||
long value = absB;
|
||||
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
|
||||
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
|
||||
value >>= 10;
|
||||
ci.next();
|
||||
}
|
||||
value *= Long.signum(bytes);
|
||||
return String.format("%.1f %cB", value / 1024.0, ci.current());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the frequency correction spinner for changed value.
|
||||
*/
|
||||
private class FrequencyAndCorrectionChangeListener implements ChangeListener, ISourceEventProcessor
|
||||
{
|
||||
//This monitors the frequency correction spinner, applies the changes to the tuner, and saves configuration.
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
final double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTuner().getTunerController().setFrequencyCorrection(value);
|
||||
}
|
||||
catch(SourceException e1)
|
||||
{
|
||||
mLog.error("Error setting frequency correction value", e1);
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
//This monitors the frequency control and saves configuration.
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
|
||||
import io.github.dsheirer.spectrum.SpectrumFrame;
|
||||
import io.github.dsheirer.util.SwingUtils;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TunerSpectralDisplayManager implements Listener<TunerEvent>
|
||||
{
|
||||
private SpectralDisplayPanel mSpectralDisplayPanel;
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private SettingsManager mSettingsManager;
|
||||
private DiscoveredTunerModel mDiscoveredTunerModel;
|
||||
|
||||
public TunerSpectralDisplayManager(SpectralDisplayPanel panel, PlaylistManager playlistManager,
|
||||
SettingsManager settingsManager, DiscoveredTunerModel discoveredTunerModel)
|
||||
{
|
||||
mSpectralDisplayPanel = panel;
|
||||
mPlaylistManager = playlistManager;
|
||||
mSettingsManager = settingsManager;
|
||||
mDiscoveredTunerModel = discoveredTunerModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the first available tuner from the discovered tuner model
|
||||
*/
|
||||
public Tuner showFirstTuner()
|
||||
{
|
||||
List<DiscoveredTuner> availableTuners = mDiscoveredTunerModel.getAvailableTuners();
|
||||
|
||||
for(DiscoveredTuner discoveredTuner: availableTuners)
|
||||
{
|
||||
if(discoveredTuner.hasTuner())
|
||||
{
|
||||
mSpectralDisplayPanel.showTuner(discoveredTuner.getTuner());
|
||||
return discoveredTuner.getTuner();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(TunerEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY:
|
||||
SwingUtils.run(() -> mSpectralDisplayPanel.clearTuner());
|
||||
break;
|
||||
case REQUEST_MAIN_SPECTRAL_DISPLAY:
|
||||
SwingUtils.run(() -> mSpectralDisplayPanel.showTuner(event.getTuner()));
|
||||
break;
|
||||
case REQUEST_NEW_SPECTRAL_DISPLAY:
|
||||
final SpectrumFrame frame = new SpectrumFrame(mPlaylistManager, mSettingsManager, mDiscoveredTunerModel, event.getTuner());
|
||||
SwingUtils.run(() -> frame.setVisible(true));
|
||||
break;
|
||||
case NOTIFICATION_ERROR_STATE:
|
||||
case NOTIFICATION_SHUTTING_DOWN:
|
||||
if(event.getTuner().equals(mSpectralDisplayPanel.getTuner()))
|
||||
{
|
||||
SwingUtils.run(() ->
|
||||
{
|
||||
mSpectralDisplayPanel.clearTuner();
|
||||
ThreadPool.SCHEDULED.schedule(() -> SwingUtils.run(() ->
|
||||
{
|
||||
showFirstTuner();
|
||||
}), 1, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.ui;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.RowSorter;
|
||||
import javax.swing.SortOrder;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Panel containing a discovered tuners table and a tuner editor for a selected tuner.
|
||||
*/
|
||||
public class TunerViewPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerViewPanel.class);
|
||||
private static final String TABLE_PREFERENCE_KEY = "tuner.view.panel";
|
||||
|
||||
private DiscoveredTunerModel mDiscoveredTunerModel;
|
||||
private JTable mTunerTable;
|
||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||
private TableRowSorter<DiscoveredTunerModel> mRowSorter;
|
||||
private JideSplitPane mSplitPane;
|
||||
private DiscoveredTunerEditor mDiscoveredTunerEditor;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tunerManager for tuners
|
||||
* @param userPreferences for making recordings in the tuner editor
|
||||
*/
|
||||
public TunerViewPanel(TunerManager tunerManager, UserPreferences userPreferences)
|
||||
{
|
||||
mDiscoveredTunerModel = tunerManager.getDiscoveredTunerModel();
|
||||
mDiscoveredTunerEditor = new DiscoveredTunerEditor(userPreferences, tunerManager);
|
||||
mUserPreferences = userPreferences;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[fill,grow]", "[fill,grow]"));
|
||||
|
||||
mRowSorter = new TableRowSorter<>(mDiscoveredTunerModel);
|
||||
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
|
||||
sortKeys.add(new RowSorter.SortKey(DiscoveredTunerModel.COLUMN_TUNER_TYPE, SortOrder.ASCENDING));
|
||||
mRowSorter.setSortKeys(sortKeys);
|
||||
|
||||
mTunerTable = new JTable(mDiscoveredTunerModel);
|
||||
mTunerTable.setRowSorter(mRowSorter);
|
||||
mTunerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
mTunerTable.getSelectionModel().addListSelectionListener(event ->
|
||||
{
|
||||
if(!event.getValueIsAdjusting())
|
||||
{
|
||||
int row = mTunerTable.getSelectedRow();
|
||||
|
||||
if(row >= 0)
|
||||
{
|
||||
int modelRow = mTunerTable.convertRowIndexToModel(row);
|
||||
mDiscoveredTunerEditor.setItem(mDiscoveredTunerModel.getDiscoveredTuner(modelRow));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Monitor for tuner removal events so we can update the editor when our selected tuner is removed
|
||||
mDiscoveredTunerModel.addTableModelListener(e ->
|
||||
{
|
||||
//Detect when status is for the currently selected tuner
|
||||
if(e.getType() == TableModelEvent.DELETE &&
|
||||
mDiscoveredTunerEditor.hasItem() &&
|
||||
!mDiscoveredTunerModel.hasTuner(mDiscoveredTunerEditor.getItem()))
|
||||
{
|
||||
mDiscoveredTunerEditor.setItem(null);
|
||||
}
|
||||
});
|
||||
|
||||
mDiscoveredTunerModel.addListener(tunerEvent ->
|
||||
{
|
||||
switch(tunerEvent.getEvent())
|
||||
{
|
||||
case UPDATE_LOCK_STATE:
|
||||
if(tunerEvent.getTuner() != null)
|
||||
{
|
||||
int row = mTunerTable.getSelectedRow();
|
||||
|
||||
if(row >= 0)
|
||||
{
|
||||
int modelRow = mTunerTable.convertRowIndexToModel(row);
|
||||
DiscoveredTuner selectedTuner = mDiscoveredTunerModel.getDiscoveredTuner(modelRow);
|
||||
|
||||
if(selectedTuner != null && selectedTuner.hasTuner() && tunerEvent.getTuner() == selectedTuner.getTuner())
|
||||
{
|
||||
mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLocked());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
TableCellRenderer errorCellRenderer = new TunerStatusCellRenderer();
|
||||
mTunerTable.getColumnModel().getColumn(DiscoveredTunerModel.COLUMN_TUNER_STATUS).setCellRenderer(errorCellRenderer);
|
||||
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTunerTable, TABLE_PREFERENCE_KEY);
|
||||
JScrollPane tunerTableScroller = new JScrollPane(mTunerTable);
|
||||
|
||||
JPanel tunerTablePanel = new JPanel();
|
||||
tunerTablePanel.setLayout(new MigLayout("insets 0 0 0 0", "[fill,grow][]", "[fill,grow][]"));
|
||||
tunerTablePanel.add(tunerTableScroller, "span");
|
||||
|
||||
tunerTablePanel.add(new JLabel("")); //Empty spacer
|
||||
|
||||
// JButton addRecordingTunerButton = new JButton("Add Recording Tuner");
|
||||
// addRecordingTunerButton.addActionListener(e ->
|
||||
// {
|
||||
// AddRecordingTunerDialog dialog = new AddRecordingTunerDialog(mUserPreferences, mDiscoveredTunerModel);
|
||||
// dialog.setLocationRelativeTo(TunerViewPanel.this);
|
||||
// EventQueue.invokeLater(() -> dialog.setVisible(true));
|
||||
// });
|
||||
//
|
||||
// tunerTablePanel.add(addRecordingTunerButton);
|
||||
|
||||
tunerTablePanel.setPreferredSize(new Dimension(200,200));
|
||||
JScrollPane editorScroller = new JScrollPane(mDiscoveredTunerEditor);
|
||||
editorScroller.setPreferredSize(new Dimension(200, 200));
|
||||
|
||||
mSplitPane = new JideSplitPane();
|
||||
mSplitPane.setOrientation(JideSplitPane.HORIZONTAL_SPLIT);
|
||||
mSplitPane.add(tunerTablePanel);
|
||||
mSplitPane.add(editorScroller);
|
||||
|
||||
add(mSplitPane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for the TunerStatus enumeration column
|
||||
*/
|
||||
public class TunerStatusCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
|
||||
int row, int column)
|
||||
{
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof TunerStatus tunerStatus)
|
||||
{
|
||||
if(tunerStatus == TunerStatus.ERROR)
|
||||
{
|
||||
component.setForeground(Color.RED);
|
||||
}
|
||||
else if(tunerStatus == TunerStatus.DISABLED)
|
||||
{
|
||||
component.setForeground(Color.DARK_GRAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.setForeground(table.getForeground());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
component.setForeground(table.getForeground());
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsb;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Scheduled runnable for the primary LibUsb processing thread. Each USB device's transfer processor
|
||||
* registers with this processor to support auto-start/stop of this event processing thread.
|
||||
*/
|
||||
public class USBMasterProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(USBMasterProcessor.class);
|
||||
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
|
||||
private List<USBTransferProcessor> mRegisteredProcessors = new CopyOnWriteArrayList<>();
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private Processor mProcessor = new Processor();
|
||||
private Thread mProcessorThread;
|
||||
|
||||
/**
|
||||
* 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))
|
||||
{
|
||||
mLog.info("Starting USB master processor thread");
|
||||
if(mProcessorThread == null)
|
||||
{
|
||||
mProcessor.reset();
|
||||
mProcessorThread = new Thread(mProcessor);
|
||||
mProcessorThread.setName("sdrtrunk USB event processor");
|
||||
mProcessorThread.setPriority(Thread.MAX_PRIORITY);
|
||||
mProcessorThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops processing of LibUSB timeout events
|
||||
*/
|
||||
private void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
mLog.info("Stopping USB master processor thread");
|
||||
if(mProcessorThread != null)
|
||||
{
|
||||
mProcessor.stop();
|
||||
mProcessorThread.interrupt();
|
||||
|
||||
try
|
||||
{
|
||||
mProcessorThread.join(1000);
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
//No action
|
||||
}
|
||||
|
||||
mProcessorThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the libusb timeout processor and prepares for shutdown.
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable to invoke LibUSB timeout handler. All downstream completed transfer processing will occur on this
|
||||
* thread instance.
|
||||
*/
|
||||
class Processor implements Runnable
|
||||
{
|
||||
private boolean mRunning = true;
|
||||
|
||||
/**
|
||||
* Sets stop flag to signal to stop processing
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this processor so that it can be reused.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while(mRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
LibUsb.handleEvents(null);
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing LibUSB timeout events", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,587 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
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 = 40;
|
||||
|
||||
private LinkedTransferQueue<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
|
||||
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
||||
private LinkedTransferQueue<Transfer> mCompletedTransfers = new LinkedTransferQueue<>();
|
||||
private List<Transfer> mTransfersToDispose = new ArrayList<>();
|
||||
private List<Transfer> mTransfersToSubmit = new ArrayList<>();
|
||||
|
||||
//Tuner specific native buffer factory for processing/conversion of raw samples
|
||||
private INativeBufferFactory mNativeBufferFactory;
|
||||
|
||||
//Byte array transfer buffers size in bytes
|
||||
private int mBufferSize;
|
||||
|
||||
private Listener<INativeBuffer> mNativeBufferListener;
|
||||
|
||||
//Handle to the USB bulk transfer device
|
||||
private DeviceHandle mUsbBulkTransferDeviceHandle;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private AtomicBoolean mRestarting = new AtomicBoolean();
|
||||
private CompletedTransferProcessor mCompletedTransferProcessor = new CompletedTransferProcessor();
|
||||
private ScheduledFuture mBufferDispatcherFuture;
|
||||
private String mDeviceName;
|
||||
private int mTransferErrorLoggingCount = 0;
|
||||
private int mRestartCount = 0;
|
||||
private ITunerErrorListener mITunerErrorListener;
|
||||
|
||||
/**
|
||||
* 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 usbBulkTransferDeviceHandle to the USB bulk transfer device
|
||||
* @param nativeBufferFactory 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 usbBulkTransferDeviceHandle,
|
||||
INativeBufferFactory nativeBufferFactory, int bufferSize,
|
||||
ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
mDeviceName = deviceName;
|
||||
mUsbBulkTransferDeviceHandle = usbBulkTransferDeviceHandle;
|
||||
mNativeBufferFactory = nativeBufferFactory;
|
||||
mBufferSize = bufferSize;
|
||||
mITunerErrorListener = tunerErrorListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the usb transfer buffer size used for transfering native byte buffers from the
|
||||
* USB device. Note: changing the buffer size while the transfer processor is running causes
|
||||
* the transfer to stop momentarily while the existing buffers are destroyed and new buffers
|
||||
* with the correct size are recreated.
|
||||
*
|
||||
* Note: this method is not thread safe. Ensure that no other threads invoke stop() or start()
|
||||
* while a buffer size change is in progress.
|
||||
*
|
||||
* @param bufferSize to use for native usb buffer transfers
|
||||
*/
|
||||
public void setBufferSize(int bufferSize)
|
||||
{
|
||||
if(bufferSize % 2 == 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer size must be a multiple of 2 for complex samples");
|
||||
}
|
||||
|
||||
if(mBufferSize != bufferSize)
|
||||
{
|
||||
stop();
|
||||
mBufferSize = bufferSize;
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start USB transfer buffer processing. Subsequent calls to this method after started will be ignored.
|
||||
*
|
||||
* @return true if start successful with no errors or is already running. returns false if errors on startup.
|
||||
*/
|
||||
private boolean start()
|
||||
{
|
||||
boolean success = false;
|
||||
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
prepareDeviceStart();
|
||||
prepareTransfers();
|
||||
|
||||
if(submitTransfers())
|
||||
{
|
||||
success = true;
|
||||
//Start transferred buffer dispatcher
|
||||
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mCompletedTransferProcessor,
|
||||
0, 6, TimeUnit.MILLISECONDS);
|
||||
|
||||
//Register with LibUSB processor so that it auto-starts LibUSB processing
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop USB transfer buffer processing. Subsequent calls to this method after stopped will be ignored.
|
||||
*/
|
||||
private void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mBufferDispatcherFuture != null)
|
||||
{
|
||||
mBufferDispatcherFuture.cancel(false);
|
||||
mBufferDispatcherFuture = null;
|
||||
}
|
||||
|
||||
//Cancel all buffers that are currently in progress
|
||||
for(Transfer transfer : mInProgressTransfers)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
|
||||
//Await completion of all in-progress transfers
|
||||
int waitCycleCount = 0;
|
||||
while(!mInProgressTransfers.isEmpty() && waitCycleCount < 30)
|
||||
{
|
||||
waitCycleCount++;
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
//No - action
|
||||
}
|
||||
}
|
||||
|
||||
//Clear all completed buffers
|
||||
Transfer completedTransfer = mCompletedTransfers.poll();
|
||||
|
||||
while(completedTransfer != null)
|
||||
{
|
||||
mAvailableTransfers.add(completedTransfer);
|
||||
completedTransfer = mCompletedTransfers.poll();
|
||||
}
|
||||
|
||||
//Unregister from LibUSB processor so that it auto-stops LibUSB event timeout processing
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this);
|
||||
|
||||
executeDeviceStop();
|
||||
|
||||
disposeTransfers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the device after there is an error. Initially stops the device and then schedules a start() to
|
||||
* occur in 10 milliseconds.
|
||||
*/
|
||||
private void restart()
|
||||
{
|
||||
if(mRestarting.compareAndSet(false, true))
|
||||
{
|
||||
stop();
|
||||
|
||||
if(mRestartCount >= 3)
|
||||
{
|
||||
mLog.warn("[" + mDeviceName + "] - maximum restart attempts [" + mRestartCount +
|
||||
"] exceeded for tuner - stopping");
|
||||
|
||||
if(mITunerErrorListener != null)
|
||||
{
|
||||
mITunerErrorListener.setErrorMessage("USB ERROR");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mRestartCount++;
|
||||
|
||||
//Attempt to clear any halt condition
|
||||
LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT);
|
||||
|
||||
mLog.info("[" + mDeviceName + "] - restart attempt [" + mRestartCount + "]");
|
||||
|
||||
Runnable runnable = () ->
|
||||
{
|
||||
mRestarting.set(false);
|
||||
|
||||
boolean success = start();
|
||||
|
||||
if(!success)
|
||||
{
|
||||
mLog.warn("[" + mDeviceName + "] - restart attempt [" + mRestartCount + "] failed - retrying");
|
||||
restart();
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 20, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Submits transfer buffers to the USB device. Since we're offloading byte buffer to complex sample conversion
|
||||
* to another thread, there may be times when sufficient transfer buffers are not available to keep a level number
|
||||
* of transfer buffers in progress. Therefore, we track the transfer buffers to submit deficit number and attempt
|
||||
* to play catch-up each time this method is invoked.
|
||||
*
|
||||
* @return boolean true if there were no errors submitting transfer buffers. A false value indicates that there
|
||||
* were errors and that the device likely needs to be reset.
|
||||
*/
|
||||
private boolean submitTransfers()
|
||||
{
|
||||
if(mRunning.get())
|
||||
{
|
||||
mAvailableTransfers.drainTo(mTransfersToSubmit);
|
||||
|
||||
if(!mTransfersToSubmit.isEmpty())
|
||||
{
|
||||
Transfer transfer = mTransfersToSubmit.remove(0);
|
||||
|
||||
while(transfer != null)
|
||||
{
|
||||
int status = LibUsb.submitTransfer(transfer);
|
||||
|
||||
switch(status)
|
||||
{
|
||||
case LibUsb.SUCCESS:
|
||||
mInProgressTransfers.add(transfer);
|
||||
|
||||
if(!mTransfersToSubmit.isEmpty())
|
||||
{
|
||||
transfer = mTransfersToSubmit.remove(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
transfer = null;
|
||||
}
|
||||
break;
|
||||
case LibUsb.ERROR_PIPE:
|
||||
mLog.warn("[" + mDeviceName + "] - USB Pipe Error while submitting transfer buffers");
|
||||
LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT);
|
||||
mTransfersToDispose.add(transfer);
|
||||
transfer = null;
|
||||
break;
|
||||
default:
|
||||
mLog.error("[" + mDeviceName + "] - " + getErrorStatus(status) + " while submitting transfer buffers");
|
||||
mTransfersToDispose.add(transfer);
|
||||
transfer = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we aborted submitting transfers or if we had an error while submitting, move the remaining transfers
|
||||
// back to the available queue and restart the device
|
||||
if(!mTransfersToSubmit.isEmpty())
|
||||
{
|
||||
mAvailableTransfers.addAll(mTransfersToSubmit);
|
||||
mTransfersToSubmit.clear();;
|
||||
}
|
||||
}
|
||||
|
||||
return mTransfersToDispose.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all allocated transfers in preparation for shutdown
|
||||
*/
|
||||
private void disposeTransfers()
|
||||
{
|
||||
mAvailableTransfers.drainTo(mTransfersToDispose);
|
||||
|
||||
if(mTransfersToDispose.size() != TRANSFER_BUFFER_POOL_SIZE)
|
||||
{
|
||||
mLog.warn("[" + mDeviceName + "] - potential memory leak [" +
|
||||
mTransfersToDispose.size() + " of " + TRANSFER_BUFFER_POOL_SIZE +
|
||||
"] USB transfer buffers will be disposed during shutdown");
|
||||
}
|
||||
|
||||
for(Transfer transfer: mTransfersToDispose)
|
||||
{
|
||||
LibUsb.freeTransfer(transfer);
|
||||
}
|
||||
|
||||
mTransfersToDispose.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener and auto-starts the buffer processor
|
||||
*/
|
||||
public void setListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
if(mNativeBufferListener == null || !mNativeBufferListener.equals(listener))
|
||||
{
|
||||
mNativeBufferListener = listener;
|
||||
|
||||
boolean success = start();
|
||||
|
||||
if(!success)
|
||||
{
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-stops the buffer processor and removes the listener
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
if(mNativeBufferListener != null)
|
||||
{
|
||||
stop();
|
||||
mNativeBufferListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 memory 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, mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this,
|
||||
"Buffer", USB_TIMEOUT_MS);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a filled transfer buffer received back from the USB device. Note: this method is invoked on the USB
|
||||
* bus processing thread, so we try to keep processing to a minimum and place transfers in the completed
|
||||
* transfer queue so that the scheduled processor thread handles any conversion and additional downstream
|
||||
* processing workload.
|
||||
*/
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mInProgressTransfers.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
case LibUsb.TRANSFER_TIMED_OUT:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
mCompletedTransfers.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
transfer.buffer().rewind();
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_ERROR:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
mCompletedTransfers.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
transfer.buffer().rewind();
|
||||
mAvailableTransfers.add(transfer);
|
||||
|
||||
mTransferErrorLoggingCount++;
|
||||
|
||||
if(mTransferErrorLoggingCount <= 5)
|
||||
{
|
||||
//Handle integer overflow
|
||||
if(mTransferErrorLoggingCount < 0)
|
||||
{
|
||||
mTransferErrorLoggingCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
transfer.buffer().rewind();
|
||||
mAvailableTransfers.add(transfer);
|
||||
break;
|
||||
default:
|
||||
//Unexpected transfer error - need to reset the bulk transfer interface
|
||||
mLog.error("[" + mDeviceName + "] - transfer error [" + getTransferStatus(transfer.status()) +
|
||||
"] transferred actual: " + transfer.actualLength());
|
||||
transfer.buffer().rewind();
|
||||
mAvailableTransfers.add(transfer);
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the error status code to a textual description
|
||||
*/
|
||||
public static String getErrorStatus(int status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case LibUsb.SUCCESS:
|
||||
return "SUCCESS";
|
||||
case LibUsb.ERROR_IO:
|
||||
return "USB I/O ERROR";
|
||||
case LibUsb.ERROR_INVALID_PARAM:
|
||||
return "USB INVALID PARAMETERS ERROR";
|
||||
case LibUsb.ERROR_ACCESS:
|
||||
return "USB ACCESS ERROR";
|
||||
case LibUsb.ERROR_NO_DEVICE:
|
||||
return "USB NO DEVICE ERROR";
|
||||
case LibUsb.ERROR_NOT_FOUND:
|
||||
return "USB NOT FOUND ERROR";
|
||||
case LibUsb.ERROR_BUSY:
|
||||
return "USB BUSY ERROR";
|
||||
case LibUsb.ERROR_TIMEOUT:
|
||||
return "USB TIMEOUT ERROR";
|
||||
case LibUsb.ERROR_OVERFLOW:
|
||||
return "USB OVERFLOW ERROR";
|
||||
case LibUsb.ERROR_PIPE:
|
||||
return "USB PIPE ERROR";
|
||||
case LibUsb.ERROR_INTERRUPTED:
|
||||
return "USB INTERRUPTED ERROR";
|
||||
case LibUsb.ERROR_NO_MEM:
|
||||
return "USB NO MEMORY ERROR";
|
||||
case LibUsb.ERROR_NOT_SUPPORTED:
|
||||
return "USB NOT SUPPORTED ERROR";
|
||||
default:
|
||||
return "USB UNKNOWN [" + status + "] ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes completed USB buffer transfers, converts the transferred bytes into complex samples and dispatches the
|
||||
* sample buffer to the listener.
|
||||
*/
|
||||
public class CompletedTransferProcessor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Transfer transfer = mCompletedTransfers.poll();
|
||||
|
||||
while(transfer != null)
|
||||
{
|
||||
if(mRunning.get())
|
||||
{
|
||||
if(mNativeBufferListener != null)
|
||||
{
|
||||
//Pass the transfer's byte buffer so the native buffer factory can make a copy of the
|
||||
//byte array contents and package it as a native buffer.
|
||||
INativeBuffer nativeBuffer = mNativeBufferFactory.getBuffer(transfer.buffer(),
|
||||
System.currentTimeMillis());
|
||||
mNativeBufferListener.receive(nativeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
transfer.buffer().rewind();
|
||||
mAvailableTransfers.add(transfer);
|
||||
|
||||
boolean success = submitTransfers();
|
||||
|
||||
if(success)
|
||||
{
|
||||
transfer = mCompletedTransfers.poll();
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadPool.CACHED.submit(() -> restart());
|
||||
transfer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("[" + mDeviceName + "] - error while processing USB transfer buffers", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,54 +19,679 @@
|
||||
package io.github.dsheirer.source.tuner.usb;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Context;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.DeviceList;
|
||||
import org.usb4java.LibUsb;
|
||||
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.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Tuner controller implementation for USB tuners. Manages general USB operations and incorporates threaded USB
|
||||
* Transfer processing.
|
||||
*/
|
||||
public abstract class USBTunerController extends TunerController
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(USBTunerController.class);
|
||||
private static final int USB_INTERFACE = 0x0; //Common value for all currently supported devices
|
||||
private static final int USB_CONFIGURATION = 0x1; //Common value for all currently supported devices
|
||||
private static final int USB_BULK_TRANSFER_BUFFER_POOL_SIZE = 8;
|
||||
private static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
|
||||
private static final long USB_BULK_TRANSFER_TIMEOUT_MS = 2000l;
|
||||
|
||||
private int mBus;
|
||||
private int mPort;
|
||||
private Context mDeviceContext = new Context();
|
||||
private Device mDevice;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
private DeviceDescriptor mDeviceDescriptor;
|
||||
private TransferManager mTransferManager = new TransferManager();
|
||||
private UsbEventProcessor mEventProcessor = new UsbEventProcessor();
|
||||
private AtomicBoolean mStreaming = new AtomicBoolean();
|
||||
private ReentrantLock mListenerLock = new ReentrantLock();
|
||||
private boolean mRunning = false;
|
||||
|
||||
/**
|
||||
* USB tuner controller class. Provides auto-start and auto-stop function when complex buffer listeners are added
|
||||
* or removed from this tuner controller.
|
||||
*
|
||||
* @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
|
||||
* @param bus number USB
|
||||
* @param port number USB
|
||||
* @param tunerErrorListener to receive errors from this tuner controller
|
||||
*/
|
||||
public USBTunerController(long minimumFrequency, long maximumFrequency, int middleUnusable, double usableBandwidth)
|
||||
public USBTunerController(int bus, int port, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(minimumFrequency, maximumFrequency, middleUnusable, usableBandwidth);
|
||||
super(tunerErrorListener);
|
||||
mBus = bus;
|
||||
mPort = port;
|
||||
}
|
||||
|
||||
protected abstract USBTransferProcessor getUSBTransferProcessor();
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus usb
|
||||
* @param port usb
|
||||
* @param minimum tunable frequency in Hertz
|
||||
* @param maximum tunable frequency in Hertz
|
||||
* @param halfBandwidth that is unusable for DC spike avoidance
|
||||
* @param usablePercent bandwith in Hertz
|
||||
* @param tunerErrorListener to receive errors from this tuner controller
|
||||
*/
|
||||
public USBTunerController(int bus, int port, long minimum, long maximum, int halfBandwidth, double usablePercent,
|
||||
ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
this(bus, port, tunerErrorListener);
|
||||
setMinimumFrequency(minimum);
|
||||
setMaximumFrequency(maximum);
|
||||
setMiddleUnusableHalfBandwidth(halfBandwidth);
|
||||
setUsableBandwidthPercentage(usablePercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started.
|
||||
* Tuner type for this USB controller
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Factory for converting received streaming sample data into native buffers, as provided by sub-class.
|
||||
*/
|
||||
protected abstract INativeBufferFactory getNativeBufferFactory();
|
||||
|
||||
/**
|
||||
* Sub-class definition of transfer buffer sizes to use for the tuner. Note: this should be a power-of-two
|
||||
* value for compatibility with downstream (SIMD) operations (e.g. 65,536, 131072, 262144, etc.)
|
||||
* @return transfer buffer size.
|
||||
*/
|
||||
protected abstract int getTransferBufferSize();
|
||||
|
||||
/**
|
||||
* Sub-class method to perform additional device setup steps after the USB interface has been claimed and before any
|
||||
* transfer operations start.
|
||||
* @throws SourceException if there is an issue in configuring the device
|
||||
*/
|
||||
protected abstract void deviceStart() throws SourceException;
|
||||
|
||||
/**
|
||||
* Sub-class method to perform additional device shutdown steps after transfer processing has stopped and before
|
||||
* the USB interface is released.
|
||||
*/
|
||||
protected abstract void deviceStop();
|
||||
|
||||
/**
|
||||
* Starts or initializes this tuner.
|
||||
*
|
||||
* Note: sub-class implementations should override and invoke this method and then perform additional initialization
|
||||
* operations for the tuner.
|
||||
*
|
||||
* @throws SourceException if there is an error and/or this tuner is unusable.
|
||||
*/
|
||||
public final void start() throws SourceException
|
||||
{
|
||||
if(mDeviceContext == null)
|
||||
{
|
||||
throw new SourceException("Device cannot be reused once it has been shutdown");
|
||||
}
|
||||
|
||||
int status = LibUsb.init(mDeviceContext);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDevice = findDevice();
|
||||
mDeviceDescriptor = new DeviceDescriptor();
|
||||
status = LibUsb.getDeviceDescriptor(mDevice, mDeviceDescriptor);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
status = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
if(status == LibUsb.ERROR_ACCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
|
||||
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
|
||||
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Detach the kernel driver if active and detach is supported. Otherwise, let the claim interface fail.
|
||||
status = LibUsb.kernelDriverActive(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status == 1) //kernel driver is attached and detach operation is supported
|
||||
{
|
||||
status = LibUsb.detachKernelDriver(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPort);
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't detach kernel driver");
|
||||
}
|
||||
}
|
||||
|
||||
//Set the configuration which also invokes a soft reset on the device
|
||||
status = LibUsb.setConfiguration(mDeviceHandle, USB_CONFIGURATION);
|
||||
|
||||
if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mLog.error("Unable to set USB configuration on tuner - device is busy (in use by another application)");
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("USB tuner is in-use by another application");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't set configuration (ie reset) on the USB tuner - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Claim the interface
|
||||
status = LibUsb.claimInterface(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("USB tuner is in-use by another application");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Set running true for deviceStart() operations that require it.
|
||||
mRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
deviceStart();
|
||||
}
|
||||
catch(Exception se)
|
||||
{
|
||||
mRunning = false;
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the tuner for full shutdown by stopping streaming, shutdown the device, and releasing the USB resources.
|
||||
*/
|
||||
public final void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
stopStreaming();
|
||||
mNativeBufferBroadcaster.clear();
|
||||
deviceStop();
|
||||
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
|
||||
LibUsb.close(mDeviceHandle);
|
||||
mDeviceHandle = null;
|
||||
mDevice = null;
|
||||
mDeviceDescriptor = null;
|
||||
}
|
||||
|
||||
LibUsb.exit(mDeviceContext);
|
||||
mDeviceContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts streaming data from the tuner
|
||||
*/
|
||||
private void startStreaming()
|
||||
{
|
||||
if(mStreaming.compareAndSet(false, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
prepareStreaming();
|
||||
List<Transfer> transfers = mTransferManager.getTransfers();
|
||||
mEventProcessor.start();
|
||||
mTransferManager.setAutoResubmitTransfers(true);
|
||||
mTransferManager.submitTransfers(transfers);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error starting streaming on USB tuner", se);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to start streaming. This method can be overridden by sub-class to implement additional actions
|
||||
* need to prepare before start streaming.
|
||||
*/
|
||||
protected void prepareStreaming()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop streaming data from the tuner
|
||||
*/
|
||||
private void stopStreaming()
|
||||
{
|
||||
if(mStreaming.compareAndSet(true, false))
|
||||
{
|
||||
//Turn off auto-resubmit of USB transfer buffers
|
||||
mTransferManager.setAutoResubmitTransfers(false);
|
||||
|
||||
//Stop event processing thread to put all submitted tranfers in a stable state - blocks until stopped
|
||||
mEventProcessor.stop();
|
||||
|
||||
//Cancel all currently submitted transfers
|
||||
mTransferManager.cancelTransfers();
|
||||
|
||||
//Perform final event processing iteration so LibUsb returns all of our cancelled tranfers
|
||||
mEventProcessor.handleFinalEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the USB device for this tuner at the specified USB bus and port.
|
||||
* @return discovered USB device
|
||||
* @throws SourceException if there is an error or the device is not discovered.
|
||||
*/
|
||||
private Device findDevice() throws SourceException
|
||||
{
|
||||
DeviceList deviceList = new DeviceList();
|
||||
int count = LibUsb.getDeviceList(mDeviceContext, deviceList);
|
||||
|
||||
if(count >= 0)
|
||||
{
|
||||
for(Device device: deviceList)
|
||||
{
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
int port = LibUsb.getPortNumber(device);
|
||||
|
||||
if(mBus == bus && mPort == port)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new SourceException("LibUsb couldn't discover USB device [" + mBus + ":" + mPort +
|
||||
"] from device list" + (count < 0 ? " - error: " + LibUsb.errorName(count) : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the discovered USB device.
|
||||
*/
|
||||
protected Device getDevice()
|
||||
{
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb context for this device.
|
||||
*/
|
||||
protected Context getDeviceContext()
|
||||
{
|
||||
return mDeviceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb device descriptor for this device
|
||||
*/
|
||||
protected DeviceDescriptor getDeviceDescriptor()
|
||||
{
|
||||
return mDeviceDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* USB Device Handle for the claimed device
|
||||
*/
|
||||
protected DeviceHandle getDeviceHandle()
|
||||
{
|
||||
return mDeviceHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the device handle is non-null
|
||||
*/
|
||||
protected boolean hasDeviceHandle()
|
||||
{
|
||||
return getDeviceHandle() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this device is usable, meaning it has been started and is not yet stopping.
|
||||
*
|
||||
* Note: this is a general usability flag for controlling all code that touches the USB interface(s)
|
||||
*/
|
||||
protected boolean isRunning()
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the IQ buffer listener and automatically starts stream buffer transfer processing, if not already started.
|
||||
*/
|
||||
@Override
|
||||
public void addBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
boolean hasExistingListeners = hasBufferListeners();
|
||||
|
||||
super.addBufferListener(listener);
|
||||
|
||||
if(!hasExistingListeners)
|
||||
if(isRunning())
|
||||
{
|
||||
getUSBTransferProcessor().setListener(this);
|
||||
mListenerLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
boolean hasExistingListeners = hasBufferListeners();
|
||||
|
||||
super.addBufferListener(listener);
|
||||
|
||||
if(!hasExistingListeners)
|
||||
{
|
||||
startStreaming();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mListenerLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners.
|
||||
* Removes the IQ buffer listener and stops stream buffer transfer processing if there are no more listeners.
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
super.removeBufferListener(listener);
|
||||
mListenerLock.lock();
|
||||
|
||||
if(!hasBufferListeners())
|
||||
try
|
||||
{
|
||||
getUSBTransferProcessor().removeListener();
|
||||
super.removeBufferListener(listener);
|
||||
|
||||
if(!hasBufferListeners())
|
||||
{
|
||||
stopStreaming();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mListenerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages USB transfer (ie zero-copy) buffer processing
|
||||
*/
|
||||
class TransferManager implements TransferCallback
|
||||
{
|
||||
private List<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
||||
private boolean mAutoResubmitTransfers = false;
|
||||
|
||||
/**
|
||||
* Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory
|
||||
* byte buffers outside the JVM.
|
||||
*
|
||||
* @return list of transfers
|
||||
* @throws SourceException if there is an error creating transfers
|
||||
*/
|
||||
private List<Transfer> getTransfers() throws SourceException
|
||||
{
|
||||
if(mAvailableTransfers == null)
|
||||
{
|
||||
mAvailableTransfers = new ArrayList<>();
|
||||
|
||||
for(int x = 0; x < USB_BULK_TRANSFER_BUFFER_POOL_SIZE; x++)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new SourceException("Couldn't allocate USB transfer buffer - out of memory");
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(getTransferBufferSize());
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer,
|
||||
TransferManager.this, "Transfer Buffer " + x, USB_BULK_TRANSFER_TIMEOUT_MS);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTransfers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to stop processing transfers when stopping streaming of data.
|
||||
*/
|
||||
private void setAutoResubmitTransfers(boolean resubmit)
|
||||
{
|
||||
mAutoResubmitTransfers = resubmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the transfers to start sample stream processing
|
||||
* @param transfers to submit
|
||||
*/
|
||||
private void submitTransfers(List<Transfer> transfers)
|
||||
{
|
||||
for(Transfer transfer: transfers)
|
||||
{
|
||||
submitTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Submits the transfer for stream processing
|
||||
* @param transfer to (re)submit
|
||||
*/
|
||||
private void submitTransfer(Transfer transfer)
|
||||
{
|
||||
int status = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
mInProgressTransfers.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error submitting USB transfer - " + LibUsb.errorName(status));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any in-progress transfers to prepare for shutdown.
|
||||
*
|
||||
* Note: this should only be invoked after the LibUsb event processing thread has been stopped so that the
|
||||
* transfer buffers are in a stable (submitted vs callback) state and we can then flip their cancel state and
|
||||
* then finish processing the timeout events under the control of a single (shutdown) thread.
|
||||
*/
|
||||
private void cancelTransfers()
|
||||
{
|
||||
for(Transfer transfer: mInProgressTransfers)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mInProgressTransfers.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
case LibUsb.TRANSFER_TIMED_OUT:
|
||||
case LibUsb.TRANSFER_ERROR:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
dispatchTransfer(transfer);
|
||||
}
|
||||
|
||||
transfer.buffer().rewind();
|
||||
|
||||
if(mAutoResubmitTransfers)
|
||||
{
|
||||
submitTransfer(transfer);
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
//Reset the transfer but don't do anything else
|
||||
transfer.buffer().rewind();
|
||||
break;
|
||||
default:
|
||||
//Unexpected transfer error - need to reset the bulk transfer interface
|
||||
transfer.buffer().rewind();
|
||||
//TODO: usually the device is in a bad state at this point ... need to raise the error flag
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the transfer and dereferences the native byte buffer so that it can be garbage collected.
|
||||
*/
|
||||
private void disposeTransfers()
|
||||
{
|
||||
for(Transfer transfer: mAvailableTransfers)
|
||||
{
|
||||
transfer.setBuffer(null);
|
||||
}
|
||||
|
||||
mAvailableTransfers.clear();
|
||||
mAvailableTransfers = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of the transfer's native memory byte array payload so that the transfer can be reused.
|
||||
* Dispatches the native buffer to registered listeners.
|
||||
* @param transfer to copy and dispatch
|
||||
*/
|
||||
private void dispatchTransfer(Transfer transfer)
|
||||
{
|
||||
//Pass the transfer's byte buffer so the native buffer factory can make a copy of the byte array contents
|
||||
//and package it as a native buffer.
|
||||
INativeBuffer nativeBuffer = getNativeBufferFactory().getBuffer(transfer.buffer(), System.currentTimeMillis());
|
||||
mNativeBufferBroadcaster.broadcast(nativeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Threaded LibUsb event processor - continuously polls LibUsb to process events exclusively for this USB tuner
|
||||
* device using the device context.
|
||||
*/
|
||||
class UsbEventProcessor implements Runnable
|
||||
{
|
||||
private Thread mThread;
|
||||
private boolean mProcessing = false;
|
||||
|
||||
/**
|
||||
* Start the event processing thread
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mThread == null)
|
||||
{
|
||||
mProcessing = true;
|
||||
mThread = new Thread(this);
|
||||
mThread.setName("sdrtrunk USB tuner - bus [" + mBus + "] port [" + mPort + "]");
|
||||
mThread.setPriority(Thread.MAX_PRIORITY);
|
||||
mThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stop processing flag and block until the thread stops, blocking up to 1000 ms.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mProcessing = false;
|
||||
|
||||
try
|
||||
{
|
||||
//Give the thread a second to stop - it should happen quickly because it's only checking transfers
|
||||
//for completed status and returning them to us to dispatch.
|
||||
mThread.join(1000);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error stopping LibUsb event processing thread - " + e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
mThread = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This performs a final handle-events invocation after the event processing thread has been shutdown and
|
||||
* transfers have been flagged as cancelled. This should cause LibUsb to return all in-progress and now
|
||||
* canceled transfers back to us via the TransferManager.processTransfer() method.
|
||||
*/
|
||||
public void handleFinalEvents()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Use a short timeout since this is a shutdown operation
|
||||
LibUsb.handleEventsTimeout(mDeviceContext, 50);
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing stop-streaming LibUsb timeout events", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb event/timeout processing loop
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mProcessing = true;
|
||||
|
||||
while(mProcessing)
|
||||
{
|
||||
try
|
||||
{
|
||||
LibUsb.handleEventsTimeout(mDeviceContext, 250);
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing LibUsb timeout events", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
{
|
||||
UsbServices services = UsbHostManager.getUsbServices();
|
||||
|
||||
UsbHub root = services.getRootUsbHub();
|
||||
|
||||
ArrayList<UsbDevice> devices = new ArrayList<UsbDevice>(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();
|
||||
|
||||
for (UsbDevice child : children) {
|
||||
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()).append("\n\n");
|
||||
|
||||
for( Object configObject: device.getUsbConfigurations() )
|
||||
{
|
||||
UsbConfiguration config = (UsbConfiguration)configObject;
|
||||
|
||||
sb.append(config.getUsbConfigurationDescriptor().toString()).append("\n\n");
|
||||
|
||||
for( Object interfaceObject: config.getUsbInterfaces() )
|
||||
{
|
||||
UsbInterface iface = (UsbInterface)interfaceObject;
|
||||
|
||||
sb.append(iface.getUsbInterfaceDescriptor().toString()).append("\n\n");
|
||||
|
||||
for( Object endpointObject: iface.getUsbEndpoints() )
|
||||
{
|
||||
UsbEndpoint endpoint = (UsbEndpoint)endpointObject;
|
||||
|
||||
sb.append(endpoint.getUsbEndpointDescriptor().toString()).append("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
141
src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java
Normal file
141
src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.List;
|
||||
|
||||
public class UsbUtils
|
||||
{
|
||||
/**
|
||||
* Returns a List of currently connected usb devices
|
||||
*/
|
||||
public static List<UsbDevice> getDevices() throws SecurityException, UsbException, UnsupportedEncodingException
|
||||
{
|
||||
UsbServices services = UsbHostManager.getUsbServices();
|
||||
UsbHub root = services.getRootUsbHub();
|
||||
List<UsbDevice> devices = new ArrayList<UsbDevice>(getHubDevices(root));
|
||||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of devices attached to the hub
|
||||
*/
|
||||
public static List<UsbDevice> getHubDevices(UsbHub hub) throws UnsupportedEncodingException, UsbException
|
||||
{
|
||||
List<UsbDevice> devices = new ArrayList<UsbDevice>();
|
||||
List<UsbDevice> children = hub.getAttachedUsbDevices();
|
||||
|
||||
for(UsbDevice child : children)
|
||||
{
|
||||
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()).append("\n\n");
|
||||
|
||||
for(Object configObject : device.getUsbConfigurations())
|
||||
{
|
||||
UsbConfiguration config = (UsbConfiguration) configObject;
|
||||
|
||||
sb.append(config.getUsbConfigurationDescriptor().toString()).append("\n\n");
|
||||
|
||||
for(Object interfaceObject : config.getUsbInterfaces())
|
||||
{
|
||||
UsbInterface iface = (UsbInterface) interfaceObject;
|
||||
|
||||
sb.append(iface.getUsbInterfaceDescriptor().toString()).append("\n\n");
|
||||
|
||||
for(Object endpointObject : iface.getUsbEndpoints())
|
||||
{
|
||||
UsbEndpoint endpoint = (UsbEndpoint) endpointObject;
|
||||
|
||||
sb.append(endpoint.getUsbEndpointDescriptor().toString()).append("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getDeviceClass(byte deviceClass)
|
||||
{
|
||||
switch(deviceClass)
|
||||
{
|
||||
case 0:
|
||||
return "Unknown Device - Class 0";
|
||||
case 2:
|
||||
return "Communications Device";
|
||||
case 3:
|
||||
return "HID Device";
|
||||
case 5:
|
||||
return "Physical Device";
|
||||
case 6:
|
||||
return "Still Imaging Device";
|
||||
case 7:
|
||||
return "Printer Device";
|
||||
case 8:
|
||||
return "Mass Storage Device";
|
||||
case 9:
|
||||
return "Hub Device";
|
||||
case 0xA:
|
||||
return "Communications Device";
|
||||
case 0xB:
|
||||
return "Smart Card Device";
|
||||
case 0xD:
|
||||
return "Content Security Device";
|
||||
case 0xE:
|
||||
return "Video Device";
|
||||
case 0xF:
|
||||
return "Personal Healthcare Device";
|
||||
case 0x10:
|
||||
return "Audio/Video Device";
|
||||
case (byte)0xDC:
|
||||
return "Diagnostic Device";
|
||||
case (byte)0xE0:
|
||||
return "Wireless Controller Device";
|
||||
case (byte)0xEF:
|
||||
return "Miscellaneous Device";
|
||||
default:
|
||||
return "Unknown Device - Class " + deviceClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.converter;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class ByteSampleConverter extends NativeBufferConverter
|
||||
{
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
|
||||
//Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values
|
||||
//of -1.0 to 0 to 1.0
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = (float)(x - 127) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private FloatBuffer mFloatBuffer;
|
||||
|
||||
/**
|
||||
* Converts native byte buffers containing 8-bit complex samples into complex float samples loaded into a tracked,
|
||||
* reusable complex sample buffer. Internally tracks the reusable buffer until all downstream consumers have finished
|
||||
* processing the buffer contents and then reuses the buffer (and memory) for subsequent samples.
|
||||
*/
|
||||
public ByteSampleConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the 8-bit complex samples contained in the native buffer into floats that are loaded into a float
|
||||
* buffer and subsequently transferred to a reusable complex buffer by the parent class.
|
||||
*
|
||||
* @param nativeBuffer containing 8-bit complex samples
|
||||
* @param length of bytes to read from the native buffer
|
||||
* @return float buffer loaded with converted samples
|
||||
*/
|
||||
@Override
|
||||
protected FloatBuffer convertSamples(ByteBuffer nativeBuffer, int length)
|
||||
{
|
||||
nativeBuffer.rewind();
|
||||
|
||||
if(mFloatBuffer == null || mFloatBuffer.capacity() != nativeBuffer.capacity())
|
||||
{
|
||||
mFloatBuffer = FloatBuffer.allocate(nativeBuffer.capacity());
|
||||
}
|
||||
|
||||
mFloatBuffer.rewind();
|
||||
|
||||
int count = 0;
|
||||
|
||||
while(nativeBuffer.hasRemaining() && count < length)
|
||||
{
|
||||
byte sample = nativeBuffer.get();
|
||||
count++;
|
||||
|
||||
mFloatBuffer.put(LOOKUP_VALUES[(sample & 0xFF)]);
|
||||
}
|
||||
|
||||
return mFloatBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.converter;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public abstract class NativeBufferConverter
|
||||
{
|
||||
/**
|
||||
* Converts native byte buffers into complex float samples and produces reusable complex sample buffers. Tracks
|
||||
* each reusable buffer until the downstream consumer(s) are finished with the buffer and then reuses the buffer.
|
||||
*/
|
||||
public NativeBufferConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the native byte buffer to complex float samples.
|
||||
*
|
||||
* @param byteBuffer containing native memory byte samples
|
||||
* @return native buffer samples converted to interleaved complex floats
|
||||
*/
|
||||
public InterleavedComplexSamples convert(ByteBuffer byteBuffer, int length)
|
||||
{
|
||||
FloatBuffer floatBuffer = convertSamples(byteBuffer, length);
|
||||
|
||||
float[] samples = new float[floatBuffer.capacity()];
|
||||
floatBuffer.rewind();
|
||||
floatBuffer.get(samples);
|
||||
|
||||
return new InterleavedComplexSamples(samples, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the native byte buffer bytes into complex float samples.
|
||||
*
|
||||
* @param buffer containing native byte buffer samples
|
||||
* @return a buffer with complex float samples. This float buffer will not be modified, therefore sub-class
|
||||
* implementations can reuse the float buffer.
|
||||
*/
|
||||
protected abstract FloatBuffer convertSamples(ByteBuffer buffer, int length);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.converter;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class SignedByteSampleConverter extends NativeBufferConverter
|
||||
{
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
|
||||
//Creates a static lookup table that converts the signed 8-bit valued range from -128 to 127 into scaled float values
|
||||
//of -1.0 to 0 to 1.0
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = (float)((byte)x) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private FloatBuffer mFloatBuffer;
|
||||
|
||||
/**
|
||||
* Converts native byte buffers containing signed 8-bit complex samples into complex float samples loaded into a tracked,
|
||||
* reusable complex sample buffer. Internally tracks the reusable buffer until all downstream consumers have finished
|
||||
* processing the buffer contents and then reuses the buffer (and memory) for subsequent samples.
|
||||
*/
|
||||
public SignedByteSampleConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the signed 8-bit complex samples contained in the native buffer into floats that are loaded into a float
|
||||
* buffer and subsequently transferred to a reusable complex buffer by the parent class.
|
||||
*
|
||||
* @param nativeBuffer containing signed 8-bit complex samples
|
||||
* @param length of bytes to read from the native buffer
|
||||
* @return float buffer loaded with converted samples
|
||||
*/
|
||||
@Override
|
||||
protected FloatBuffer convertSamples(ByteBuffer nativeBuffer, int length)
|
||||
{
|
||||
nativeBuffer.rewind();
|
||||
|
||||
if(mFloatBuffer == null || mFloatBuffer.capacity() != nativeBuffer.capacity())
|
||||
{
|
||||
mFloatBuffer = FloatBuffer.allocate(nativeBuffer.capacity());
|
||||
}
|
||||
|
||||
mFloatBuffer.rewind();
|
||||
|
||||
int count = 0;
|
||||
|
||||
while(nativeBuffer.hasRemaining() && count < length)
|
||||
{
|
||||
byte sample = nativeBuffer.get();
|
||||
count++;
|
||||
|
||||
mFloatBuffer.put(LOOKUP_VALUES[(sample & 0xFF)]);
|
||||
}
|
||||
|
||||
return mFloatBuffer;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
SignedByteSampleConverter converter = new SignedByteSampleConverter();
|
||||
int a = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user