package de.tu.darmstadt.seemoo.ansian.model.sources.hackrf; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbRequest; import android.util.Log; import android.widget.Toast; /** * <h1>HackRF USB Library for Android</h1> * * Module: Hackrf.java Description: The Hackrf class represents the HackRF * device and acts as abstraction layer that manages the USB communication * between the device and the application. * * @author Dennis Mantz * * Copyright (C) 2014 Dennis Mantz based on code of libhackrf * [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. * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher * * This library 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 2 of the License, or (at * your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ public class Hackrf implements Runnable { // Attributes to hold the USB related objects: private UsbManager usbManager = null; private UsbDevice usbDevice = null; private UsbInterface usbInterface = null; private UsbDeviceConnection usbConnection = null; private UsbEndpoint usbEndpointIN = null; private UsbEndpoint usbEndpointOUT = null; private int transceiverMode = HACKRF_TRANSCEIVER_MODE_OFF; // current mode // of the HackRF private Thread usbThread = null; // hold the transceiver Thread if running private ArrayBlockingQueue<byte[]> queue = null; // queue that buffers // samples to pass them // between // hackrf_android and // the application private ArrayBlockingQueue<byte[]> bufferPool = null; // queue that holds // old buffers which // can be // reused while // receiving or // transmitting // samples // startTime (in ms since 1970) and packetCounter for statistics: private long transceiveStartTime = 0; private long transceivePacketCounter = 0; // Transceiver Modes: public static final int HACKRF_TRANSCEIVER_MODE_OFF = 0; public static final int HACKRF_TRANSCEIVER_MODE_RECEIVE = 1; public static final int HACKRF_TRANSCEIVER_MODE_TRANSMIT = 2; // USB Vendor Requests (from hackrf.c) private static final int HACKRF_VENDOR_REQUEST_SET_TRANSCEIVER_MODE = 1; private static final int HACKRF_VENDOR_REQUEST_MAX2837_WRITE = 2; private static final int HACKRF_VENDOR_REQUEST_MAX2837_READ = 3; private static final int HACKRF_VENDOR_REQUEST_SI5351C_WRITE = 4; private static final int HACKRF_VENDOR_REQUEST_SI5351C_READ = 5; private static final int HACKRF_VENDOR_REQUEST_SAMPLE_RATE_SET = 6; private static final int HACKRF_VENDOR_REQUEST_BASEBAND_FILTER_BANDWIDTH_SET = 7; private static final int HACKRF_VENDOR_REQUEST_RFFC5071_WRITE = 8; private static final int HACKRF_VENDOR_REQUEST_RFFC5071_READ = 9; private static final int HACKRF_VENDOR_REQUEST_SPIFLASH_ERASE = 10; private static final int HACKRF_VENDOR_REQUEST_SPIFLASH_WRITE = 11; private static final int HACKRF_VENDOR_REQUEST_SPIFLASH_READ = 12; private static final int HACKRF_VENDOR_REQUEST_BOARD_ID_READ = 14; private static final int HACKRF_VENDOR_REQUEST_VERSION_STRING_READ = 15; private static final int HACKRF_VENDOR_REQUEST_SET_FREQ = 16; private static final int HACKRF_VENDOR_REQUEST_AMP_ENABLE = 17; private static final int HACKRF_VENDOR_REQUEST_BOARD_PARTID_SERIALNO_READ = 18; private static final int HACKRF_VENDOR_REQUEST_SET_LNA_GAIN = 19; private static final int HACKRF_VENDOR_REQUEST_SET_VGA_GAIN = 20; private static final int HACKRF_VENDOR_REQUEST_SET_TXVGA_GAIN = 21; private static final int HACKRF_VENDOR_REQUEST_ANTENNA_ENABLE = 23; private static final int HACKRF_VENDOR_REQUEST_SET_FREQ_EXPLICIT = 24; // RF Filter Paths (from hackrf.c) public static final int RF_PATH_FILTER_BYPASS = 0; public static final int RF_PATH_FILTER_LOW_PASS = 1; public static final int RF_PATH_FILTER_HIGH_PASS = 2; // Some Constants: @SuppressWarnings("unused") private static final String LOGTAG = "hackrf_android"; private static final String HACKRF_USB_PERMISSION = "de.tu.darmstadt.seemoo.ansian.model.sources.USB_PERMISSION"; private static final int numUsbRequests = 4; // Number of parallel // UsbRequests private static final int packetSize = 1024 * 16; // Buffer Size of each // UsbRequest /** * Initializing the Hackrf Instance with a USB Device. This will try to * request the permissions to open the USB device and then create an * instance of the Hackrf class and pass it back via the callbackInterface * * @param context * Application context. Used to retrieve System Services (USB) * @param callbackInterface * This interface declares two methods that are called if the * device is ready or if there was an error * @param queueSize * Size of the receive/transmit queue in bytes * @return false if no Hackrf could be found */ public static boolean initHackrf(Context context, final HackrfCallbackInterface callbackInterface, final int queueSize) { final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); UsbDevice hackrfUsbDvice = null; if (usbManager == null) { Log.e(LOGTAG, "initHackrf: Couldn't get an instance of UsbManager!"); return false; } // Get a list of connected devices HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList(); if (deviceList == null) { Log.e(LOGTAG, "initHackrf: Couldn't read the USB device list!"); return false; } Log.i(LOGTAG, "initHackrf: Found " + deviceList.size() + " USB devices."); // Iterate over the list. Use the first Device that matches a HackRF Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while (deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); Log.d(LOGTAG, "initHackrf: deviceList: vendor=" + device.getVendorId() + " product=" + device.getProductId()); // HackRF One (Vendor ID: 7504 [0x1d50]; Product ID: 24713 [0x6089] // ) if (device.getVendorId() == 7504 && device.getProductId() == 24713) { Log.i(LOGTAG, "initHackrf: Found HackRF One at " + device.getDeviceName()); hackrfUsbDvice = device; } // HackRF Jawbreaker (Vendor ID: 7504 [0x1d50]; Product ID: 24651 // [0x604b]) if (device.getVendorId() == 7504 && device.getProductId() == 24651) { Log.i(LOGTAG, "initHackrf: Found HackRF Jawbreaker at " + device.getDeviceName()); hackrfUsbDvice = device; } } // Check if we found a device: if (hackrfUsbDvice == null) { Log.e(LOGTAG, "initHackrf: No HackRF Device found."); return false; } // Requesting Permissions: // First we define a broadcast receiver that handles the // permission_granted intend: BroadcastReceiver permissionBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (HACKRF_USB_PERMISSION.equals(intent.getAction())) { UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) && device != null) { // We have permissions to open the device! Lets init the // hackrf instance and // return it to the calling application. Log.d(LOGTAG, "initHackrf: Permission granted for device " + device.getDeviceName()); try { Hackrf hackrf = new Hackrf(usbManager, device, queueSize); Toast.makeText(context, "HackRF at " + device.getDeviceName() + " is ready!", Toast.LENGTH_LONG).show(); callbackInterface.onHackrfReady(hackrf); } catch (HackrfUsbException e) { Log.e(LOGTAG, "initHackrf: Couldn't open device " + device.getDeviceName()); Toast.makeText(context, "Couldn't open HackRF device", Toast.LENGTH_LONG).show(); callbackInterface.onHackrfError("Couldn't open device " + device.getDeviceName()); } } else { Log.e(LOGTAG, "initHackrf: Permission denied for device " + device.getDeviceName()); Toast.makeText(context, "Permission denied to open HackRF device", Toast.LENGTH_LONG).show(); callbackInterface.onHackrfError("Permission denied for device " + device.getDeviceName()); } } // unregister the Broadcast Receiver: context.unregisterReceiver(this); } }; // Now create a intent to request for the permissions and register the // broadcast receiver for it: PendingIntent mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(HACKRF_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(HACKRF_USB_PERMISSION); context.registerReceiver(permissionBroadcastReceiver, filter); // Fire the request: usbManager.requestPermission(hackrfUsbDvice, mPermissionIntent); Log.d(LOGTAG, "Permission request for device " + hackrfUsbDvice.getDeviceName() + " was send. waiting..."); return true; } /** * Initializing the Hackrf Instance with a USB Device. Note: The application * must have reclaimed permissions to access the USB Device BEFOR calling * this constructor. * * @param usbManager * Instance of the USB Manager (System Service) * @param usbDevice * Instance of an USB Device representing the HackRF * @param queueSize * Size of the receive/transmit queue in bytes * @throws HackrfUsbException */ private Hackrf(UsbManager usbManager, UsbDevice usbDevice, int queueSize) throws HackrfUsbException { // Initialize the class attributes: this.usbManager = usbManager; this.usbDevice = usbDevice; // For detailed trouble shooting: Read out information of the device: Log.i(LOGTAG, "constructor: create Hackrf instance from " + usbDevice.getDeviceName() + ". Vendor ID: " + usbDevice.getVendorId() + " Product ID: " + usbDevice.getProductId()); Log.i(LOGTAG, "constructor: device protocol: " + usbDevice.getDeviceProtocol()); Log.i(LOGTAG, "constructor: device class: " + usbDevice.getDeviceClass() + " subclass: " + usbDevice.getDeviceSubclass()); Log.i(LOGTAG, "constructor: interface count: " + usbDevice.getInterfaceCount()); try { // Extract interface from the device: this.usbInterface = usbDevice.getInterface(0); // For detailed trouble shooting: Read out interface information of // the device: Log.i(LOGTAG, "constructor: [interface 0] interface protocol: " + usbInterface.getInterfaceProtocol() + " subclass: " + usbInterface.getInterfaceSubclass()); Log.i(LOGTAG, "constructor: [interface 0] interface class: " + usbInterface.getInterfaceClass()); Log.i(LOGTAG, "constructor: [interface 0] endpoint count: " + usbInterface.getEndpointCount()); // Extract the endpoints from the device: this.usbEndpointIN = usbInterface.getEndpoint(0); this.usbEndpointOUT = usbInterface.getEndpoint(1); // For detailed trouble shooting: Read out endpoint information of // the interface: Log.i(LOGTAG, "constructor: [endpoint 0 (IN)] address: " + usbEndpointIN.getAddress() + " attributes: " + usbEndpointIN.getAttributes() + " direction: " + usbEndpointIN.getDirection() + " max_packet_size: " + usbEndpointIN.getMaxPacketSize()); Log.i(LOGTAG, "constructor: [endpoint 1 (OUT)] address: " + usbEndpointOUT.getAddress() + " attributes: " + usbEndpointOUT.getAttributes() + " direction: " + usbEndpointOUT.getDirection() + " max_packet_size: " + usbEndpointOUT.getMaxPacketSize()); // Open the device: this.usbConnection = usbManager.openDevice(usbDevice); if (this.usbConnection == null) { Log.e(LOGTAG, "constructor: Couldn't open HackRF USB Device: openDevice() returned null!"); throw (new HackrfUsbException("Couldn't open HackRF USB Device! (device is gone)")); } } catch (Exception e) { Log.e(LOGTAG, "constructor: Couldn't open HackRF USB Device: " + e.getMessage()); throw (new HackrfUsbException("Error: Couldn't open HackRF USB Device!")); } // Create the queue that is used to transport samples to the // application. // Each queue element is a byte array of size // usbEndpointIN.getMaxPacketSize() (512 Bytes) this.queue = new ArrayBlockingQueue<byte[]>(queueSize / getPacketSize()); // Create another queue that will be used to collect old buffers for // reusing them. // This will speed up things a lot! this.bufferPool = new ArrayBlockingQueue<byte[]>(queueSize / getPacketSize()); } /** * This returns the size of the packets that are used in receiving / * transmitting samples. Note that the size is measured in bytes and a * complex sample always consists of 2 bytes! * * @return Packet size in Bytes */ public int getPacketSize() { // return this.usbEndpointIN.getMaxPacketSize(); <= gives 512 which is // way too small return packetSize; } /** * Get a buffer (byte array with size getPacketSize()) that can be used to * hold samples for transmitting. Use this function to allocate your buffers * which you will pass into the queue while transmitting. It will reuse old * buffers and save a lot of expensive memory allocation and garbage * collection time. If no old buffers are existing, it will allocate a new * one. * * @return allocated buffer of size getPacketSize() */ public byte[] getBufferFromBufferPool() { byte[] buffer = this.bufferPool.poll(); // Check if we got a buffer: if (buffer == null) buffer = new byte[getPacketSize()]; return buffer; } /** * Returns a buffer that isn't used by the application any more to the * buffer pool of this hackrf instance. The buffer must be a byte array with * size getPacketSize() (the one you got from the queue while receiving). * This will reuse old buffers while receiving and save a lot of expensive * memory allocation and garbage collection time. * * @param buffer * a byte array of size getPacketSize() that is not used by the * application any more. */ public void returnBufferToBufferPool(byte[] buffer) { if (buffer.length == getPacketSize()) { // Throw it into the pool (don't care if it's working or not): this.bufferPool.offer(buffer); } else Log.w(LOGTAG, "returnBuffer: Got a buffer with wrong size. Ignore it!"); } /** * This returns the number of packets (of size getPacketSize()) * received/transmitted since start. * * @return Number of packets (of size getPacketSize()) received/transmitted * since start */ public long getTransceiverPacketCounter() { return this.transceivePacketCounter; } /** * This returns the time in milliseconds since receiving/transmitting was * started. * * @return time in milliseconds since receiving/transmitting was started. */ public long getTransceivingTime() { if (this.transceiveStartTime == 0) return 0; return System.currentTimeMillis() - this.transceiveStartTime; } /** * Returns the average rx/tx transfer rate in byte/seconds. * * @return average transfer rate in byte/seconds */ public long getAverageTransceiveRate() { long transTime = this.getTransceivingTime() / 1000; // Transfer Time in // seconds if (transTime == 0) return 0; return this.getTransceiverPacketCounter() * this.getPacketSize() / transTime; } /** * Returns the current mode of receiving / transmitting * * @return HACKRF_TRANSCEIVER_MODE_OFF, *_RECEIVE, *_TRANSMIT */ public int getTransceiverMode() { return transceiverMode; } /** * Converts a byte array into an integer using little endian byteorder. * * @param b * byte array (length 4) * @param offset * offset pointing to the first byte in the bytearray that should * be used * @return integer */ private int byteArrayToInt(byte[] b, int offset) { return b[offset + 0] & 0xFF | (b[offset + 1] & 0xFF) << 8 | (b[offset + 2] & 0xFF) << 16 | (b[offset + 3] & 0xFF) << 24; } /** * Converts a byte array into a long integer using little endian byteorder. * * @param b * byte array (length 8) * @param offset * offset pointing to the first byte in the bytearray that should * be used * @return long integer */ private long byteArrayToLong(byte[] b, int offset) { return b[offset + 0] & 0xFF | (b[offset + 1] & 0xFF) << 8 | (b[offset + 2] & 0xFF) << 16 | (b[offset + 3] & 0xFF) << 24 | (b[offset + 4] & 0xFF) << 32 | (b[offset + 5] & 0xFF) << 40 | (b[offset + 6] & 0xFF) << 48 | (b[offset + 7] & 0xFF) << 56; } /** * Converts an integer into a byte array using little endian byteorder. * * @param i * integer * @return byte array (length 4) */ private byte[] intToByteArray(int i) { byte[] b = new byte[4]; b[0] = (byte) (i & 0xff); b[1] = (byte) ((i >> 8) & 0xff); b[2] = (byte) ((i >> 16) & 0xff); b[3] = (byte) ((i >> 24) & 0xff); return b; } /** * Converts a long integer into a byte array using little endian byteorder. * * @param i * long integer * @return byte array (length 8) */ private byte[] longToByteArray(long i) { byte[] b = new byte[8]; b[0] = (byte) (i & 0xff); b[1] = (byte) ((i >> 8) & 0xff); b[2] = (byte) ((i >> 16) & 0xff); b[3] = (byte) ((i >> 24) & 0xff); b[4] = (byte) ((i >> 32) & 0xff); b[5] = (byte) ((i >> 40) & 0xff); b[6] = (byte) ((i >> 48) & 0xff); b[7] = (byte) ((i >> 56) & 0xff); return b; } /** * Executes a Request to the USB interface. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param endpoint * USB_DIR_IN or USB_DIR_OUT * @param request * request type (HACKRF_VENDOR_REQUEST_**_READ) * @param value * value to use in the controlTransfer call * @param index * index to use in the controlTransfer call * @param buffer * buffer to use in the controlTransfer call * @return count of received bytes. Negative on error * @throws HackrfUsbException */ private int sendUsbRequest(int endpoint, int request, int value, int index, byte[] buffer) throws HackrfUsbException { int len = 0; // Determine the length of the buffer: if (buffer != null) len = buffer.length; // Claim the usb interface if (!this.usbConnection.claimInterface(this.usbInterface, true)) { Log.e(LOGTAG, "Couldn't claim HackRF USB Interface!"); throw (new HackrfUsbException("Couldn't claim HackRF USB Interface!")); } // Send Board ID Read request len = this.usbConnection.controlTransfer(endpoint | UsbConstants.USB_TYPE_VENDOR, // Request // Type request, // Request value, // Value (unused) index, // Index (unused) buffer, // Buffer len, // Length 0 // Timeout ); // Release usb interface this.usbConnection.releaseInterface(this.usbInterface); return len; } /** * Returns the Board ID of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @return HackRF Board ID * @throws HackrfUsbException */ public byte getBoardID() throws HackrfUsbException { byte[] buffer = new byte[1]; if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_BOARD_ID_READ, 0, 0, buffer) != 1) { Log.e(LOGTAG, "getBoardID: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return buffer[0]; } /** * Converts the Board ID into a human readable String (e.g. #2 => * "HackRF One") * * @param boardID * boardID to convert * @return Board ID interpretation as String * @throws HackrfUsbException */ public static String convertBoardIdToString(int boardID) { switch (boardID) { case 0: return "Jellybean"; case 1: return "Jawbreaker"; case 2: return "HackRF One"; case 3: return "rad1o"; default: return "INVALID BOARD ID"; } } /** * Returns the Version String of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @return HackRF Version String * @throws HackrfUsbException */ public String getVersionString() throws HackrfUsbException { byte[] buffer = new byte[255]; int len = 0; len = this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_VERSION_STRING_READ, 0, 0, buffer); if (len < 1) { Log.e(LOGTAG, "getVersionString: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return new String(buffer); } /** * Returns the Part ID + Serial Number of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @return int[2+6] => int[0-1] is Part ID; int[2-5] is Serial No * @throws HackrfUsbException */ public int[] getPartIdAndSerialNo() throws HackrfUsbException { byte[] buffer = new byte[8 + 16]; int[] ret = new int[2 + 4]; if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_BOARD_PARTID_SERIALNO_READ, 0, 0, buffer) != 8 + 16) { Log.e(LOGTAG, "getPartIdAndSerialNo: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } for (int i = 0; i < 6; i++) { ret[i] = this.byteArrayToInt(buffer, 4 * i); } return ret; } /** * Sets the Sample Rate of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param sampRate * Sample Rate in Hz * @param divider * Divider * @return true on success * @throws HackrfUsbException */ public boolean setSampleRate(int sampRate, int divider) throws HackrfUsbException { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); try { byteOut.write(this.intToByteArray(sampRate)); byteOut.write(this.intToByteArray(divider)); } catch (IOException e) { Log.e(LOGTAG, "setSampleRate: Error while converting arguments to byte buffer."); return false; } if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_SAMPLE_RATE_SET, 0, 0, byteOut.toByteArray()) != 8) { Log.e(LOGTAG, "setSampleRate: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Computes a valid Baseband Filter Bandwidth that is closest to a given * Sample Rate. If there is no exact match, the returned Bandwidth will be * smaller than the Sample Rate. * * @param sampRate * Bandwidth for the Baseband Filter * @return Baseband Filter Bandwidth * @throws HackrfUsbException */ public static int computeBasebandFilterBandwidth(int sampRate) { int bandwidth = 1750000; int[] supportedBandwidthValues = { 1750000, 2500000, 3500000, 5000000, 5500000, 6000000, 7000000, 8000000, 9000000, 10000000, 12000000, 14000000, 15000000, 20000000, 24000000, 28000000 }; for (int candidate : supportedBandwidthValues) { if (sampRate < candidate) break; bandwidth = candidate; } return bandwidth; } /** * Sets the baseband filter bandwidth of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param bandwidth * Bandwidth for the Baseband Filter * @return true on success * @throws HackrfUsbException */ public boolean setBasebandFilterBandwidth(int bandwidth) throws HackrfUsbException { if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_BASEBAND_FILTER_BANDWIDTH_SET, bandwidth & 0xffff, (bandwidth >> 16) & 0xffff, null) != 0) { Log.e(LOGTAG, "setBasebandFilterBandwidth: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Sets the RX VGA Gain of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param gain * RX VGA Gain (0-62 in steps of 2) * @return true on success * @throws HackrfUsbException */ public boolean setRxVGAGain(int gain) throws HackrfUsbException { byte[] retVal = new byte[1]; if (gain > 62) { Log.e(LOGTAG, "setRxVGAGain: RX VGA Gain must be within 0-62!"); return false; } // Must be in steps of two! if (gain % 2 != 0) gain = gain - (gain % 2); if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_SET_VGA_GAIN, 0, gain, retVal) != 1) { Log.e(LOGTAG, "setRxVGAGain: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } if (retVal[0] == 0) { Log.e(LOGTAG, "setRxVGAGain: HackRF returned with an error!"); return false; } return true; } /** * Sets the TX VGA Gain of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param gain * TX VGA Gain (0-62) * @return true on success * @throws HackrfUsbException */ public boolean setTxVGAGain(int gain) throws HackrfUsbException { byte[] retVal = new byte[1]; if (gain > 47) { Log.e(LOGTAG, "setTxVGAGain: TX VGA Gain must be within 0-47!"); return false; } if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_SET_TXVGA_GAIN, 0, gain, retVal) != 1) { Log.e(LOGTAG, "setTxVGAGain: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } if (retVal[0] == 0) { Log.e(LOGTAG, "setTxVGAGain: HackRF returned with an error!"); return false; } return true; } /** * Sets the RX LNA Gain of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param gain * RX LNA Gain (0-40 in steps of 8) * @return true on success * @throws HackrfUsbException */ public boolean setRxLNAGain(int gain) throws HackrfUsbException { byte[] retVal = new byte[1]; if (gain > 40) { Log.e(LOGTAG, "setRxLNAGain: RX LNA Gain must be within 0-40!"); return false; } // Must be in steps of 8! if (gain % 8 != 0) gain = gain - (gain % 8); if (this.sendUsbRequest(UsbConstants.USB_DIR_IN, HACKRF_VENDOR_REQUEST_SET_LNA_GAIN, 0, gain, retVal) != 1) { Log.e(LOGTAG, "setRxLNAGain: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } if (retVal[0] == 0) { Log.e(LOGTAG, "setRxLNAGain: HackRF returned with an error!"); return false; } return true; } /** * Sets the Frequency of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param frequency * Frequency in Hz * @return true on success * @throws HackrfUsbException */ public boolean setFrequency(long frequency) throws HackrfUsbException { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); int mhz = (int) (frequency / 1000000l); int hz = (int) (frequency % 1000000l); Log.d(LOGTAG, "Tune HackRF to " + mhz + "." + hz + "MHz..."); try { byteOut.write(this.intToByteArray(mhz)); byteOut.write(this.intToByteArray(hz)); } catch (IOException e) { Log.e(LOGTAG, "setFrequency: Error while converting arguments to byte buffer."); return false; } if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_SET_FREQ, 0, 0, byteOut.toByteArray()) != 8) { Log.e(LOGTAG, "setFrequency: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Sets the explicit IF and LO frequency of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param ifFrequency * Intermediate Frequency in Hz. Must be in [2150000000; * 2750000000] * @param loFrequency * Local Oscillator Frequency in Hz. Must be in [84375000; * 5400000000] * @param path * RF_PATH_FILTER_BYPASS, *_HIGH_PASS or *_LOW_PASS * @return true on success * @throws HackrfUsbException */ public boolean setFrequencyExplicit(long ifFrequency, long loFrequency, int rfPath) throws HackrfUsbException { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); // check range of IF Frequency: if (ifFrequency < 2150000000l || ifFrequency > 2750000000l) { Log.e(LOGTAG, "setFrequencyExplicit: IF Frequency must be in [2150000000; 2750000000]!"); return false; } if ((rfPath != RF_PATH_FILTER_BYPASS) && (loFrequency < 84375000l || loFrequency > 5400000000l)) { Log.e(LOGTAG, "setFrequencyExplicit: LO Frequency must be in [84375000; 5400000000]!"); return false; } // Check if path is in the valid range: if (rfPath < 0 || rfPath > 2) { Log.e(LOGTAG, "setFrequencyExplicit: Invalid value for rf_path!"); return false; } Log.d(LOGTAG, "Tune HackRF to IF:" + ifFrequency + " Hz; LO:" + loFrequency + " Hz..."); try { byteOut.write(this.longToByteArray(ifFrequency)); byteOut.write(this.longToByteArray(loFrequency)); byteOut.write(rfPath); } catch (IOException e) { Log.e(LOGTAG, "setFrequencyExplicit: Error while converting arguments to byte buffer."); return false; } if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_SET_FREQ_EXPLICIT, 0, 0, byteOut.toByteArray()) != 17) { Log.e(LOGTAG, "setFrequencyExplicit: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Enables or Disables the Amplifier of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param enable * true for enable or false for disable * @return true on success * @throws HackrfUsbException */ public boolean setAmp(boolean enable) throws HackrfUsbException { if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_AMP_ENABLE, (enable ? 1 : 0), 0, null) != 0) { Log.e(LOGTAG, "setAmp: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Enables or Disables the Antenna Port Power of the HackRF. * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param enable * true for enable or false for disable * @return true on success * @throws HackrfUsbException */ public boolean setAntennaPower(boolean enable) throws HackrfUsbException { // The Jawbreaker doesn't support this command! if (this.getBoardID() == 1) { // == Jawbreaker Log.w(LOGTAG, "setAntennaPower: Antenna Power is not supported for HackRF Jawbreaker. Ignore."); return false; } // The rad1o doesn't support this command! if (this.getBoardID() == 3) { // == rad1o Log.w(LOGTAG, "setAntennaPower: Antenna Power is not supported for rad1o. Ignore."); return false; } if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_ANTENNA_ENABLE, (enable ? 1 : 0), 0, null) != 0) { Log.e(LOGTAG, "setAntennaPower: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Sets the Transceiver Mode of the HackRF (OFF,RX,TX) * * Note: This function interacts with the USB Hardware and should not be * called from a GUI Thread! * * @param mode * HACKRF_TRANSCEIVER_MODE_OFF, *_RECEIVE or *_TRANSMIT * @return true on success * @throws HackrfUsbException */ public boolean setTransceiverMode(int mode) throws HackrfUsbException { if (mode < 0 || mode > 2) { Log.e(LOGTAG, "Invalid Transceiver Mode: " + mode); return false; } this.transceiverMode = mode; if (this.sendUsbRequest(UsbConstants.USB_DIR_OUT, HACKRF_VENDOR_REQUEST_SET_TRANSCEIVER_MODE, mode, 0, null) != 0) { Log.e(LOGTAG, "setTransceiverMode: USB Transfer failed!"); throw (new HackrfUsbException("USB Transfer failed!")); } return true; } /** * Starts receiving. * * @return An ArrayBlockingQueue that will fill with the samples as they * arrive. Each queue element is a block of samples (byte[]) of size * getPacketSize(). * @throws HackrfUsbException */ public ArrayBlockingQueue<byte[]> startRX() throws HackrfUsbException { // Flush the queue this.queue.clear(); // Signal the HackRF Device to start receiving: this.setTransceiverMode(HACKRF_TRANSCEIVER_MODE_RECEIVE); // Start the Thread to queue the received samples: this.usbThread = new Thread(this); this.usbThread.start(); // Reset the packet counter and start time for statistics: this.transceiveStartTime = System.currentTimeMillis(); this.transceivePacketCounter = 0; return this.queue; } /** * Starts transmitting. * * @return An ArrayBlockingQueue from which the hackrf will read the samples * to transmit. Each queue element must be a block of samples * (byte[]) of size getPacketSize(). * @throws HackrfUsbException */ public ArrayBlockingQueue<byte[]> startTX() throws HackrfUsbException { // Flush the queue this.queue.clear(); // Signal the HackRF Device to start transmitting: this.setTransceiverMode(HACKRF_TRANSCEIVER_MODE_TRANSMIT); // Start the Thread to queue the received samples: this.usbThread = new Thread(this); this.usbThread.start(); // Reset the packet counter and start time for statistics: this.transceiveStartTime = System.currentTimeMillis(); this.transceivePacketCounter = 0; return this.queue; } /** * Stops receiving or transmitting. * * @throws HackrfUsbException */ public void stop() throws HackrfUsbException { // Signal the HackRF Device to start receiving: this.setTransceiverMode(HACKRF_TRANSCEIVER_MODE_OFF); } /** * This method will be executed in a separate Thread after the HackRF starts * receiving Samples. It will return as soon as the transceiverMode changes * or an error occurs. */ private void receiveLoop() { UsbRequest[] usbRequests = new UsbRequest[numUsbRequests]; ByteBuffer buffer; try { // Create, initialize and queue all usb requests: for (int i = 0; i < numUsbRequests; i++) { // Get a ByteBuffer for the request from the buffer pool: buffer = ByteBuffer.wrap(this.getBufferFromBufferPool()); // Initialize the USB Request: usbRequests[i] = new UsbRequest(); usbRequests[i].initialize(usbConnection, usbEndpointIN); usbRequests[i].setClientData(buffer); // Queue the request if (usbRequests[i].queue(buffer, getPacketSize()) == false) { Log.e(LOGTAG, "receiveLoop: Couldn't queue USB Request."); this.stop(); break; } } // Run loop until transceiver mode changes... while (this.transceiverMode == HACKRF_TRANSCEIVER_MODE_RECEIVE) { // Wait for a request to return. This will block until one of // the requests is ready. UsbRequest request = usbConnection.requestWait(); if (request == null) { Log.e(LOGTAG, "receiveLoop: Didn't receive USB Request."); break; } // Make sure we got an UsbRequest for the IN endpoint! if (request.getEndpoint() != usbEndpointIN) continue; // Extract the buffer buffer = (ByteBuffer) request.getClientData(); // Increment the packetCounter (for statistics) this.transceivePacketCounter++; // Put the received samples into the queue, so that they can be // read by the application if (!this.queue.offer(buffer.array())) { // We hit the timeout. Log.e(LOGTAG, "receiveLoop: Queue is full. Stop receiving!"); break; } // Get a fresh ByteBuffer for the request from the buffer pool: buffer = ByteBuffer.wrap(this.getBufferFromBufferPool()); request.setClientData(buffer); // Queue the request again... if (request.queue(buffer, getPacketSize()) == false) { Log.e(LOGTAG, "receiveLoop: Couldn't queue USB Request."); break; } } } catch (HackrfUsbException e) { Log.e(LOGTAG, "receiveLoop: USB Error!"); } // Receiving is done. Cancel and close all usb requests: for (UsbRequest request : usbRequests) { if (request != null) { request.cancel(); // request.close(); <-- This will cause the VM to crash with a // SIGABRT when the next transceive starts?!? } } // If the transceiverMode is still on RECEIVE, we stop Receiving: if (this.transceiverMode == HACKRF_TRANSCEIVER_MODE_RECEIVE) { try { this.stop(); } catch (HackrfUsbException e) { Log.e(LOGTAG, "receiveLoop: Error while stopping RX!"); } } } /** * This method will be executed in a separate Thread after the HackRF starts * transmitting Samples. It will return as soon as the transceiverMode * changes or an error occurs. */ private void transmitLoop() { UsbRequest[] usbRequests = new UsbRequest[numUsbRequests]; ByteBuffer buffer; byte[] packet; try { // Create, initialize and queue all usb requests: for (int i = 0; i < numUsbRequests; i++) { // Get a packet from the queue: packet = (byte[]) queue.poll(1000, TimeUnit.MILLISECONDS); if (packet == null || packet.length != getPacketSize()) { Log.e(LOGTAG, "transmitLoop: Queue empty or wrong packet format. Abort."); this.stop(); break; } // Wrap the packet in a ByteBuffer object: buffer = ByteBuffer.wrap(packet); // Initialize the USB Request: usbRequests[i] = new UsbRequest(); usbRequests[i].initialize(usbConnection, usbEndpointOUT); usbRequests[i].setClientData(buffer); // Queue the request if (usbRequests[i].queue(buffer, getPacketSize()) == false) { Log.e(LOGTAG, "receiveLoop: Couldn't queue USB Request."); this.stop(); break; } } // Run loop until transceiver mode changes... while (this.transceiverMode == HACKRF_TRANSCEIVER_MODE_TRANSMIT) { // Wait for a request to return. This will block until one of // the requests is ready. UsbRequest request = usbConnection.requestWait(); if (request == null) { Log.e(LOGTAG, "transmitLoop: Didn't receive USB Request."); break; } // Make sure we got an UsbRequest for the OUT endpoint! if (request.getEndpoint() != usbEndpointOUT) continue; // Increment the packetCounter (for statistics) this.transceivePacketCounter++; // Extract the buffer and return it to the buffer pool: buffer = (ByteBuffer) request.getClientData(); this.returnBufferToBufferPool(buffer.array()); // Get the next packet from the queue: packet = (byte[]) queue.poll(1000, TimeUnit.MILLISECONDS); if (packet == null || packet.length != getPacketSize()) { Log.e(LOGTAG, "transmitLoop: Queue empty or wrong packet format. Stop transmitting."); break; } // Wrap the packet in a ByteBuffer object: buffer = ByteBuffer.wrap(packet); request.setClientData(buffer); // Queue the request again... if (request.queue(buffer, getPacketSize()) == false) { Log.e(LOGTAG, "transmitLoop: Couldn't queue USB Request."); break; } } } catch (HackrfUsbException e) { Log.e(LOGTAG, "transmitLoop: USB Error!"); } catch (InterruptedException e) { Log.e(LOGTAG, "transmitLoop: Interrup while waiting on queue!"); } // Transmitting is done. Cancel and close all usb requests: for (UsbRequest request : usbRequests) { if (request != null) { request.cancel(); // request.close(); <-- This will cause the VM to crash with a // SIGABRT when the next transceive starts?!? } } // If the transceiverMode is still on TRANSMIT, we stop Transmitting: if (this.transceiverMode == HACKRF_TRANSCEIVER_MODE_TRANSMIT) { try { this.stop(); } catch (HackrfUsbException e) { Log.e(LOGTAG, "transmitLoop: Error while stopping TX!"); } } } /** * This method will run when a new Thread was created. It simply calls * receiveLoop() or transmitLoop() according to the transceiveMode. */ @Override public void run() { switch (this.transceiverMode) { case HACKRF_TRANSCEIVER_MODE_RECEIVE: receiveLoop(); break; case HACKRF_TRANSCEIVER_MODE_TRANSMIT: transmitLoop(); break; default: } } }