mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
calculation when additional channels are allocated. Incorporates tuner specific definition of overall usable bandwidth, so that tuners like airspy can specify 90% of 10 MHz (ie 9 MHz) is truly usable and the fringe 10% is unusable rolloff. Also includes tuner-specific defining of the width of the central spike, or a value of 0 indicates that the entire range is usable (ie airspy).
1146 lines
28 KiB
Java
1146 lines
28 KiB
Java
package source.tuner.hackrf;
|
|
/*******************************************************************************
|
|
* SDR Trunk
|
|
* Copyright (C) 2014 Dennis Sheirer
|
|
*
|
|
* Ported from libhackrf at:
|
|
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf
|
|
*
|
|
* Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
|
|
* Copyright (c) 2013, Benjamin Vernoux <titanmkd@gmail.com>
|
|
* copyright (c) 2013, Michael Ossmann <mike@ossmann.com>
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* Neither the name of Great Scott Gadgets nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* 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/>
|
|
******************************************************************************/
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.LinkedTransferQueue;
|
|
import java.util.concurrent.ScheduledFuture;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import javax.usb.UsbException;
|
|
|
|
import org.apache.commons.io.EndianUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.usb4java.Device;
|
|
import org.usb4java.DeviceDescriptor;
|
|
import org.usb4java.DeviceHandle;
|
|
import org.usb4java.LibUsb;
|
|
import org.usb4java.LibUsbException;
|
|
import org.usb4java.Transfer;
|
|
import org.usb4java.TransferCallback;
|
|
|
|
import sample.Broadcaster;
|
|
import sample.Listener;
|
|
import sample.adapter.ByteSampleAdapter;
|
|
import sample.complex.ComplexBuffer;
|
|
import source.SourceException;
|
|
import source.tuner.TunerConfiguration;
|
|
import source.tuner.TunerController;
|
|
import controller.ThreadPoolManager;
|
|
import controller.ThreadPoolManager.ThreadType;
|
|
|
|
|
|
public class HackRFTunerController extends TunerController
|
|
{
|
|
private final static Logger mLog =
|
|
LoggerFactory.getLogger( HackRFTunerController.class );
|
|
|
|
public final static long TIMEOUT_US = 1000000l; //uSeconds
|
|
public static final byte USB_ENDPOINT = (byte)0x81;
|
|
public static final byte USB_INTERFACE = (byte)0x0;
|
|
|
|
public static final byte REQUEST_TYPE_IN =
|
|
(byte)( LibUsb.ENDPOINT_IN |
|
|
LibUsb.REQUEST_TYPE_VENDOR |
|
|
LibUsb.RECIPIENT_DEVICE );
|
|
|
|
public static final byte REQUEST_TYPE_OUT =
|
|
(byte)( LibUsb.ENDPOINT_OUT |
|
|
LibUsb.REQUEST_TYPE_VENDOR |
|
|
LibUsb.RECIPIENT_DEVICE );
|
|
|
|
public static final long MIN_FREQUENCY = 10000000l;
|
|
public static final long MAX_FREQUENCY = 6000000000l;
|
|
public static final long DEFAULT_FREQUENCY = 101100000;
|
|
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
|
|
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
|
|
|
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
|
|
|
|
private LinkedTransferQueue<byte[]> mFilledBuffers =
|
|
new LinkedTransferQueue<byte[]>();
|
|
|
|
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
|
|
|
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
|
private BufferProcessor mBufferProcessor = new BufferProcessor();
|
|
|
|
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
|
|
public int mBufferSize = 262144;
|
|
private boolean mAmplifierEnabled = false;
|
|
|
|
private Device mDevice;
|
|
private DeviceDescriptor mDeviceDescriptor;
|
|
private DeviceHandle mDeviceHandle;
|
|
private ThreadPoolManager mThreadPoolManager;
|
|
|
|
public HackRFTunerController( Device device, DeviceDescriptor descriptor,
|
|
ThreadPoolManager threadPoolManager ) throws SourceException
|
|
{
|
|
super( MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT );
|
|
|
|
mDevice = device;
|
|
mDeviceDescriptor = descriptor;
|
|
mThreadPoolManager = threadPoolManager;
|
|
}
|
|
|
|
public void init() throws SourceException
|
|
{
|
|
mDeviceHandle = new DeviceHandle();
|
|
|
|
int result = LibUsb.open( mDevice, mDeviceHandle );
|
|
|
|
if( result != 0 )
|
|
{
|
|
if( result == LibUsb.ERROR_ACCESS )
|
|
{
|
|
mLog.error( "Unable to access HackRF - insufficient permissions. "
|
|
+ "If you are running a Linux OS, have you installed the "
|
|
+ "hackRF rules file in \\etc\\udev\\rules.d ??" );
|
|
}
|
|
|
|
throw new SourceException( "Couldn't open hackrf device - " +
|
|
LibUsb.strError( result ) );
|
|
}
|
|
|
|
try
|
|
{
|
|
claimInterface();
|
|
|
|
setMode( Mode.RECEIVE );
|
|
|
|
setFrequency( DEFAULT_FREQUENCY );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
throw new SourceException( "HackRF Tuner Controller - couldn't "
|
|
+ "claim USB interface or get endpoint or pipe", e );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Claims the USB interface. If another application currently has
|
|
* the interface claimed, the USB_FORCE_CLAIM_INTERFACE setting
|
|
* will dictate if the interface is forcibly claimed from the other
|
|
* application
|
|
*/
|
|
private void claimInterface() throws SourceException
|
|
{
|
|
if( mDeviceHandle != null )
|
|
{
|
|
int result = LibUsb.kernelDriverActive( mDeviceHandle, USB_INTERFACE );
|
|
|
|
if( result == 1 )
|
|
{
|
|
result = LibUsb.detachKernelDriver( mDeviceHandle, USB_INTERFACE );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "failed attempt to detach kernel driver [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
|
|
throw new SourceException( "couldn't detach kernel driver "
|
|
+ "from device" );
|
|
}
|
|
}
|
|
|
|
result = LibUsb.claimInterface( mDeviceHandle, USB_INTERFACE );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
throw new SourceException( "couldn't claim usb interface [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new SourceException( "couldn't claim usb interface - no "
|
|
+ "device handle" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* HackRF board identifier/type
|
|
*/
|
|
public BoardID getBoardID() throws UsbException
|
|
{
|
|
int id = readByte( Request.BOARD_ID_READ, (byte)0, (byte)0, false );
|
|
|
|
return BoardID.lookup( id );
|
|
}
|
|
|
|
/**
|
|
* HackRF firmware version string
|
|
*/
|
|
public String getFirmwareVersion() throws UsbException
|
|
{
|
|
ByteBuffer buffer = readArray( Request.VERSION_STRING_READ, 0, 0, 255 );
|
|
|
|
byte[] data = new byte[ 255 ];
|
|
|
|
buffer.get( data );
|
|
|
|
return new String( data );
|
|
}
|
|
|
|
/**
|
|
* HackRF part id number and serial number
|
|
*/
|
|
public Serial getSerial() throws UsbException
|
|
{
|
|
ByteBuffer buffer = readArray( Request.BOARD_PARTID_SERIALNO_READ, 0, 0, 24 );
|
|
|
|
return new Serial( buffer );
|
|
}
|
|
|
|
/**
|
|
* Sets the HackRF transceiver mode
|
|
*/
|
|
public void setMode( Mode mode ) throws UsbException
|
|
{
|
|
write( Request.SET_TRANSCEIVER_MODE, mode.getNumber(), 0 );
|
|
}
|
|
|
|
/**
|
|
* Sets the HackRF baseband filter
|
|
*/
|
|
public void setBasebandFilter( BasebandFilter filter ) throws UsbException
|
|
{
|
|
write( Request.BASEBAND_FILTER_BANDWIDTH_SET,
|
|
filter.getLowValue(),
|
|
filter.getHighValue() );
|
|
}
|
|
|
|
/**
|
|
* Enables (true) or disables (false) the amplifier
|
|
*/
|
|
public void setAmplifierEnabled( boolean enabled ) throws UsbException
|
|
{
|
|
write( Request.AMP_ENABLE, ( enabled ? 1 : 0 ), 0 );
|
|
|
|
mAmplifierEnabled = enabled;
|
|
}
|
|
|
|
public boolean getAmplifier()
|
|
{
|
|
return mAmplifierEnabled;
|
|
}
|
|
|
|
/**
|
|
* Sets the IF LNA Gain
|
|
*/
|
|
public void setLNAGain( HackRFLNAGain gain ) throws UsbException
|
|
{
|
|
int result = readByte( Request.SET_LNA_GAIN, 0, gain.getValue(), true );
|
|
|
|
if( result != 1 )
|
|
{
|
|
throw new UsbException( "couldn't set lna gain to " + gain );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the Baseband VGA Gain
|
|
*/
|
|
public void setVGAGain( HackRFVGAGain gain ) throws UsbException
|
|
{
|
|
int result = readByte( Request.SET_VGA_GAIN, 0, gain.getValue(), true );
|
|
|
|
if( result != 1 )
|
|
{
|
|
throw new UsbException( "couldn't set vga gain to " + gain );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Not implemented
|
|
*/
|
|
public long getTunedFrequency() throws SourceException
|
|
{
|
|
return mFrequencyController.getTunedFrequency();
|
|
}
|
|
|
|
@Override
|
|
public void setTunedFrequency( long frequency ) throws SourceException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 8 );
|
|
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
|
|
|
int mhz = (int)( frequency / 1E6 );
|
|
int hz = (int)( frequency - ( mhz * 1E6 ) );
|
|
|
|
buffer.putInt( mhz );
|
|
buffer.putInt( hz );
|
|
|
|
buffer.rewind();
|
|
|
|
try
|
|
{
|
|
write( Request.SET_FREQUENCY, 0, 0, buffer );
|
|
}
|
|
catch( UsbException e )
|
|
{
|
|
mLog.error( "error setting frequency [" + frequency + "]", e );
|
|
|
|
throw new SourceException( "error setting frequency [" +
|
|
frequency + "]", e );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getCurrentSampleRate() throws SourceException
|
|
{
|
|
return mSampleRate.getRate();
|
|
}
|
|
|
|
@Override
|
|
public void apply( TunerConfiguration config ) throws SourceException
|
|
{
|
|
if( config instanceof HackRFTunerConfiguration )
|
|
{
|
|
HackRFTunerConfiguration hackRFConfig =
|
|
(HackRFTunerConfiguration)config;
|
|
|
|
try
|
|
{
|
|
setSampleRate( hackRFConfig.getSampleRate() );
|
|
setFrequencyCorrection( hackRFConfig.getFrequencyCorrection() );
|
|
setAmplifierEnabled( hackRFConfig.getAmplifierEnabled() );
|
|
setLNAGain( hackRFConfig.getLNAGain() );
|
|
setVGAGain( hackRFConfig.getVGAGain() );
|
|
setFrequency( getFrequency() );
|
|
}
|
|
catch ( UsbException e )
|
|
{
|
|
throw new SourceException( "Error while applying tuner "
|
|
+ "configuration", e );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException( "Invalid tuner configuration "
|
|
+ "type [" + config.getClass() + "]" );
|
|
}
|
|
}
|
|
|
|
public ByteBuffer readArray( Request request,
|
|
int value,
|
|
int index,
|
|
int length ) throws UsbException
|
|
{
|
|
if( mDeviceHandle != null )
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( length );
|
|
|
|
int transferred = LibUsb.controlTransfer( mDeviceHandle,
|
|
REQUEST_TYPE_IN,
|
|
request.getRequestNumber(),
|
|
(short)value,
|
|
(short)index,
|
|
buffer,
|
|
TIMEOUT_US );
|
|
|
|
if( transferred < 0 )
|
|
{
|
|
throw new LibUsbException( "read error", transferred );
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
else
|
|
{
|
|
throw new LibUsbException( "device handle is null",
|
|
LibUsb.ERROR_NO_DEVICE );
|
|
}
|
|
}
|
|
|
|
public int read( Request request, int value, int index, int length )
|
|
throws UsbException
|
|
{
|
|
if( !( length == 1 || length == 2 || length == 4 ) )
|
|
{
|
|
throw new IllegalArgumentException( "invalid length [" + length +
|
|
"] must be: byte=1, short=2, int=4 to read a primitive" );
|
|
}
|
|
|
|
ByteBuffer buffer = readArray( request, value, index, length );
|
|
|
|
byte[] data = new byte[ buffer.capacity() ];
|
|
|
|
buffer.get( data );
|
|
|
|
switch( data.length )
|
|
{
|
|
case 1:
|
|
return data[ 0 ];
|
|
case 2:
|
|
return EndianUtils.readSwappedShort( data, 0 );
|
|
case 4:
|
|
return EndianUtils.readSwappedInteger( data, 0 );
|
|
default:
|
|
throw new UsbException( "read() primitive returned an "
|
|
+ "unrecognized byte array " + Arrays.toString( data ) );
|
|
}
|
|
}
|
|
|
|
public int readByte( Request request, int value, int index, boolean signed )
|
|
throws UsbException
|
|
{
|
|
ByteBuffer buffer = readArray( request, value, index, 1 );
|
|
|
|
if( signed )
|
|
{
|
|
return (int)( buffer.get() );
|
|
}
|
|
else
|
|
{
|
|
return (int)( buffer.get() & 0xFF );
|
|
}
|
|
}
|
|
|
|
public void write( Request request,
|
|
int value,
|
|
int index,
|
|
ByteBuffer buffer ) throws UsbException
|
|
{
|
|
if( mDeviceHandle != null )
|
|
{
|
|
int transferred = LibUsb.controlTransfer( mDeviceHandle,
|
|
REQUEST_TYPE_OUT,
|
|
request.getRequestNumber(),
|
|
(short)value,
|
|
(short)index,
|
|
buffer,
|
|
TIMEOUT_US );
|
|
|
|
if( transferred < 0 )
|
|
{
|
|
throw new LibUsbException( "error writing byte buffer",
|
|
transferred );
|
|
}
|
|
else if( transferred != buffer.capacity() )
|
|
{
|
|
throw new LibUsbException( "transferred bytes [" +
|
|
transferred + "] is not what was expected [" +
|
|
buffer.capacity() + "]", transferred );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new LibUsbException( "device handle is null",
|
|
LibUsb.ERROR_NO_DEVICE );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends a request that doesn't have a data payload
|
|
*/
|
|
public void write( Request request,
|
|
int value,
|
|
int index ) throws UsbException
|
|
{
|
|
write( request, value, index, ByteBuffer.allocateDirect( 0 ) );
|
|
}
|
|
|
|
/**
|
|
* Sample Rate
|
|
*
|
|
* Note: the libhackrf set sample rate method is designed to allow fractional
|
|
* sample rates. However, since we're only using integral sample rates, we
|
|
* simply invoke the setSampleRateManual method directly.
|
|
*/
|
|
public void setSampleRate( HackRFSampleRate rate ) throws UsbException
|
|
{
|
|
setSampleRateManual( rate.getRate(), 1 );
|
|
|
|
mFrequencyController.setSampleRate( rate.getRate() );
|
|
|
|
setBasebandFilter( rate.getFilter() );
|
|
|
|
mSampleRate = rate;
|
|
}
|
|
|
|
public void setSampleRateManual( int frequency, int divider )
|
|
throws UsbException
|
|
{
|
|
ByteBuffer buffer = ByteBuffer.allocateDirect( 8 );
|
|
|
|
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
|
|
|
buffer.putInt( frequency );
|
|
buffer.putInt( divider );
|
|
|
|
write( Request.SET_SAMPLE_RATE, 0, 0, buffer );
|
|
}
|
|
|
|
public int getSampleRate()
|
|
{
|
|
return mSampleRate.getRate();
|
|
}
|
|
|
|
public enum Request
|
|
{
|
|
SET_TRANSCEIVER_MODE( 1 ),
|
|
MAX2837_TRANSCEIVER_WRITE( 2 ),
|
|
MAX2837_TRANSCEIVER_READ( 3 ),
|
|
SI5351C_CLOCK_GENERATOR_WRITE( 4 ),
|
|
SI5351C_CLOCK_GENERATOR_READ( 5 ),
|
|
SET_SAMPLE_RATE( 6 ),
|
|
BASEBAND_FILTER_BANDWIDTH_SET( 7 ),
|
|
RFFC5071_MIXER_WRITE( 8 ),
|
|
RFFC5071_MIXER_READ( 9 ),
|
|
SPIFLASH_ERASE( 10 ),
|
|
SPIFLASH_WRITE( 11 ),
|
|
SPIFLASH_READ( 12 ),
|
|
BOARD_ID_READ( 14 ),
|
|
VERSION_STRING_READ( 15 ),
|
|
SET_FREQUENCY( 16 ),
|
|
AMP_ENABLE( 17 ),
|
|
BOARD_PARTID_SERIALNO_READ( 18 ),
|
|
SET_LNA_GAIN( 19 ),
|
|
SET_VGA_GAIN( 20 ),
|
|
SET_TXVGA_GAIN( 21 ),
|
|
ANTENNA_ENABLE( 23 ),
|
|
SET_FREQUENCY_EXPLICIT( 24 );
|
|
|
|
private byte mRequestNumber;
|
|
|
|
private Request( int number )
|
|
{
|
|
mRequestNumber = (byte)number;
|
|
}
|
|
|
|
public byte getRequestNumber()
|
|
{
|
|
return mRequestNumber;
|
|
}
|
|
}
|
|
|
|
public enum HackRFSampleRate
|
|
{
|
|
RATE2_016MHZ( 2016000, "2.016 MHz", BasebandFilter.F3_50 ),
|
|
RATE3_024MHZ( 3024000, "3.024 MHz", BasebandFilter.F5_00 ),
|
|
RATE4_464MHZ( 4464000, "4.464 MHz", BasebandFilter.F6_00 ),
|
|
RATE5_376MHZ( 5376000, "5.376 MHz", BasebandFilter.F7_00 ),
|
|
RATE7_488MHZ( 7488000, "7.488 MHz", BasebandFilter.F9_00 ),
|
|
RATE10_080MHZ( 10080000, "10.080 MHz", BasebandFilter.F12_00 ),
|
|
RATE12_000MHZ( 12000000, "12.000 MHz", BasebandFilter.F14_00 ),
|
|
RATE13_440MHZ( 13440000, "13.440 MHz", BasebandFilter.F15_00 ),
|
|
RATE14_976MHZ( 14976000, "14.976 MHz", BasebandFilter.F20_00 ),
|
|
RATE19_968MHZ( 19968000, "19.968 MHz", BasebandFilter.F24_00 );
|
|
|
|
private int mRate;
|
|
private String mLabel;
|
|
private BasebandFilter mFilter;
|
|
|
|
private HackRFSampleRate( int rate,
|
|
String label,
|
|
BasebandFilter filter )
|
|
{
|
|
mRate = rate;
|
|
mLabel = label;
|
|
mFilter = filter;
|
|
}
|
|
|
|
public int getRate()
|
|
{
|
|
return mRate;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public BasebandFilter getFilter()
|
|
{
|
|
return mFilter;
|
|
}
|
|
}
|
|
|
|
public enum BasebandFilter
|
|
{
|
|
FAUTO( 0, "AUTO" ),
|
|
F1_75( 1750000, "1.75 MHz" ),
|
|
F2_50( 2500000, "2.50 MHz" ),
|
|
F3_50( 3500000, "3.50 MHz" ),
|
|
F5_00( 5000000, "5.00 MHz" ),
|
|
F5_50( 5500000, "5.50 MHz" ),
|
|
F6_00( 6000000, "6.00 MHz" ),
|
|
F7_00( 7000000, "7.00 MHz" ),
|
|
F8_00( 8000000, "8.00 MHz" ),
|
|
F9_00( 9000000, "9.00 MHz" ),
|
|
F10_00( 10000000, "10.00 MHz" ),
|
|
F12_00( 12000000, "12.00 MHz" ),
|
|
F14_00( 14000000, "14.00 MHz" ),
|
|
F15_00( 15000000, "15.00 MHz" ),
|
|
F20_00( 20000000, "20.00 MHz" ),
|
|
F24_00( 24000000, "24.00 MHz" ),
|
|
F28_00( 28000000, "28.00 MHz" );
|
|
|
|
private int mBandwidth;
|
|
private String mLabel;
|
|
|
|
private BasebandFilter( int bandwidth, String label )
|
|
{
|
|
mBandwidth = bandwidth;
|
|
mLabel = label;
|
|
}
|
|
|
|
public int getBandwidth()
|
|
{
|
|
return mBandwidth;
|
|
}
|
|
|
|
public int getHighValue()
|
|
{
|
|
return mBandwidth >> 16;
|
|
}
|
|
|
|
public int getLowValue()
|
|
{
|
|
return mBandwidth & 0xFFFF;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
}
|
|
|
|
public enum BoardID
|
|
{
|
|
JELLYBEAN( 0x00, "HackRF Jelly Bean" ),
|
|
JAWBREAKER( 0x01, "HackRF Jaw Breaker" ),
|
|
HACKRF_ONE( 0x02, "HackRF One" ),
|
|
INVALID( 0xFF, "HackRF Unknown Board" );
|
|
|
|
private byte mIDNumber;
|
|
private String mLabel;
|
|
|
|
private BoardID( int number, String label )
|
|
{
|
|
mIDNumber = (byte)number;
|
|
mLabel = label;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public byte getNumber()
|
|
{
|
|
return mIDNumber;
|
|
}
|
|
|
|
public static BoardID lookup( int value )
|
|
{
|
|
switch( value )
|
|
{
|
|
case 0:
|
|
return JELLYBEAN;
|
|
case 1:
|
|
return JAWBREAKER;
|
|
case 2:
|
|
return HACKRF_ONE;
|
|
default:
|
|
return INVALID;
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum Mode
|
|
{
|
|
OFF( 0, "Off" ),
|
|
RECEIVE( 1, "Receive" ),
|
|
TRANSMIT( 2, "Transmit" ),
|
|
SS( 3, "SS" );
|
|
|
|
private byte mNumber;
|
|
private String mLabel;
|
|
|
|
private Mode( int number, String label )
|
|
{
|
|
mNumber = (byte)number;
|
|
mLabel = label;
|
|
}
|
|
|
|
public byte getNumber()
|
|
{
|
|
return mNumber;
|
|
}
|
|
|
|
public String getLabel()
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return mLabel;
|
|
}
|
|
}
|
|
|
|
public enum HackRFLNAGain
|
|
{
|
|
GAIN_0( 0 ),
|
|
GAIN_8( 8 ),
|
|
GAIN_16( 16 ),
|
|
GAIN_24( 24 ),
|
|
GAIN_32( 32 ),
|
|
GAIN_40( 40 );
|
|
|
|
private int mValue;
|
|
|
|
private HackRFLNAGain( int value )
|
|
{
|
|
mValue = value;
|
|
}
|
|
|
|
public int getValue()
|
|
{
|
|
return mValue;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return String.valueOf( mValue ) + " dB";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Receive (baseband) VGA Gain values
|
|
*/
|
|
public enum HackRFVGAGain
|
|
{
|
|
GAIN_0( 0 ),
|
|
GAIN_2( 2 ),
|
|
GAIN_4( 4 ),
|
|
GAIN_6( 6 ),
|
|
GAIN_8( 8 ),
|
|
GAIN_10( 10 ),
|
|
GAIN_12( 12 ),
|
|
GAIN_14( 14 ),
|
|
GAIN_16( 16 ),
|
|
GAIN_18( 18 ),
|
|
GAIN_20( 20 ),
|
|
GAIN_22( 22 ),
|
|
GAIN_23( 24 ),
|
|
GAIN_26( 26 ),
|
|
GAIN_28( 28 ),
|
|
GAIN_30( 30 ),
|
|
GAIN_32( 32 ),
|
|
GAIN_34( 34 ),
|
|
GAIN_36( 36 ),
|
|
GAIN_38( 38 ),
|
|
GAIN_40( 40 ),
|
|
GAIN_42( 42 ),
|
|
GAIN_44( 44 ),
|
|
GAIN_46( 46 ),
|
|
GAIN_48( 48 ),
|
|
GAIN_50( 50 ),
|
|
GAIN_52( 52 ),
|
|
GAIN_54( 54 ),
|
|
GAIN_56( 56 ),
|
|
GAIN_58( 58 ),
|
|
GAIN_60( 60 ),
|
|
GAIN_62( 62 );
|
|
|
|
private int mValue;
|
|
|
|
private HackRFVGAGain( int value )
|
|
{
|
|
mValue = value;
|
|
}
|
|
|
|
public int getValue()
|
|
{
|
|
return mValue;
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return String.valueOf( mValue ) + " dB";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* HackRF part id and serial number parsing class
|
|
*/
|
|
public class Serial
|
|
{
|
|
private byte[] mData;
|
|
|
|
public Serial( ByteBuffer buffer )
|
|
{
|
|
mData = new byte[ buffer.capacity() ];
|
|
|
|
buffer.get( mData );
|
|
}
|
|
|
|
public String getPartID()
|
|
{
|
|
int part0 = EndianUtils.readSwappedInteger( mData, 0 );
|
|
int part1 = EndianUtils.readSwappedInteger( mData, 4 );
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.append( String.format( "%08X", part0 ) );
|
|
sb.append( "-" );
|
|
sb.append( String.format( "%08X", part1 ) );
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
public String getSerialNumber()
|
|
{
|
|
int serial0 = EndianUtils.readSwappedInteger( mData, 8 );
|
|
int serial1 = EndianUtils.readSwappedInteger( mData, 12 );
|
|
int serial2 = EndianUtils.readSwappedInteger( mData, 16 );
|
|
int serial3 = EndianUtils.readSwappedInteger( mData, 20 );
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.append( String.format( "%08X", serial0 ) );
|
|
sb.append( "-" );
|
|
sb.append( String.format( "%08X", serial1 ) );
|
|
sb.append( "-" );
|
|
sb.append( String.format( "%08X", serial2 ) );
|
|
sb.append( "-" );
|
|
sb.append( String.format( "%08X", serial3 ) );
|
|
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a sample listener. If the buffer processing thread is
|
|
* not currently running, starts it running in a new thread.
|
|
*/
|
|
public void addListener( Listener<ComplexBuffer> listener )
|
|
{
|
|
mComplexBufferBroadcaster.addListener( listener );
|
|
|
|
if( mBufferProcessor == null || !mBufferProcessor.isRunning() )
|
|
{
|
|
mBufferProcessor = new BufferProcessor();
|
|
|
|
Thread thread = new Thread( mBufferProcessor );
|
|
thread.setDaemon( true );
|
|
thread.setName( "HackRF Sample Processor" );
|
|
|
|
thread.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the sample listener. If this is the last registered listener,
|
|
* shuts down the buffer processing thread.
|
|
*/
|
|
public void removeListener( Listener<ComplexBuffer> listener )
|
|
{
|
|
mComplexBufferBroadcaster.removeListener( listener );
|
|
|
|
if( !mComplexBufferBroadcaster.hasListeners() )
|
|
{
|
|
mBufferProcessor.stop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Buffer processing thread. Fetches samples from the HackRF Tuner and
|
|
* dispatches them to all registered listeners
|
|
*/
|
|
public class BufferProcessor implements Runnable, TransferCallback
|
|
{
|
|
private ScheduledFuture<?> mSampleDispatcherTask;
|
|
private CopyOnWriteArrayList<Transfer> mTransfers;
|
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
|
|
|
@Override
|
|
public void run()
|
|
{
|
|
if( mRunning.compareAndSet( false, true ) )
|
|
{
|
|
mLog.info( "starting HackRF sample fetch thread" );
|
|
|
|
prepareTransfers();
|
|
|
|
for( Transfer transfer: mTransfers )
|
|
{
|
|
int result = LibUsb.submitTransfer( transfer );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error submitting transfer [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
|
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
|
20, TimeUnit.MILLISECONDS );
|
|
|
|
while( mRunning.get() )
|
|
{
|
|
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
|
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error handling events for libusb" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the sample fetching thread
|
|
*/
|
|
public void stop()
|
|
{
|
|
if( mRunning.compareAndSet( true, false ) )
|
|
{
|
|
mLog.info( "stopping HackRF sample fetch thread" );
|
|
|
|
cancel();
|
|
|
|
if( mSampleDispatcherTask != null )
|
|
{
|
|
mSampleDispatcherTask.cancel( true );
|
|
mFilledBuffers.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates if this thread is running
|
|
*/
|
|
public boolean isRunning()
|
|
{
|
|
return mRunning.get();
|
|
}
|
|
|
|
@Override
|
|
public void processTransfer( Transfer transfer )
|
|
{
|
|
if( transfer.status() == LibUsb.TRANSFER_COMPLETED )
|
|
{
|
|
ByteBuffer buffer = transfer.buffer();
|
|
|
|
byte[] data = new byte[ transfer.actualLength() ];
|
|
|
|
buffer.get( data );
|
|
|
|
buffer.rewind();
|
|
|
|
mFilledBuffers.add( data );
|
|
|
|
if( !isRunning() )
|
|
{
|
|
LibUsb.cancelTransfer( transfer );
|
|
}
|
|
}
|
|
|
|
switch( transfer.status() )
|
|
{
|
|
case LibUsb.TRANSFER_COMPLETED:
|
|
/* resubmit the transfer */
|
|
int result = LibUsb.submitTransfer( transfer );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "couldn't resubmit buffer transfer to tuner" );
|
|
LibUsb.freeTransfer( transfer );
|
|
mTransfers.remove( transfer );
|
|
}
|
|
break;
|
|
case LibUsb.TRANSFER_CANCELLED:
|
|
/* free the transfer and remove it */
|
|
LibUsb.freeTransfer( transfer );
|
|
mTransfers.remove( transfer );
|
|
break;
|
|
default:
|
|
/* unexpected error */
|
|
mLog.error( "transfer error [" +
|
|
getTransferStatus( transfer.status() ) +
|
|
"] transferred actual: " + transfer.actualLength() );
|
|
}
|
|
}
|
|
|
|
private void cancel()
|
|
{
|
|
for( Transfer transfer: mTransfers )
|
|
{
|
|
LibUsb.cancelTransfer( transfer );
|
|
}
|
|
|
|
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
|
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
|
|
if( result != LibUsb.SUCCESS )
|
|
{
|
|
mLog.error( "error handling usb events during cancel [" +
|
|
LibUsb.errorName( result ) + "]" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepares (allocates) a set of transfer buffers for use in
|
|
* transferring data from the tuner via the bulk interface
|
|
*/
|
|
private void prepareTransfers() throws LibUsbException
|
|
{
|
|
mTransfers = new CopyOnWriteArrayList<Transfer>();
|
|
|
|
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
|
{
|
|
Transfer transfer = LibUsb.allocTransfer();
|
|
|
|
if( transfer == null )
|
|
{
|
|
throw new LibUsbException( "couldn't allocate transfer",
|
|
LibUsb.ERROR_NO_MEM );
|
|
}
|
|
|
|
final ByteBuffer buffer =
|
|
ByteBuffer.allocateDirect( mBufferSize );
|
|
|
|
LibUsb.fillBulkTransfer( transfer,
|
|
mDeviceHandle,
|
|
USB_ENDPOINT,
|
|
buffer,
|
|
BufferProcessor.this,
|
|
"Buffer #" + x,
|
|
TIMEOUT_US );
|
|
|
|
mTransfers.add( transfer );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
|
* array and broadcasts the array to all registered listeners
|
|
*/
|
|
public class BufferDispatcher implements Runnable
|
|
{
|
|
@Override
|
|
public void run()
|
|
{
|
|
try
|
|
{
|
|
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
|
|
|
|
mFilledBuffers.drainTo( buffers );
|
|
|
|
for( byte[] buffer: buffers )
|
|
{
|
|
float[] samples = mSampleAdapter.convert( buffer );
|
|
|
|
mComplexBufferBroadcaster.broadcast( new ComplexBuffer( samples ) );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
mLog.error( "error duruing HackRF buffer dispatching", e );
|
|
}
|
|
}
|
|
}
|
|
|
|
public static String getTransferStatus( int status )
|
|
{
|
|
switch( status )
|
|
{
|
|
case 0:
|
|
return "TRANSFER COMPLETED (0)";
|
|
case 1:
|
|
return "TRANSFER ERROR (1)";
|
|
case 2:
|
|
return "TRANSFER TIMED OUT (2)";
|
|
case 3:
|
|
return "TRANSFER CANCELLED (3)";
|
|
case 4:
|
|
return "TRANSFER STALL (4)";
|
|
case 5:
|
|
return "TRANSFER NO DEVICE (5)";
|
|
case 6:
|
|
return "TRANSFER OVERFLOW (6)";
|
|
default:
|
|
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
|
}
|
|
}
|
|
}
|