/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2017 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/>
******************************************************************************/
package source.tuner.hackrf;
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 sample.Broadcaster;
import sample.Listener;
import sample.adapter.ByteSampleAdapter;
import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration;
import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.concurrent.LinkedTransferQueue;
public class HackRFTunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
public static final byte USB_ENDPOINT = (byte)0x81;
public static final byte USB_INTERFACE = (byte)0x0;
public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final long MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l;
public static final long DEFAULT_FREQUENCY = 101100000;
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private USBTransferProcessor mUSBTransferProcessor;
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
private boolean mAmplifierEnabled = false;
private Device mDevice;
private DeviceDescriptor mDeviceDescriptor;
private DeviceHandle mDeviceHandle;
public HackRFTunerController(Device device, DeviceDescriptor descriptor) throws SourceException
{
super(MIN_FREQUENCY, MAX_FREQUENCY, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
mDevice = device;
mDeviceDescriptor = descriptor;
}
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);
}
String name;
try
{
name = "HackRF " + getSerial().getSerialNumber();
}
catch(UsbException ue)
{
//Do nothing, we couldn't determine the serial number
name = "HackRF - Unidentified Serial";
}
mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE);
}
/**
* 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);
}
try
{
setFrequency(hackRFConfig.getFrequency());
}
catch(SourceException se)
{
//Do nothing, we couldn't set the frequency
}
}
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,
USB_TIMEOUT_US);
if(transferred < 0)
{
throw new LibUsbException("read error", transferred);
}
return buffer;
}
else
{
throw new LibUsbException("device handle is null",
LibUsb.ERROR_NO_DEVICE);
}
}
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,
USB_TIMEOUT_US);
if(transferred < 0)
{
throw new LibUsbException("error writing byte buffer",
transferred);
}
else if(transferred != buffer.capacity())
{
throw new LibUsbException("transferred bytes [" +
transferred + "] is not what was expected [" +
buffer.capacity() + "]", transferred);
}
}
else
{
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, SourceException
{
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)
{
mUSBTransferProcessor.addListener(listener);
}
/**
* Removes the sample listener. If this is the last registered listener,
* shuts down the buffer processing thread.
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
mUSBTransferProcessor.removeListener(listener);
}
}