diff --git a/README.md b/README.md index a2c3057..2f99779 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java index 0447317..80eaf71 100644 --- a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java @@ -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; @@ -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); } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java index 45c63a3..11a3e41 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -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 @@ -297,51 +306,9 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { } + @SuppressWarnings({"unused"}) public static Map getSupportedDevices() { - final Map 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<>(); } } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java index 00fa8a1..55afd57 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java @@ -374,6 +374,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver { } } + @SuppressWarnings({"unused"}) public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap<>(); supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java index 883c7ed..c178a67 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java @@ -320,6 +320,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver { } } + @SuppressWarnings({"unused"}) public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap<>(); supportedDevices.put(UsbId.VENDOR_SILABS, diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java index f34ba90..cb3087b 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -414,6 +414,7 @@ public class FtdiSerialDriver implements UsbSerialDriver { } + @SuppressWarnings({"unused"}) public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap<>(); supportedDevices.put(UsbId.VENDOR_FTDI, diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java index 615e046..67ea226 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java @@ -6,6 +6,7 @@ package com.hoho.android.usbserial.driver; +import android.hardware.usb.UsbDevice; import android.util.Pair; import java.lang.reflect.InvocationTargetException; @@ -14,14 +15,14 @@ import java.util.LinkedHashMap; import java.util.Map; /** - * Maps (vendor id, product id) pairs to the corresponding serial driver. - * - * @author mike wakerly (opensource@hoho.com) + * 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, Class> mProbeTable = + private final Map, Class> mVidPidProbeTable = new LinkedHashMap<>(); + private final Map> mMethodProbeTable = new LinkedHashMap<>(); /** * Adds or updates a (vendor, product) pair in the table. @@ -33,7 +34,7 @@ public class ProbeTable { */ public ProbeTable addProduct(int vendorId, int productId, Class driverClass) { - mProbeTable.put(Pair.create(vendorId, productId), driverClass); + mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass); return this; } @@ -41,12 +42,11 @@ public class ProbeTable { * 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 driverClass) { - final Method method; + void addDriver(Class driverClass) { + Method method; try { 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} - * 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 findDriver(int vendorId, int productId) { - final Pair pair = Pair.create(vendorId, productId); - return mProbeTable.get(pair); + public Class findDriver(final UsbDevice usbDevice) { + final Pair pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId()); + Class driverClass = mVidPidProbeTable.get(pair); + if (driverClass != null) + return driverClass; + for (Map.Entry> 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; } } diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java index 53cf664..92c8a65 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java @@ -566,6 +566,7 @@ public class ProlificSerialDriver implements UsbSerialDriver { } } + @SuppressWarnings({"unused"}) public static Map getSupportedDevices() { final Map supportedDevices = new LinkedHashMap<>(); supportedDevices.put(UsbId.VENDOR_PROLIFIC, diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java index 48a0f70..bed2ffe 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java @@ -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; @@ -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"); diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java index d6539b2..78c1665 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java @@ -10,12 +10,17 @@ import android.hardware.usb.UsbDevice; import java.util.List; -/** - * - * @author mike wakerly (opensource@hoho.com) - */ public interface UsbSerialDriver { + /* + * Additional interface properties. Invoked thru reflection. + * + UsbSerialDriver(UsbDevice device); // constructor with device + static Map getSupportedDevices(); + static boolean probe(UsbDevice device); // optional + */ + + /** * Returns the raw {@link UsbDevice} backing this port. * diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java index d6b4dd7..fd94a18 100644 --- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java +++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java @@ -69,11 +69,7 @@ public class UsbSerialProber { * {@code null} if none available. */ public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { - final int vendorId = usbDevice.getVendorId(); - final int productId = usbDevice.getProductId(); - - final Class driverClass = - mProbeTable.findDriver(vendorId, productId); + final Class driverClass = mProbeTable.findDriver(usbDevice); if (driverClass != null) { final UsbSerialDriver driver; try { diff --git a/usbSerialForAndroid/src/test/java/android/util/Pair.java b/usbSerialForAndroid/src/test/java/android/util/Pair.java new file mode 100644 index 0000000..34c7597 --- /dev/null +++ b/usbSerialForAndroid/src/test/java/android/util/Pair.java @@ -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 { + 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 Pair create(A a, B b) { + return new Pair(a, b); + } +} diff --git a/usbSerialForAndroid/src/test/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriverTest.java b/usbSerialForAndroid/src/test/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriverTest.java index 0e7e9d1..092ee8a 100644 --- a/usbSerialForAndroid/src/test/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriverTest.java +++ b/usbSerialForAndroid/src/test/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriverTest.java @@ -53,6 +53,10 @@ public class CdcAcmSerialDriverTest { port.openInt(); assertEquals(readEndpoint, port.mReadEndpoint); assertEquals(writeEndpoint, port.mWriteEndpoint); + + ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); + Class probeDriver = probeTable.findDriver(usbDevice); + assertEquals(driver.getClass(), probeDriver); } @Test @@ -84,6 +88,10 @@ public class CdcAcmSerialDriverTest { port.openInt(); assertEquals(readEndpoint, port.mReadEndpoint); assertEquals(writeEndpoint, port.mWriteEndpoint); + + ProbeTable probeTable = UsbSerialProber.getDefaultProbeTable(); + Class probeDriver = probeTable.findDriver(usbDevice); + assertNull(probeDriver); } @Test