1
0
mirror of https://github.com/mik3y/usb-serial-for-android synced 2025-06-08 00:16:13 +00:00

probe CDC devices by USB interface types instead of fixed VID+PID

- 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
This commit is contained in:
kai-morich 2023-03-11 19:12:42 +01:00
parent 85f64aff96
commit 5db45548ba
13 changed files with 169 additions and 119 deletions

View File

@ -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 UsbSerialProber is a class to help you find and instantiate compatible
UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use UsbSerialDrivers from the tree of connected UsbDevices. Normally, you will use
the default prober returned by ``UsbSerialProber.getDefaultProber()``, which the default prober returned by ``UsbSerialProber.getDefaultProber()``, which
uses the built-in list of well-known VIDs and PIDs that are supported by our uses USB interface types and the built-in list of well-known VIDs and PIDs that
drivers. are supported by our drivers.
To use your own set of rules, create and use a custom prober: To use your own set of rules, create and use a custom prober:
```java ```java
// Probe for our custom CDC devices, which use VID 0x1234 // Probe for our custom FTDI device, which use VID 0x1234 and PID 0x0001 and 0x0002.
// and PIDS 0x0001 and 0x0002.
ProbeTable customTable = new ProbeTable(); ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, CdcAcmSerialDriver.class); customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, CdcAcmSerialDriver.class); customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable); UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager); 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 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 instantiate driver classes directly if you know what you're doing; just supply

View File

@ -1,6 +1,6 @@
package com.hoho.android.usbserial.examples; 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.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.driver.UsbSerialProber;
@ -14,7 +14,8 @@ class CustomProber {
static UsbSerialProber getCustomProber() { static UsbSerialProber getCustomProber() {
ProbeTable customTable = new ProbeTable(); 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); return new UsbSerialProber(customTable);
} }

View File

@ -37,23 +37,32 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
public CdcAcmSerialDriver(UsbDevice device) { public CdcAcmSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPorts = new ArrayList<>(); mPorts = new ArrayList<>();
int ports = countPorts(device);
int controlInterfaceCount = 0; for (int port = 0; port < ports; port++) {
int dataInterfaceCount = 0;
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)
dataInterfaceCount++;
}
for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) {
mPorts.add(new CdcAcmSerialPort(mDevice, port)); mPorts.add(new CdcAcmSerialPort(mDevice, port));
} }
if(mPorts.size() == 0) { if (mPorts.size() == 0) {
mPorts.add(new CdcAcmSerialPort(mDevice, -1)); 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)
controlInterfaceCount++;
if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA)
dataInterfaceCount++;
}
return Math.min(controlInterfaceCount, dataInterfaceCount);
}
@Override @Override
public UsbDevice getDevice() { public UsbDevice getDevice() {
return mDevice; return mDevice;
@ -297,51 +306,9 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); return 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;
} }
} }

View File

@ -374,6 +374,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
} }
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{

View File

@ -320,6 +320,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
} }
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS, supportedDevices.put(UsbId.VENDOR_SILABS,

View File

@ -414,6 +414,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI, supportedDevices.put(UsbId.VENDOR_FTDI,

View File

@ -6,6 +6,7 @@
package com.hoho.android.usbserial.driver; package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
import android.util.Pair; import android.util.Pair;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -14,14 +15,14 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
/** /**
* Maps (vendor id, product id) pairs to the corresponding serial driver. * Maps (vendor id, product id) pairs to the corresponding serial driver,
* * or invoke 'probe' method to check actual USB devices for matching interfaces.
* @author mike wakerly (opensource@hoho.com)
*/ */
public class ProbeTable { 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<>(); new LinkedHashMap<>();
private final Map<Method, Class<? extends UsbSerialDriver>> mMethodProbeTable = new LinkedHashMap<>();
/** /**
* Adds or updates a (vendor, product) pair in the table. * Adds or updates a (vendor, product) pair in the table.
@ -33,7 +34,7 @@ public class ProbeTable {
*/ */
public ProbeTable addProduct(int vendorId, int productId, public ProbeTable addProduct(int vendorId, int productId,
Class<? extends UsbSerialDriver> driverClass) { Class<? extends UsbSerialDriver> driverClass) {
mProbeTable.put(Pair.create(vendorId, productId), driverClass); mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this; return this;
} }
@ -41,12 +42,11 @@ public class ProbeTable {
* Internal method to add all supported products from * Internal method to add all supported products from
* {@code getSupportedProducts} static method. * {@code getSupportedProducts} static method.
* *
* @param driverClass * @param driverClass to be added
* @return
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) { void addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method; Method method;
try { try {
method = driverClass.getMethod("getSupportedDevices"); method = driverClass.getMethod("getSupportedDevices");
@ -68,20 +68,35 @@ public class ProbeTable {
} }
} }
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} * Returns the driver for the given USB device, or {@code null} if no match.
* if no match.
* *
* @param vendorId the USB vendor id * @param usbDevice the USB device to be probed
* @param productId the USB product id
* @return the driver class matching this pair, or {@code null} * @return the driver class matching this pair, or {@code null}
*/ */
public Class<? extends UsbSerialDriver> findDriver(int vendorId, int productId) { public Class<? extends UsbSerialDriver> findDriver(final UsbDevice usbDevice) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId); final Pair<Integer, Integer> pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId());
return mProbeTable.get(pair); 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;
} }
} }

View File

@ -566,6 +566,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
} }
@SuppressWarnings({"unused"})
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_PROLIFIC, supportedDevices.put(UsbId.VENDOR_PROLIFIC,

View File

@ -23,27 +23,6 @@ public final class UsbId {
public static final int FTDI_FT232H = 0x6014; public static final int FTDI_FT232H = 0x6014;
public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD 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 VENDOR_SILABS = 0x10c4;
public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109
public static final int SILABS_CP2105 = 0xea70; public static final int SILABS_CP2105 = 0xea70;
@ -61,18 +40,7 @@ public final class UsbId {
public static final int VENDOR_QINHENG = 0x1a86; public static final int VENDOR_QINHENG = 0x1a86;
public static final int QINHENG_CH340 = 0x7523; public static final int QINHENG_CH340 = 0x7523;
public static final int QINHENG_CH341A = 0x5523; 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() { private UsbId() {
throw new IllegalAccessError("Non-instantiable class"); throw new IllegalAccessError("Non-instantiable class");

View File

@ -10,12 +10,17 @@ import android.hardware.usb.UsbDevice;
import java.util.List; import java.util.List;
/**
*
* @author mike wakerly (opensource@hoho.com)
*/
public interface UsbSerialDriver { 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. * Returns the raw {@link UsbDevice} backing this port.
* *

View File

@ -69,11 +69,7 @@ public class UsbSerialProber {
* {@code null} if none available. * {@code null} if none available.
*/ */
public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { public UsbSerialDriver probeDevice(final UsbDevice usbDevice) {
final int vendorId = usbDevice.getVendorId(); final Class<? extends UsbSerialDriver> driverClass = mProbeTable.findDriver(usbDevice);
final int productId = usbDevice.getProductId();
final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId);
if (driverClass != null) { if (driverClass != null) {
final UsbSerialDriver driver; final UsbSerialDriver driver;
try { try {

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import androidx.annotation.Nullable;
import java.util.Objects;
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
public class Pair<F, S> {
public final F first;
public final S second;
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
/**
* Checks the two objects for equality by delegating to their respective
* {@link Object#equals(Object)} methods.
*
* @param o the {@link Pair} to which this one is to be checked for equality
* @return true if the underlying objects of the Pair are both considered
* equal
*/
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof Pair)) {
return false;
}
Pair<?, ?> p = (Pair<?, ?>) o;
return Objects.equals(p.first, first) && Objects.equals(p.second, second);
}
/**
* Compute a hash code using the hash codes of the underlying objects
*
* @return a hashcode of the Pair
*/
@Override
public int hashCode() {
return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
}
@Override
public String toString() {
return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}";
}
/**
* Convenience method for creating an appropriately typed pair.
* @param a the first object in the Pair
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
public static <A, B> Pair <A, B> create(A a, B b) {
return new Pair<A, B>(a, b);
}
}

View File

@ -53,6 +53,10 @@ public class CdcAcmSerialDriverTest {
port.openInt(); port.openInt();
assertEquals(readEndpoint, port.mReadEndpoint); assertEquals(readEndpoint, port.mReadEndpoint);
assertEquals(writeEndpoint, port.mWriteEndpoint); assertEquals(writeEndpoint, port.mWriteEndpoint);
ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable();
Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice);
assertEquals(driver.getClass(), probeDriver);
} }
@Test @Test
@ -84,6 +88,10 @@ public class CdcAcmSerialDriverTest {
port.openInt(); port.openInt();
assertEquals(readEndpoint, port.mReadEndpoint); assertEquals(readEndpoint, port.mReadEndpoint);
assertEquals(writeEndpoint, port.mWriteEndpoint); assertEquals(writeEndpoint, port.mWriteEndpoint);
ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable();
Class<? extends UsbSerialDriver> probeDriver = probeTable.findDriver(usbDevice);
assertNull(probeDriver);
} }
@Test @Test