2019-11-02 12:03:11 +00:00
|
|
|
/* Copyright 2011-2013 Google Inc.
|
|
|
|
* Copyright 2013 mike wakerly <opensource@hoho.com>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
|
|
* USA.
|
|
|
|
*
|
|
|
|
* Project home page: https://github.com/mik3y/usb-serial-for-android
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.hoho.android.usbserial.driver;
|
|
|
|
|
|
|
|
import android.hardware.usb.UsbConstants;
|
|
|
|
import android.hardware.usb.UsbDevice;
|
|
|
|
import android.hardware.usb.UsbDeviceConnection;
|
|
|
|
import android.hardware.usb.UsbEndpoint;
|
|
|
|
import android.hardware.usb.UsbInterface;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2020-01-08 16:11:08 +00:00
|
|
|
import java.util.ArrayList;
|
2019-11-02 12:03:11 +00:00
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* USB CDC/ACM serial driver implementation.
|
|
|
|
*
|
|
|
|
* @author mike wakerly (opensource@hoho.com)
|
|
|
|
* @see <a
|
|
|
|
* href="http://www.usb.org/developers/devclass_docs/usbcdc11.pdf">Universal
|
|
|
|
* Serial Bus Class Definitions for Communication Devices, v1.1</a>
|
|
|
|
*/
|
|
|
|
public class CdcAcmSerialDriver implements UsbSerialDriver {
|
|
|
|
|
|
|
|
private final String TAG = CdcAcmSerialDriver.class.getSimpleName();
|
|
|
|
|
|
|
|
private final UsbDevice mDevice;
|
2020-01-08 16:11:08 +00:00
|
|
|
private final List<UsbSerialPort> mPorts;
|
2019-11-02 12:03:11 +00:00
|
|
|
|
|
|
|
public CdcAcmSerialDriver(UsbDevice device) {
|
|
|
|
mDevice = device;
|
2020-01-08 16:11:08 +00:00
|
|
|
mPorts = new ArrayList<>();
|
|
|
|
|
|
|
|
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++;
|
|
|
|
}
|
|
|
|
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));
|
|
|
|
}
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public UsbDevice getDevice() {
|
|
|
|
return mDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<UsbSerialPort> getPorts() {
|
2020-01-08 16:11:08 +00:00
|
|
|
return mPorts;
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class CdcAcmSerialPort extends CommonUsbSerialPort {
|
|
|
|
|
|
|
|
private UsbInterface mControlInterface;
|
|
|
|
private UsbInterface mDataInterface;
|
|
|
|
|
|
|
|
private UsbEndpoint mControlEndpoint;
|
|
|
|
|
|
|
|
private int mControlIndex;
|
|
|
|
|
|
|
|
private boolean mRts = false;
|
|
|
|
private boolean mDtr = false;
|
|
|
|
|
|
|
|
private static final int USB_RECIP_INTERFACE = 0x01;
|
|
|
|
private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE;
|
|
|
|
|
|
|
|
private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2
|
|
|
|
private static final int GET_LINE_CODING = 0x21;
|
|
|
|
private static final int SET_CONTROL_LINE_STATE = 0x22;
|
|
|
|
private static final int SEND_BREAK = 0x23;
|
|
|
|
|
|
|
|
public CdcAcmSerialPort(UsbDevice device, int portNumber) {
|
|
|
|
super(device, portNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public UsbSerialDriver getDriver() {
|
|
|
|
return CdcAcmSerialDriver.this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-12-09 21:42:51 +00:00
|
|
|
public void openInt(UsbDeviceConnection connection) throws IOException {
|
2020-01-08 16:11:08 +00:00
|
|
|
if (mPortNumber == -1) {
|
2019-12-09 21:42:51 +00:00
|
|
|
Log.d(TAG,"device might be castrated ACM device, trying single interface logic");
|
|
|
|
openSingleInterface();
|
|
|
|
} else {
|
|
|
|
Log.d(TAG,"trying default interface logic");
|
|
|
|
openInterface();
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void openSingleInterface() throws IOException {
|
2020-01-08 16:11:08 +00:00
|
|
|
// the following code is inspired by the cdc-acm driver in the linux kernel
|
2019-11-02 12:03:11 +00:00
|
|
|
|
|
|
|
mControlIndex = 0;
|
|
|
|
mControlInterface = mDevice.getInterface(0);
|
|
|
|
mDataInterface = mDevice.getInterface(0);
|
|
|
|
if (!mConnection.claimInterface(mControlInterface, true)) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("Could not claim shared control/data interface");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
2020-01-08 16:11:08 +00:00
|
|
|
for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) {
|
2019-11-02 12:03:11 +00:00
|
|
|
UsbEndpoint ep = mControlInterface.getEndpoint(i);
|
2020-01-08 16:11:08 +00:00
|
|
|
if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
|
2019-11-02 12:03:11 +00:00
|
|
|
mControlEndpoint = ep;
|
2020-01-08 16:11:08 +00:00
|
|
|
} else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
|
2019-11-02 12:03:11 +00:00
|
|
|
mReadEndpoint = ep;
|
2020-01-08 16:11:08 +00:00
|
|
|
} else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) {
|
2019-11-02 12:03:11 +00:00
|
|
|
mWriteEndpoint = ep;
|
|
|
|
}
|
|
|
|
}
|
2020-01-08 16:11:08 +00:00
|
|
|
if (mControlEndpoint == null) {
|
|
|
|
throw new IOException("No control endpoint");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void openInterface() throws IOException {
|
|
|
|
Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount());
|
|
|
|
|
2020-01-08 16:11:08 +00:00
|
|
|
int controlInterfaceCount = 0;
|
|
|
|
int dataInterfaceCount = 0;
|
2019-11-02 12:03:11 +00:00
|
|
|
mControlInterface = null;
|
|
|
|
mDataInterface = null;
|
|
|
|
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
|
|
|
|
UsbInterface usbInterface = mDevice.getInterface(i);
|
|
|
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) {
|
2020-01-08 16:11:08 +00:00
|
|
|
if(controlInterfaceCount == mPortNumber) {
|
|
|
|
mControlIndex = i;
|
|
|
|
mControlInterface = usbInterface;
|
|
|
|
}
|
|
|
|
controlInterfaceCount++;
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) {
|
2020-01-08 16:11:08 +00:00
|
|
|
if(dataInterfaceCount == mPortNumber) {
|
|
|
|
mDataInterface = usbInterface;
|
|
|
|
}
|
|
|
|
dataInterfaceCount++;
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(mControlInterface == null) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("No control interface");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
Log.d(TAG, "Control iface=" + mControlInterface);
|
|
|
|
|
|
|
|
if (!mConnection.claimInterface(mControlInterface, true)) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("Could not claim control interface");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mControlEndpoint = mControlInterface.getEndpoint(0);
|
|
|
|
if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("Invalid control endpoint");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(mDataInterface == null) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("No data interface");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
Log.d(TAG, "data iface=" + mDataInterface);
|
|
|
|
|
|
|
|
if (!mConnection.claimInterface(mDataInterface, true)) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("Could not claim data interface");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < mDataInterface.getEndpointCount(); i++) {
|
|
|
|
UsbEndpoint ep = mDataInterface.getEndpoint(i);
|
|
|
|
if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
|
|
|
|
mReadEndpoint = ep;
|
|
|
|
if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)
|
|
|
|
mWriteEndpoint = ep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException {
|
|
|
|
int len = mConnection.controlTransfer(
|
|
|
|
USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000);
|
|
|
|
if(len < 0) {
|
2019-11-02 12:08:03 +00:00
|
|
|
throw new IOException("controlTransfer failed");
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-11-09 21:48:00 +00:00
|
|
|
public void closeInt() {
|
2019-11-02 12:03:11 +00:00
|
|
|
try {
|
|
|
|
mConnection.releaseInterface(mControlInterface);
|
|
|
|
mConnection.releaseInterface(mDataInterface);
|
|
|
|
} catch(Exception ignored) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
|
2019-11-02 12:08:03 +00:00
|
|
|
if(baudRate <= 0) {
|
|
|
|
throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
|
|
|
|
}
|
|
|
|
if(dataBits < DATABITS_5 || dataBits > DATABITS_8) {
|
|
|
|
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
|
|
|
|
}
|
2019-11-02 12:03:11 +00:00
|
|
|
byte stopBitsByte;
|
|
|
|
switch (stopBits) {
|
|
|
|
case STOPBITS_1: stopBitsByte = 0; break;
|
|
|
|
case STOPBITS_1_5: stopBitsByte = 1; break;
|
|
|
|
case STOPBITS_2: stopBitsByte = 2; break;
|
2019-11-02 12:08:03 +00:00
|
|
|
default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
byte parityBitesByte;
|
|
|
|
switch (parity) {
|
|
|
|
case PARITY_NONE: parityBitesByte = 0; break;
|
|
|
|
case PARITY_ODD: parityBitesByte = 1; break;
|
|
|
|
case PARITY_EVEN: parityBitesByte = 2; break;
|
|
|
|
case PARITY_MARK: parityBitesByte = 3; break;
|
|
|
|
case PARITY_SPACE: parityBitesByte = 4; break;
|
2019-11-02 12:08:03 +00:00
|
|
|
default: throw new IllegalArgumentException("Invalid parity: " + parity);
|
2019-11-02 12:03:11 +00:00
|
|
|
}
|
|
|
|
byte[] msg = {
|
|
|
|
(byte) ( baudRate & 0xff),
|
|
|
|
(byte) ((baudRate >> 8 ) & 0xff),
|
|
|
|
(byte) ((baudRate >> 16) & 0xff),
|
|
|
|
(byte) ((baudRate >> 24) & 0xff),
|
|
|
|
stopBitsByte,
|
|
|
|
parityBitesByte,
|
|
|
|
(byte) dataBits};
|
|
|
|
sendAcmControlMessage(SET_LINE_CODING, 0, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getCD() throws IOException {
|
|
|
|
return false; // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getCTS() throws IOException {
|
|
|
|
return false; // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getDSR() throws IOException {
|
|
|
|
return false; // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getDTR() throws IOException {
|
|
|
|
return mDtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setDTR(boolean value) throws IOException {
|
|
|
|
mDtr = value;
|
|
|
|
setDtrRts();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getRI() throws IOException {
|
|
|
|
return false; // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getRTS() throws IOException {
|
|
|
|
return mRts;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setRTS(boolean value) throws IOException {
|
|
|
|
mRts = value;
|
|
|
|
setDtrRts();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setDtrRts() throws IOException {
|
|
|
|
int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0);
|
|
|
|
sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Map<Integer, int[]> getSupportedDevices() {
|
|
|
|
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
|
|
|
|
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,
|
|
|
|
});
|
|
|
|
return supportedDevices;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|