#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:
Denny Sheirer
2022-04-18 07:30:58 -04:00
committed by GitHub
parent 0fb148d31e
commit 96d3996bec
259 changed files with 11974 additions and 12454 deletions

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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;
}
}

View File

@@ -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()

View File

@@ -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));
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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")

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View 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");
};
}
}

View File

@@ -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));
}
/**

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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()
{

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 ) );
}
}

View File

@@ -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;
}
}

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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()
{

View File

@@ -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 + "]");
}
}

View File

@@ -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);
}
}
});

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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()
{

View File

@@ -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";
}
}

View File

@@ -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();
}
}
}

View File

@@ -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())

View File

@@ -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.
*

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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
*

View File

@@ -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!");
}
}

View File

@@ -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;
}
}

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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];
}
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View 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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}