Skip to content

Commit

Permalink
probe CDC devices by USB interface types instead of fixed VID+PID
Browse files Browse the repository at this point in the history
- no more custom prober required for standard CDC devices
- legacy (singleInterface) CDC devices still have to be added by VID+PID
- for autostart VID+PID still have to be added to device_filter.xml
  • Loading branch information
kai-morich committed Mar 11, 2023
1 parent 85f64af commit 5db4554
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 120 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,23 @@ new device or for one using a custom VID/PID pair.
UsbSerialProber is a class to help you find and instantiate compatible
UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use
the default prober returned by ``UsbSerialProber.getDefaultProber()``, which
uses the built-in list of well-known VIDs and PIDs that are supported by our
drivers.
uses USB interface types and the built-in list of well-known VIDs and PIDs that
are supported by our drivers.

To use your own set of rules, create and use a custom prober:

```java
// Probe for our custom CDC devices, which use VID 0x1234
// and PIDS 0x0001 and 0x0002.
// Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002.
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class);
customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class);

UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager);
// ...
```
*Note*: as of v3.5.0 this library detects CDC devices by USB interface types instead of fixed VID+PID,
so custom probers are typically not required any more for CDC devices.

Of course, nothing requires you to use UsbSerialProber at all: you can
instantiate driver classes directly if you know what you're doing; just supply
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hoho.android.usbserial.examples;

import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialProber;

Expand All @@ -14,7 +14,8 @@ class CustomProber {

static UsbSerialProber getCustomProber() {
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC
customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class); // e.g. device with custom VID+PID
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class); // e.g. device with custom VID+PID
return new UsbSerialProber(customTable);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,30 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device;
mPorts = new ArrayList<>();

int ports = countPorts(device);
for (int port = 0; port < ports; port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port));
}
if (mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
}
}

@SuppressWarnings({"unused"})
public static boolean probe(UsbDevice device) {
return countPorts(device) > 0;
}

private static int countPorts(UsbDevice device) {
int controlInterfaceCount = 0;
int dataInterfaceCount = 0;
for( int i = 0; i < device.getInterfaceCount(); i++) {
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM)
for (int i = 0; i < device.getInterfaceCount(); i++) {
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM)
controlInterfaceCount++;
if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port));
}
if(mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1));
}
return Math.min(controlInterfaceCount, dataInterfaceCount);
}

@Override
Expand Down Expand Up @@ -297,51 +306,9 @@ public void setBreak(boolean value) throws IOException {

}

@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_ARDUINO,
new int[] {
UsbId.ARDUINO_UNO,
UsbId.ARDUINO_UNO_R3,
UsbId.ARDUINO_MEGA_2560,
UsbId.ARDUINO_MEGA_2560_R3,
UsbId.ARDUINO_SERIAL_ADAPTER,
UsbId.ARDUINO_SERIAL_ADAPTER_R3,
UsbId.ARDUINO_MEGA_ADK,
UsbId.ARDUINO_MEGA_ADK_R3,
UsbId.ARDUINO_LEONARDO,
UsbId.ARDUINO_MICRO,
});
supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH,
new int[] {
UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL,
});
supportedDevices.put(UsbId.VENDOR_ATMEL,
new int[] {
UsbId.ATMEL_LUFA_CDC_DEMO_APP,
});
supportedDevices.put(UsbId.VENDOR_LEAFLABS,
new int[] {
UsbId.LEAFLABS_MAPLE,
});
supportedDevices.put(UsbId.VENDOR_ARM,
new int[] {
UsbId.ARM_MBED,
});
supportedDevices.put(UsbId.VENDOR_ST,
new int[] {
UsbId.ST_CDC,
});
supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI,
new int[] {
UsbId.RASPBERRY_PI_PICO_MICROPYTHON,
UsbId.RASPBERRY_PI_PICO_SDK,
});
supportedDevices.put(UsbId.VENDOR_QINHENG,
new int[] {
UsbId.QINHENG_CH9102F,
});
return supportedDevices;
return new LinkedHashMap<>();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ public void setBreak(boolean value) throws IOException {
}
}

@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ public void setBreak(boolean value) throws IOException {
}
}

@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ public int getLatencyTimer() throws IOException {

}

@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.hoho.android.usbserial.driver;

import android.hardware.usb.UsbDevice;
import android.util.Pair;

import java.lang.reflect.InvocationTargetException;
Expand All @@ -14,14 +15,14 @@
import java.util.Map;

/**
* Maps (vendor id, product id) pairs to the corresponding serial driver.
*
* @author mike wakerly ([email protected])
* Maps (vendor id, product id) pairs to the corresponding serial driver,
* or invoke 'probe' method to check actual USB devices for matching interfaces.
*/
public class ProbeTable {

private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable =
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mVidPidProbeTable =
new LinkedHashMap<>();
private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>();

/**
* Adds or updates a (vendor, product) pair in the table.
Expand All @@ -33,20 +34,19 @@ public class ProbeTable {
*/
public ProbeTable addProduct(int vendorId, int productId,
Class<? extends UsbSerialDriver> driverClass) {
mProbeTable.put(Pair.create(vendorId, productId), driverClass);
mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this;
}

/**
* Internal method to add all supported products from
* {@code getSupportedProducts} static method.
*
* @param driverClass
* @return
* @param driverClass to be added
*/
@SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method;
void addDriver(Class<? extends UsbSerialDriver> driverClass) {
Method method;

try {
method = driverClass.getMethod("getSupportedDevices");
Expand All @@ -68,20 +68,35 @@ ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
}
}

return this;
try {
method = driverClass.getMethod("probe", UsbDevice.class);
mMethodProbeTable.put(method, driverClass);
} catch (SecurityException | NoSuchMethodException ignored) {
}
}

/**
* Returns the driver for the given (vendor, product) pair, or {@code null}
* if no match.
* Returns the driver for the given USB device, or {@code null} if no match.
*
* @param vendorId the USB vendor id
* @param productId the USB product id
* @param usbDevice the USB device to be probed
* @return the driver class matching this pair, or {@code null}
*/
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId);
return mProbeTable.get(pair);
public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) {
final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId());
Class<? extends UsbSerialDriver> driverClass = mVidPidProbeTable.get(pair);
if (driverClass != null)
return driverClass;
for (Map.Entry<Method, Class<? extends UsbSerialDriver>> entry : mMethodProbeTable.entrySet()) {
try {
Method method = entry.getKey();
Object o = method.invoke(null, usbDevice);
if((boolean)o)
return entry.getValue();
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ public void setBreak(boolean value) throws IOException {
}
}

@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_PROLIFIC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,6 @@ public final class UsbId {
public static final int FTDI_FT232H = 0x6014;
public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD

public static final int VENDOR_ATMEL = 0x03EB;
public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044;

public static final int VENDOR_ARDUINO = 0x2341;
public static final int ARDUINO_UNO = 0x0001;
public static final int ARDUINO_MEGA_2560 = 0x0010;
public static final int ARDUINO_SERIAL_ADAPTER = 0x003b;
public static final int ARDUINO_MEGA_ADK = 0x003f;
public static final int ARDUINO_MEGA_2560_R3 = 0x0042;
public static final int ARDUINO_UNO_R3 = 0x0043;
public static final int ARDUINO_MEGA_ADK_R3 = 0x0044;
public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044;
public static final int ARDUINO_LEONARDO = 0x8036;
public static final int ARDUINO_MICRO = 0x8037;

public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0;
public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483;

public static final int VENDOR_LEAFLABS = 0x1eaf;
public static final int LEAFLABS_MAPLE = 0x0004;

public static final int VENDOR_SILABS = 0x10c4;
public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109
public static final int SILABS_CP2105 = 0xea70;
Expand All @@ -61,18 +40,7 @@ public final class UsbId {
public static final int VENDOR_QINHENG = 0x1a86;
public static final int QINHENG_CH340 = 0x7523;
public static final int QINHENG_CH341A = 0x5523;
public static final int QINHENG_CH9102F = 0x55D4;

// at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids
public static final int VENDOR_ARM = 0x0d28;
public static final int ARM_MBED = 0x0204;

public static final int VENDOR_ST = 0x0483;
public static final int ST_CDC = 0x5740;

public static final int VENDOR_RASPBERRY_PI = 0x2e8a;
public static final int RASPBERRY_PI_PICO_MICROPYTHON = 0x0005;
public static final int RASPBERRY_PI_PICO_SDK = 0x000a;

private UsbId() {
throw new IllegalAccessError("Non-instantiable class");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@

import java.util.List;

/**
*
* @author mike wakerly ([email protected])
*/
public interface UsbSerialDriver {

/*
* Additional interface properties. Invoked thru reflection.
*
UsbSerialDriver(UsbDevice device); // constructor with device
static Map<Integer, int[]> getSupportedDevices();
static boolean probe(UsbDevice device); // optional
*/


/**
* Returns the raw {@link UsbDevice} backing this port.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@ public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
* {@code null} if none available.
*/
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
final int vendorId = usbDevice.getVendorId();
final int productId = usbDevice.getProductId();

final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId);
final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice);
if (driverClass != null) {
final UsbSerialDriver driver;
try {
Expand Down
Loading

0 comments on commit 5db4554

Please sign in to comment.