mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -99,6 +99,7 @@ dependencies {
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'commons-io:commons-io:2.22.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
|
||||
|
||||
@@ -16,5 +16,13 @@ dependencyResolutionManagement {
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
|
||||
}
|
||||
|
||||
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jbladerf' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public enum TunerClass
|
||||
{
|
||||
AIRSPY("Airspy"),
|
||||
AIRSPY_HF("Airspy HF+"),
|
||||
BLADERF("bladeRF"),
|
||||
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro" ),
|
||||
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
|
||||
HACKRF("HackRF" ),
|
||||
@@ -37,7 +38,7 @@ public enum TunerClass
|
||||
RECORDING_TUNER("Recording"),
|
||||
UNKNOWN("Unknown" );
|
||||
|
||||
private String mDescription;
|
||||
private final String mDescription;
|
||||
|
||||
/**
|
||||
* Constructs an entry
|
||||
@@ -54,8 +55,8 @@ public enum TunerClass
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
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> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, BLADERF, 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);
|
||||
|
||||
@@ -122,6 +123,10 @@ public enum TunerClass
|
||||
case 0x1D50CC15: //HACKRF ONE
|
||||
case 0x1D50604B: //HACKRF JAWBREAKER
|
||||
return HACKRF;
|
||||
case 0x2CF05246: //BLADERF X40/X115
|
||||
case 0x1D506066: //BLADERF X40/X115 LEGACY VID/PID
|
||||
case 0x2CF05250: //BLADERF 2.0 MICRO
|
||||
return BLADERF;
|
||||
case 0x38AF0001: //HYDRASDR RFONE (official VID 38af, PID 0001)
|
||||
return HYDRASDR;
|
||||
case 0x1D5060A1:
|
||||
@@ -129,7 +134,7 @@ public enum TunerClass
|
||||
case 0x03EB800C:
|
||||
return AIRSPY_HF;
|
||||
}
|
||||
|
||||
|
||||
return TunerClass.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTuner;
|
||||
import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerController;
|
||||
import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.bladerf.BladeRfTuner;
|
||||
import io.github.dsheirer.source.tuner.bladerf.BladeRfTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.bladerf.BladeRfTunerController;
|
||||
import io.github.dsheirer.source.tuner.bladerf.BladeRfTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
|
||||
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerConfiguration;
|
||||
@@ -361,6 +365,8 @@ public class TunerFactory
|
||||
return new AirspyTuner(new AirspyTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
case AIRSPY_HF:
|
||||
return new AirspyHfTuner(new AirspyHfTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
case BLADERF:
|
||||
return new BladeRfTuner(new BladeRfTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
TargetDataLine tdl1 = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO);
|
||||
if(tdl1 != null)
|
||||
@@ -418,6 +424,8 @@ public class TunerFactory
|
||||
return new AirspyHfTunerConfiguration(uniqueID);
|
||||
case AIRSPY_R820T:
|
||||
return new AirspyTunerConfiguration(uniqueID);
|
||||
case BLADERF:
|
||||
return new BladeRfTunerConfiguration(uniqueID);
|
||||
case ELONICS_E4000:
|
||||
return new E4KTunerConfiguration(uniqueID);
|
||||
case FITIPOWER_FC0013:
|
||||
@@ -473,6 +481,8 @@ public class TunerFactory
|
||||
return new FCD1TunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
return new FCD2TunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case BLADERF:
|
||||
return new BladeRfTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case HACKRF:
|
||||
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||
case HYDRASDR:
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.EnumSet;
|
||||
public enum TunerType
|
||||
{
|
||||
AIRSPY_R820T("R820T"),
|
||||
BLADERF("bladeRF"),
|
||||
AIRSPY_HF_PLUS("HF+"),
|
||||
ELONICS_E4000("E4000"),
|
||||
FCI_FC2580("FC2580"),
|
||||
@@ -51,7 +52,7 @@ public enum TunerType
|
||||
RECORDING("Recording"),
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private String mLabel;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -73,13 +74,13 @@ public enum TunerType
|
||||
/**
|
||||
* Supported USB tuner types
|
||||
*/
|
||||
public static EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, ELONICS_E4000, FITIPOWER_FC0012,
|
||||
public static final EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, BLADERF, ELONICS_E4000, FITIPOWER_FC0012,
|
||||
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HYDRASDR_R828D, RAFAELMICRO_R820T);
|
||||
|
||||
/**
|
||||
* Supported sound card tuner types
|
||||
*/
|
||||
public static EnumSet<TunerType> SUPPORTED_SOUND_CARD_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
||||
public static final 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
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
/**
|
||||
* bladeRF device information, formatted for display.
|
||||
*
|
||||
* @param serial number
|
||||
* @param board name reported by the library
|
||||
* @param firmware version
|
||||
* @param fpga version
|
||||
* @param speed of the USB connection
|
||||
*/
|
||||
public record BladeRfDeviceInformation(String serial, String board, String firmware, String fpga, String speed)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sample rates offered for bladeRF tuners. Each is a multiple of the 25 kHz polyphase channel spacing.
|
||||
*/
|
||||
public enum BladeRfSampleRate
|
||||
{
|
||||
RATE_1_25(1_250_000, "1.25 MHz"),
|
||||
RATE_2_5(2_500_000, "2.50 MHz"),
|
||||
RATE_5_0(5_000_000, "5.00 MHz"),
|
||||
RATE_7_5(7_500_000, "7.50 MHz"),
|
||||
RATE_10_0(10_000_000, "10.00 MHz"),
|
||||
RATE_12_5(12_500_000, "12.50 MHz"),
|
||||
RATE_15_0(15_000_000, "15.00 MHz"),
|
||||
RATE_20_0(20_000_000, "20.00 MHz"),
|
||||
RATE_25_0(25_000_000, "25.00 MHz"),
|
||||
RATE_30_0(30_000_000, "30.00 MHz");
|
||||
|
||||
public static final BladeRfSampleRate DEFAULT = RATE_10_0;
|
||||
private final int mRate;
|
||||
private final String mLabel;
|
||||
|
||||
BladeRfSampleRate(int rate, String label)
|
||||
{
|
||||
mRate = rate;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample rate in Hertz
|
||||
*/
|
||||
public int getRate()
|
||||
{
|
||||
return mRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample rates within the device maximum, which is the lower of the hardware limit and what the USB link sustains.
|
||||
* @param maximumRate in Hertz
|
||||
* @return rates, lowest to highest
|
||||
*/
|
||||
public static List<BladeRfSampleRate> getRates(long maximumRate)
|
||||
{
|
||||
List<BladeRfSampleRate> rates = new ArrayList<>();
|
||||
|
||||
for(BladeRfSampleRate rate : values())
|
||||
{
|
||||
if(rate.getRate() <= maximumRate)
|
||||
{
|
||||
rates.add(rate);
|
||||
}
|
||||
}
|
||||
|
||||
return rates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the highest rate from the list that doesn't exceed the requested rate.
|
||||
* @param rates to search, lowest to highest
|
||||
* @param requested rate in Hertz
|
||||
* @return closest rate, or the lowest rate when the requested rate is below all of them.
|
||||
*/
|
||||
public static BladeRfSampleRate getClosest(List<BladeRfSampleRate> rates, int requested)
|
||||
{
|
||||
BladeRfSampleRate closest = rates.getFirst();
|
||||
|
||||
for(BladeRfSampleRate rate : rates)
|
||||
{
|
||||
if(rate.getRate() <= requested)
|
||||
{
|
||||
closest = rate;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* bladeRF tuner
|
||||
*/
|
||||
public class BladeRfTuner extends Tuner
|
||||
{
|
||||
private static final int BITS_PER_SAMPLE = 32;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller for the bladeRF
|
||||
* @param tunerErrorListener to listen for errors from this tuner
|
||||
* @param channelizerType for the channelizer
|
||||
*/
|
||||
public BladeRfTuner(BladeRfTunerController controller, ITunerErrorListener tunerErrorListener,
|
||||
ChannelizerType channelizerType)
|
||||
{
|
||||
super(controller, tunerErrorListener, channelizerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* bladeRF tuner controller
|
||||
*/
|
||||
public BladeRfTunerController getController()
|
||||
{
|
||||
return (BladeRfTunerController)getTunerController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
return "bladeRF " + getUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
return getController().getDeviceInfo().serial();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return TunerClass.BLADERF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getSampleSize()
|
||||
{
|
||||
return 12.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumUSBBitsPerSecond()
|
||||
{
|
||||
return BladeRfSampleRate.RATE_30_0.getRate() * BITS_PER_SAMPLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.bladerf.GainMode;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
|
||||
/**
|
||||
* bladeRF tuner configuration.
|
||||
*/
|
||||
public class BladeRfTunerConfiguration extends TunerConfiguration
|
||||
{
|
||||
private int mSampleRate = BladeRfSampleRate.DEFAULT.getRate();
|
||||
private GainMode mGainMode = BladeRfTunerController.GAIN_MODE_DEFAULT;
|
||||
private int mGain = BladeRfTunerController.GAIN_DEFAULT;
|
||||
private boolean mBiasT = false;
|
||||
|
||||
/**
|
||||
* Default constructor for Jackson
|
||||
*/
|
||||
public BladeRfTunerConfiguration()
|
||||
{
|
||||
super(BladeRfTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ, BladeRfTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param uniqueID for the tuner
|
||||
*/
|
||||
public BladeRfTunerConfiguration(String uniqueID)
|
||||
{
|
||||
super(uniqueID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.BLADERF;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||
public int getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gain_mode")
|
||||
public GainMode getGainMode()
|
||||
{
|
||||
return mGainMode;
|
||||
}
|
||||
|
||||
public void setGainMode(GainMode gainMode)
|
||||
{
|
||||
mGainMode = gainMode;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
||||
public int getGain()
|
||||
{
|
||||
return mGain;
|
||||
}
|
||||
|
||||
public void setGain(int gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
|
||||
public boolean isBiasT()
|
||||
{
|
||||
return mBiasT;
|
||||
}
|
||||
|
||||
public void setBiasT(boolean enabled)
|
||||
{
|
||||
mBiasT = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
import io.github.dsheirer.bladerf.BladeRf;
|
||||
import io.github.dsheirer.bladerf.BladeRfDevice;
|
||||
import io.github.dsheirer.bladerf.BladeRfError;
|
||||
import io.github.dsheirer.bladerf.BladeRfException;
|
||||
import io.github.dsheirer.bladerf.BladeRfStream;
|
||||
import io.github.dsheirer.bladerf.Channel;
|
||||
import io.github.dsheirer.bladerf.ChannelLayout;
|
||||
import io.github.dsheirer.bladerf.DeviceInfo;
|
||||
import io.github.dsheirer.bladerf.DeviceSpeed;
|
||||
import io.github.dsheirer.bladerf.GainMode;
|
||||
import io.github.dsheirer.bladerf.GainModeInfo;
|
||||
import io.github.dsheirer.bladerf.Range;
|
||||
import io.github.dsheirer.bladerf.SampleBuffer;
|
||||
import io.github.dsheirer.bladerf.SampleFormat;
|
||||
import io.github.dsheirer.bladerf.StreamConfig;
|
||||
import io.github.dsheirer.buffer.FloatNativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
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 io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Context;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceList;
|
||||
import org.usb4java.LibUsb;
|
||||
|
||||
/**
|
||||
* bladeRF tuner controller, implemented with the jBladeRF library (libbladeRF).
|
||||
*
|
||||
* The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however libbladeRF owns the USB
|
||||
* device and its transfers.
|
||||
*
|
||||
* Only the first receive channel (RX1 connector) is used. The bladeRF 2.0 has a second receive channel, but it shares
|
||||
* the local oscillator and sample rate with the first, so it couldn't be tuned as an independent tuner.
|
||||
*/
|
||||
public class BladeRfTunerController extends TunerController
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(BladeRfTunerController.class);
|
||||
//The bladeRF 2.0 range. The device reports its own range when it's started.
|
||||
public static final long MINIMUM_TUNABLE_FREQUENCY_HZ = 70_000_000L;
|
||||
public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 6_000_000_000L;
|
||||
public static final GainMode GAIN_MODE_DEFAULT = GainMode.MANUAL;
|
||||
public static final int GAIN_DEFAULT = 30;
|
||||
private static final long FREQUENCY_DEFAULT = 101_100_000L;
|
||||
private static final Channel CHANNEL = Channel.RX0;
|
||||
private static final float SAMPLE_SCALE = 1.0f / 2048.0f;
|
||||
private static final SampleFormat SAMPLE_FORMAT = SampleFormat.SC16_Q11;
|
||||
private static final int BUFFER_SAMPLE_COUNT = 65536;
|
||||
private static final StreamConfig STREAM_CONFIG = new StreamConfig(32, BUFFER_SAMPLE_COUNT, 16);
|
||||
private static final long USB2_MAXIMUM_BYTES_PER_SECOND = 32_000_000L;
|
||||
private static final double MAXIMUM_USABLE_BANDWIDTH_PERCENT = 0.90;
|
||||
private static final long STREAM_CHECK_INTERVAL_SECONDS = 1;
|
||||
|
||||
private final int mBus;
|
||||
private final String mPortAddress;
|
||||
private BladeRfDevice mDevice;
|
||||
private BladeRfDeviceInformation mDeviceInfo;
|
||||
private BladeRfStream mStream;
|
||||
private ScheduledFuture<?> mStreamCheck;
|
||||
private List<BladeRfSampleRate> mSampleRates = List.of();
|
||||
private BladeRfSampleRate mSampleRate;
|
||||
private List<GainModeInfo> mGainModes = List.of();
|
||||
private Range mGainRange;
|
||||
private long mMinimumTunableFrequency = MINIMUM_TUNABLE_FREQUENCY_HZ;
|
||||
private long mMaximumTunableFrequency = MAXIMUM_TUNABLE_FREQUENCY_HZ;
|
||||
private GainMode mGainMode = GAIN_MODE_DEFAULT;
|
||||
private int mGain = GAIN_DEFAULT;
|
||||
private boolean mBiasTSupported;
|
||||
private boolean mBiasT;
|
||||
private volatile float mSamplesPerMillisecond = 0.0f;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus usb
|
||||
* @param portAddress usb
|
||||
* @param tunerErrorListener to receive error notifications from this controller
|
||||
*/
|
||||
public BladeRfTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(tunerErrorListener);
|
||||
mBus = bus;
|
||||
mPortAddress = portAddress;
|
||||
setMinimumFrequency(MINIMUM_TUNABLE_FREQUENCY_HZ);
|
||||
setMaximumFrequency(MAXIMUM_TUNABLE_FREQUENCY_HZ);
|
||||
setMiddleUnusableHalfBandwidth(0);
|
||||
setUsableBandwidthPercentage(MAXIMUM_USABLE_BANDWIDTH_PERCENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.BLADERF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return BUFFER_SAMPLE_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mDevice = BladeRf.open(findDevice());
|
||||
|
||||
if(!mDevice.isFpgaConfigured())
|
||||
{
|
||||
throw new SourceException("bladeRF FPGA isn't loaded - use bladeRF-cli to flash an FPGA image to the device");
|
||||
}
|
||||
|
||||
mBiasTSupported = isBiasTSupported(mDevice);
|
||||
mDeviceInfo = new BladeRfDeviceInformation(mDevice.serial(), mDevice.boardName(),
|
||||
mDevice.firmwareVersion().toString(), mDevice.fpgaVersion().toString(), mDevice.speed().name());
|
||||
mSampleRates = BladeRfSampleRate.getRates(getMaximumSampleRate(mDevice));
|
||||
Range frequencies = mDevice.frequencyRange(CHANNEL);
|
||||
mMinimumTunableFrequency = frequencies.min();
|
||||
mMaximumTunableFrequency = frequencies.max();
|
||||
setFrequencyExtents(mMinimumTunableFrequency, mMaximumTunableFrequency);
|
||||
mGainModes = mDevice.gainModes(CHANNEL);
|
||||
|
||||
setSampleRate(BladeRfSampleRate.getClosest(mSampleRates, BladeRfSampleRate.DEFAULT.getRate()));
|
||||
setFrequency(FREQUENCY_DEFAULT);
|
||||
setGainMode(GAIN_MODE_DEFAULT);
|
||||
setGain(GAIN_DEFAULT);
|
||||
mGainRange = mDevice.gainRange(CHANNEL);
|
||||
}
|
||||
catch(BladeRfException | LinkageError e)
|
||||
{
|
||||
closeDevice(mDevice, false);
|
||||
mDevice = null;
|
||||
throw new SourceException("Can't start bladeRF tuner - " + e.getMessage(), e);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
closeDevice(mDevice, false);
|
||||
mDevice = null;
|
||||
throw se;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
BladeRfDevice device;
|
||||
BladeRfStream stream;
|
||||
boolean biasT;
|
||||
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
device = mDevice;
|
||||
mDevice = null;
|
||||
stream = clearStream();
|
||||
biasT = mBiasT;
|
||||
mBiasT = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
|
||||
mNativeBufferBroadcaster.clear();
|
||||
|
||||
if(stream != null)
|
||||
{
|
||||
stream.stop();
|
||||
}
|
||||
|
||||
closeDevice(device, biasT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the device. The bias-T stays on after the device is closed, so it has to be turned off first.
|
||||
* @param device to close, or null
|
||||
* @param biasT true if the bias-T is on
|
||||
*/
|
||||
private static void closeDevice(BladeRfDevice device, boolean biasT)
|
||||
{
|
||||
if(device != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(biasT)
|
||||
{
|
||||
device.setBiasTee(CHANNEL, false);
|
||||
}
|
||||
}
|
||||
catch(BladeRfException | RuntimeException e)
|
||||
{
|
||||
mLog.error("Error turning off bladeRF bias-T", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
device.close();
|
||||
}
|
||||
catch(RuntimeException e)
|
||||
{
|
||||
mLog.error("Error closing bladeRF device", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probes for bias-T support, since only the bladeRF 2.0 has a bias-T.
|
||||
*/
|
||||
private static boolean isBiasTSupported(BladeRfDevice device) throws BladeRfException
|
||||
{
|
||||
try
|
||||
{
|
||||
device.biasTee(CHANNEL);
|
||||
return true;
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
if(e.error() == BladeRfError.UNSUPPORTED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest sample rate supported by the hardware and the USB connection.
|
||||
*/
|
||||
private static long getMaximumSampleRate(BladeRfDevice device) throws BladeRfException
|
||||
{
|
||||
long maximum = device.sampleRateRange(CHANNEL).max();
|
||||
|
||||
if(device.speed() != DeviceSpeed.SUPER)
|
||||
{
|
||||
maximum = Math.min(maximum, USB2_MAXIMUM_BYTES_PER_SECOND / SAMPLE_FORMAT.bytesPerSample());
|
||||
}
|
||||
|
||||
return maximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the libbladeRF description of the device at this controller's USB bus and port.
|
||||
*/
|
||||
private DeviceInfo findDevice() throws SourceException, BladeRfException
|
||||
{
|
||||
int address = readUsbAddress();
|
||||
|
||||
for(DeviceInfo info : BladeRf.listDevices())
|
||||
{
|
||||
if(info.usbBus() == mBus && info.usbAddress() == address)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
throw new SourceException("bladeRF library can't access the USB device at bus [" + mBus + "] port [" +
|
||||
mPortAddress + "] - if using linux, check the udev rules. If using windows, install the WinUSB driver.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current USB address of the device at this controller's USB bus and port. The address changes each time
|
||||
* that the device is plugged in.
|
||||
*/
|
||||
private int readUsbAddress() throws SourceException
|
||||
{
|
||||
Context context = new Context();
|
||||
int status = LibUsb.init(context);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DeviceList deviceList = new DeviceList();
|
||||
int count = LibUsb.getDeviceList(context, deviceList);
|
||||
|
||||
if(count < 0)
|
||||
{
|
||||
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for(Device device : deviceList)
|
||||
{
|
||||
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||
{
|
||||
return LibUsb.getDeviceAddress(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LibUsb.freeDeviceList(deviceList, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LibUsb.exit(context);
|
||||
}
|
||||
|
||||
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(TunerConfiguration tunerConfiguration) throws SourceException
|
||||
{
|
||||
if(!(tunerConfiguration instanceof BladeRfTunerConfiguration config))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid tuner config:" + tunerConfiguration.getClass());
|
||||
}
|
||||
|
||||
//Saved limits can be wider than this device, since the defaults are for the bladeRF 2.0.
|
||||
config.setMinimumFrequency(Math.max(config.getMinimumFrequency(), mMinimumTunableFrequency));
|
||||
config.setMaximumFrequency(Math.min(config.getMaximumFrequency() > 0 ? config.getMaximumFrequency() :
|
||||
mMaximumTunableFrequency, mMaximumTunableFrequency));
|
||||
|
||||
BladeRfSampleRate rate = BladeRfSampleRate.getClosest(mSampleRates, config.getSampleRate());
|
||||
setSampleRate(rate);
|
||||
config.setSampleRate(rate.getRate());
|
||||
|
||||
//Invoke super for frequency, frequency correction and autoPPM
|
||||
super.apply(config);
|
||||
setBiasT(config.isBiasT());
|
||||
setGain(config.getGain());
|
||||
setGainMode(isSupported(config.getGainMode()) ? config.getGainMode() : GAIN_MODE_DEFAULT);
|
||||
}
|
||||
|
||||
private boolean isSupported(GainMode mode)
|
||||
{
|
||||
return mGainModes.stream().anyMatch(info -> info.mode() == mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTunedFrequency() throws SourceException
|
||||
{
|
||||
return mFrequencyController.getTunedFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunedFrequency(long frequency) throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mDevice != null)
|
||||
{
|
||||
mDevice.setFrequency(CHANNEL, frequency);
|
||||
}
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
throw new SourceException("Can't tune bladeRF to [" + frequency + "] - " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCurrentSampleRate()
|
||||
{
|
||||
return mSampleRate != null ? mSampleRate.getRate() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample rates supported by the device across its current USB connection.
|
||||
*/
|
||||
public List<BladeRfSampleRate> getSampleRates()
|
||||
{
|
||||
return mSampleRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current sample rate.
|
||||
*/
|
||||
public BladeRfSampleRate getBladeRfSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate and the matching analog filter bandwidth. The sample stream is stopped for the change.
|
||||
* @param rate to set
|
||||
* @throws SourceException if the sample rate is locked or can't be set
|
||||
*/
|
||||
public void setSampleRate(BladeRfSampleRate rate) throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(rate != mSampleRate && mDevice != null)
|
||||
{
|
||||
if(isLockedSampleRate())
|
||||
{
|
||||
throw new SourceException("Can't change bladeRF sample rate while channels are allocated");
|
||||
}
|
||||
|
||||
boolean streaming = mStream != null;
|
||||
BladeRfStream stream = clearStream();
|
||||
|
||||
if(stream != null)
|
||||
{
|
||||
stream.stop();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mDevice.setSampleRate(CHANNEL, rate.getRate());
|
||||
long requested = (long)(rate.getRate() * MAXIMUM_USABLE_BANDWIDTH_PERCENT);
|
||||
long bandwidth = mDevice.setBandwidth(CHANNEL, requested);
|
||||
setUsableBandwidthPercentage(Math.min(MAXIMUM_USABLE_BANDWIDTH_PERCENT,
|
||||
(double)bandwidth / rate.getRate()));
|
||||
mSampleRate = rate;
|
||||
mSamplesPerMillisecond = rate.getRate() / 1000.0f;
|
||||
mFrequencyController.setSampleRate(rate.getRate());
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
throw new SourceException("Can't set bladeRF sample rate to [" + rate + "] - " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(streaming)
|
||||
{
|
||||
startStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gain modes supported by the device.
|
||||
*/
|
||||
public List<GainModeInfo> getGainModes()
|
||||
{
|
||||
return mGainModes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Range of gain values, available once the tuner has been started.
|
||||
*/
|
||||
public Range getGainRange()
|
||||
{
|
||||
return mGainRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowest frequency that the device can tune.
|
||||
*/
|
||||
public long getMinimumTunableFrequency()
|
||||
{
|
||||
return mMinimumTunableFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest frequency that the device can tune.
|
||||
*/
|
||||
public long getMaximumTunableFrequency()
|
||||
{
|
||||
return mMaximumTunableFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain mode.
|
||||
* @param mode to set
|
||||
* @throws SourceException if the mode can't be set
|
||||
*/
|
||||
public void setGainMode(GainMode mode) throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mDevice != null)
|
||||
{
|
||||
mDevice.setGainMode(CHANNEL, mode);
|
||||
|
||||
if(mode == GainMode.MANUAL)
|
||||
{
|
||||
mDevice.setGain(CHANNEL, mGain);
|
||||
}
|
||||
}
|
||||
|
||||
mGainMode = mode;
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
throw new SourceException("Can't set bladeRF gain mode to [" + mode + "] - " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain, which is used when the gain mode is manual.
|
||||
* @param gain in dB
|
||||
* @throws SourceException if the gain can't be set
|
||||
*/
|
||||
public void setGain(int gain) throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mGain = gain;
|
||||
|
||||
if(mDevice != null && mGainMode == GainMode.MANUAL)
|
||||
{
|
||||
mDevice.setGain(CHANNEL, gain);
|
||||
}
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
throw new SourceException("Can't set bladeRF gain to [" + gain + "] - " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the device has a bias-T.
|
||||
*/
|
||||
public boolean isBiasTSupported()
|
||||
{
|
||||
return mBiasTSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the bias-T on or off. On the bladeRF 2.0 this switches both receive connectors.
|
||||
* @param enabled true to turn on
|
||||
* @throws SourceException if the bias-T can't be set
|
||||
*/
|
||||
public void setBiasT(boolean enabled) throws SourceException
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mDevice != null && mBiasTSupported)
|
||||
{
|
||||
mDevice.setBiasTee(CHANNEL, enabled);
|
||||
mBiasT = enabled;
|
||||
}
|
||||
}
|
||||
catch(BladeRfException e)
|
||||
{
|
||||
throw new SourceException("Can't set bladeRF bias-T - " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Device information, available once the tuner has been started.
|
||||
*/
|
||||
public BladeRfDeviceInformation getDeviceInfo()
|
||||
{
|
||||
return mDeviceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the IQ buffer listener and automatically starts streaming, if not already started.
|
||||
*/
|
||||
@Override
|
||||
public void addBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mDevice != null)
|
||||
{
|
||||
super.addBufferListener(listener);
|
||||
|
||||
if(mStream == null)
|
||||
{
|
||||
startStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error starting streaming on bladeRF tuner", se);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the IQ buffer listener and stops streaming if there are no more listeners.
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
super.removeBufferListener(listener);
|
||||
|
||||
if(!hasBufferListeners())
|
||||
{
|
||||
BladeRfStream stream = clearStream();
|
||||
|
||||
if(stream != null)
|
||||
{
|
||||
stream.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the sample stream.
|
||||
*/
|
||||
private void startStream() throws SourceException
|
||||
{
|
||||
try
|
||||
{
|
||||
mStream = mDevice.startRx(ChannelLayout.RX_X1, SAMPLE_FORMAT, STREAM_CONFIG, this::receive);
|
||||
mStreamCheck = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::checkStream, STREAM_CHECK_INTERVAL_SECONDS,
|
||||
STREAM_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
catch(BladeRfException | RuntimeException e)
|
||||
{
|
||||
throw new SourceException("Can't start bladeRF sample stream - " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the sample stream so that it's no longer monitored. The caller is responsible for stopping it.
|
||||
* @return stream or null if there is no stream
|
||||
*/
|
||||
private BladeRfStream clearStream()
|
||||
{
|
||||
if(mStreamCheck != null)
|
||||
{
|
||||
mStreamCheck.cancel(false);
|
||||
mStreamCheck = null;
|
||||
}
|
||||
|
||||
BladeRfStream stream = mStream;
|
||||
mStream = null;
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects a stream that ended by itself, such as when the device has an error, and flags the tuner.
|
||||
*/
|
||||
private void checkStream()
|
||||
{
|
||||
String message = null;
|
||||
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mStream != null && !mStream.isRunning())
|
||||
{
|
||||
message = "bladeRF sample stream stopped - " + mStream.failure();
|
||||
clearStream();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
|
||||
//Outside of the lock because this stops the tuner
|
||||
if(message != null)
|
||||
{
|
||||
mLog.error(message);
|
||||
setErrorMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives samples on the stream thread and dispatches them to the buffer listeners. Exceptions are contained here
|
||||
* since an exception reaching the library stops the sample stream.
|
||||
* @param buffer from the library, which is only valid until this method returns.
|
||||
* @return true to continue streaming
|
||||
*/
|
||||
private boolean receive(SampleBuffer buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
broadcast(new FloatNativeBuffer(scale(buffer.copyShorts()), System.currentTimeMillis(), mSamplesPerMillisecond));
|
||||
}
|
||||
catch(RuntimeException e)
|
||||
{
|
||||
mLog.error("Error dispatching bladeRF sample buffer", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales 12-bit samples to the range -1 to 1.
|
||||
* @param samples interleaved complex samples
|
||||
* @return scaled interleaved complex samples
|
||||
*/
|
||||
static float[] scale(short[] samples)
|
||||
{
|
||||
float[] scaled = new float[samples.length];
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
scaled[x] = samples[x] * SAMPLE_SCALE;
|
||||
}
|
||||
|
||||
return scaled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
import io.github.dsheirer.bladerf.GainMode;
|
||||
import io.github.dsheirer.bladerf.GainModeInfo;
|
||||
import io.github.dsheirer.bladerf.Range;
|
||||
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.manager.TunerStatus;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import java.awt.Component;
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
|
||||
/**
|
||||
* bladeRF tuner editor
|
||||
*/
|
||||
public class BladeRfTunerEditor extends TunerEditor<BladeRfTuner, BladeRfTunerConfiguration>
|
||||
{
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(BladeRfTunerEditor.class);
|
||||
private JButton mTunerInfoButton;
|
||||
private JComboBox<BladeRfSampleRate> mSampleRateCombo;
|
||||
private JCheckBox mBiasTCheckBox;
|
||||
private JComboBox<GainModeInfo> mGainModeCombo;
|
||||
private JSlider mGainSlider;
|
||||
private JLabel mGainValueLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for wide-band recordings
|
||||
* @param tunerManager for saving tuner configuration
|
||||
* @param discoveredTuner for the optionally usable bladeRF tuner and controller
|
||||
*/
|
||||
public BladeRfTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
||||
{
|
||||
super(userPreferences, tunerManager, discoveredTuner);
|
||||
init();
|
||||
tunerStatusUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinimumTunableFrequency()
|
||||
{
|
||||
return hasTuner() ? getTuner().getController().getMinimumTunableFrequency() :
|
||||
BladeRfTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximumTunableFrequency()
|
||||
{
|
||||
return hasTuner() ? getTuner().getController().getMaximumTunableFrequency() :
|
||||
BladeRfTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ;
|
||||
}
|
||||
|
||||
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(new JLabel("Gain:"));
|
||||
add(getGainSlider());
|
||||
add(getGainValueLabel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tunerStatusUpdated()
|
||||
{
|
||||
setLoading(true);
|
||||
|
||||
boolean started = hasStartedTuner();
|
||||
getTunerIdLabel().setText(started ? getTuner().getPreferredName() : getDiscoveredTuner().getId());
|
||||
|
||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||
if(getDiscoveredTuner().hasErrorMessage())
|
||||
{
|
||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
getTunerStatusLabel().setText(status);
|
||||
getButtonPanel().updateControls();
|
||||
getFrequencyPanel().updateControls();
|
||||
getTunerInfoButton().setEnabled(started);
|
||||
|
||||
if(started)
|
||||
{
|
||||
BladeRfTunerController controller = getTuner().getController();
|
||||
List<BladeRfSampleRate> rates = controller.getSampleRates();
|
||||
getSampleRateCombo().setModel(new DefaultComboBoxModel<>(rates.toArray(new BladeRfSampleRate[0])));
|
||||
getSampleRateCombo().setSelectedItem(controller.getBladeRfSampleRate());
|
||||
getSampleRateCombo().setEnabled(!controller.isLockedSampleRate());
|
||||
|
||||
getBiasTCheckBox().setVisible(controller.isBiasTSupported());
|
||||
getBiasTCheckBox().setEnabled(true);
|
||||
|
||||
List<GainModeInfo> modes = controller.getGainModes();
|
||||
getGainModeCombo().setModel(new DefaultComboBoxModel<>(modes.toArray(new GainModeInfo[0])));
|
||||
getGainModeCombo().setEnabled(true);
|
||||
|
||||
Range range = controller.getGainRange();
|
||||
getGainSlider().setMinimum((int)range.min());
|
||||
getGainSlider().setMaximum((int)range.max());
|
||||
|
||||
if(hasConfiguration())
|
||||
{
|
||||
getBiasTCheckBox().setSelected(getConfiguration().isBiasT());
|
||||
getGainSlider().setValue(getConfiguration().getGain());
|
||||
|
||||
for(GainModeInfo mode : modes)
|
||||
{
|
||||
if(mode.mode() == getConfiguration().getGainMode())
|
||||
{
|
||||
getGainModeCombo().setSelectedItem(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateGainControls();
|
||||
}
|
||||
else
|
||||
{
|
||||
getSampleRateCombo().setModel(new DefaultComboBoxModel<>());
|
||||
getSampleRateCombo().setEnabled(false);
|
||||
getBiasTCheckBox().setEnabled(false);
|
||||
getBiasTCheckBox().setSelected(false);
|
||||
getGainModeCombo().setModel(new DefaultComboBoxModel<>());
|
||||
getGainModeCombo().setEnabled(false);
|
||||
getGainSlider().setEnabled(false);
|
||||
getGainValueLabel().setEnabled(false);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tuner has finished starting, since a status update can arrive while it's starting or restarting.
|
||||
*/
|
||||
private boolean hasStartedTuner()
|
||||
{
|
||||
return hasTuner() && getTuner().getController().getGainRange() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the gain slider when the gain mode is manual and refreshes the gain value label.
|
||||
*/
|
||||
private void updateGainControls()
|
||||
{
|
||||
GainModeInfo mode = (GainModeInfo)getGainModeCombo().getSelectedItem();
|
||||
boolean manual = hasTuner() && mode != null && mode.mode() == GainMode.MANUAL;
|
||||
getGainSlider().setEnabled(manual);
|
||||
getGainValueLabel().setEnabled(manual);
|
||||
getGainValueLabel().setText(manual ? getGainSlider().getValue() + " dB" : "AGC");
|
||||
}
|
||||
|
||||
/**
|
||||
* Button that provides tuner information
|
||||
*/
|
||||
private JButton getTunerInfoButton()
|
||||
{
|
||||
if(mTunerInfoButton == null)
|
||||
{
|
||||
mTunerInfoButton = new JButton("Info");
|
||||
mTunerInfoButton.setEnabled(false);
|
||||
mTunerInfoButton.addActionListener(_ -> JOptionPane.showMessageDialog(BladeRfTunerEditor.this,
|
||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||
}
|
||||
|
||||
return mTunerInfoButton;
|
||||
}
|
||||
|
||||
private JComboBox<BladeRfSampleRate> getSampleRateCombo()
|
||||
{
|
||||
if(mSampleRateCombo == null)
|
||||
{
|
||||
mSampleRateCombo = new JComboBox<>();
|
||||
mSampleRateCombo.setEnabled(false);
|
||||
mSampleRateCombo.addActionListener(_ ->
|
||||
{
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
BladeRfSampleRate rate = (BladeRfSampleRate)mSampleRateCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().setSampleRate(rate);
|
||||
|
||||
//Adjust the min/max values for the sample rate.
|
||||
adjustForSampleRate(rate.getRate());
|
||||
|
||||
save();
|
||||
}
|
||||
catch(Exception e1)
|
||||
{
|
||||
mLog.error("Error setting bladeRF sample rate", e1);
|
||||
JOptionPane.showMessageDialog(BladeRfTunerEditor.this, "Couldn't set sample rate to " + rate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSampleRateCombo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. Also powers the RX2 connector.");
|
||||
mBiasTCheckBox.addActionListener(_ ->
|
||||
{
|
||||
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 JComboBox<GainModeInfo> getGainModeCombo()
|
||||
{
|
||||
if(mGainModeCombo == null)
|
||||
{
|
||||
mGainModeCombo = new JComboBox<>();
|
||||
mGainModeCombo.setEnabled(false);
|
||||
mGainModeCombo.setRenderer(new DefaultListCellRenderer()
|
||||
{
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean selected,
|
||||
boolean focused)
|
||||
{
|
||||
Object label = value instanceof GainModeInfo mode ? mode.name() : value;
|
||||
return super.getListCellRendererComponent(list, label, index, selected, focused);
|
||||
}
|
||||
});
|
||||
mGainModeCombo.addActionListener(_ ->
|
||||
{
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
GainModeInfo mode = (GainModeInfo)mGainModeCombo.getSelectedItem();
|
||||
|
||||
try
|
||||
{
|
||||
getTuner().getController().setGainMode(mode.mode());
|
||||
updateGainControls();
|
||||
save();
|
||||
}
|
||||
catch(Exception e1)
|
||||
{
|
||||
mLog.error("Error setting bladeRF gain mode", e1);
|
||||
JOptionPane.showMessageDialog(mGainModeCombo, "Couldn't set gain mode to " + mode.name());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mGainModeCombo;
|
||||
}
|
||||
|
||||
private JSlider getGainSlider()
|
||||
{
|
||||
if(mGainSlider == null)
|
||||
{
|
||||
mGainSlider = new JSlider(JSlider.HORIZONTAL, 0, 60, BladeRfTunerController.GAIN_DEFAULT);
|
||||
mGainSlider.setEnabled(false);
|
||||
mGainSlider.setMajorTickSpacing(5);
|
||||
mGainSlider.setPaintTicks(true);
|
||||
mGainSlider.addChangeListener(_ ->
|
||||
{
|
||||
int gain = mGainSlider.getValue();
|
||||
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTuner().getController().setGain(gain);
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't set bladeRF gain to:" + gain, e);
|
||||
JOptionPane.showMessageDialog(mGainSlider, "Couldn't set gain value to " + gain);
|
||||
}
|
||||
}
|
||||
|
||||
updateGainControls();
|
||||
});
|
||||
}
|
||||
|
||||
return mGainSlider;
|
||||
}
|
||||
|
||||
private JLabel getGainValueLabel()
|
||||
{
|
||||
if(mGainValueLabel == null)
|
||||
{
|
||||
mGainValueLabel = new JLabel("0 dB");
|
||||
mGainValueLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
return mGainValueLabel;
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
if(getSampleRateCombo().getSelectedItem() instanceof BladeRfSampleRate rate)
|
||||
{
|
||||
getConfiguration().setSampleRate(rate.getRate());
|
||||
}
|
||||
|
||||
if(getGainModeCombo().getSelectedItem() instanceof GainModeInfo mode)
|
||||
{
|
||||
getConfiguration().setGainMode(mode.mode());
|
||||
}
|
||||
|
||||
getConfiguration().setGain(getGainSlider().getValue());
|
||||
getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
|
||||
saveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTunerLockState(boolean locked)
|
||||
{
|
||||
getFrequencyPanel().updateControls();
|
||||
getSampleRateCombo().setEnabled(!locked);
|
||||
getSampleRateCombo().setToolTipText(locked ? "Sample Rate is locked. Disable decoding channels to unlock." :
|
||||
"Select a sample rate for the tuner");
|
||||
}
|
||||
|
||||
private String getTunerInfo()
|
||||
{
|
||||
if(getDiscoveredTuner().getTunerStatus() == TunerStatus.ERROR)
|
||||
{
|
||||
return getDiscoveredTuner().getErrorMessage();
|
||||
}
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
BladeRfDeviceInformation info = getTuner().getController().getDeviceInfo();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<html><h3>bladeRF Tuner</h3>");
|
||||
sb.append("<b>Serial: </b>").append(info.serial()).append("<br>");
|
||||
sb.append("<b>Board: </b>").append(info.board()).append("<br>");
|
||||
sb.append("<b>Firmware: </b>").append(info.firmware()).append("<br>");
|
||||
sb.append("<b>FPGA: </b>").append(info.fpga()).append("<br>");
|
||||
sb.append("<b>USB Speed: </b>").append(info.speed()).append("<br>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.airspy.AirspyTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.airspy.hf.AirspyHfTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.bladerf.BladeRfTunerConfiguration;
|
||||
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;
|
||||
@@ -44,6 +45,7 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = AirspyTunerConfiguration.class, name = "airspyTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = AirspyHfTunerConfiguration.class, name = "airspyHfTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = BladeRfTunerConfiguration.class, name = "bladeRfTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = E4KTunerConfiguration.class, name = "e4KTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FC0013TunerConfiguration.class, name = "fc0013TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = FCD1TunerConfiguration.class, name = "fcd1TunerConfiguration"),
|
||||
|
||||
9
src/main/resources/88-nuand-bladerf.rules
Normal file
9
src/main/resources/88-nuand-bladerf.rules
Normal file
@@ -0,0 +1,9 @@
|
||||
# Nuand bladeRF udev rules
|
||||
# Grants access to the logged-in user (uaccess) and to the plugdev group.
|
||||
|
||||
# bladeRF 2.0 micro
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="2cf0", ATTR{idProduct}=="5250", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
||||
# bladeRF x40/x115
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="2cf0", ATTR{idProduct}=="5246", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
||||
# bladeRF x40/x115 with the legacy vendor and product ID
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6066", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 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.bladerf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.dsheirer.bladerf.GainMode;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for the parts of the bladeRF tuner that don't require a device.
|
||||
*/
|
||||
class BladeRfTunerTest
|
||||
{
|
||||
private static final float SCALE = 2048.0f;
|
||||
|
||||
@Test
|
||||
void lookupFindsEachUsbProduct()
|
||||
{
|
||||
assertEquals(TunerClass.BLADERF, TunerClass.lookup((short)0x2CF0, (short)0x5250));
|
||||
assertEquals(TunerClass.BLADERF, TunerClass.lookup((short)0x2CF0, (short)0x5246));
|
||||
assertEquals(TunerClass.BLADERF, TunerClass.lookup((short)0x1D50, (short)0x6066));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scalesSamples()
|
||||
{
|
||||
short[] samples = {2047, -2048, 1024, -1024};
|
||||
|
||||
assertArrayEquals(new float[]{2047 / SCALE, -1.0f, 0.5f, -0.5f}, BladeRfTunerController.scale(samples));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sampleRatesAreLimitedByTheDevice()
|
||||
{
|
||||
List<BladeRfSampleRate> rates = BladeRfSampleRate.getRates(4_000_000);
|
||||
|
||||
assertEquals(List.of(BladeRfSampleRate.RATE_1_25, BladeRfSampleRate.RATE_2_5), rates);
|
||||
assertEquals(BladeRfSampleRate.RATE_2_5, BladeRfSampleRate.getClosest(rates, BladeRfSampleRate.DEFAULT.getRate()));
|
||||
assertEquals(BladeRfSampleRate.RATE_1_25, BladeRfSampleRate.getClosest(rates, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sampleRatesDivideIntoPolyphaseChannels()
|
||||
{
|
||||
for(BladeRfSampleRate rate : BladeRfSampleRate.values())
|
||||
{
|
||||
assertEquals(0, rate.getRate() % 50_000, rate.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationSurvivesSerialization() throws Exception
|
||||
{
|
||||
BladeRfTunerConfiguration configuration = new BladeRfTunerConfiguration("test");
|
||||
configuration.setSampleRate(BladeRfSampleRate.RATE_5_0.getRate());
|
||||
configuration.setGainMode(GainMode.SLOW_ATTACK_AGC);
|
||||
configuration.setGain(12);
|
||||
configuration.setBiasT(true);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
String json = mapper.writeValueAsString(configuration);
|
||||
TunerConfiguration restored = mapper.readValue(json, TunerConfiguration.class);
|
||||
|
||||
BladeRfTunerConfiguration bladeRf = assertInstanceOf(BladeRfTunerConfiguration.class, restored);
|
||||
assertEquals(TunerType.BLADERF, bladeRf.getTunerType());
|
||||
assertEquals(BladeRfSampleRate.RATE_5_0.getRate(), bladeRf.getSampleRate());
|
||||
assertEquals(GainMode.SLOW_ATTACK_AGC, bladeRf.getGainMode());
|
||||
assertEquals(12, bladeRf.getGain());
|
||||
assertEquals(true, bladeRf.isBiasT());
|
||||
assertEquals("test", bladeRf.getUniqueID());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user