Resolves #373 - Adds support for Tuner frequency error PPM monitoring

and auto-correction for R820T tuners.

-Updated TunerModel and TunerViewPanel to add 2 new columns to
display the current tuner's PPM setting and (when available) the
measured frequency error averaged over 5 second intervals provided by
certain decoders (e.g. P25 C4FM and LSM).  Frequency error value from
P25 decoders is provided from the Costas Loop PLL once 2 or more P25
message sync events have been observed in a short period.
-Updated R820T tuner to auto-correct PPM when measured frequency error
exceeds the +/- 0.4PPM threshold for a period of 30 seconds or more.
Feature can be enabled/disabled by user in the tuner editor panel.

Resolved several issues with P25 C4FM/LSM decoders that was impacting
performance related to channel misalignment from tuner PPM offsets.
This commit is contained in:
Denny
2018-07-29 10:48:29 -04:00
parent 5c0d97d942
commit 0c47efea03
44 changed files with 1296 additions and 483 deletions

View File

@@ -31,6 +31,8 @@ public class SourceEvent
NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED,
NOTIFICATION_FREQUENCY_CHANGE,
NOTIFICATION_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
NOTIFICATION_SAMPLE_RATE_CHANGE,
NOTIFICATION_STOP_SAMPLE_STREAM,
@@ -124,6 +126,14 @@ public class SourceEvent
return mSource;
}
/**
* Sets the source associated with this event
*/
public void setSource(Source source)
{
mSource = source;
}
/**
* Indicates if this event contains an optional source
*/
@@ -189,6 +199,28 @@ public class SourceEvent
return new SourceEvent(Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
}
/**
* Creates a new frequency error measurement notification event.
*
* @param frequencyError in hertz
*/
public static SourceEvent frequencyErrorMeasurement(long frequencyError)
{
return new SourceEvent(Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR, frequencyError);
}
/**
* Creates a new frequency error measurement notification event. This event is different from
* the raw frequency error measurement and indicates that the current state of the PLL is
* locked and tracking the signal.
*
* @param frequencyError in hertz
*/
public static SourceEvent frequencyErrorMeasurementSyncLocked(long frequencyError, String eventDescription)
{
return new SourceEvent(Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED, frequencyError, eventDescription);
}
/**
* Creates a new sample rate change event
*
@@ -288,7 +320,7 @@ public class SourceEvent
{
return "SOURCE EVENT:" + mEvent +
" VALUE:" + (mValue != null ? mValue : "(empty)") +
" SOURCE:" + (mSource != null ? mSource.getClass() : "(null)") +
" SOURCE:" + (mSource != null ? mSource.toString() : "(null)") +
" DESCRIPTION:" + (mEventDescription != null ? mEventDescription : "");
}
}

View File

@@ -0,0 +1,149 @@
/*******************************************************************************
* 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.source.SourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Monitors measured frequency error PPM values from the tuner and (when enabled) applies the current
* error measurement to the tuner controller to adjust the tuner's PPM and align the frequency value.
*/
public class FrequencyErrorCorrectionManager
{
private final static Logger mLog = LoggerFactory.getLogger(FrequencyErrorCorrectionManager.class);
private static final double FREQUENCY_CORRECTION_ERROR_THRESHOLD = 0.4;
private static final long AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS = 30 * 1000; //30 seconds
private long mObservationPeriodStart;
private double mPPMRequired;
private boolean mEnabled = true;
private TunerController mTunerController;
/**
* Constructs a frequency error correction manager for the specified tuner controller.
*/
public FrequencyErrorCorrectionManager(TunerController tunerController)
{
mTunerController = tunerController;
}
/**
* Enables or disables automatic frequency PPM correction.
* @param enabled to true to turn on automatic PPM adjustments.
*/
public void setEnabled(boolean enabled)
{
mEnabled = enabled;
}
/**
* Resets monitoring
*/
public void reset()
{
mObservationPeriodStart = 0;
mPPMRequired = 0;
}
/**
* Applies a PPM adjustment value to the tuner's current PPM setting, when auto-correction is enabled.
*/
private void applyCorrection()
{
if(mEnabled && mTunerController.hasMeasuredFrequencyError())
{
double frequencyCorrection = mTunerController.getFrequencyCorrection();
frequencyCorrection -= mTunerController.getPPMFrequencyError();
try
{
mLog.info("Auto-Correcting Tuner PPM to [" + frequencyCorrection + "]");
mTunerController.setFrequencyCorrection(frequencyCorrection);
reset();
}
catch(SourceException se)
{
mLog.error("Error while applying automatic frequency error PPM correction value: " + frequencyCorrection);
}
}
}
/**
* Updates the monitor with a new PPM frequency error measurement from the tuner.
*/
public void updatePPM(double ppm)
{
if(ppm > FREQUENCY_CORRECTION_ERROR_THRESHOLD)
{
if(mObservationPeriodStart == 0)
{
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
else
{
if(mPPMRequired > 0)
{
mPPMRequired = ppm;
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
{
applyCorrection();
}
}
else
{
//Opposite polarity ppm value ... restart the timer
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
}
}
else if(ppm < -FREQUENCY_CORRECTION_ERROR_THRESHOLD)
{
if(mObservationPeriodStart == 0)
{
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
else
{
if(mPPMRequired < 0)
{
mPPMRequired = ppm;
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
{
applyCorrection();
}
}
else
{
//Opposite polarity ppm value ... restart the timer
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
}
}
else
{
reset();
}
}
}

View File

@@ -22,6 +22,8 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.TunerChannelSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tuner provides an interface to a software or hardware tuner controller that provides I/Q sample data coupled with a
@@ -29,9 +31,12 @@ import io.github.dsheirer.source.tuner.manager.TunerChannelSourceManager;
*/
public abstract class Tuner implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(Tuner.class);
private Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
private ChannelSourceManager mChannelSourceManager;
private TunerController mTunerController;
private TunerFrequencyErrorMonitor mTunerFrequencyErrorMonitor;
private String mName;
/**
@@ -49,6 +54,9 @@ public abstract class Tuner implements ISourceEventProcessor
mChannelSourceManager = new TunerChannelSourceManager(mTunerController);
//Register to receive channel count change notifications
mChannelSourceManager.addSourceEventListener(this::process);
mTunerFrequencyErrorMonitor = new TunerFrequencyErrorMonitor(this);
mTunerFrequencyErrorMonitor.start();
}
@Override
@@ -60,16 +68,23 @@ public abstract class Tuner implements ISourceEventProcessor
broadcast(new TunerEvent(Tuner.this, Event.CHANNEL_COUNT));
break;
case NOTIFICATION_FREQUENCY_CHANGE:
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY));
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY_UPDATED));
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
broadcast(new TunerEvent(Tuner.this, Event.FREQUENCY_ERROR_UPDATED));
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE));
broadcast(new TunerEvent(Tuner.this, Event.SAMPLE_RATE_UPDATED));
break;
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
broadcast(new TunerEvent(Tuner.this, Event.LOCK_STATE_CHANGE));
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
mTunerFrequencyErrorMonitor.receive(event);
break;
default:
mLog.debug("Unrecognized Source Event: " + event.toString());
break;
}
}

View File

@@ -41,6 +41,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
private int mMiddleUnusableHalfBandwidth;
private double mUsableBandwidthPercentage;
private Listener<SourceEvent> mSourceEventListener;
private int mMeasuredFrequencyError;
/**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
@@ -242,6 +243,46 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
return (int)(getUsableBandwidth() / 2);
}
/**
* Current measured frequency error as received/reported from certain downstream decoders.
* @return measured frequency error in hertz averaged over 1-second intervals
*/
public int getMeasuredFrequencyError()
{
return mMeasuredFrequencyError;
}
/**
* Indicates if this tuner controller has a non-zero measured frequency error value.
*/
public boolean hasMeasuredFrequencyError()
{
return mMeasuredFrequencyError != 0;
}
/**
* Calculates the current PPM error value as measured by certain downstream decoders.
* @return
*/
public double getPPMFrequencyError()
{
if(hasMeasuredFrequencyError())
{
return mMeasuredFrequencyError / ((double)getFrequency() / 1E6d);
}
return 0.0d;
}
/**
* Sets the measured frequency error average.
* @param measuredFrequencyError in hertz averaged over a 5 second interval.
*/
public void setMeasuredFrequencyError(int measuredFrequencyError)
{
mMeasuredFrequencyError = measuredFrequencyError;
}
/**
* Sets the listener to be notified any time that the tuner changes frequency
* or bandwidth/sample rate.

View File

@@ -23,12 +23,13 @@ public class TunerEvent
public enum Event
{
ADD,
LOCK_STATE_CHANGE,
REMOVE,
CHANNEL_COUNT,
FREQUENCY,
SAMPLE_RATE,
FREQUENCY_UPDATED,
FREQUENCY_ERROR_UPDATED,
LOCK_STATE_CHANGE,
MEASURED_FREQUENCY_ERROR_UPDATED,
SAMPLE_RATE_UPDATED,
CLEAR_MAIN_SPECTRAL_DISPLAY,
REQUEST_MAIN_SPECTRAL_DISPLAY,
REQUEST_NEW_SPECTRAL_DISPLAY;

View File

@@ -0,0 +1,137 @@
/*******************************************************************************
* 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.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Monitors frequency error measurements received from certain decoders (e.g. P25) and averages those
* measurements over an interval period. Averaged measurement values are applied to the tuner controller
* and broadcast as a TunerEvent for visual display.
*/
public class TunerFrequencyErrorMonitor implements Listener<SourceEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(TunerFrequencyErrorMonitor.class);
private static final int PROCESSING_INTERVAL_SECONDS = 5;
private LinkedTransferQueue<Integer> mMeasurementsQueue = new LinkedTransferQueue<>();
private List<Integer> mProcessingMeasurements = new ArrayList();
private ScheduledFuture<?> mTimerHandle;
private Tuner mTuner;
/**
* Constructs a monitor for the tuner argument.
* @param tuner to monitor for frequency error measurements
*/
public TunerFrequencyErrorMonitor(Tuner tuner)
{
mTuner = tuner;
}
/**
* Primary method for receiving frequency error measurements from downstream decoders.
*/
@Override
public void receive(SourceEvent sourceEvent)
{
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED)
{
mMeasurementsQueue.add(sourceEvent.getValue().intValue());
}
}
/**
* Calculates the average frequency error measurement value from discrete measurements received from
* the decoders over the interval period.
*/
private void process()
{
mMeasurementsQueue.drainTo(mProcessingMeasurements);
if(mProcessingMeasurements.size() > 0)
{
int sum = 0;
for(Integer measurement: mProcessingMeasurements)
{
sum += measurement;
}
//Note: the measurements are the frequency correction being applied to each channel to compensate
//for the error int the channel. So, we report the negated value as the current error measurement
broadcast(-(sum / mProcessingMeasurements.size()));
}
else
{
broadcast(0);
}
mProcessingMeasurements.clear();
}
private void broadcast(int averageError)
{
mTuner.getTunerController().setMeasuredFrequencyError(averageError);
mTuner.broadcast(new TunerEvent(mTuner, TunerEvent.Event.MEASURED_FREQUENCY_ERROR_UPDATED));
}
/**
* Starts this monitor processing frequency error measurements received from select channel decoders
* and averaging those values over a 1 second interval for transmission as a tuner event.
*/
public void start()
{
if(mTimerHandle == null)
{
mTimerHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0,
PROCESSING_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}
/**
* Stops this monitor
*/
public void stop()
{
if(mTimerHandle != null)
{
mTimerHandle.cancel(true);
mTimerHandle = null;
broadcast(0);
}
}
/**
* Runnable implementation to invoke the process() method when the timer fires.
*/
public class Processor implements Runnable
{
@Override
public void run()
{
process();
}
}
}

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* 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.properties.SystemProperties;
@@ -31,17 +46,19 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
public static final int SAMPLE_RATE = 2;
public static final int FREQUENCY = 3;
public static final int CHANNEL_COUNT = 4;
public static final int SPECTRAL_DISPLAY_MAIN = 5;
public static final int SPECTRAL_DISPLAY_NEW = 6;
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 String MHZ = " MHz";
private static final String[] COLUMNS =
{"Tuner", "ID", "Sample Rate", "Frequency", "Channels", "Spectral", "Display"};
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;
@@ -206,15 +223,21 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
case CHANNEL_COUNT:
fireTableCellUpdated(index, CHANNEL_COUNT);
break;
case FREQUENCY:
case FREQUENCY_UPDATED:
fireTableCellUpdated(index, FREQUENCY);
mTunerConfigurationModel.tunerFrequencyChanged(event.getTuner());
break;
case SAMPLE_RATE:
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, SPECTRAL_DISPLAY_MAIN);
fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
default:
break;
}
@@ -275,8 +298,21 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
{
return "0";
}
case SPECTRAL_DISPLAY_MAIN:
return "Main";
case FREQUENCY_ERROR:
double ppm = tuner.getTunerController().getFrequencyCorrection();
return mFrequencyErrorPPMFormat.format(ppm);
case MEASURED_FREQUENCY_ERROR:
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:
return "New";
default:

View File

@@ -130,20 +130,7 @@ public class TunerViewPanel extends JPanel
{
int column = mTunerTable.columnAtPoint(e.getPoint());
if(column == TunerModel.SPECTRAL_DISPLAY_MAIN)
{
int tableRow = mTunerTable.rowAtPoint(e.getPoint());
int modelRow = mTunerTable.convertRowIndexToModel(tableRow);
Tuner tuner = mTunerModel.getTuner(modelRow);
if(tuner != null)
{
mTunerModel.broadcast(new TunerEvent(tuner,
Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
}
}
else if(column == TunerModel.SPECTRAL_DISPLAY_NEW)
if(column == TunerModel.SPECTRAL_DISPLAY_NEW)
{
int tableRow = mTunerTable.rowAtPoint(e.getPoint());
int modelRow = mTunerTable.convertRowIndexToModel(tableRow);
@@ -160,9 +147,7 @@ public class TunerViewPanel extends JPanel
});
TableCellRenderer renderer = new LinkCellRenderer();
mTunerTable.getColumnModel().getColumn(5).setCellRenderer(renderer);
mTunerTable.getColumnModel().getColumn(6).setCellRenderer(renderer);
mTunerTable.getColumnModel().getColumn(TunerModel.SPECTRAL_DISPLAY_NEW).setCellRenderer(renderer);
JScrollPane listScroller = new JScrollPane(mTunerTable);
listScroller.setPreferredSize(new Dimension(400, 20));
@@ -191,15 +176,7 @@ public class TunerViewPanel extends JPanel
value, isSelected, hasFocus, row, column);
label.setForeground(Color.BLUE.brighter());
if(column == TunerModel.SPECTRAL_DISPLAY_MAIN)
{
label.setToolTipText("Show this tuner in the main spectral display");
}
else if(column == TunerModel.SPECTRAL_DISPLAY_NEW)
{
label.setToolTipText("Show this tuner in a new spectral display");
}
label.setToolTipText("Show this tuner in a new spectral display");
return label;
}

View File

@@ -181,6 +181,15 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
case NOTIFICATION_SAMPLE_RATE_CHANGE:
setSampleRate(sourceEvent.getValue().doubleValue());
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Ignore these raw frequency measurement errors. We're only interested in the measurements
//that occur when the channel state indicates that we're sync-locked.
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast this measurement event so the producer can process it
sourceEvent.setSource(this);
broadcastProducerSourceEvent(sourceEvent);
break;
//Request events from the consumer
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());

View File

@@ -459,6 +459,11 @@ public class TunerChannelSourceManager extends ChannelSourceManager
//when the polyphase manager has channels allocated
mTunerController.setLocked(getTunerChannelCount() > 0);
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast these frequency measurement errors to the tuner and tuner controller
//for display and/or correction
broadcast(sourceEvent);
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent);
break;

View File

@@ -30,6 +30,7 @@ public class R820TTunerConfiguration extends TunerConfiguration
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;
/**
* Default constructor for JAXB
@@ -115,4 +116,23 @@ public class R820TTunerConfiguration extends TunerConfiguration
{
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

@@ -23,6 +23,7 @@
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.RTL2832TunerController;
@@ -33,7 +34,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import javax.swing.JPanel;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
@@ -70,12 +70,36 @@ public class R820TTunerController extends RTL2832TunerController
0x6C, 0x83, 0x80, 0x00, 0x0F, 0x00, 0xC0, 0x30,
0x48, 0xCC, 0x60, 0x00, 0x54, 0xAE, 0x4A, 0xC0};
private JPanel mEditor;
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);
}
/**
* 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
*/
public FrequencyErrorCorrectionManager getFrequencyErrorCorrectionManager()
{
return mFrequencyErrorCorrectionManager;
}
/**
* 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.
*/
@Override
public void setMeasuredFrequencyError(int measuredFrequencyError)
{
super.setMeasuredFrequencyError(measuredFrequencyError);
getFrequencyErrorCorrectionManager().updatePPM(getPPMFrequencyError());
}
@Override
@@ -106,6 +130,8 @@ public class R820TTunerController extends RTL2832TunerController
double correction = config.getFrequencyCorrection();
setFrequencyCorrection(correction);
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
R820TGain masterGain = config.getMasterGain();
setGain(masterGain, true);

View File

@@ -33,6 +33,7 @@ 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;
@@ -64,6 +65,7 @@ public class R820TTunerEditor extends TunerConfigurationEditor
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;
@@ -202,6 +204,22 @@ public class R820TTunerEditor extends TunerConfigurationEditor
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");
/**
@@ -435,6 +453,11 @@ public class R820TTunerEditor extends TunerConfigurationEditor
mFrequencyCorrection.setEnabled(enabled);
}
if(mAutoPPMEnabled.isEnabled() != enabled)
{
mAutoPPMEnabled.setEnabled(enabled);
}
updateSampleRateToolTip();
if(mController.isLocked())
@@ -524,6 +547,8 @@ public class R820TTunerEditor extends TunerConfigurationEditor
.getModel()).getNumber().doubleValue();
config.setFrequencyCorrection(value);
config.setAutoPPMCorrectionEnabled(mAutoPPMEnabled.isSelected());
config.setSampleRate((SampleRate)mComboSampleRate.getSelectedItem());
R820TGain gain = (R820TGain)mComboMasterGain.getSelectedItem();
@@ -560,6 +585,7 @@ public class R820TTunerEditor extends TunerConfigurationEditor
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());

View File

@@ -217,7 +217,8 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer);
ReusableComplexBuffer reusableBuffer = mReusableComplexBufferQueue.getBuffer(samples.length);
reusableBuffer.reloadFrom(samples, System.currentTimeMillis());
System.arraycopy(samples, 0, reusableBuffer.getSamples(), 0, samples.length);
reusableBuffer.setTimestamp(System.currentTimeMillis());
mListener.receive(reusableBuffer);
}
}