Hydrasdr Tuner Support (#2339)

HydraSDR tuner support
* Adds Bias-T option
* Update nightly.yml
* Fix BoardID
* Consolidated calibrations and sample converters and removed logging.

---------
Co-authored-by: Benjamin Vernoux <bvernoux@gmail.com>
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
Denny Sheirer
2025-12-25 04:18:12 -05:00
committed by GitHub
parent b0a0ad4b43
commit d8e2e0ac57
53 changed files with 4153 additions and 221 deletions

View File

@@ -30,6 +30,7 @@ public enum TunerClass
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro" ),
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
HACKRF("HackRF" ),
HYDRASDR("HydraSDR"),
RTL2832("RTL-2832"),
RSP("RSP"),
TEST_TUNER("Test"),
@@ -53,8 +54,8 @@ public enum TunerClass
return mDescription;
}
public static final EnumSet<TunerClass> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, HACKRF, RTL2832,
FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
public static final EnumSet<TunerClass> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, HACKRF, HYDRASDR,
RTL2832, FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
public static final EnumSet<TunerClass> FUNCUBE_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
@@ -121,6 +122,8 @@ public enum TunerClass
case 0x1D50CC15: //HACKRF ONE
case 0x1D50604B: //HACKRF JAWBREAKER
return HACKRF;
case 0x38AF0001: //HYDRASDR RFONE (official VID 38af, PID 0001)
return HYDRASDR;
case 0x1D5060A1:
return AIRSPY;
case 0x03EB800C:

View File

@@ -44,6 +44,10 @@ import io.github.dsheirer.source.tuner.hackrf.HackRFTuner;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerEditor;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTuner;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerEditor;
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
@@ -375,6 +379,8 @@ public class TunerFactory
throw new SourceException("Unable to find matching tuner sound card mixer");
case HACKRF:
return new HackRFTuner(new HackRFTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
case HYDRASDR:
return new HydraSdrTuner(new HydraSdrTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
case RTL2832:
return new RTL2832Tuner(new RTL2832TunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
default:
@@ -424,6 +430,8 @@ public class TunerFactory
case HACKRF_ONE:
case HACKRF_RAD1O:
return new HackRFTunerConfiguration(uniqueID);
case HYDRASDR_R828D:
return new HydraSdrTunerConfiguration(uniqueID);
case RAFAELMICRO_R820T:
return new R820TTunerConfiguration(uniqueID);
case RAFAELMICRO_R828D:
@@ -467,6 +475,8 @@ public class TunerFactory
return new FCD2TunerEditor(userPreferences, tunerManager, discoveredTuner);
case HACKRF:
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
case HYDRASDR:
return new HydraSdrTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RSP:
if(discoveredTuner instanceof DiscoveredRspTuner discoveredRspTuner)
{

View File

@@ -36,6 +36,7 @@ public enum TunerType
HACKRF_ONE("ONE"),
HACKRF_JAWBREAKER("Jawbreaker"),
HACKRF_RAD1O("RAD1O"),
HYDRASDR_R828D("R828D"),
RAFAELMICRO_R820T("R820T"),
RAFAELMICRO_R828D("R828D"),
RSP_1("RSP1"),
@@ -73,7 +74,7 @@ public enum TunerType
* 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);
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HYDRASDR_R828D, RAFAELMICRO_R820T);
/**
* Supported sound card tuner types

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 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
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner.airspy;
import io.github.dsheirer.buffer.INativeBufferFactory;
import io.github.dsheirer.buffer.airspy.AirspyNativeBufferFactory;
import io.github.dsheirer.buffer.sample.SampleNativeBufferFactory;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.TunerType;
@@ -69,7 +69,7 @@ public class AirspyTunerController extends USBTunerController
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");
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = new AirspySampleRate(0, 10000000, "10.00 MHz");
private AirspyNativeBufferFactory mNativeBufferFactory = new AirspyNativeBufferFactory();
private SampleNativeBufferFactory mNativeBufferFactory = new SampleNativeBufferFactory();
private AirspyDeviceInformation mDeviceInfo;
private List<AirspySampleRate> mSampleRates = new ArrayList<>();
private int mSampleRate = 0;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 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
@@ -20,7 +20,7 @@
package io.github.dsheirer.source.tuner.airspy.hf;
import io.github.dsheirer.buffer.INativeBufferFactory;
import io.github.dsheirer.buffer.airspy.hf.AirspyHfNativeBufferFactory;
import io.github.dsheirer.buffer.airspy.AirspyHfNativeBufferFactory;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.TunerType;

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerConfiguration;
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
@@ -48,6 +49,7 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
@JsonSubTypes.Type(value = HydraSdrTunerConfiguration.class, name = "hydraSdrTunerConfiguration"),
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),

View File

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

View File

@@ -0,0 +1,35 @@
package io.github.dsheirer.source.tuner.hydrasdr;
public class HydraSdrSampleRate
{
private int mIndex;
private int mValue;
private String mLabel;
public HydraSdrSampleRate( int index, int value, String label )
{
mIndex = index;
mValue = value;
mLabel = label;
}
public int getIndex()
{
return mIndex;
}
public int getRate()
{
return mValue;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return getLabel();
}
}

View File

@@ -0,0 +1,95 @@
/*
* *****************************************************************************
* 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.hydrasdr;
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.hydrasdr.HydraSdrTunerController.BoardID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HydraSDR Tuner
*/
public class HydraSdrTuner extends Tuner
{
private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTuner.class);
/**
* Constructs an instance
* @param controller for the HydraSDR
* @param tunerErrorListener to listen for errors from this tuner
* @param channelizerType for the channelizer
*/
public HydraSdrTuner(HydraSdrTunerController controller, ITunerErrorListener tunerErrorListener,
ChannelizerType channelizerType)
{
super(controller, tunerErrorListener, channelizerType);
}
@Override
public String getPreferredName()
{
return "HydraSDR " + getController().getDeviceInfo().getSerialNumber();
}
/**
* HydraSDR tuner controller
*/
public HydraSdrTunerController getController()
{
return (HydraSdrTunerController)getTunerController();
}
@Override
public String getUniqueID()
{
try
{
return getController().getDeviceInfo().getSerialNumber();
}
catch(Exception e)
{
mLog.error("error getting serial number", e);
}
return BoardID.HYDRASDR_RFONE.getLabel();
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.HYDRASDR;
}
@Override
public double getSampleSize()
{
return 13.0;
}
@Override
public int getMaximumUSBBitsPerSecond()
{
//4-bytes per sample = 32 bits times 10 MSps = 320,000,000 bits per second
return 320000000;
}
}

View File

@@ -0,0 +1,144 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.hydrasdr;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.Gain;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
public class HydraSdrTunerConfiguration extends TunerConfiguration
{
private Gain mGain = HydraSdrTunerController.LINEARITY_GAIN_DEFAULT;
private int mSampleRate = HydraSdrTunerController.DEFAULT_SAMPLE_RATE.getRate();
private int mIFGain = HydraSdrTunerController.IF_GAIN_DEFAULT;
private int mMixerGain = HydraSdrTunerController.MIXER_GAIN_DEFAULT;
private int mLNAGain = HydraSdrTunerController.LNA_GAIN_DEFAULT;
private boolean mMixerAGC = false;
private boolean mLNAAGC = false;
private boolean mBiasT = false;
/**
* Default constructor for JAXB
*/
public HydraSdrTunerConfiguration()
{
super(HydraSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ, HydraSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ);
}
@Override
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public TunerType getTunerType()
{
return TunerType.HYDRASDR_R828D;
}
public HydraSdrTunerConfiguration(String uniqueID)
{
super(uniqueID);
}
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
public int getSampleRate()
{
return mSampleRate;
}
public void setSampleRate(int sampleRate)
{
mSampleRate = sampleRate;
}
@JacksonXmlProperty(isAttribute = true, localName = "gain")
public Gain getGain()
{
return mGain;
}
public void setGain(Gain gain)
{
mGain = gain;
}
@JacksonXmlProperty(isAttribute = true, localName = "if_gain")
public int getIFGain()
{
return mIFGain;
}
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;
}
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
public boolean isBiasT()
{
return mBiasT;
}
public void setBiasT(boolean enabled)
{
mBiasT = enabled;
}
}

View File

@@ -0,0 +1,742 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.hydrasdr;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.Gain;
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController.GainMode;
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 io.github.dsheirer.source.tuner.ui.TunerEditor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* HydraSDR tuner editor/controller
*/
public class HydraSdrTunerEditor extends TunerEditor<HydraSdrTuner, HydraSdrTunerConfiguration>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(HydraSdrTunerEditor.class);
private JButton mTunerInfoButton;
private JComboBox<HydraSdrSampleRate> mSampleRateCombo;
private JComboBox<GainMode> mGainModeCombo;
private JSlider mMasterGainSlider;
private JLabel mMasterGainLabel;
private JLabel mMasterGainValueLabel;
private JSlider mIFGainSlider;
private JLabel mIFGainLabel;
private JLabel mIFGainValueLabel;
private JSlider mLNAGainSlider;
private JLabel mLNAGainValueLabel;
private JSlider mMixerGainSlider;
private JLabel mMixerGainValueLabel;
private JCheckBox mLNAAGCCheckBox;
private JCheckBox mMixerAGCCheckBox;
private JCheckBox mBiasTCheckBox;
/**
* Constructs an instance
* @param userPreferences for wide-band recordings
* @param tunerManager for saving tuner configuration
* @param discoveredTuner for the optionally usable HydraSDR tuner and controller
*/
public HydraSdrTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
{
super(userPreferences, tunerManager, discoveredTuner);
init();
tunerStatusUpdated();
}
@Override
public long getMinimumTunableFrequency()
{
return HydraSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ;
}
@Override
public long getMaximumTunableFrequency()
{
return HydraSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ;
}
@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().isLockedSampleRate());
getTunerInfoButton().setEnabled(hasTuner());
getBiasTCheckBox().setEnabled(hasTuner());
if(hasTuner() && hasConfiguration())
{
getBiasTCheckBox().setSelected(getConfiguration().isBiasT());
}
updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);
if(hasTuner())
{
List<HydraSdrSampleRate> rates = getTuner().getController().getSampleRates();
getSampleRateCombo().setModel(new DefaultComboBoxModel<>(rates.toArray(new HydraSdrSampleRate[rates.size()])));
if(hasConfiguration())
{
HydraSdrSampleRate sampleRate = getSampleRate(getConfiguration().getSampleRate());
getSampleRateCombo().setSelectedItem(sampleRate);
}
}
else
{
getSampleRateCombo().setModel(new DefaultComboBoxModel<>());
}
setLoading(false);
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]",
"[][][][][][][][][][][][][][][][grow]"));
add(new JLabel("Tuner:"));
add(getTunerIdLabel());
add(getTunerInfoButton());
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 JLabel());
add(getBiasTCheckBox(), "wrap");
add(new JSeparator(), "span,growx,push");
add(new JLabel("Gain Control"), "wrap");
add(new JLabel("Mode:"));
add(getGainModeCombo(), "wrap");
add(getMasterGainLabel());
add(getMasterGainSlider());
add(getMasterGainValueLabel());
add(getIFGainLabel());
add(getIFGainSlider());
add(getIFGainValueLabel());
add(getMixerAGCCheckBox());
add(getMixerGainSlider());
add(getMixerGainValueLabel());
add(getLNAAGCCheckBox());
add(getLNAGainSlider());
add(getLNAGainValueLabel());
}
private JCheckBox getLNAAGCCheckBox()
{
if(mLNAAGCCheckBox == null)
{
mLNAAGCCheckBox = new JCheckBox("AGC LNA:");
mLNAAGCCheckBox.setEnabled(false);
mLNAAGCCheckBox.addActionListener(e ->
{
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setLNAAGC(getLNAAGCCheckBox().isSelected());
getLNAGainSlider().setEnabled(!getLNAAGCCheckBox().isSelected());
save();
}
catch(Exception e1)
{
mLog.error("Error setting LNA AGC Enabled");
}
}
});
}
return mLNAAGCCheckBox;
}
private JCheckBox getMixerAGCCheckBox()
{
if(mMixerAGCCheckBox == null)
{
mMixerAGCCheckBox = new JCheckBox("AGC Mixer:");
mMixerAGCCheckBox.setEnabled(false);
mMixerAGCCheckBox.addActionListener(e ->
{
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setMixerAGC(getMixerAGCCheckBox().isSelected());
getMixerGainSlider().setEnabled(!getMixerAGCCheckBox().isSelected());
save();
}
catch(Exception e1)
{
mLog.error("Error setting Mixer AGC Enabled");
}
}
});
}
return mMixerAGCCheckBox;
}
/**
* Checkbox to enable/disable Bias-T power for active antennas
*/
private JCheckBox getBiasTCheckBox()
{
if(mBiasTCheckBox == null)
{
mBiasTCheckBox = new JCheckBox("Bias-T");
mBiasTCheckBox.setEnabled(false);
mBiasTCheckBox.setToolTipText("Enable Bias-T power output for active antennas");
mBiasTCheckBox.addActionListener(e ->
{
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setBiasT(mBiasTCheckBox.isSelected());
save();
}
catch(Exception e1)
{
mLog.error("Error setting Bias-T", e1);
JOptionPane.showMessageDialog(mBiasTCheckBox, "Couldn't set Bias-T: " + e1.getMessage());
}
}
});
}
return mBiasTCheckBox;
}
private JLabel getLNAGainValueLabel()
{
if(mLNAGainValueLabel == null)
{
mLNAGainValueLabel = new JLabel("0");
mLNAGainValueLabel.setEnabled(false);
}
return mLNAGainValueLabel;
}
private JSlider getLNAGainSlider()
{
if(mLNAGainSlider == null)
{
mLNAGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.LNA_GAIN_MIN,
HydraSdrTunerController.LNA_GAIN_MAX, HydraSdrTunerController.LNA_GAIN_MIN);
mLNAGainSlider.setEnabled(false);
mLNAGainSlider.setMajorTickSpacing(1);
mLNAGainSlider.setPaintTicks(true);
mLNAGainSlider.addChangeListener(event ->
{
int gain = mLNAGainSlider.getValue();
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setLNAGain(gain);
save();
}
catch(Exception e)
{
mLog.error("Couldn't set HydraSDR LNA gain to:" + gain, e);
JOptionPane.showMessageDialog(mLNAGainSlider, "Couldn't set LNA gain value to " + gain);
}
}
getLNAGainValueLabel().setText(String.valueOf(gain));
});
}
return mLNAGainSlider;
}
private JLabel getMixerGainValueLabel()
{
if(mMixerGainValueLabel == null)
{
mMixerGainValueLabel = new JLabel("0");
mMixerGainValueLabel.setEnabled(false);
}
return mMixerGainValueLabel;
}
private JSlider getMixerGainSlider()
{
if(mMixerGainSlider == null)
{
mMixerGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.MIXER_GAIN_MIN,
HydraSdrTunerController.MIXER_GAIN_MAX, HydraSdrTunerController.MIXER_GAIN_MIN);
mMixerGainSlider.setEnabled(false);
mMixerGainSlider.setMajorTickSpacing(1);
mMixerGainSlider.setPaintTicks(true);
mMixerGainSlider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent event)
{
int gain = mMixerGainSlider.getValue();
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setMixerGain(gain);
save();
}
catch(Exception e)
{
mLog.error("Couldn't set HydraSDR Mixer gain to:" + gain, e);
JOptionPane.showMessageDialog(mMixerGainSlider, "Couldn't set Mixer gain value to " + gain);
}
}
getMixerGainValueLabel().setText(String.valueOf(gain));
}
});
}
return mMixerGainSlider;
}
private JLabel getIFGainLabel()
{
if(mIFGainLabel == null)
{
mIFGainLabel = new JLabel("IF:");
}
return mIFGainLabel;
}
private JLabel getIFGainValueLabel()
{
if(mIFGainValueLabel == null)
{
mIFGainValueLabel = new JLabel("0");
mIFGainValueLabel.setEnabled(false);
}
return mIFGainValueLabel;
}
private JSlider getIFGainSlider()
{
if(mIFGainSlider == null)
{
mIFGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.IF_GAIN_MIN,
HydraSdrTunerController.IF_GAIN_MAX, HydraSdrTunerController.IF_GAIN_MIN);
mIFGainSlider.setEnabled(false);
mIFGainSlider.setMajorTickSpacing(1);
mIFGainSlider.setPaintTicks(true);
mIFGainSlider.addChangeListener(event ->
{
int gain = mIFGainSlider.getValue();
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setIFGain(gain);
save();
}
catch(Exception e)
{
mLog.error("Couldn't set HydraSDR IF gain to:" + gain, e);
JOptionPane.showMessageDialog(mIFGainSlider, "Couldn't set IF gain value to " + gain);
}
}
getIFGainValueLabel().setText(String.valueOf(gain));
});
}
return mIFGainSlider;
}
private JLabel getMasterGainLabel()
{
if(mMasterGainLabel == null)
{
mMasterGainLabel = new JLabel("Master:");
}
return mMasterGainLabel;
}
private JLabel getMasterGainValueLabel()
{
if(mMasterGainValueLabel == null)
{
mMasterGainValueLabel = new JLabel("0");
mMasterGainValueLabel.setEnabled(false);
}
return mMasterGainValueLabel;
}
private JSlider getMasterGainSlider()
{
if(mMasterGainSlider == null)
{
mMasterGainSlider = new JSlider(JSlider.HORIZONTAL, HydraSdrTunerController.GAIN_MIN,
HydraSdrTunerController.GAIN_MAX, HydraSdrTunerController.GAIN_MIN);
mMasterGainSlider.setEnabled(false);
mMasterGainSlider.setMajorTickSpacing(1);
mMasterGainSlider.setPaintTicks(true);
mMasterGainSlider.addChangeListener(event ->
{
GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
int value = mMasterGainSlider.getValue();
Gain gain = Gain.getGain(mode, value);
if(hasTuner() && !isLoading())
{
try
{
getTuner().getController().setGain(gain);
save();
}
catch(Exception e)
{
mLog.error("Couldn't set HydraSDR gain to:" + gain.name(), e);
JOptionPane.showMessageDialog(mMasterGainSlider, "Couldn't set gain value to " +
gain.getValue());
}
}
getMasterGainValueLabel().setText(String.valueOf(value));
});
}
return mMasterGainSlider;
}
private JComboBox<GainMode> getGainModeCombo()
{
if(mGainModeCombo == null)
{
mGainModeCombo = new JComboBox<>(GainMode.values());
mGainModeCombo.setEnabled(false);
mGainModeCombo.addActionListener(e ->
{
if(hasTuner() && !isLoading())
{
GainMode mode = (GainMode)mGainModeCombo.getSelectedItem();
int value = getMasterGainSlider().getValue();
Gain gain = Gain.getGain(mode, value);
updateGainComponents(gain);
save();
}
});
}
return mGainModeCombo;
}
private JComboBox<HydraSdrSampleRate> getSampleRateCombo()
{
if(mSampleRateCombo == null)
{
mSampleRateCombo = new JComboBox<>();
mSampleRateCombo.setEnabled(false);
mSampleRateCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if(hasTuner() && !isLoading())
{
HydraSdrSampleRate rate = (HydraSdrSampleRate)mSampleRateCombo.getSelectedItem();
try
{
getTuner().getController().setSampleRate(rate);
//Adjust the min/max values for the sample rate.
adjustForSampleRate(rate.getRate());
save();
}
catch(Exception e1)
{
JOptionPane.showMessageDialog(HydraSdrTunerEditor.this,
"Couldn't set sample rate to " + rate.getLabel());
mLog.error("Error setting HydraSDR sample rate", e1);
}
}
}
});
}
return mSampleRateCombo;
}
/**
* Hyperlink button that provides tuner information
*/
private JButton getTunerInfoButton()
{
if(mTunerInfoButton == null)
{
mTunerInfoButton = new JButton("Info");
mTunerInfoButton.setEnabled(false);
mTunerInfoButton.addActionListener(e -> JOptionPane.showMessageDialog(HydraSdrTunerEditor.this,
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
}
return mTunerInfoButton;
}
/**
* Updates the enabled state of each of the gain controls according to the
* specified gain mode. The master gain control is enabled for linearity
* and sensitivity and the individual gain controls are disabled, and
* vice-versa for custom mode.
*/
private void updateGainComponents(Gain gain)
{
if(hasTuner() && gain != null)
{
boolean isCustom = gain.equals(Gain.CUSTOM);
getGainModeCombo().setEnabled(true);
getGainModeCombo().setSelectedItem(gain.getGainMode());
getMasterGainLabel().setEnabled(!isCustom);
getMasterGainSlider().setEnabled(!isCustom);
getMasterGainSlider().setValue(gain.getValue());
getMasterGainValueLabel().setEnabled(!isCustom);
getIFGainLabel().setEnabled(isCustom);
getIFGainSlider().setEnabled(isCustom);
getIFGainValueLabel().setEnabled(isCustom);
getLNAAGCCheckBox().setEnabled(isCustom);
getLNAGainSlider().setEnabled(isCustom && !getConfiguration().isLNAAGC());
getLNAGainValueLabel().setEnabled(isCustom);
getMixerAGCCheckBox().setEnabled(isCustom);
getMixerGainSlider().setEnabled(isCustom && !getConfiguration().isMixerAGC());
getMixerGainValueLabel().setEnabled(isCustom);
if(isCustom)
{
getIFGainSlider().setValue(getConfiguration().getIFGain());
getLNAGainSlider().setValue(getConfiguration().getLNAGain());
getMixerGainSlider().setValue(getConfiguration().getMixerGain());
getMixerAGCCheckBox().setSelected(getConfiguration().isMixerAGC());
getLNAAGCCheckBox().setSelected(getConfiguration().isLNAAGC());
}
else
{
getIFGainSlider().setValue(0);
getLNAGainSlider().setValue(0);
getMixerGainSlider().setValue(0);
getMixerAGCCheckBox().setSelected(false);
getLNAAGCCheckBox().setSelected(false);
}
}
else
{
getGainModeCombo().setEnabled(false);
getMasterGainLabel().setEnabled(false);
getMasterGainSlider().setEnabled(false);
getMasterGainSlider().setValue(0);
getMasterGainValueLabel().setEnabled(false);
getIFGainLabel().setEnabled(false);
getIFGainSlider().setEnabled(false);
getIFGainSlider().setValue(0);
getIFGainValueLabel().setEnabled(false);
getLNAAGCCheckBox().setEnabled(false);
getLNAAGCCheckBox().setSelected(false);
getLNAGainSlider().setEnabled(false);
getLNAGainSlider().setValue(0);
getLNAGainValueLabel().setEnabled(false);
getMixerAGCCheckBox().setEnabled(false);
getMixerAGCCheckBox().setSelected(false);
getMixerGainSlider().setEnabled(false);
getMixerGainSlider().setValue(0);
getMixerGainValueLabel().setEnabled(false);
}
}
@Override
public void save()
{
if(hasConfiguration() && !isLoading())
{
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency());
getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency());
double value = ((SpinnerNumberModel) getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
getConfiguration().setFrequencyCorrection(value);
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
getConfiguration().setSampleRate(((HydraSdrSampleRate)getSampleRateCombo().getSelectedItem()).getRate());
Gain gain = Gain.getGain((GainMode)mGainModeCombo.getSelectedItem(), getMasterGainSlider().getValue());
getConfiguration().setGain(gain);
getConfiguration().setIFGain(getIFGainSlider().getValue());
getConfiguration().setMixerGain(getMixerGainSlider().getValue());
getConfiguration().setLNAGain(getLNAGainSlider().getValue());
getConfiguration().setMixerAGC(getMixerAGCCheckBox().isSelected());
getConfiguration().setLNAAGC(getLNAAGCCheckBox().isSelected());
getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
saveConfiguration();
}
}
/**
* Finds the HydraSDR sample rate entry that matches the value.
* @param value in Hertz
* @return the matching rate entry or null.
*/
private HydraSdrSampleRate getSampleRate(int value)
{
if(hasTuner())
{
List<HydraSdrSampleRate> rates = getTuner().getController().getSampleRates();
for(HydraSdrSampleRate rate : rates)
{
if(rate.getRate() == value)
{
return rate;
}
}
if(rates.size() > 0)
{
return rates.get(0);
}
}
return null;
}
/**
* Updates the sample rate tooltip according to the tuner controller's lock state.
*/
private void updateSampleRateToolTip()
{
if(hasTuner() && getTuner().getController().isLockedSampleRate())
{
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
}
else
{
getSampleRateCombo().setToolTipText("Select a sample rate for the tuner");
}
}
@Override
public void setTunerLockState(boolean locked)
{
getFrequencyPanel().updateControls();
getSampleRateCombo().setEnabled(!locked);
updateSampleRateToolTip();
}
private String getTunerInfo()
{
if(getDiscoveredTuner().getTunerStatus() == TunerStatus.ERROR)
{
return getDiscoveredTuner().getErrorMessage();
}
if(hasTuner())
{
StringBuilder sb = new StringBuilder();
HydraSdrDeviceInformation info = getTuner().getController().getDeviceInfo();
sb.append("<html><h3>HydraSDR Tuner</h3>");
sb.append("<b>Serial: </b>");
sb.append(info.getSerialNumber());
sb.append("<br>");
sb.append("<b>Firmware: </b>");
String[] firmware = info.getVersion().split(" ");
sb.append(firmware.length > 1 ? firmware[0] : info.getVersion());
sb.append("<br>");
sb.append("<b>Part: </b>");
sb.append(info.getPartNumber());
sb.append("<br>");
sb.append("<b>Board ID: </b>");
sb.append(info.getBoardID().getLabel());
sb.append("<br>");
return sb.toString();
}
return null;
}
}