From a7301e93d9373c8852bcb3519e8a0043f8c885c1 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sat, 7 Nov 2026 06:57:08 -0500 Subject: [PATCH] #2596 Use jHackRF native wrapper library and remove unused USBTunerController code. (#2597) Co-authored-by: denny --- build.gradle | 16 +- settings.gradle | 8 + .../dsheirer/source/tuner/TunerFactory.java | 1 + .../dsheirer/source/tuner/TunerType.java | 3 +- .../source/tuner/fcd/FCDTunerController.java | 4 +- .../tuner/fcd/proV1/FCD1TunerController.java | 41 +- .../source/tuner/hackrf/HackRFTuner.java | 30 +- .../tuner/hackrf/HackRFTunerController.java | 864 ++++++++++-------- .../tuner/hackrf/HackRFTunerEditor.java | 94 +- .../source/tuner/usb/USBTunerController.java | 854 ----------------- .../dsheirer/source/tuner/usb/UsbUtils.java | 141 --- src/main/resources/javax.usb.properties | 1 - 12 files changed, 527 insertions(+), 1530 deletions(-) delete mode 100644 src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java delete mode 100644 src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java delete mode 100644 src/main/resources/javax.usb.properties diff --git a/build.gradle b/build.gradle index 46f7aa86..92b6dd3b 100644 --- a/build.gradle +++ b/build.gradle @@ -127,13 +127,16 @@ dependencies { implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jairspyhf:1.8.1' //Airspy HF+ tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle + implementation 'io.github.dsheirer:jhackrf:2026.01.3' //HackRF tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jhydrasdr:1.1.4' //HydraSDR tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jrtl:1.4.0.1' //RTL-SDR tuners - GitHub release, see settings.gradle implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP 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 - implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version - implementation 'javax.usb:usb-api:1.0.2' + implementation('io.github.dsheirer:usb4java:1.3.5') { //Fork with Loader support for OSX Multi-Version + //Excludes the javax.usb API and the duplicate original usb4java classes. The native libraries are listed below. + exclude group: 'org.usb4java', module: 'usb4java-javax' + } implementation 'net.coderazzi:tablefilter-swing:5.5.4' implementation 'org.apache.commons:commons-compress:1.28.0' implementation 'org.apache.commons:commons-lang3:3.20.0' @@ -144,8 +147,13 @@ dependencies { implementation 'org.controlsfx:controlsfx:11.2.3' implementation 'org.rauschig:jarchivelib:1.2.0' implementation 'org.slf4j:slf4j-api:2.0.17' - implementation 'org.usb4java:libusb4java:1.3.0' - implementation 'org.usb4java:usb4java-javax:1.3.0' + implementation 'org.usb4java:libusb4java:1.3.0:darwin-x86-64' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:linux-aarch64' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:linux-arm' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:linux-x86' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:linux-x86-64' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:win32-x86' //usb4java native library + implementation 'org.usb4java:libusb4java:1.3.0:win32-x86-64' //usb4java native library implementation 'pl.edu.icm:JLargeArrays:1.6' } diff --git a/settings.gradle b/settings.gradle index 0bd37ccf..175225ff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,6 +33,14 @@ dependencyResolutionManagement { content { includeModule 'io.github.dsheirer', 'jrtl' } } + //jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar + ivy { + url = 'https://github.com/DSheirer/jHackRF/releases/download' + patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' } + metadataSources { artifact() } + content { includeModule 'io.github.dsheirer', 'jhackrf' } + } + //jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar ivy { url = 'https://github.com/DSheirer/jHydraSDR/releases/download' diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java index b5e7817b..1d297aad 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerFactory.java @@ -347,6 +347,7 @@ public class TunerFactory return new FCD2TunerConfiguration(uniqueID); case HACKRF_JAWBREAKER: case HACKRF_ONE: + case HACKRF_PRO: case HACKRF_RAD1O: return new HackRFTunerConfiguration(uniqueID); case HYDRASDR_R828D: diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerType.java b/src/main/java/io/github/dsheirer/source/tuner/TunerType.java index 39b75201..4b119be7 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerType.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerType.java @@ -35,6 +35,7 @@ public enum TunerType FUNCUBE_DONGLE_PRO("Funcube Dongle Pro"), FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro Plus"), HACKRF_ONE("ONE"), + HACKRF_PRO("Pro"), HACKRF_JAWBREAKER("Jawbreaker"), HACKRF_RAD1O("RAD1O"), HYDRASDR_R828D("R828D"), @@ -75,7 +76,7 @@ public enum TunerType * Supported USB tuner types */ public static final EnumSet SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, BLADERF, ELONICS_E4000, FITIPOWER_FC0012, - HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HYDRASDR_R828D, RAFAELMICRO_R820T); + HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HACKRF_PRO, HYDRASDR_R828D, RAFAELMICRO_R820T); /** * Supported sound card tuner types diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index 912b6c1d..6d81432a 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -40,8 +40,6 @@ import org.usb4java.LibUsb; import org.usb4java.LibUsbException; import javax.sound.sampled.TargetDataLine; -import javax.usb.UsbClaimException; -import javax.usb.UsbException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; @@ -428,7 +426,7 @@ public abstract class FCDTunerController extends TunerController /** * Set fcd interface mode */ - public void setFCDMode(Mode mode) throws UsbException, UsbClaimException + public void setFCDMode(Mode mode) { ByteBuffer response = null; diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java index c5238c24..bc1fa859 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java @@ -32,8 +32,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sound.sampled.TargetDataLine; -import javax.usb.UsbClaimException; -import javax.usb.UsbException; /** * Funcube Dongle Pro tuner controller. Combines USB HID control with Audio Mixer data streaming interface. @@ -131,7 +129,7 @@ public class FCD1TunerController extends FCDTunerController /** * Gets the current LNA Gain value from the controller and stores it */ - public LNAGain getLNAGainSetting() throws UsbClaimException, UsbException + public LNAGain getLNAGainSetting() { if(mLNAGain == null) { @@ -148,10 +146,8 @@ public class FCD1TunerController extends FCDTunerController * Sets lna gain for the controller * * @param gain - * @throws UsbException - * @throws UsbClaimException */ - public void setLNAGain(LNAGain gain) throws UsbException, UsbClaimException + public void setLNAGain(LNAGain gain) { send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting()); } @@ -169,10 +165,8 @@ public class FCD1TunerController extends FCDTunerController * * @param value - new phase correction * @throws IllegalArgumentException for value outside limits (0.0 <> 1.0) - * @throws UsbClaimException - if cannot claim the FCD HID controller - * @throws UsbException - if there was a usb error */ - public void setPhaseCorrection(double value) throws UsbClaimException, UsbException + public void setPhaseCorrection(double value) { if(value < -1.0 || value > 1.0) { @@ -194,11 +188,8 @@ public class FCD1TunerController extends FCDTunerController /** * @param value - gain correction value * @throws IllegalArgumentException for value outside limits (0.0 <> 1.0) - * @throws UsbClaimException - if cannot claim the FCD HID controller - * @throws UsbException - if there was a usb error */ public void setGainCorrection(double value) - throws UsbClaimException, UsbException { if(value < -1.0 || value > 1.0) { @@ -220,7 +211,7 @@ public class FCD1TunerController extends FCDTunerController * Gain correction values range -1.0 to 0.0 to 1.0 and applied to the dongle as values 0 to 65534 * Phase correction values range -1.0 to 0.0 to 1.0 and applied to the dongle as signed values -32768 to 32767 */ - private void setPhaseAndGainCorrection() throws UsbClaimException, UsbException + private void setPhaseAndGainCorrection() { //UNSIGNED short gain value - masked into a long to avoid sign-extension long gain = (long)((1.0 + mGainCorrection) * Short.MAX_VALUE); @@ -239,10 +230,8 @@ public class FCD1TunerController extends FCDTunerController * Note: testing shows that when the dongle is unplugged, any stored phase and gain correction values are reset. * Reading the dongle after a fresh plugin shows the values reset. * - * @throws UsbClaimException - * @throws UsbException */ - private void getPhaseAndGainCorrection() throws UsbClaimException, UsbException + private void getPhaseAndGainCorrection() { ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -276,10 +265,8 @@ public class FCD1TunerController extends FCDTunerController * * @param value - new DC correction value for the inphase component * @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0) - * @throws UsbClaimException - if cannot claim the FCD HID controller - * @throws UsbException - if there was a usb error */ - public void setDCCorrectionInPhase(double value) throws UsbClaimException, UsbException + public void setDCCorrectionInPhase(double value) { if(value < -1.0 || value > 1.0) { @@ -304,10 +291,8 @@ public class FCD1TunerController extends FCDTunerController * * @param value - new DC correction value for the quadrature component * @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0) - * @throws UsbClaimException - if cannot claim the FCD HID controller - * @throws UsbException - if there was a usb error */ - public void setDCCorrectionQuadrature(double value) throws UsbClaimException, UsbException + public void setDCCorrectionQuadrature(double value) { if(value < -1.0 || value > 1.0) { @@ -332,7 +317,7 @@ public class FCD1TunerController extends FCDTunerController * Both Inphase and Quadrature correction values range -1.0 to 0.0 to 1.0 and are applied to the dongle as signed * short values -32768 to 0 to 32767 */ - private void setDCIQCorrection() throws UsbClaimException, UsbException + private void setDCIQCorrection() { //I & Q DC offset correction values are signed short values short inphase = (short)(mDCCorrectionInPhase * Short.MAX_VALUE); @@ -348,7 +333,7 @@ public class FCD1TunerController extends FCDTunerController send(FCDCommand.APP_SET_DC_CORRECTION, correction); } - private void getDCIQCorrection() throws UsbClaimException, UsbException + private void getDCIQCorrection() { ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -370,7 +355,7 @@ public class FCD1TunerController extends FCDTunerController mDCCorrectionInPhase = ((short)inphase / (double)Short.MAX_VALUE); } - private void getLNAEnhanceSetting() throws UsbClaimException, UsbException + private void getLNAEnhanceSetting() { ByteBuffer buffer = send(FCDCommand.APP_GET_LNA_ENHANCE); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -383,12 +368,12 @@ public class FCD1TunerController extends FCDTunerController return mLNAEnhance; } - public void setLNAEnhance(LNAEnhance enhance) throws UsbClaimException, UsbException + public void setLNAEnhance(LNAEnhance enhance) { send(FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting()); } - private void getMixerGainSetting() throws UsbClaimException, UsbException + private void getMixerGainSetting() { ByteBuffer buffer = send(FCDCommand.APP_GET_MIXER_GAIN); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -401,7 +386,7 @@ public class FCD1TunerController extends FCDTunerController return mMixerGain; } - public void setMixerGain(MixerGain gain) throws UsbClaimException, UsbException + public void setMixerGain(MixerGain gain) { send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting()); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTuner.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTuner.java index f55dbff6..81483d2d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTuner.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -22,17 +22,12 @@ import io.github.dsheirer.preference.source.ChannelizerType; import io.github.dsheirer.source.tuner.ITunerErrorListener; import io.github.dsheirer.source.tuner.Tuner; import io.github.dsheirer.source.tuner.TunerClass; -import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.BoardID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * HackRF Tuner */ public class HackRFTuner extends Tuner { - private final static Logger mLog = LoggerFactory.getLogger(HackRFTuner.class); - /** * Constructs an instance * @param controller for the HackRF @@ -48,14 +43,11 @@ public class HackRFTuner extends Tuner @Override public String getPreferredName() { - try + String serialNumber = getController().getSerialNumber(); + + if(serialNumber != null) { - HackRFTunerController.Serial serial = getController().getSerial(); - return "HackRF " + getController().getTunerType() + " " + serial.getSerialNumber(); - } - catch(Exception e) - { - //No - op + return "HackRF " + getController().getTunerType() + " " + serialNumber; } return "HackRF " + getController().getTunerType(); @@ -81,16 +73,14 @@ public class HackRFTuner extends Tuner @Override public String getUniqueID() { - try + String serialNumber = getController().getSerialNumber(); + + if(serialNumber != null) { - return getTunerClass().toString() + " " + getController().getSerial().getSerialNumber(); - } - catch(Exception e) - { - mLog.error("error gettting serial number", e); + return getTunerClass().toString() + " " + serialNumber; } - return BoardID.HACKRF_ONE.getLabel(); + return "HackRF One"; } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java index 55e3313d..5a4f1b55 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * 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 @@ -18,190 +18,520 @@ */ package io.github.dsheirer.source.tuner.hackrf; -import io.github.dsheirer.buffer.INativeBufferFactory; +import io.github.dsheirer.buffer.INativeBuffer; import io.github.dsheirer.buffer.SignedByteNativeBufferFactory; +import io.github.dsheirer.hackrf.BoardId; +import io.github.dsheirer.hackrf.BoardInfo; +import io.github.dsheirer.hackrf.HackRf; +import io.github.dsheirer.hackrf.HackRfDevice; +import io.github.dsheirer.hackrf.HackRfException; +import io.github.dsheirer.hackrf.SampleBuffer; +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.usb.USBTunerController; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; +import io.github.dsheirer.source.tuner.manager.TunerManager; import java.util.EnumSet; -import org.apache.commons.io.EndianUtils; +import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.usb4java.Context; +import org.usb4java.Device; +import org.usb4java.DeviceDescriptor; +import org.usb4java.DeviceHandle; +import org.usb4java.DeviceList; import org.usb4java.LibUsb; -import org.usb4java.LibUsbException; -import javax.usb.UsbException; - -public class HackRFTunerController extends USBTunerController +/** + * HackRF One, HackRF Pro, Jawbreaker and rad1o tuner controller, implemented with the jHackRF library (libhackrf). + * + * The tuner is discovered with LibUsb at a USB bus and port, like all other USB tuners, however libhackrf owns the + * USB device and its transfers. + */ +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 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 MINIMUM_TUNABLE_FREQUENCY_HZ = 10000000l; public static final long MAXIMUM_TUNABLE_FREQUENCY_HZ = 6000000000l; public static final long DEFAULT_FREQUENCY = 101100000; public static final double USABLE_BANDWIDTH = 0.90; public static final int DC_HALF_BANDWIDTH = 5000; + //Sample count of each buffer from libhackrf (262,144 bytes), used until the device is opened. + private static final int DEFAULT_BUFFER_SAMPLE_COUNT = 131072; - private INativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory(); + private final int mBus; + private final String mPortAddress; + private final SignedByteNativeBufferFactory mNativeBufferFactory = new SignedByteNativeBufferFactory(); + private HackRfDevice mDevice; + private TunerType mTunerType = TunerType.HACKRF_ONE; + private String mBoardName; + private String mSerialNumber; + private String mPartId; + private String mFirmwareVersion; private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE_5_0; private boolean mAmplifierEnabled = false; + private volatile int mBufferSampleCount = DEFAULT_BUFFER_SAMPLE_COUNT; + private boolean mStreaming = false; /** * Constructs an instance * @param bus usb * @param portAddress usb + * @param tunerErrorListener to receive tuner error notifications. */ public HackRFTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener) { - super(bus, portAddress, MINIMUM_TUNABLE_FREQUENCY_HZ, MAXIMUM_TUNABLE_FREQUENCY_HZ, DC_HALF_BANDWIDTH, USABLE_BANDWIDTH, - tunerErrorListener); + super(tunerErrorListener); + mBus = bus; + mPortAddress = portAddress; + setMinimumFrequency(MINIMUM_TUNABLE_FREQUENCY_HZ); + setMaximumFrequency(MAXIMUM_TUNABLE_FREQUENCY_HZ); + setMiddleUnusableHalfBandwidth(DC_HALF_BANDWIDTH); + setUsableBandwidthPercentage(USABLE_BANDWIDTH); } /** - * Tuner type for this HackRf + * Tuner type for this HackRF, available once the tuner is started. */ @Override public TunerType getTunerType() { - if(isRunning()) - { - try - { - BoardID boardID = getBoardID(); - - switch(boardID) - { - case RAD1O: - return TunerType.HACKRF_RAD1O; - case JAWBREAKER: - return TunerType.HACKRF_JAWBREAKER; - case HACKRF_ONE: - return TunerType.HACKRF_ONE; - }; - } - catch(UsbException ue) - { - mLog.error("Couldn't read HackRF board ID - " + ue.getLocalizedMessage()); - } - } - - return TunerType.HACKRF_ONE; - } - - @Override - protected INativeBufferFactory getNativeBufferFactory() - { - return mNativeBufferFactory; - } - - @Override - protected int getTransferBufferSize() - { - return USB_TRANSFER_BUFFER_SIZE; + return mTunerType; } @Override public int getBufferSampleCount() { - return USB_TRANSFER_BUFFER_SIZE / 2; + return mBufferSampleCount; } - /** - * Device startup actions invoked by the parent start() method. - * @throws SourceException if there is an error performing these operations - */ - protected void deviceStart() throws SourceException + @Override + public void start() throws SourceException { + String serialNumber = readSerialNumber(); + + getLock().lock(); + try { - setMode(Mode.RECEIVE); + mDevice = open(serialNumber); + + BoardId boardId = mDevice.boardId(); + mBoardName = boardId.name(); + mTunerType = getTunerType(boardId); + + BoardInfo boardInfo = mDevice.boardInfo(); + mSerialNumber = formatSerialNumber(boardInfo.serialNumber()); + mPartId = String.format(Locale.ROOT, "%08X-%08X", boardInfo.partId1(), boardInfo.partId2()); + mFirmwareVersion = mDevice.firmwareVersion(); + mBufferSampleCount = mDevice.transferBufferSize() / 2; + + //The device keeps its previous settings, so force the default sample rate to be applied. + mSampleRate = null; + setSampleRate(HackRFSampleRate.RATE_5_0); setFrequency(DEFAULT_FREQUENCY); } - catch(Exception e) + catch(HackRfException | SourceException | RuntimeException | LinkageError e) { - mLog.error("Error on Hack RF startup", e); - throw new SourceException("HackRF Tuner Controller - couldn't claim USB interface or get endpoint or pipe", e); + closeDevice(mDevice); + mDevice = null; + throw new SourceException("Can't start HackRF tuner - " + e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + @Override + public void stop() + { + HackRfDevice device; + + getLock().lock(); + + try + { + device = mDevice; + mDevice = null; + mStreaming = false; + } + finally + { + getLock().unlock(); + } + + mNativeBufferBroadcaster.clear(); + closeDevice(device); + } + + /** + * Opens the device by serial number, or when the device doesn't report a USB serial number, opens it when it is + * the only HackRF device connected. + * @param serialNumber from the USB serial string descriptor, or null. + * @return opened device + */ + private static HackRfDevice open(String serialNumber) throws HackRfException, SourceException + { + if(serialNumber != null) + { + return HackRf.open(serialNumber); + } + + if(HackRf.listDevices().size() == 1) + { + return HackRf.open(0); + } + + throw new SourceException("HackRF doesn't report a USB serial number and can't be distinguished from the " + + "other HackRF devices - update the HackRF firmware"); + } + + /** + * Closes the device, which also stops streaming. + * @param device to close, or null + */ + private static void closeDevice(HackRfDevice device) + { + if(device != null) + { + try + { + device.close(); + } + catch(HackRfException | RuntimeException e) + { + mLog.error("Error closing HackRF device", e); + } } } /** - * Devices shutdown actions invoked by the parent stop() method + * Maps the board identifier to a tuner type. */ - @Override - protected void deviceStop() + private static TunerType getTunerType(BoardId boardId) + { + return switch(boardId.code()) + { + case 1 -> TunerType.HACKRF_JAWBREAKER; + case 3 -> TunerType.HACKRF_RAD1O; + case BoardId.PRALINE -> TunerType.HACKRF_PRO; + default -> TunerType.HACKRF_ONE; + }; + } + + /** + * Formats the 32 hex digit serial number as four dash-separated groups, as sdrtrunk has always identified HackRF + * tuners, so that existing tuner configurations continue to match. + */ + private static String formatSerialNumber(String serialNumber) + { + String serial = serialNumber.toUpperCase(Locale.ROOT); + + if(serial.length() != 32) + { + return serial; + } + + return serial.substring(0, 8) + "-" + serial.substring(8, 16) + "-" + serial.substring(16, 24) + "-" + + serial.substring(24, 32); + } + + /** + * Reads the USB serial number of the device at this controller's USB bus and port, since libhackrf opens a + * specific device by serial number. The device is closed again before returning so that libhackrf can open it. + * @return serial number or null if the device doesn't report a serial number. + * @throws SourceException if the device isn't found or can't be accessed. + */ + private String readSerialNumber() 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 readSerialNumber(device); + } + } + } + finally + { + LibUsb.freeDeviceList(deviceList, true); + } + } + finally + { + LibUsb.exit(context); + } + + throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]"); + } + + /** + * Reads the serial number from the device's USB serial string descriptor. + * @param device to read + * @return serial number or null if the device doesn't have a serial string. + * @throws SourceException if the device can't be opened. + */ + private static String readSerialNumber(Device device) throws SourceException + { + DeviceDescriptor descriptor = new DeviceDescriptor(); + int status = LibUsb.getDeviceDescriptor(device, descriptor); + + if(status != LibUsb.SUCCESS) + { + throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status)); + } + + if(descriptor.iSerialNumber() == 0) + { + return null; + } + + DeviceHandle handle = new DeviceHandle(); + status = LibUsb.open(device, handle); + + if(status == LibUsb.ERROR_ACCESS) + { + mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules"); + throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules"); + } + else if(status != LibUsb.SUCCESS) + { + mLog.error("Can't open USB tuner - check driver or Linux udev rules"); + throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status)); + } + + try + { + String serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber()); + + if(serial != null) + { + serial = serial.trim(); + + //libhackrf compares, at most, the last 32 characters of the serial string + if(serial.length() > 32) + { + serial = serial.substring(serial.length() - 32); + } + } + + return serial == null || serial.isEmpty() ? null : serial; + } + finally + { + LibUsb.close(handle); + } + } + + /** + * Operation that requires the device. + */ + private interface DeviceOperation + { + void execute(HackRfDevice device) throws HackRfException; + } + + /** + * Executes the operation under the controller lock so that the device can't be closed while it is in use. The + * operation is ignored when the device isn't open, which allows for calls that arrive during tuner shutdown. + * @param operation to execute + * @throws SourceException if the operation fails + */ + private void execute(DeviceOperation operation) throws SourceException + { + getLock().lock(); + + try + { + if(mDevice != null) + { + operation.execute(mDevice); + } + } + catch(HackRfException | IllegalArgumentException | IllegalStateException e) + { + throw new SourceException(e.getMessage(), e); + } + finally + { + getLock().unlock(); + } + } + + /** + * Receives samples on the libusb event thread and dispatches them to the buffer listeners. Exceptions are + * contained here since an exception reaching the library would stop 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 { - setMode(Mode.OFF); + //The buffer factory copies the samples from native memory + broadcast(mNativeBufferFactory.getBuffer(buffer.samples().asByteBuffer(), System.currentTimeMillis())); } - catch(UsbException ue) + catch(RuntimeException e) { - mLog.error("Error while setting HackRF mode to OFF", ue); + mLog.error("Error dispatching HackRF sample buffer", e); + } + + return true; + } + + /** + * Starts the sample stream if it's not already started. + */ + private void startStreaming() throws SourceException + { + if(!mStreaming) + { + execute(device -> device.startRx(this::receive)); + mStreaming = mDevice != null; } } /** - * HackRF board identifier/type + * Stops the sample stream if it's started. */ - public BoardID getBoardID() throws UsbException + private void stopStreaming() throws SourceException { - int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false); - return BoardID.lookup(id); + if(mStreaming) + { + mStreaming = false; + execute(HackRfDevice::stopRx); + } } /** - * HackRF firmware version string + * Adds the IQ buffer listener and automatically starts streaming, if not already started. */ - public String getFirmwareVersion() throws UsbException + @Override + public void addBufferListener(Listener listener) { - ByteBuffer buffer = readArray(Request.VERSION_STRING_READ, 0, 0, 255); - byte[] data = new byte[255]; - buffer.get(data); - return new String(data); + getLock().lock(); + + try + { + if(mDevice != null) + { + super.addBufferListener(listener); + startStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error starting streaming on HackRF tuner", se); + } + finally + { + getLock().unlock(); + } } /** - * HackRF part id number and serial number + * Removes the IQ buffer listener and stops streaming if there are no more listeners. */ - public Serial getSerial() throws UsbException + @Override + public void removeBufferListener(Listener listener) { - ByteBuffer buffer = readArray(Request.BOARD_PARTID_SERIALNO_READ, 0, 0, 24); - return new Serial(buffer); + getLock().lock(); + + try + { + super.removeBufferListener(listener); + + if(!hasBufferListeners()) + { + stopStreaming(); + } + } + catch(SourceException se) + { + mLog.error("Error stopping streaming on HackRF tuner", se); + } + finally + { + getLock().unlock(); + } } /** - * Sets the HackRF transceiver mode + * HackRF board name, available once the tuner is started. + * @return board name or null */ - public void setMode(Mode mode) throws UsbException + public String getBoardName() { - write(Request.SET_TRANSCEIVER_MODE, mode.getNumber(), 0); + return mBoardName; + } + + /** + * HackRF firmware version string, available once the tuner is started. + * @return firmware version or null + */ + public String getFirmwareVersion() + { + return mFirmwareVersion; + } + + /** + * HackRF serial number, formatted as four dash-separated groups of 8 hex digits, available once the tuner is + * started. + * @return serial number or null + */ + public String getSerialNumber() + { + return mSerialNumber; + } + + /** + * HackRF microcontroller part ID, available once the tuner is started. + * @return part ID or null + */ + public String getPartId() + { + return mPartId; } /** * Sets the HackRF baseband filter */ - public void setBasebandFilter(BasebandFilter filter) throws UsbException + public void setBasebandFilter(BasebandFilter filter) throws SourceException { - write(Request.BASEBAND_FILTER_BANDWIDTH_SET, filter.getLowValue(), filter.getHighValue()); + execute(device -> device.setBasebandFilterBandwidth(filter.getBandwidth())); } /** * Enables (true) or disables (false) the amplifier */ - public void setAmplifierEnabled(boolean enabled) throws UsbException + public void setAmplifierEnabled(boolean enabled) throws SourceException { - write(Request.AMP_ENABLE, (enabled ? 1 : 0), 0); + execute(device -> device.setAmpEnable(enabled)); mAmplifierEnabled = enabled; } @@ -213,32 +543,20 @@ public class HackRFTunerController extends USBTunerController /** * Sets the IF LNA Gain */ - public void setLNAGain(HackRFLNAGain gain) throws UsbException + public void setLNAGain(HackRFLNAGain gain) throws SourceException { - int result = readByte(Request.SET_LNA_GAIN, 0, gain.getValue(), true); - - if(result != 1) - { - throw new UsbException("couldn't set lna gain to " + gain); - } + execute(device -> device.setLnaGain(gain.getValue())); } /** * Sets the Baseband VGA Gain */ - public void setVGAGain(HackRFVGAGain gain) throws UsbException + public void setVGAGain(HackRFVGAGain gain) throws SourceException { - int result = readByte(Request.SET_VGA_GAIN, 0, gain.getValue(), true); - - if(result != 1) - { - throw new UsbException("couldn't set vga gain to " + gain); - } + execute(device -> device.setVgaGain(gain.getValue())); } - /** - * Not implemented - */ + @Override public long getTunedFrequency() throws SourceException { return mFrequencyController.getTunedFrequency(); @@ -247,32 +565,21 @@ public class HackRFTunerController extends USBTunerController @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 + if(MINIMUM_TUNABLE_FREQUENCY_HZ <= frequency && frequency <= MAXIMUM_TUNABLE_FREQUENCY_HZ) { - write(Request.SET_FREQUENCY, 0, 0, buffer); + execute(device -> device.setFrequency(frequency)); } - catch(UsbException e) + else { - mLog.error("error setting frequency [" + frequency + "]", e); - throw new SourceException("error setting frequency [" + frequency + "]", e); + throw new SourceException("Frequency [" + frequency + "] outside of tunable range " + + MINIMUM_TUNABLE_FREQUENCY_HZ + "-" + MAXIMUM_TUNABLE_FREQUENCY_HZ); } } @Override public double getCurrentSampleRate() { - return mSampleRate.getRate(); + return mSampleRate != null ? mSampleRate.getRate() : 0; } @Override @@ -298,7 +605,7 @@ public class HackRFTunerController extends USBTunerController setLNAGain(hackRFConfig.getLNAGain()); setVGAGain(hackRFConfig.getVGAGain()); } - catch(UsbException e) + catch(SourceException e) { throw new SourceException("Error while applying tuner configuration", e); } @@ -309,153 +616,39 @@ public class HackRFTunerController extends USBTunerController } } - public ByteBuffer readArray(Request request, int value, int index, int length) throws UsbException - { - ByteBuffer buffer = ByteBuffer.allocateDirect(length); - - int transferred = LibUsb.controlTransfer(getDeviceHandle(), REQUEST_TYPE_IN, request.getRequestNumber(), - (short)value, (short)index, buffer, USB_TIMEOUT_US); - - if(transferred < 0) - { - throw new LibUsbException("read error", transferred); - } - - return buffer; - } - - 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 synchronized int readByte(Request request, int value, int index, boolean signed) throws UsbException - { - ByteBuffer buffer = readArray(request, value, index, 1); - - if(signed) - { - return buffer.get(); - } - else - { - return (buffer.get() & 0xFF); - } - } - - public synchronized void write(Request request, int value, int index, ByteBuffer buffer) throws UsbException - { - int status = LibUsb.controlTransfer(getDeviceHandle(), REQUEST_TYPE_OUT, request.getRequestNumber(), - (short)value, (short)index, buffer, USB_TIMEOUT_US); - - if(status < LibUsb.SUCCESS) - { - throw new LibUsbException("error writing control transfer for request [" + request.name() + - "] value [" + value + "]", status); - } - } - /** - * Sends a request that doesn't have a data payload + * Sets the sample rate and the baseband filter for the sample rate. Setting the sample rate resets the + * baseband filter, so the filter is set afterward. */ - public void write(Request request, int value, int index) throws UsbException + public void setSampleRate(HackRFSampleRate rate) throws SourceException { - 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 - { - if(!rate.isValidSampleRate()) + if(rate == null || !rate.isValidSampleRate()) { rate = HackRFSampleRate.RATE_5_0; } - setSampleRateManual(rate.getRate(), 1); - mFrequencyController.setSampleRate(rate.getRate()); - setBasebandFilter(rate.getFilter()); - mSampleRate = rate; - getNativeBufferFactory().setSamplesPerMillisecond((float)getSampleRate() / 1000.0f); - } - - 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 double 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; - - Request(int number) + if(rate == mSampleRate) { - mRequestNumber = (byte)number; + return; } - public byte getRequestNumber() + HackRFSampleRate sampleRate = rate; + + getLock().lock(); + + try { - return mRequestNumber; + execute(device -> { + device.setSampleRateManual(sampleRate.getRate(), 1); + device.setBasebandFilterBandwidth(sampleRate.getFilter().getBandwidth()); + }); + mSampleRate = sampleRate; + mNativeBufferFactory.setSamplesPerMillisecond(sampleRate.getRate() / 1000.0f); + mFrequencyController.setSampleRate(sampleRate.getRate()); + } + finally + { + getLock().unlock(); } } @@ -571,104 +764,12 @@ public class HackRFTunerController extends USBTunerController 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"), - RAD1O(0x03, "RAD1O"), - INVALID(0xFF, "HackRF Unknown Board"); - - private byte mIDNumber; - private String mLabel; - - 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; - case 3: - return RAD1O; - default: - return INVALID; - } - } - } - - public enum Mode - { - OFF(0, "Off"), - RECEIVE(1, "Receive"), - TRANSMIT(2, "Transmit"), - SS(3, "SS"); - - private byte mNumber; - private String mLabel; - - 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), @@ -751,53 +852,4 @@ public class HackRFTunerController extends USBTunerController return 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(); - } - } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java index 4190e677..cbbefbd6 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer + * 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 @@ -18,6 +18,7 @@ */ package io.github.dsheirer.source.tuner.hackrf; +import io.github.dsheirer.hackrf.HackRf; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFLNAGain; @@ -37,7 +38,6 @@ import javax.swing.JOptionPane; import javax.swing.JSeparator; import javax.swing.JToggleButton; import javax.swing.SpinnerNumberModel; -import javax.usb.UsbException; /** * HackRF Tuner Editor @@ -174,7 +174,7 @@ public class HackRFTunerEditor extends TunerEditor

HackRF Tuner

"); - sb.append("Board: "); - sb.append(board.getLabel()); - sb.append("
"); - - HackRFTunerController.Serial serial = null; + appendInfo(sb, "Board", controller != null ? controller.getBoardName() : null); + appendInfo(sb, "Serial", controller != null ? controller.getSerialNumber() : null); + appendInfo(sb, "Part", controller != null ? controller.getPartId() : null); + appendInfo(sb, "Firmware", controller != null ? controller.getFirmwareVersion() : null); try { - if(hasTuner()) - { - serial = getTuner().getController().getSerial(); - } + appendInfo(sb, "Library", "libhackrf " + HackRf.libraryVersion() + " (" + HackRf.libraryRelease() + ")"); } - catch(Exception e) + catch(RuntimeException | LinkageError e) { - mLog.error("couldn't read HackRF serial number", e); - } - - if(serial != null) - { - sb.append("Serial: "); - sb.append(serial.getSerialNumber()); - sb.append("
"); - - sb.append("Part: "); - sb.append(serial.getPartID()); - sb.append("
"); - } - else - { - sb.append("Serial: Unknown
"); - sb.append("Part: Unknown
"); - } - - String firmware = null; - - try - { - if(hasTuner()) - { - firmware = getTuner().getController().getFirmwareVersion(); - } - } - catch(Exception e) - { - mLog.error("couldn't read HackRF firmware version", e); - } - - if(firmware != null) - { - sb.append("Firmware: "); - sb.append(firmware); - sb.append("
"); - } - else - { - sb.append("Firmware: Unknown
"); + mLog.error("couldn't read libhackrf version", e); } return sb.toString(); } + /** + * Appends a labeled tuner info value, or Unknown when the value is not available. + */ + private static void appendInfo(StringBuilder sb, String label, String value) + { + sb.append("").append(label).append(": ").append(value != null ? value : "Unknown").append("
"); + } + @Override public void save() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java deleted file mode 100644 index 59eb7138..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java +++ /dev/null @@ -1,854 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2024 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ -package io.github.dsheirer.source.tuner.usb; - -import io.github.dsheirer.buffer.INativeBuffer; -import io.github.dsheirer.buffer.INativeBufferFactory; -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.manager.TunerManager; -import io.github.dsheirer.util.ThreadPool; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.usb4java.Context; -import org.usb4java.Device; -import org.usb4java.DeviceDescriptor; -import org.usb4java.DeviceHandle; -import org.usb4java.DeviceList; -import org.usb4java.LibUsb; -import org.usb4java.Transfer; -import org.usb4java.TransferCallback; - -/** - * Tuner controller implementation for USB tuners. Manages general USB operations and incorporates threaded USB - * Transfer processing. - */ -public abstract class USBTunerController extends TunerController -{ - private Logger mLog = LoggerFactory.getLogger(USBTunerController.class); - private static final int USB_INTERFACE = 0x0; //Common value for all currently supported devices - private static final int USB_CONFIGURATION = 0x1; //Common value for all currently supported devices - private static final int USB_BULK_TRANSFER_BUFFER_POOL_SIZE = 8; - protected static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81; - private static final long USB_BULK_TRANSFER_TIMEOUT_MS = 2000l; - - protected int mBus; - protected String mPortAddress; - private Context mDeviceContext = new Context(); - private Device mDevice; - private DeviceHandle mDeviceHandle; - private DeviceDescriptor mDeviceDescriptor; - private TransferManager mTransferManager = new TransferManager(); - private UsbEventProcessor mEventProcessor = new UsbEventProcessor(); - private AtomicBoolean mStreaming = new AtomicBoolean(); - private boolean mRunning = false; - - //Troubleshooting libusb bug: https://github.com/DSheirer/sdrtrunk/issues/1253 - private int mAnomalousTransfersDetected = 0; - - /** - * USB tuner controller class. Provides auto-start and auto-stop function when complex buffer listeners are added - * or removed from this tuner controller. - * - * @param bus number USB - * @param portAddress address USB - * @param tunerErrorListener to receive errors from this tuner controller - */ - public USBTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener) - { - super(tunerErrorListener); - mBus = bus; - mPortAddress = portAddress; - } - - /** - * Constructs an instance - * @param bus usb - * @param portAddress usb - * @param minimum tunable frequency in Hertz - * @param maximum tunable frequency in Hertz - * @param halfBandwidth that is unusable for DC spike avoidance - * @param usablePercent bandwith in Hertz - * @param tunerErrorListener to receive errors from this tuner controller - */ - public USBTunerController(int bus, String portAddress, long minimum, long maximum, int halfBandwidth, double usablePercent, - ITunerErrorListener tunerErrorListener) - { - this(bus, portAddress, tunerErrorListener); - setMinimumFrequency(minimum); - setMaximumFrequency(maximum); - setMiddleUnusableHalfBandwidth(halfBandwidth); - setUsableBandwidthPercentage(usablePercent); - } - - /** - * Tuner type for this USB controller - */ - public abstract TunerType getTunerType(); - - /** - * Factory for converting received streaming sample data into native buffers, as provided by sub-class. - */ - protected abstract INativeBufferFactory getNativeBufferFactory(); - - /** - * Sub-class definition of transfer buffer sizes to use for the tuner. Note: this should be a power-of-two - * value for compatibility with downstream (SIMD) operations (e.g. 65,536, 131072, 262144, etc.) - * @return transfer buffer size. - */ - protected abstract int getTransferBufferSize(); - - /** - * Sub-class method to perform additional device setup steps after the USB interface has been claimed and before any - * transfer operations start. - * @throws SourceException if there is an issue in configuring the device - */ - protected abstract void deviceStart() throws SourceException; - - /** - * Sub-class method to perform additional device shutdown steps after transfer processing has stopped and before - * the USB interface is released. - */ - protected abstract void deviceStop(); - - /** - * Starts or initializes this tuner. - * - * Note: sub-class implementations should override and invoke this method and then perform additional initialization - * operations for the tuner. - * - * @throws SourceException if there is an error and/or this tuner is unusable. - */ - public final void start() throws SourceException - { - if(mDeviceContext == null) - { - throw new SourceException("Device cannot be reused once it has been shutdown"); - } - - int status = LibUsb.init(mDeviceContext); - - if(status != LibUsb.SUCCESS) - { - throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status)); - } - - mDevice = findDevice(); - - if(mDevice == null) - { - throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]"); - } - - mDeviceDescriptor = new DeviceDescriptor(); - status = LibUsb.getDeviceDescriptor(mDevice, mDeviceDescriptor); - - if(status != LibUsb.SUCCESS) - { - mDeviceDescriptor = null; - throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status)); - } - - mDeviceHandle = new DeviceHandle(); - status = LibUsb.open(mDevice, mDeviceHandle); - - //Now that we have opened the device and added an additional reference, remove the original reference placed on - // the device during the findDevice() operation - LibUsb.unrefDevice(mDevice); - - if(status == LibUsb.ERROR_ACCESS) - { - mDeviceHandle = null; - mDeviceDescriptor = null; - - mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules"); - throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules"); - } - else if(status != LibUsb.SUCCESS) - { - mDeviceHandle = null; - mDeviceDescriptor = null; - - mLog.error("Can't open USB tuner - check driver or Linux udev rules"); - throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status)); - } - - //Detach the kernel driver if active and detach is supported. Otherwise, let the claim interface fail. - status = LibUsb.kernelDriverActive(mDeviceHandle, USB_INTERFACE); - - if(status == 1) //kernel driver is attached and detach operation is supported - { - status = LibUsb.detachKernelDriver(mDeviceHandle, USB_INTERFACE); - - if(status != LibUsb.SUCCESS) - { - mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPortAddress); - mDeviceHandle = null; - mDeviceDescriptor = null; - throw new SourceException("Can't detach kernel driver"); - } - } - - //Set the configuration which also invokes a soft reset on the device - status = LibUsb.setConfiguration(mDeviceHandle, USB_CONFIGURATION); - - if(status == LibUsb.ERROR_BUSY) - { - mLog.error("Unable to set USB configuration on tuner - device is busy (in use by another application)"); - mDeviceHandle = null; - mDeviceDescriptor = null; - throw new SourceException("USB tuner is in-use by another application"); - } - else if(status != LibUsb.SUCCESS) - { - mDeviceHandle = null; - mDeviceDescriptor = null; - throw new SourceException("Can't set configuration (ie reset) on the USB tuner - " + LibUsb.errorName(status)); - } - - //Claim the interface - status = LibUsb.claimInterface(mDeviceHandle, USB_INTERFACE); - - if(status == LibUsb.ERROR_BUSY) - { - mDeviceHandle = null; - mDeviceDescriptor = null; - throw new SourceException("USB tuner is in-use by another application"); - } - else if(status != LibUsb.SUCCESS) - { - mDeviceHandle = null; - mDeviceDescriptor = null; - throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(status)); - } - - //Set running true for deviceStart() operations that require it. - mRunning = true; - - try - { - deviceStart(); - } - catch(Exception se) - { - mRunning = false; - throw se; - } - } - - /** - * Prepares the tuner for full shutdown by stopping streaming, shutdown the device, and releasing the USB resources. - */ - public final void stop() - { - mRunning = false; - - //Spin the shutdown onto a new thread so that we can set a max wait threshold. - Thread t = new Thread(() -> { - stopStreaming(); - mNativeBufferBroadcaster.clear(); - deviceStop(); - }); - - t.start(); - - try - { - //Wait up to 500 milliseconds for the shutdown to complete ... otherwise interrupt it and finish shutdown - t.join(500); - - if(t.isAlive()) - { - mLog.info("Tuner shutdown exceeded 500ms - forcing shutdown"); - t.interrupt(); - } - } - catch(InterruptedException ie) - { - } - - //Release transfers - mTransferManager.freeTransfers(); - - if(mDeviceHandle != null) - { - LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE); - LibUsb.close(mDeviceHandle); - mDeviceHandle = null; - mDevice = null; - mDeviceDescriptor = null; - } - - LibUsb.exit(mDeviceContext); - mDeviceContext = null; - } - - /** - * Starts streaming data from the tuner - */ - private void startStreaming() - { - if(mStreaming.compareAndSet(false, true)) - { - try - { - prepareStreaming(); - List transfers = mTransferManager.getTransfers(); - mEventProcessor.start(); - mTransferManager.setAutoResubmitTransfers(true); - mTransferManager.submitTransfers(transfers); - } - catch(SourceException se) - { - mLog.error("Error starting streaming on USB tuner", se); - } - } - } - - /** - * Prepares to start streaming. This method can be overridden by sub-class to implement additional actions - * need to prepare before start streaming. - */ - protected void prepareStreaming() - { - } - - /** - * Stop streaming data from the tuner - */ - private void stopStreaming() - { - if(mStreaming.compareAndSet(true, false)) - { - //Turn off auto-resubmit of USB transfer buffers - mTransferManager.setAutoResubmitTransfers(false); - - //Stop event processing thread to put all submitted tranfers in a stable state - blocks until stopped - mEventProcessor.stop(); - - //Cancel all currently submitted transfers - mTransferManager.cancelTransfers(); - - //Perform final event processing iteration so LibUsb returns all of our cancelled tranfers - mEventProcessor.handleFinalEvents(); - - streamingCleanup(); - } - } - - /** - * Post streaming cleanup actions. This method can be overridden by sub-class to implement additional actions - * needed to cleanup after streaming stops. - */ - protected void streamingCleanup() - { - } - - /** - * Finds the USB device for this tuner at the specified USB bus and port. - * @return discovered USB device - * @throws SourceException if there is an error or the device is not discovered. - */ - private Device findDevice() throws SourceException - { - Device foundDevice = null; - - DeviceList deviceList = new DeviceList(); - int count = LibUsb.getDeviceList(mDeviceContext, deviceList); - - if(count >= 0) - { - for(Device device: deviceList) - { - int bus = LibUsb.getBusNumber(device); - int port = LibUsb.getPortNumber(device); - - if(port > 0) - { - String portAddress = TunerManager.getPortAddress(device); - - if(mBus == bus && mPortAddress != null && mPortAddress.equals(portAddress)) - { - foundDevice = device; - } - else - { - LibUsb.unrefDevice(device); - } - } - else - { - LibUsb.unrefDevice(device); - } - } - } - - //Free the device list but don't auto-unref all the devices ... we already did that during iteration - LibUsb.freeDeviceList(deviceList, false); - - if(foundDevice != null) - { - return foundDevice; - } - - throw new SourceException("LibUsb couldn't discover USB device [" + mBus + ":" + mPortAddress + - "] from device list" + (count < 0 ? " - error: " + LibUsb.errorName(count) : "")); - } - - /** - * Access the discovered USB device. - */ - protected Device getDevice() - { - return mDevice; - } - - /** - * LibUsb context for this device. - */ - protected Context getDeviceContext() - { - return mDeviceContext; - } - - /** - * LibUsb device descriptor for this device - */ - protected DeviceDescriptor getDeviceDescriptor() - { - return mDeviceDescriptor; - } - - /** - * USB Device Handle for the claimed device - */ - protected DeviceHandle getDeviceHandle() - { - return mDeviceHandle; - } - - /** - * Indicates if the device handle is non-null - */ - protected boolean hasDeviceHandle() - { - return getDeviceHandle() != null; - } - - /** - * Indicates if this device is usable, meaning it has been started and is not yet stopping. - * - * Note: this is a general usability flag for controlling all code that touches the USB interface(s) - */ - protected boolean isRunning() - { - return mRunning; - } - - /** - * Adds the IQ buffer listener and automatically starts stream buffer transfer processing, if not already started. - */ - @Override - public void addBufferListener(Listener listener) - { - if(isRunning()) - { - getLock().lock(); - - try - { - boolean hasExistingListeners = hasBufferListeners(); - - super.addBufferListener(listener); - - if(!hasExistingListeners) - { - startStreaming(); - } - } - finally - { - getLock().unlock(); - } - } - } - - /** - * Removes the IQ buffer listener and stops stream buffer transfer processing if there are no more listeners. - */ - @Override - public void removeBufferListener(Listener listener) - { - getLock().lock(); - - try - { - super.removeBufferListener(listener); - - if(!hasBufferListeners()) - { - stopStreaming(); - } - } - finally - { - getLock().unlock(); - } - } - - /** - * Manages USB transfer (ie zero-copy) buffer processing - */ - class TransferManager implements TransferCallback - { - private List mAvailableTransfers; - private LinkedTransferQueue mInProgressTransfers = new LinkedTransferQueue<>(); - private boolean mAutoResubmitTransfers = false; - private int mTransferErrorCount = 0; - private List mErrorTransfers = new ArrayList<>(); - private int mResubmitFailureLogCount = 0; - - /** - * Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory - * byte buffers outside the JVM. - * - * @return list of transfers - * @throws SourceException if there is an error creating transfers - */ - private List getTransfers() throws SourceException - { - if(mAvailableTransfers == null) - { - mAvailableTransfers = new ArrayList<>(); - - for(int x = 0; x < USB_BULK_TRANSFER_BUFFER_POOL_SIZE; x++) - { - Transfer transfer = LibUsb.allocTransfer(); - - if(transfer == null) - { - throw new SourceException("Couldn't allocate USB transfer buffer - out of memory"); - } - - final ByteBuffer buffer = ByteBuffer.allocateDirect(getTransferBufferSize()); - - LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, - TransferManager.this, "Transfer Buffer " + x, USB_BULK_TRANSFER_TIMEOUT_MS); - - mAvailableTransfers.add(transfer); - } - } - - return mAvailableTransfers; - } - - /** - * Prepare to stop processing transfers when stopping streaming of data. - */ - private void setAutoResubmitTransfers(boolean resubmit) - { - mAutoResubmitTransfers = resubmit; - } - - /** - * Submits the transfers to start sample stream processing - * @param transfers to submit - */ - private void submitTransfers(List transfers) - { - for(Transfer transfer: transfers) - { - submitTransfer(transfer); - } - } - - /** - * (Re)Submits the transfer for stream processing - * - * Note: synchronized used here because there can be multiple threads can invoke LibUsb.handleTimeoutEvents - * (scheduled thread pool and a dedicated shutdown thread) during tuner shutdown and this has caused transfer - * tracking issues. - * - * @param transfer to (re)submit - */ - private synchronized void submitTransfer(Transfer transfer) - { - int status = LibUsb.submitTransfer(transfer); - - if(status == LibUsb.SUCCESS) - { - mInProgressTransfers.add(transfer); - - //Attempt to resubmit any previous transfers that failed on submit - if(!mErrorTransfers.isEmpty()) - { - Transfer toResubmit = mErrorTransfers.remove(0); - int resubmitStatus = LibUsb.submitTransfer(toResubmit); - - if(resubmitStatus == LibUsb.SUCCESS) - { - mInProgressTransfers.add(transfer); - - //Only log this if more than half of the total transfer buffers are in error-holding - if(mErrorTransfers.size() >= (mAvailableTransfers.size() / 2)) - { - mLog.info("Successfully resubmitted previous error USB transfer buffer. Current transfer buffer" + - " status (error queue/total available) [" + mErrorTransfers.size() + "/" + - mAvailableTransfers.size() + "]"); - } - } - else if(resubmitStatus == LibUsb.ERROR_BUSY) - { - //Ignore - this indicates the transfer was previously submitted and libusb is still working it. - } - else - { - //Add it back to the queue to try again later. - mErrorTransfers.add(transfer); - mTransferErrorCount++; - } - } - } - else if(status == LibUsb.ERROR_BUSY) - { - //Ignore - this indicates the transfer was previously submitted and libusb is still working it. I'm not - //sure how this happens because we give libusb a transfer and it hands it back when it's full. If - //libusb is still working it, then why did it indicate the transfer was completed? So, we simply - //ignore this error code. Other libraries simply ignore the submit status code altogether. - } - else - { - mLog.error("USB transfer [" + transfer + "] submit attempt failed with error [" + LibUsb.errorName(status) + - "] - adding to error queue to resubmit later - this may be a temporary USB issue and has happened [" + - mTransferErrorCount + "] time(s) so far. Current transfer error queue (error/total) [" + - mErrorTransfers.size() + "/" + mAvailableTransfers.size() + "]"); - - mErrorTransfers.add(transfer); - mTransferErrorCount++; - } - - if(mErrorTransfers.size() >= mAvailableTransfers.size()) - { - mLog.error("Maximum USB transfer buffer errors reached - transfer buffers exhausted - shutting down USB tuner"); - ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfer Buffers Exhausted")); - } - } - - /** - * Cancels any in-progress transfers to prepare for shutdown. - * - * Note: this should only be invoked after the LibUsb event processing thread has been stopped so that the - * transfer buffers are in a stable (submitted vs callback) state and we can then flip their cancel state and - * then finish processing the timeout events under the control of a single (shutdown) thread. - */ - private void cancelTransfers() - { - for(Transfer transfer: mInProgressTransfers) - { - LibUsb.cancelTransfer(transfer); - } - for(Transfer transfer: mErrorTransfers) - { - LibUsb.cancelTransfer(transfer); - } - } - - /** - * Frees/disposes allocated USB transfer buffers. - */ - private void freeTransfers() - { - if(mAvailableTransfers != null) - { - for(Transfer transfer: mAvailableTransfers) - { - try - { - LibUsb.freeTransfer(transfer); - } - catch(Exception e) - { - mLog.error("Error releasing allocated USB transfer buffer during tuner shutdown: " + - e.getLocalizedMessage()); - } - } - - mAvailableTransfers.clear(); - mAvailableTransfers = null; - } - } - - @Override - public void processTransfer(Transfer transfer) - { - mInProgressTransfers.remove(transfer); - - if(mErrorTransfers.contains(transfer)) - { - mLog.warn("USB transfer [" + transfer + "] that was being tracked as an error transfer, has just been " + - "delivered as completed with transfer status [" + LibUsb.errorName(transfer.status()) + - "] - removing it from the transfer error queue"); - mErrorTransfers.remove(transfer); - } - - switch(transfer.status()) - { - case LibUsb.TRANSFER_COMPLETED: - case LibUsb.TRANSFER_STALL: - case LibUsb.TRANSFER_TIMED_OUT: - case LibUsb.TRANSFER_ERROR: - //Note: cancel flag can be set by libusb, independent of commanded cancel of transfers - we simply - //resubmit the transfer for continued use. - case LibUsb.TRANSFER_CANCELLED: - int transferLength = transfer.actualLength(); - - if(transferLength > 0) - { - dispatchTransfer(transfer); - } - - transfer.buffer().rewind(); - - if(mAutoResubmitTransfers) - { - submitTransfer(transfer); - } - break; - default: - //Unexpected transfer error - shutdown the tuner - transfer.buffer().rewind(); - - //Only set an error if we're not shutting down - if(mAutoResubmitTransfers) - { - //spin this off onto the thread pool, so it doesn't impact the usb processor thread. - ThreadPool.CACHED.submit(() -> setErrorMessage("LibUsb Transfer Error - stopping device - " + - "status [" + transfer.status() + "] - " + LibUsb.errorName(transfer.status()))); - } - break; - } - } - - /** - * Makes a copy of the transfer's native memory byte array payload so that the transfer can be reused. - * Dispatches the native buffer to registered listeners. - * @param transfer to copy and dispatch - */ - private void dispatchTransfer(Transfer transfer) - { - //Pass the transfer's byte buffer so the native buffer factory can make a copy of the byte array contents - //and package it as a native buffer. - INativeBuffer nativeBuffer = getNativeBufferFactory().getBuffer(transfer.buffer(), System.currentTimeMillis()); - mNativeBufferBroadcaster.broadcast(nativeBuffer); - } - } - - /** - * Threaded LibUsb event processor - continuously polls LibUsb to process events exclusively for this USB tuner - * device using the device context. - */ - class UsbEventProcessor implements Runnable - { - private Thread mThread; - private boolean mProcessing = false; - - /** - * Start the event processing thread - */ - public void start() - { - if(mThread == null) - { - mProcessing = true; - mThread = new Thread(this); - mThread.setName("sdrtrunk USB tuner - bus [" + mBus + "] port [" + mPortAddress + "]"); - mThread.setPriority(Thread.MAX_PRIORITY); - mThread.start(); - } - } - - /** - * Set the stop processing flag and block until the thread stops, blocking up to 1000 ms. - */ - public void stop() - { - mProcessing = false; - - try - { - //Give the thread a second to stop - it should happen quickly because it's only checking transfers - //for completed status and returning them to us to dispatch. - mThread.join(1000); - } - catch(Exception e) - { - mLog.error("Error stopping LibUsb event processing thread - " + e.getLocalizedMessage()); - } - - mThread = null; - } - - /** - * This performs a final handle-events invocation after the event processing thread has been shutdown and - * transfers have been flagged as cancelled. This should cause LibUsb to return all in-progress and now - * canceled transfers back to us via the TransferManager.processTransfer() method. - */ - public void handleFinalEvents() - { - try - { - //Use a short timeout since this is a shutdown operation - LibUsb.handleEventsTimeout(mDeviceContext, 50); - } - catch(Throwable throwable) - { - mLog.error("Error while processing stop-streaming LibUsb timeout events", throwable); - } - } - - /** - * LibUsb event/timeout processing loop - */ - @Override - public void run() - { - mProcessing = true; - - while(mProcessing) - { - try - { - LibUsb.handleEventsTimeout(mDeviceContext, 250); - } - catch(Throwable throwable) - { - mLog.error("Error while processing LibUsb timeout events", throwable); - } - } - } - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java b/src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java deleted file mode 100644 index cd7322b4..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/UsbUtils.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ -package io.github.dsheirer.source.tuner.usb; - -import javax.usb.UsbConfiguration; -import javax.usb.UsbDevice; -import javax.usb.UsbDisconnectedException; -import javax.usb.UsbEndpoint; -import javax.usb.UsbException; -import javax.usb.UsbHostManager; -import javax.usb.UsbHub; -import javax.usb.UsbInterface; -import javax.usb.UsbServices; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - -public class UsbUtils -{ - /** - * Returns a List of currently connected usb devices - */ - public static List getDevices() throws SecurityException, UsbException, UnsupportedEncodingException - { - UsbServices services = UsbHostManager.getUsbServices(); - UsbHub root = services.getRootUsbHub(); - List devices = new ArrayList(getHubDevices(root)); - return devices; - } - - /** - * Returns a list of devices attached to the hub - */ - public static List getHubDevices(UsbHub hub) throws UnsupportedEncodingException, UsbException - { - List devices = new ArrayList(); - List children = hub.getAttachedUsbDevices(); - - for(UsbDevice child : children) - { - if(child.isUsbHub()) - { - devices.addAll(getHubDevices((UsbHub) child)); - } - else - { - devices.add(child); - } - } - - return devices; - } - - public static String getDeviceDetails(UsbDevice device) throws UsbException, UnsupportedEncodingException, UsbDisconnectedException - { - StringBuilder sb = new StringBuilder(); - sb.append(device.getUsbDeviceDescriptor().toString()).append("\n\n"); - - for(Object configObject : device.getUsbConfigurations()) - { - UsbConfiguration config = (UsbConfiguration) configObject; - - sb.append(config.getUsbConfigurationDescriptor().toString()).append("\n\n"); - - for(Object interfaceObject : config.getUsbInterfaces()) - { - UsbInterface iface = (UsbInterface) interfaceObject; - - sb.append(iface.getUsbInterfaceDescriptor().toString()).append("\n\n"); - - for(Object endpointObject : iface.getUsbEndpoints()) - { - UsbEndpoint endpoint = (UsbEndpoint) endpointObject; - - sb.append(endpoint.getUsbEndpointDescriptor().toString()).append("\n\n"); - } - } - } - - return sb.toString(); - } - - public static String getDeviceClass(byte deviceClass) - { - switch(deviceClass) - { - case 0: - return "Unknown Device - Class 0"; - case 2: - return "Communications Device"; - case 3: - return "HID Device"; - case 5: - return "Physical Device"; - case 6: - return "Still Imaging Device"; - case 7: - return "Printer Device"; - case 8: - return "Mass Storage Device"; - case 9: - return "Hub Device"; - case 0xA: - return "Communications Device"; - case 0xB: - return "Smart Card Device"; - case 0xD: - return "Content Security Device"; - case 0xE: - return "Video Device"; - case 0xF: - return "Personal Healthcare Device"; - case 0x10: - return "Audio/Video Device"; - case (byte)0xDC: - return "Diagnostic Device"; - case (byte)0xE0: - return "Wireless Controller Device"; - case (byte)0xEF: - return "Miscellaneous Device"; - default: - return "Unknown Device - Class " + deviceClass; - } - } -} diff --git a/src/main/resources/javax.usb.properties b/src/main/resources/javax.usb.properties deleted file mode 100644 index 50e944e4..00000000 --- a/src/main/resources/javax.usb.properties +++ /dev/null @@ -1 +0,0 @@ -javax.usb.services = org.usb4java.javax.Services