Update the usb serial stack and fix several serial issues.

This commit is contained in:
dgis 2021-08-31 12:05:49 +02:00
parent cfbb0c16bc
commit e7a7503151
27 changed files with 3026 additions and 2746 deletions

View file

@ -58,9 +58,15 @@ CHANGES
Version 2.3 (2021-02-xx) Version 2.3 (2021-02-xx)
- Add the serial port support (via USB OTG). - Add the serial port support (via USB OTG).
TODO: When stop the app, Serial exception seems to delay the save of the calc state!!!! FIX: When stop the app, Serial exception seems to delay the save of the calc state!!!!
TODO: Inform if the connection is not possible. FIX: Inform if the connection is not possible.
TODO: What's happen if I hot unplug? BUG: From Windows to Android with HP49G QINHENG CH340 -> all character at once bug
No issue with Prolific PL2303GT3
Not reproducible!
FIX: No 'No driver' on real device
BUG: When openIO on real device, 1st OPENIO failed, 2nd OPENIO succeeded
BUG: ID change -> replace id by vendor:device ids?
TEST: With real HP48SX
TODO: Check the self test about UART (http://regis.cosnier.free.fr/private/private.php?journal=HP48&index=-4637&nomenu) TODO: Check the self test about UART (http://regis.cosnier.free.fr/private/private.php?journal=HP48&index=-4637&nomenu)
- Allows pressing a calculator button with the right button of the mouse and prevents its release to allow the On+A+F key combination (with Android version >= 5.0). - Allows pressing a calculator button with the right button of the mouse and prevents its release to allow the On+A+F key combination (with Android version >= 5.0).
- Update the embedded help file "Emu48.html" to the latest version. - Update the embedded help file "Emu48.html" to the latest version.

View file

@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.4.1)
#add_compile_options(-DDEBUG_ANDROID_PAINT) #add_compile_options(-DDEBUG_ANDROID_PAINT)
#add_compile_options(-DDEBUG_ANDROID_THREAD) #add_compile_options(-DDEBUG_ANDROID_THREAD)
#add_compile_options(-DDEBUG_ANDROID_FILE) #add_compile_options(-DDEBUG_ANDROID_FILE)
#add_compile_options(-DDEBUG_ANDROID_SERIAL) add_compile_options(-DDEBUG_ANDROID_SERIAL)
#add_compile_options(-DNEW_WIN32_SOUND_ENGINE) #add_compile_options(-DNEW_WIN32_SOUND_ENGINE)

View file

@ -375,6 +375,20 @@ int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize) {
return result; return result;
} }
int serialPortPurgeComm(int serialPortId, int dwFlags) {
int result = 0;
JNIEnv *jniEnv = getJNIEnvironment();
if(jniEnv) {
jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity);
if(mainActivityClass) {
jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "serialPortPurgeComm", "(II)I");
result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, serialPortId);
(*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass);
}
}
return result;
}
int serialPortSetBreak(int serialPortId) { int serialPortSetBreak(int serialPortId) {
int result = 0; int result = 0;
JNIEnv *jniEnv = getJNIEnvironment(); JNIEnv *jniEnv = getJNIEnvironment();
@ -1283,9 +1297,21 @@ JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setConfiguration(J
SwitchToState(nOldState); SwitchToState(nOldState);
} }
} else if(_tcscmp(_T("settings_serial_ports_wire"), configKey) == 0) { } else if(_tcscmp(_T("settings_serial_ports_wire"), configKey) == 0) {
_tcsncpy(szSerialWire, _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue, sizeof(szSerialWire)); const char * newSerialWire = _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue;
BOOL serialWireChanged = _tcscmp(szSerialWire, newSerialWire) != 0;
_tcsncpy(szSerialWire, newSerialWire, sizeof(szSerialWire));
if(bCommInit && serialWireChanged) {
// Not the right thread, but it seems to work.
bCommInit = CommOpen(szSerialWire, szSerialIr);
}
} else if(_tcscmp(_T("settings_serial_ports_ir"), configKey) == 0) { } else if(_tcscmp(_T("settings_serial_ports_ir"), configKey) == 0) {
_tcsncpy(szSerialIr, _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue, sizeof(szSerialIr)); const char * newSerialIr = _tcscmp(_T("0,0"), configStringValue) == 0 ? NO_SERIAL : configStringValue;
BOOL serialIrChanged = _tcscmp(szSerialIr, newSerialIr) != 0;
_tcsncpy(szSerialIr, newSerialIr, sizeof(szSerialIr));
if(bCommInit && serialIrChanged) {
// Not the right thread, but it seems to work.
bCommInit = CommOpen(szSerialWire, szSerialIr);
}
} }
if(configKey) if(configKey)

View file

@ -330,14 +330,13 @@ BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,LPDWO
*lpNumberOfBytesWritten = (DWORD) writenByteCount; *lpNumberOfBytesWritten = (DWORD) writenByteCount;
return writenByteCount >= 0; return writenByteCount >= 0;
} else if(hFile->handleType == HANDLE_TYPE_COM) { } else if(hFile->handleType == HANDLE_TYPE_COM) {
Sleep(4); // Seems to be needed else the kermit packet does not fully reach the genuine calculator.
ssize_t writenByteCount = writeSerialPort(hFile->commId, lpBuffer, nNumberOfBytesToWrite); ssize_t writenByteCount = writeSerialPort(hFile->commId, lpBuffer, nNumberOfBytesToWrite);
#if defined DEBUG_ANDROID_SERIAL #if defined DEBUG_ANDROID_SERIAL
char * hexAsciiDump = dumpToHexAscii(lpBuffer, writenByteCount, 8); char * hexAsciiDump = dumpToHexAscii(lpBuffer, writenByteCount, 8);
SERIAL_LOGD("WriteFile(hFile: %p, lpBuffer: 0x%08x, nNumberOfBytesToWrite: %d) -> %d bytes\n%s", hFile, lpBuffer, nNumberOfBytesToWrite, writenByteCount, hexAsciiDump); SERIAL_LOGD("WriteFile(hFile: %p, lpBuffer: 0x%08x, nNumberOfBytesToWrite: %d) -> %d bytes\n%s", hFile, lpBuffer, nNumberOfBytesToWrite, writenByteCount, hexAsciiDump);
free(hexAsciiDump); free(hexAsciiDump);
#endif #endif
commEvent(hFile->commId, EV_TXEMPTY); // Not sure about that (not the same thread)! //Sleep(4); // Seems to be needed else the kermit packet does not fully reach the genuine calculator.
if(lpNumberOfBytesWritten) if(lpNumberOfBytesWritten)
*lpNumberOfBytesWritten = (DWORD) writenByteCount; *lpNumberOfBytesWritten = (DWORD) writenByteCount;
return writenByteCount >= 0; return writenByteCount >= 0;
@ -748,7 +747,7 @@ DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)
BOOL WINAPI CloseHandle(HANDLE hObject) { BOOL WINAPI CloseHandle(HANDLE hObject) {
FILE_LOGD("CloseHandle(hObject: %p)", hObject); FILE_LOGD("CloseHandle(hObject: %p)", hObject);
if(hObject) if(!hObject)
return FALSE; return FALSE;
//https://msdn.microsoft.com/en-us/9b84891d-62ca-4ddc-97b7-c4c79482abd9 //https://msdn.microsoft.com/en-us/9b84891d-62ca-4ddc-97b7-c4c79482abd9
// Can be a thread/event/file handle! // Can be a thread/event/file handle!
@ -3107,6 +3106,7 @@ BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) {
hFile->commEventMask = 0; hFile->commEventMask = 0;
} }
pthread_mutex_unlock(&commsLock); pthread_mutex_unlock(&commsLock);
SERIAL_LOGD("WaitCommEvent(hFile: %p, lpEvtMask: %p) -> *lpEvtMask=%d", hFile, lpEvtMask, *lpEvtMask);
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -3149,8 +3149,9 @@ BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) {
return FALSE; return FALSE;
} }
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) { BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) {
SERIAL_LOGD("SetCommState(hFile: %p, dwFlags: 0x%08X) TODO", hFile, dwFlags); SERIAL_LOGD("PurgeComm(hFile: %p, dwFlags: 0x%08X) TODO", hFile, dwFlags);
//TODO if(hFile && hFile->handleType == HANDLE_TYPE_COM)
return serialPortPurgeComm(hFile->commId, dwFlags);
return FALSE; return FALSE;
} }
BOOL SetCommBreak(HANDLE hFile) { BOOL SetCommBreak(HANDLE hFile) {

View file

@ -1277,6 +1277,7 @@ extern int closeSerialPort(int serialPortId);
extern int setSerialPortParameters(int serialPortId, int baudRate); extern int setSerialPortParameters(int serialPortId, int baudRate);
extern int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead); extern int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead);
extern int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize); extern int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize);
extern int serialPortPurgeComm(int serialPortId, int dwFlags);
extern int serialPortSetBreak(int serialPortId); extern int serialPortSetBreak(int serialPortId);
extern int serialPortClearBreak(int serialPortId); extern int serialPortClearBreak(int serialPortId);
extern int showAlert(const TCHAR * messageText, int flags); extern int showAlert(const TCHAR * messageText, int flags);

View file

@ -22,6 +22,7 @@ import android.graphics.Paint;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.GestureDetector; import android.view.GestureDetector;
@ -615,7 +616,7 @@ public class PanAndScaleView extends View {
boolean osdAllowed = false; boolean osdAllowed = false;
Handler osdTimerHandler = new Handler(); Handler osdTimerHandler = new Handler(Looper.getMainLooper());
Runnable osdTimerRunnable = () -> { Runnable osdTimerRunnable = () -> {
// OSD should stop now! // OSD should stop now!
osdAllowed = false; osdAllowed = false;

View file

@ -9,9 +9,11 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import org.emulator.calculator.usbserial.CustomProber; import org.emulator.calculator.usbserial.CustomProber;
import org.emulator.calculator.usbserial.driver.SerialTimeoutException;
import org.emulator.calculator.usbserial.driver.UsbSerialDriver; import org.emulator.calculator.usbserial.driver.UsbSerialDriver;
import org.emulator.calculator.usbserial.driver.UsbSerialPort; import org.emulator.calculator.usbserial.driver.UsbSerialPort;
import org.emulator.calculator.usbserial.driver.UsbSerialProber; import org.emulator.calculator.usbserial.driver.UsbSerialProber;
@ -26,13 +28,19 @@ import java.util.regex.Pattern;
public class Serial { public class Serial {
private static final String TAG = "Serial"; private static final String TAG = "Serial";
private final boolean debug = false; private final boolean debug = true;
private final Context context; private final Context context;
private final int serialPortId; private final int serialPortId;
private static final String INTENT_ACTION_GRANT_USB = "EMU48.GRANT_USB"; private static final String INTENT_ACTION_GRANT_USB = "EMU48.GRANT_USB";
private static final int WRITE_WAIT_MILLIS = 2000; private static final int WRITE_WAIT_MILLIS = 2000;
private static final int PURGE_TXABORT = 0x0001;
private static final int PURGE_RXABORT = 0x0002;
private static final int PURGE_TXCLEAR = 0x0004;
private static final int PURGE_RXCLEAR = 0x0008;
private final Handler mainLooper; private final Handler mainLooper;
private SerialInputOutputManager usbIoManager; private SerialInputOutputManager usbIoManager;
private UsbSerialPort usbSerialPort; private UsbSerialPort usbSerialPort;
@ -41,7 +49,7 @@ public class Serial {
private boolean connected = false; private boolean connected = false;
private String connectionStatus = ""; private String connectionStatus = "";
private final ArrayDeque<Byte> reveivedByteQueue = new ArrayDeque<>(); private final ArrayDeque<Byte> readByteQueue = new ArrayDeque<>();
public Serial(Context context, int serialPortId) { public Serial(Context context, int serialPortId) {
@ -62,7 +70,7 @@ public class Serial {
mainLooper = new Handler(Looper.getMainLooper()); mainLooper = new Handler(Looper.getMainLooper());
} }
public boolean connect(String serialPort) { public synchronized boolean connect(String serialPort) {
if(debug) Log.d(TAG, "connect( " + serialPort + ")"); if(debug) Log.d(TAG, "connect( " + serialPort + ")");
Pattern patternSerialPort = Pattern.compile("\\\\.\\\\(\\d+),(\\d+)"); Pattern patternSerialPort = Pattern.compile("\\\\.\\\\(\\d+),(\\d+)");
@ -87,11 +95,11 @@ public class Serial {
return false; return false;
} }
public String getConnectionStatus() { public synchronized String getConnectionStatus() {
return connectionStatus; return connectionStatus;
} }
public boolean connect(int deviceId, int portNum) { public synchronized boolean connect(int deviceId, int portNum) {
if(debug) Log.d(TAG, "connect(deviceId: " + deviceId + ", portNum" + portNum + ")"); if(debug) Log.d(TAG, "connect(deviceId: " + deviceId + ", portNum" + portNum + ")");
UsbDevice device = null; UsbDevice device = null;
@ -154,10 +162,10 @@ public class Serial {
@Override @Override
public void onNewData(byte[] data) { public void onNewData(byte[] data) {
if(debug) Log.d(TAG, "onNewData: " + Utils.bytesToHex(data)); if(debug) Log.d(TAG, "onNewData: " + Utils.bytesToHex(data));
boolean wasEmpty = reveivedByteQueue.isEmpty(); boolean wasEmpty = readByteQueue.isEmpty();
synchronized (reveivedByteQueue) { synchronized (readByteQueue) {
for (byte datum : data) for (byte datum : data)
reveivedByteQueue.add(datum); readByteQueue.add(datum);
} }
if (wasEmpty) if (wasEmpty)
onReceivedByteQueueNotEmpty(); onReceivedByteQueueNotEmpty();
@ -172,14 +180,15 @@ public class Serial {
usbIo.setDaemon(true); usbIo.setDaemon(true);
usbIo.start(); usbIo.start();
connected = true; connected = true;
connectionStatus = "";
if(debug) Log.d(TAG, "connected!");
purgeComm(PURGE_TXCLEAR | PURGE_RXCLEAR);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
connectionStatus = "serial_connection_failed_open_failed"; connectionStatus = "serial_connection_failed_open_failed";
if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage()); if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage());
disconnect(); disconnect();
} }
if(debug) Log.d(TAG, "connected!");
connectionStatus = "";
return false; return false;
} }
@ -194,15 +203,16 @@ public class Serial {
Log.d(TAG, "onRunError: " + e.getMessage()); Log.d(TAG, "onRunError: " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }
disconnect(); //disconnect();
}); });
//disconnect();
} }
public boolean setParameters(int baudRate) { public synchronized boolean setParameters(int baudRate) {
return setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); return setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
} }
public boolean setParameters(int baudRate, int dataBits, int stopBits, int parity) { public synchronized boolean setParameters(int baudRate, int dataBits, int stopBits, int parity) {
if(debug) Log.d(TAG, "setParameters(baudRate: " + baudRate + ", dataBits: " + dataBits + ", stopBits: " + stopBits + ", parity: " + parity +")"); if(debug) Log.d(TAG, "setParameters(baudRate: " + baudRate + ", dataBits: " + dataBits + ", stopBits: " + stopBits + ", parity: " + parity +")");
try { try {
@ -214,38 +224,84 @@ public class Serial {
return false; return false;
} }
public byte[] receive(int nNumberOfBytesToRead) { public synchronized byte[] read(int nNumberOfBytesToRead) {
if(debug) Log.d(TAG, "read(nNumberOfBytesToRead: " + nNumberOfBytesToRead + ")");
if(!connected)
return new byte[0];
byte[] result; byte[] result;
synchronized (reveivedByteQueue) { synchronized (readByteQueue) {
int nNumberOfReadBytes = Math.min(nNumberOfBytesToRead, reveivedByteQueue.size()); int nNumberOfReadBytes = Math.min(nNumberOfBytesToRead, readByteQueue.size());
result = new byte[nNumberOfReadBytes]; result = new byte[nNumberOfReadBytes];
for (int i = 0; i < nNumberOfReadBytes; i++) { for (int i = 0; i < nNumberOfReadBytes; i++) {
Byte byteRead = reveivedByteQueue.poll(); Byte byteRead = readByteQueue.poll();
if(byteRead != null) if(byteRead != null)
result[i] = byteRead; result[i] = byteRead;
} }
} }
if(result == null) { if(readByteQueue.size() > 0)
result = new byte[0];
} else if(reveivedByteQueue.size() > 0) {
mainLooper.post(() -> NativeLib.commEvent(serialPortId, NativeLib.EV_RXCHAR)); mainLooper.post(() -> NativeLib.commEvent(serialPortId, NativeLib.EV_RXCHAR));
}
return result; return result;
} }
public int send(byte[] data) { long maxWritePeriod = 4; //ms
long lastTime = 0;
public synchronized int write(byte[] data) {
if(!connected) if(!connected)
return 0; return 0;
long currentTime = SystemClock.elapsedRealtime();
long writePeriod = currentTime - lastTime;
if(debug) Log.d(TAG, "write(data: [" + data.length + "]: " + Utils.bytesToHex(data) + ") writePeriod: " + writePeriod + "ms");
if(lastTime > 0 && writePeriod < maxWritePeriod) {
// Wait 1ms - (currentTime - lastTime)ms
android.os.SystemClock.sleep(maxWritePeriod - writePeriod);
}
lastTime = SystemClock.elapsedRealtime();
try { try {
return usbSerialPort.write(data, WRITE_WAIT_MILLIS); usbSerialPort.write(data, WRITE_WAIT_MILLIS);
if(debug) Log.d(TAG, "write() return: " + data.length);
// No exception, so, all the data has been sent!
// Consider that the write buffer is empty?
NativeLib.commEvent(serialPortId, NativeLib.EV_TXEMPTY);
return data.length;
} catch (SerialTimeoutException e) {
if(debug) Log.d(TAG, "write() Exception: " + e.toString());
return data.length - e.bytesTransferred;
} catch (Exception e) { } catch (Exception e) {
if(debug) Log.d(TAG, "write() Exception: " + e.toString());
onReceivedError(e); onReceivedError(e);
} }
return 0; return 0;
} }
public int setBreak() {
public synchronized int purgeComm(int dwFlags) {
if(!connected)
return 0;
if(debug) Log.d(TAG, "purgeComm(" + dwFlags + ")");
try {
boolean purgeWriteBuffers = (dwFlags & PURGE_TXABORT) == PURGE_TXABORT || (dwFlags & PURGE_TXCLEAR) == PURGE_TXCLEAR;
boolean purgeReadBuffers = (dwFlags & PURGE_RXABORT) == PURGE_RXABORT || (dwFlags & PURGE_RXCLEAR) == PURGE_RXCLEAR;
if(purgeReadBuffers)
readByteQueue.clear();
usbSerialPort.purgeHwBuffers(purgeWriteBuffers, purgeReadBuffers);
return 1;
} catch (Exception e) {
// Exception mean return 0
}
return 0;
}
public synchronized int setBreak() {
if(!connected) if(!connected)
return 0; return 0;
@ -258,7 +314,7 @@ public class Serial {
return 0; return 0;
} }
public int clearBreak() { public synchronized int clearBreak() {
if(!connected) if(!connected)
return 0; return 0;
@ -271,7 +327,7 @@ public class Serial {
return 0; return 0;
} }
public void disconnect() { public synchronized void disconnect() {
if(debug) Log.d(TAG, "disconnect()"); if(debug) Log.d(TAG, "disconnect()");
connected = false; connected = false;
@ -280,7 +336,9 @@ public class Serial {
usbIoManager = null; usbIoManager = null;
try { try {
usbSerialPort.close(); usbSerialPort.close();
} catch (IOException ignored) {} } catch (IOException ignored) {
}
usbSerialPort = null; usbSerialPort = null;
} }
} }

View file

@ -216,12 +216,15 @@ public class Utils {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) { public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 4];
for (int j = 0; j < bytes.length; j++) { for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF; int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 3] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; hexChars[j * 3 + 1] = HEX_ARRAY[v & 0x0F];
hexChars[j * 3 + 2] = ' ';
} }
for (int j = 0; j < bytes.length; j++)
hexChars[bytes.length * 3 + j] = Character.isISOControl(bytes[j]) ? '.' : (char) bytes[j];
return new String(hexChars); return new String(hexChars);
} }
} }

View file

@ -62,7 +62,8 @@ public class DevicesFragment extends ListFragment {
else else
text1.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_title")), deviceName, item.port)); text1.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_title")), deviceName, item.port));
} }
text2.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_description")), item.device.getVendorId(), item.device.getProductId())); if(item.device != null)
text2.setText(String.format(Locale.US, getString(Utils.resId(DevicesFragment.this, "string", "serial_ports_device_item_description")), item.device.getVendorId(), item.device.getProductId()));
return view; return view;
} }
}; };
@ -90,6 +91,7 @@ public class DevicesFragment extends ListFragment {
UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber(); UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber();
UsbSerialProber usbCustomProber = CustomProber.getCustomProber(); UsbSerialProber usbCustomProber = CustomProber.getCustomProber();
listItems.clear(); listItems.clear();
listItems.add(new ListItem(null, 0, null));
for(UsbDevice device : usbManager.getDeviceList().values()) { for(UsbDevice device : usbManager.getDeviceList().values()) {
UsbSerialDriver driver = usbDefaultProber.probeDevice(device); UsbSerialDriver driver = usbDefaultProber.probeDevice(device);
if(driver == null) { if(driver == null) {
@ -98,8 +100,6 @@ public class DevicesFragment extends ListFragment {
if(driver != null) { if(driver != null) {
for(int port = 0; port < driver.getPorts().size(); port++) for(int port = 0; port < driver.getPorts().size(); port++)
listItems.add(new ListItem(device, port, driver)); listItems.add(new ListItem(device, port, driver));
} else {
listItems.add(new ListItem(device, 0, null));
} }
} }
listAdapter.notifyDataSetChanged(); listAdapter.notifyDataSetChanged();

View file

@ -204,7 +204,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -287,7 +287,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_ARDUINO, supportedDevices.put(UsbId.VENDOR_ARDUINO,
new int[] { new int[] {
UsbId.ARDUINO_UNO, UsbId.ARDUINO_UNO,
@ -317,6 +317,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver {
new int[] { new int[] {
UsbId.ARM_MBED, UsbId.ARM_MBED,
}); });
supportedDevices.put(UsbId.VENDOR_ST,
new int[] {
UsbId.ST_CDC,
});
return supportedDevices; return supportedDevices;
} }

View file

@ -1,374 +1,374 @@
/* Copyright 2014 Andreas Butti /* Copyright 2014 Andreas Butti
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class Ch34xSerialDriver implements UsbSerialDriver { public class Ch34xSerialDriver implements UsbSerialDriver {
private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
private final UsbDevice mDevice; private final UsbDevice mDevice;
private final UsbSerialPort mPort; private final UsbSerialPort mPort;
private static final int LCR_ENABLE_RX = 0x80; private static final int LCR_ENABLE_RX = 0x80;
private static final int LCR_ENABLE_TX = 0x40; private static final int LCR_ENABLE_TX = 0x40;
private static final int LCR_MARK_SPACE = 0x20; private static final int LCR_MARK_SPACE = 0x20;
private static final int LCR_PAR_EVEN = 0x10; private static final int LCR_PAR_EVEN = 0x10;
private static final int LCR_ENABLE_PAR = 0x08; private static final int LCR_ENABLE_PAR = 0x08;
private static final int LCR_STOP_BITS_2 = 0x04; private static final int LCR_STOP_BITS_2 = 0x04;
private static final int LCR_CS8 = 0x03; private static final int LCR_CS8 = 0x03;
private static final int LCR_CS7 = 0x02; private static final int LCR_CS7 = 0x02;
private static final int LCR_CS6 = 0x01; private static final int LCR_CS6 = 0x01;
private static final int LCR_CS5 = 0x00; private static final int LCR_CS5 = 0x00;
private static final int GCL_CTS = 0x01; private static final int GCL_CTS = 0x01;
private static final int GCL_DSR = 0x02; private static final int GCL_DSR = 0x02;
private static final int GCL_RI = 0x04; private static final int GCL_RI = 0x04;
private static final int GCL_CD = 0x08; private static final int GCL_CD = 0x08;
private static final int SCL_DTR = 0x20; private static final int SCL_DTR = 0x20;
private static final int SCL_RTS = 0x40; private static final int SCL_RTS = 0x40;
public Ch34xSerialDriver(UsbDevice device) { public Ch34xSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPort = new Ch340SerialPort(mDevice, 0); mPort = new Ch340SerialPort(mDevice, 0);
} }
@Override @Override
public UsbDevice getDevice() { public UsbDevice getDevice() {
return mDevice; return mDevice;
} }
@Override @Override
public List<UsbSerialPort> getPorts() { public List<UsbSerialPort> getPorts() {
return Collections.singletonList(mPort); return Collections.singletonList(mPort);
} }
public class Ch340SerialPort extends CommonUsbSerialPort { public class Ch340SerialPort extends CommonUsbSerialPort {
private static final int USB_TIMEOUT_MILLIS = 5000; private static final int USB_TIMEOUT_MILLIS = 5000;
private final int DEFAULT_BAUD_RATE = 9600; private final int DEFAULT_BAUD_RATE = 9600;
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
public Ch340SerialPort(UsbDevice device, int portNumber) { public Ch340SerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
} }
@Override @Override
public UsbSerialDriver getDriver() { public UsbSerialDriver getDriver() {
return Ch34xSerialDriver.this; return Ch34xSerialDriver.this;
} }
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt(UsbDeviceConnection connection) throws IOException {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) { for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
UsbInterface usbIface = mDevice.getInterface(i); UsbInterface usbIface = mDevice.getInterface(i);
if (!mConnection.claimInterface(usbIface, true)) { if (!mConnection.claimInterface(usbIface, true)) {
throw new IOException("Could not claim data interface"); throw new IOException("Could not claim data interface");
} }
} }
UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
for (int i = 0; i < dataIface.getEndpointCount(); i++) { for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i); UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) { if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep; mReadEndpoint = ep;
} else { } else {
mWriteEndpoint = ep; mWriteEndpoint = ep;
} }
} }
} }
initialize(); initialize();
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
} }
@Override @Override
protected void closeInt() { protected void closeInt() {
try { try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) for (int i = 0; i < mDevice.getInterfaceCount(); i++)
mConnection.releaseInterface(mDevice.getInterface(i)); mConnection.releaseInterface(mDevice.getInterface(i));
} catch(Exception ignored) {} } catch(Exception ignored) {}
} }
private int controlOut(int request, int value, int index) { private int controlOut(int request, int value, int index) {
final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
value, index, null, 0, USB_TIMEOUT_MILLIS); value, index, null, 0, USB_TIMEOUT_MILLIS);
} }
private int controlIn(int request, int value, int index, byte[] buffer) { private int controlIn(int request, int value, int index, byte[] buffer) {
final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request,
value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
} }
private void checkState(String msg, int request, int value, int[] expected) throws IOException { private void checkState(String msg, int request, int value, int[] expected) throws IOException {
byte[] buffer = new byte[expected.length]; byte[] buffer = new byte[expected.length];
int ret = controlIn(request, value, 0, buffer); int ret = controlIn(request, value, 0, buffer);
if (ret < 0) { if (ret < 0) {
throw new IOException("Failed send cmd [" + msg + "]"); throw new IOException("Failed send cmd [" + msg + "]");
} }
if (ret != expected.length) { if (ret != expected.length) {
throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
} }
for (int i = 0; i < expected.length; i++) { for (int i = 0; i < expected.length; i++) {
if (expected[i] == -1) { if (expected[i] == -1) {
continue; continue;
} }
int current = buffer[i] & 0xff; int current = buffer[i] & 0xff;
if (expected[i] != current) { if (expected[i] != current) {
throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
} }
} }
} }
private void setControlLines() throws IOException { private void setControlLines() throws IOException {
if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) {
throw new IOException("Failed to set control lines"); throw new IOException("Failed to set control lines");
} }
} }
private byte getStatus() throws IOException { private byte getStatus() throws IOException {
byte[] buffer = new byte[2]; byte[] buffer = new byte[2];
int ret = controlIn(0x95, 0x0706, 0, buffer); int ret = controlIn(0x95, 0x0706, 0, buffer);
if (ret < 0) if (ret < 0)
throw new IOException("Error getting control lines"); throw new IOException("Error getting control lines");
return buffer[0]; return buffer[0];
} }
private void initialize() throws IOException { private void initialize() throws IOException {
checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
if (controlOut(0xa1, 0, 0) < 0) { if (controlOut(0xa1, 0, 0) < 0) {
throw new IOException("Init failed: #2"); throw new IOException("Init failed: #2");
} }
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
throw new IOException("Init failed: #5"); throw new IOException("Init failed: #5");
} }
checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/});
if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
throw new IOException("Init failed: #7"); throw new IOException("Init failed: #7");
} }
setBaudRate(DEFAULT_BAUD_RATE); setBaudRate(DEFAULT_BAUD_RATE);
setControlLines(); setControlLines();
checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/});
} }
private void setBaudRate(int baudRate) throws IOException { private void setBaudRate(int baudRate) throws IOException {
final long CH341_BAUDBASE_FACTOR = 1532620800; final long CH341_BAUDBASE_FACTOR = 1532620800;
final int CH341_BAUDBASE_DIVMAX = 3; final int CH341_BAUDBASE_DIVMAX = 3;
long factor = CH341_BAUDBASE_FACTOR / baudRate; long factor = CH341_BAUDBASE_FACTOR / baudRate;
int divisor = CH341_BAUDBASE_DIVMAX; int divisor = CH341_BAUDBASE_DIVMAX;
while ((factor > 0xfff0) && divisor > 0) { while ((factor > 0xfff0) && divisor > 0) {
factor >>= 3; factor >>= 3;
divisor--; divisor--;
} }
if (factor > 0xfff0) { if (factor > 0xfff0) {
throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate);
} }
factor = 0x10000 - factor; factor = 0x10000 - factor;
divisor |= 0x0080; // else ch341a waits until buffer full divisor |= 0x0080; // else ch341a waits until buffer full
int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor)); int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor));
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate: #1)"); throw new IOException("Error setting baud rate: #1)");
} }
ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff)); ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff));
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate: #2"); throw new IOException("Error setting baud rate: #2");
} }
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
setBaudRate(baudRate); setBaudRate(baudRate);
int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
switch (dataBits) { switch (dataBits) {
case DATABITS_5: case DATABITS_5:
lcr |= LCR_CS5; lcr |= LCR_CS5;
break; break;
case DATABITS_6: case DATABITS_6:
lcr |= LCR_CS6; lcr |= LCR_CS6;
break; break;
case DATABITS_7: case DATABITS_7:
lcr |= LCR_CS7; lcr |= LCR_CS7;
break; break;
case DATABITS_8: case DATABITS_8:
lcr |= LCR_CS8; lcr |= LCR_CS8;
break; break;
default: default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits); throw new IllegalArgumentException("Invalid data bits: " + dataBits);
} }
switch (parity) { switch (parity) {
case PARITY_NONE: case PARITY_NONE:
break; break;
case PARITY_ODD: case PARITY_ODD:
lcr |= LCR_ENABLE_PAR; lcr |= LCR_ENABLE_PAR;
break; break;
case PARITY_EVEN: case PARITY_EVEN:
lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN;
break; break;
case PARITY_MARK: case PARITY_MARK:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE;
break; break;
case PARITY_SPACE: case PARITY_SPACE:
lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
break; break;
default: default:
throw new IllegalArgumentException("Invalid parity: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: case STOPBITS_1:
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2: case STOPBITS_2:
lcr |= LCR_STOP_BITS_2; lcr |= LCR_STOP_BITS_2;
break; break;
default: default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
int ret = controlOut(0x9a, 0x2518, lcr); int ret = controlOut(0x9a, 0x2518, lcr);
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting control byte"); throw new IOException("Error setting control byte");
} }
} }
@Override @Override
public boolean getCD() throws IOException { public boolean getCD() throws IOException {
return (getStatus() & GCL_CD) == 0; return (getStatus() & GCL_CD) == 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getCTS() throws IOException {
return (getStatus() & GCL_CTS) == 0; return (getStatus() & GCL_CTS) == 0;
} }
@Override @Override
public boolean getDSR() throws IOException { public boolean getDSR() throws IOException {
return (getStatus() & GCL_DSR) == 0; return (getStatus() & GCL_DSR) == 0;
} }
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return dtr; return dtr;
} }
@Override @Override
public void setDTR(boolean value) throws IOException { public void setDTR(boolean value) throws IOException {
dtr = value; dtr = value;
setControlLines(); setControlLines();
} }
@Override @Override
public boolean getRI() throws IOException { public boolean getRI() throws IOException {
return (getStatus() & GCL_RI) == 0; return (getStatus() & GCL_RI) == 0;
} }
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return rts; return rts;
} }
@Override @Override
public void setRTS(boolean value) throws IOException { public void setRTS(boolean value) throws IOException {
rts = value; rts = value;
setControlLines(); setControlLines();
} }
@Override @Override
public EnumSet<ControlLine> getControlLines() throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus(); int status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS); if(rts) set.add(ControlLine.RTS);
if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); if((status & GCL_CTS) == 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR); if(dtr) set.add(ControlLine.DTR);
if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); if((status & GCL_DSR) == 0) set.add(ControlLine.DSR);
if((status & GCL_CD) == 0) set.add(ControlLine.CD); if((status & GCL_CD) == 0) set.add(ControlLine.CD);
if((status & GCL_RI) == 0) set.add(ControlLine.RI); if((status & GCL_RI) == 0) set.add(ControlLine.RI);
return set; return set;
} }
@Override @Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class); return EnumSet.allOf(ControlLine.class);
} }
@Override @Override
public void setBreak(boolean value) throws IOException { public void setBreak(boolean value) throws IOException {
byte[] req = new byte[2]; byte[] req = new byte[2];
if(controlIn(0x95, 0x1805, 0, req) < 0) { if(controlIn(0x95, 0x1805, 0, req) < 0) {
throw new IOException("Error getting BREAK condition"); throw new IOException("Error getting BREAK condition");
} }
if(value) { if(value) {
req[0] &= ~1; req[0] &= ~1;
req[1] &= ~0x40; req[1] &= ~0x40;
} else { } else {
req[0] |= 1; req[0] |= 1;
req[1] |= 0x40; req[1] |= 0x40;
} }
int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); int val = (req[1] & 0xff) << 8 | (req[0] & 0xff);
if(controlOut(0x9a, 0x1805, val) < 0) { if(controlOut(0x9a, 0x1805, val) < 0) {
throw new IOException("Error setting BREAK condition"); throw new IOException("Error setting BREAK condition");
} }
} }
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
UsbId.QINHENG_CH340, UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A, UsbId.QINHENG_CH341A,
}); });
return supportedDevices; return supportedDevices;
} }
} }

View file

@ -1,270 +1,301 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.util.Log; import android.util.Log;
import java.io.IOException; import org.emulator.calculator.usbserial.util.MonotonicClock;
import java.nio.ByteBuffer;
import java.util.EnumSet; import java.io.IOException;
import java.nio.ByteBuffer;
/** import java.util.EnumSet;
* A base class shared by several driver implementations.
* /**
* @author mike wakerly (opensource@hoho.com) * A base class shared by several driver implementations.
*/ *
public abstract class CommonUsbSerialPort implements UsbSerialPort { * @author mike wakerly (opensource@hoho.com)
*/
private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); public abstract class CommonUsbSerialPort implements UsbSerialPort {
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit private static final String TAG = CommonUsbSerialPort.class.getSimpleName();
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
protected final UsbDevice mDevice; private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit
protected final int mPortNumber;
protected final UsbDevice mDevice;
// non-null when open() protected final int mPortNumber;
protected UsbDeviceConnection mConnection = null;
protected UsbEndpoint mReadEndpoint; // non-null when open()
protected UsbEndpoint mWriteEndpoint; protected UsbDeviceConnection mConnection = null;
protected UsbRequest mUsbRequest; protected UsbEndpoint mReadEndpoint;
protected UsbEndpoint mWriteEndpoint;
protected final Object mWriteBufferLock = new Object(); protected UsbRequest mUsbRequest;
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
protected byte[] mWriteBuffer; protected final Object mWriteBufferLock = new Object();
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
public CommonUsbSerialPort(UsbDevice device, int portNumber) { protected byte[] mWriteBuffer;
mDevice = device;
mPortNumber = portNumber; public CommonUsbSerialPort(UsbDevice device, int portNumber) {
mDevice = device;
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE]; mPortNumber = portNumber;
}
mWriteBuffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
@Override }
public String toString() {
return String.format("<%s device_name=%s device_id=%s port_number=%s>", @Override
getClass().getSimpleName(), mDevice.getDeviceName(), public String toString() {
mDevice.getDeviceId(), mPortNumber); return String.format("<%s device_name=%s device_id=%s port_number=%s>",
} getClass().getSimpleName(), mDevice.getDeviceName(),
mDevice.getDeviceId(), mPortNumber);
@Override }
public UsbDevice getDevice() {
return mDevice; @Override
} public UsbDevice getDevice() {
return mDevice;
@Override }
public int getPortNumber() {
return mPortNumber; @Override
} public int getPortNumber() {
return mPortNumber;
/** }
* Returns the device serial number
* @return serial number @Override
*/ public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; }
@Override
public String getSerial() { @Override
return mConnection.getSerial(); public UsbEndpoint getReadEndpoint() { return mReadEndpoint; }
}
/**
/** * Returns the device serial number
* Sets the size of the internal buffer used to exchange data with the USB * @return serial number
* stack for write operations. Most users should not need to change this. */
* @Override
* @param bufferSize the size in bytes public String getSerial() {
*/ return mConnection.getSerial();
public final void setWriteBufferSize(int bufferSize) { }
synchronized (mWriteBufferLock) {
if (bufferSize == mWriteBuffer.length) { /**
return; * Sets the size of the internal buffer used to exchange data with the USB
} * stack for write operations. Most users should not need to change this.
mWriteBuffer = new byte[bufferSize]; *
} * @param bufferSize the size in bytes
} */
public final void setWriteBufferSize(int bufferSize) {
@Override synchronized (mWriteBufferLock) {
public void open(UsbDeviceConnection connection) throws IOException { if (bufferSize == mWriteBuffer.length) {
if (mConnection != null) { return;
throw new IOException("Already open"); }
} mWriteBuffer = new byte[bufferSize];
mConnection = connection; }
try { }
openInt(connection);
if (mReadEndpoint == null || mWriteEndpoint == null) { @Override
throw new IOException("Could not get read & write endpoints"); public void open(UsbDeviceConnection connection) throws IOException {
} if (mConnection != null) {
mUsbRequest = new UsbRequest(); throw new IOException("Already open");
mUsbRequest.initialize(mConnection, mReadEndpoint); }
} catch(Exception e) { if(connection == null) {
close(); throw new IllegalArgumentException("Connection is null");
throw e; }
} mConnection = connection;
} try {
openInt(connection);
protected abstract void openInt(UsbDeviceConnection connection) throws IOException; if (mReadEndpoint == null || mWriteEndpoint == null) {
throw new IOException("Could not get read & write endpoints");
@Override }
public void close() throws IOException { mUsbRequest = new UsbRequest();
if (mConnection == null) { mUsbRequest.initialize(mConnection, mReadEndpoint);
throw new IOException("Already closed"); } catch(Exception e) {
} try {
try { close();
mUsbRequest.cancel(); } catch(Exception ignored) {}
} catch(Exception ignored) {} throw e;
mUsbRequest = null; }
try { }
closeInt();
} catch(Exception ignored) {} protected abstract void openInt(UsbDeviceConnection connection) throws IOException;
try {
mConnection.close(); @Override
} catch(Exception ignored) {} public void close() throws IOException {
mConnection = null; if (mConnection == null) {
} throw new IOException("Already closed");
}
protected abstract void closeInt(); try {
mUsbRequest.cancel();
/** } catch(Exception ignored) {}
* use simple USB request supported by all devices to test if connection is still valid mUsbRequest = null;
*/ try {
protected void testConnection() throws IOException { closeInt();
byte[] buf = new byte[2]; } catch(Exception ignored) {}
int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); try {
if(len < 0) mConnection.close();
throw new IOException("USB get_status request failed"); } catch(Exception ignored) {}
} mConnection = null;
}
@Override
public int read(final byte[] dest, final int timeout) throws IOException { protected abstract void closeInt();
return read(dest, timeout, true);
} /**
* use simple USB request supported by all devices to test if connection is still valid
protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { */
if(mConnection == null) { protected void testConnection() throws IOException {
throw new IOException("Connection closed"); byte[] buf = new byte[2];
} int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200);
if(dest.length <= 0) { if(len < 0)
throw new IllegalArgumentException("Read buffer to small"); throw new IOException("USB get_status request failed");
} }
final int nread;
if (timeout != 0) { @Override
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer public int read(final byte[] dest, final int timeout) throws IOException {
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data return read(dest, timeout, true);
// but mConnection.requestWait(timeout) available since Android 8.0 es even worse, }
// as it crashes with short timeout, e.g.
// A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test) protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException {
// /system/lib64/libusbhost.so (usb_request_wait+192) if(mConnection == null) {
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) throw new IOException("Connection closed");
// data loss / crashes were observed with timeout up to 200 msec }
long endTime = testConnection ? System.currentTimeMillis() + timeout : 0; if(dest.length <= 0) {
int readMax = Math.min(dest.length, MAX_READ_SIZE); throw new IllegalArgumentException("Read buffer to small");
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); }
// Android error propagation is improvable, nread == -1 can be: timeout, connection lost, buffer undersized, ... final int nread;
if(nread == -1 && testConnection && System.currentTimeMillis() < endTime) if (timeout != 0) {
testConnection(); // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data
} else { // but mConnection.requestWait(timeout) available since Android 8.0 es even worse,
final ByteBuffer buf = ByteBuffer.wrap(dest); // as it crashes with short timeout, e.g.
if (!mUsbRequest.queue(buf, dest.length)) { // A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test)
throw new IOException("Queueing USB request failed"); // /system/lib64/libusbhost.so (usb_request_wait+192)
} // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
final UsbRequest response = mConnection.requestWait(); // data loss / crashes were observed with timeout up to 200 msec
if (response == null) { long endTime = testConnection ? MonotonicClock.millis() + timeout : 0;
throw new IOException("Waiting for USB request failed"); int readMax = Math.min(dest.length, MAX_READ_SIZE);
} nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
nread = buf.position(); // Android error propagation is improvable:
} // nread == -1 can be: timeout, connection lost, buffer to small, ???
if (nread > 0) if(nread == -1 && testConnection && MonotonicClock.millis() < endTime)
return nread; testConnection();
else
return 0; } else {
} final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!mUsbRequest.queue(buf, dest.length)) {
@Override throw new IOException("Queueing USB request failed");
public int write(final byte[] src, final int timeout) throws IOException { }
int offset = 0; final UsbRequest response = mConnection.requestWait();
if (response == null) {
if(mConnection == null) { throw new IOException("Waiting for USB request failed");
throw new IOException("Connection closed"); }
} nread = buf.position();
while (offset < src.length) { // Android error propagation is improvable:
final int writeLength; // response != null & nread == 0 can be: connection lost, buffer to small, ???
final int amtWritten; if(nread == 0) {
testConnection();
synchronized (mWriteBufferLock) { }
final byte[] writeBuffer; }
return Math.max(nread, 0);
writeLength = Math.min(src.length - offset, mWriteBuffer.length); }
if (offset == 0) {
writeBuffer = src; @Override
} else { public void write(final byte[] src, final int timeout) throws IOException {
// bulkTransfer does not support offsets, make a copy. int offset = 0;
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout);
writeBuffer = mWriteBuffer;
} if(mConnection == null) {
throw new IOException("Connection closed");
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, timeout); }
} while (offset < src.length) {
if (amtWritten <= 0) { int requestTimeout;
throw new IOException("Error writing " + writeLength final int requestLength;
+ " bytes at offset " + offset + " length=" + src.length); final int actualLength;
}
synchronized (mWriteBufferLock) {
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength); final byte[] writeBuffer;
offset += amtWritten;
} requestLength = Math.min(src.length - offset, mWriteBuffer.length);
return offset; if (offset == 0) {
} writeBuffer = src;
} else {
@Override // bulkTransfer does not support offsets, make a copy.
public boolean isOpen() { System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
return mConnection != null; writeBuffer = mWriteBuffer;
} }
if (timeout == 0 || offset == 0) {
@Override requestTimeout = timeout;
public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; } else {
requestTimeout = (int)(endTime - MonotonicClock.millis());
@Override if(requestTimeout == 0)
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } requestTimeout = -1;
}
@Override if (requestTimeout < 0) {
public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); } actualLength = -2;
} else {
@Override actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout);
public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); } }
}
@Override Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout);
public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); } if (actualLength <= 0) {
if (timeout != 0 && MonotonicClock.millis() >= endTime) {
@Override SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength);
public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); } ex.bytesTransferred = offset;
throw ex;
@Override } else {
public boolean getRI() throws IOException { throw new UnsupportedOperationException(); } throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length);
}
@Override }
public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); } offset += actualLength;
}
@Override }
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
@Override public boolean isOpen() {
public abstract EnumSet<ControlLine> getControlLines() throws IOException; return mConnection != null;
}
@Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException; @Override
public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { @Override
throw new UnsupportedOperationException(); public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }
}
@Override
@Override public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); }
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
} public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRI() throws IOException { throw new UnsupportedOperationException(); }
@Override
public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); }
@Override
public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); }
@Override
public abstract EnumSet<ControlLine> getControlLines() throws IOException;
@Override
public abstract EnumSet<ControlLine> getSupportedControlLines() throws IOException;
@Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); }
}

View file

@ -1,335 +1,335 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class Cp21xxSerialDriver implements UsbSerialDriver { public class Cp21xxSerialDriver implements UsbSerialDriver {
private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); private static final String TAG = Cp21xxSerialDriver.class.getSimpleName();
private final UsbDevice mDevice; private final UsbDevice mDevice;
private final List<UsbSerialPort> mPorts; private final List<UsbSerialPort> mPorts;
public Cp21xxSerialDriver(UsbDevice device) { public Cp21xxSerialDriver(UsbDevice device) {
mDevice = device; mDevice = device;
mPorts = new ArrayList<>(); mPorts = new ArrayList<>();
for( int port = 0; port < device.getInterfaceCount(); port++) { for( int port = 0; port < device.getInterfaceCount(); port++) {
mPorts.add(new Cp21xxSerialPort(mDevice, port)); mPorts.add(new Cp21xxSerialPort(mDevice, port));
} }
} }
@Override @Override
public UsbDevice getDevice() { public UsbDevice getDevice() {
return mDevice; return mDevice;
} }
@Override @Override
public List<UsbSerialPort> getPorts() { public List<UsbSerialPort> getPorts() {
return mPorts; return mPorts;
} }
public class Cp21xxSerialPort extends CommonUsbSerialPort { public class Cp21xxSerialPort extends CommonUsbSerialPort {
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
/* /*
* Configuration Request Types * Configuration Request Types
*/ */
private static final int REQTYPE_HOST_TO_DEVICE = 0x41; private static final int REQTYPE_HOST_TO_DEVICE = 0x41;
private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; private static final int REQTYPE_DEVICE_TO_HOST = 0xc1;
/* /*
* Configuration Request Codes * Configuration Request Codes
*/ */
private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00;
private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03;
private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05;
private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07;
private static final int SILABSER_SET_BAUDRATE = 0x1E; private static final int SILABSER_SET_BAUDRATE = 0x1E;
private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12;
private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08;
private static final int FLUSH_READ_CODE = 0x0a; private static final int FLUSH_READ_CODE = 0x0a;
private static final int FLUSH_WRITE_CODE = 0x05; private static final int FLUSH_WRITE_CODE = 0x05;
/* /*
* SILABSER_IFC_ENABLE_REQUEST_CODE * SILABSER_IFC_ENABLE_REQUEST_CODE
*/ */
private static final int UART_ENABLE = 0x0001; private static final int UART_ENABLE = 0x0001;
private static final int UART_DISABLE = 0x0000; private static final int UART_DISABLE = 0x0000;
/* /*
* SILABSER_SET_MHS_REQUEST_CODE * SILABSER_SET_MHS_REQUEST_CODE
*/ */
private static final int DTR_ENABLE = 0x101; private static final int DTR_ENABLE = 0x101;
private static final int DTR_DISABLE = 0x100; private static final int DTR_DISABLE = 0x100;
private static final int RTS_ENABLE = 0x202; private static final int RTS_ENABLE = 0x202;
private static final int RTS_DISABLE = 0x200; private static final int RTS_DISABLE = 0x200;
/* /*
* SILABSER_GET_MDMSTS_REQUEST_CODE * SILABSER_GET_MDMSTS_REQUEST_CODE
*/ */
private static final int STATUS_CTS = 0x10; private static final int STATUS_CTS = 0x10;
private static final int STATUS_DSR = 0x20; private static final int STATUS_DSR = 0x20;
private static final int STATUS_RI = 0x40; private static final int STATUS_RI = 0x40;
private static final int STATUS_CD = 0x80; private static final int STATUS_CD = 0x80;
private boolean dtr = false; private boolean dtr = false;
private boolean rts = false; private boolean rts = false;
// second port of Cp2105 has limited baudRate, dataBits, stopBits, parity // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity
// unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored
private boolean mIsRestrictedPort; private boolean mIsRestrictedPort;
public Cp21xxSerialPort(UsbDevice device, int portNumber) { public Cp21xxSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber); super(device, portNumber);
} }
@Override @Override
public UsbSerialDriver getDriver() { public UsbSerialDriver getDriver() {
return Cp21xxSerialDriver.this; return Cp21xxSerialDriver.this;
} }
private void setConfigSingle(int request, int value) throws IOException { private void setConfigSingle(int request, int value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value,
mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { if (result != 0) {
throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result);
} }
} }
private byte getStatus() throws IOException { private byte getStatus() throws IOException {
byte[] buffer = new byte[1]; byte[] buffer = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0,
mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS);
if (result != 1) { if (result != 1) {
throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result);
} }
return buffer[0]; return buffer[0];
} }
@Override @Override
protected void openInt(UsbDeviceConnection connection) throws IOException { protected void openInt(UsbDeviceConnection connection) throws IOException {
mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1;
if(mPortNumber >= mDevice.getInterfaceCount()) { if(mPortNumber >= mDevice.getInterfaceCount()) {
throw new IOException("Unknown port number"); throw new IOException("Unknown port number");
} }
UsbInterface dataIface = mDevice.getInterface(mPortNumber); UsbInterface dataIface = mDevice.getInterface(mPortNumber);
if (!mConnection.claimInterface(dataIface, true)) { if (!mConnection.claimInterface(dataIface, true)) {
throw new IOException("Could not claim interface " + mPortNumber); throw new IOException("Could not claim interface " + mPortNumber);
} }
for (int i = 0; i < dataIface.getEndpointCount(); i++) { for (int i = 0; i < dataIface.getEndpointCount(); i++) {
UsbEndpoint ep = dataIface.getEndpoint(i); UsbEndpoint ep = dataIface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_IN) { if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
mReadEndpoint = ep; mReadEndpoint = ep;
} else { } else {
mWriteEndpoint = ep; mWriteEndpoint = ep;
} }
} }
} }
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE);
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE));
} }
@Override @Override
protected void closeInt() { protected void closeInt() {
try { try {
setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE);
} catch (Exception ignored) {} } catch (Exception ignored) {}
try { try {
mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
} catch(Exception ignored) {} } catch(Exception ignored) {}
} }
private void setBaudRate(int baudRate) throws IOException { private void setBaudRate(int baudRate) throws IOException {
byte[] data = new byte[] { byte[] data = new byte[] {
(byte) ( baudRate & 0xff), (byte) ( baudRate & 0xff),
(byte) ((baudRate >> 8 ) & 0xff), (byte) ((baudRate >> 8 ) & 0xff),
(byte) ((baudRate >> 16) & 0xff), (byte) ((baudRate >> 16) & 0xff),
(byte) ((baudRate >> 24) & 0xff) (byte) ((baudRate >> 24) & 0xff)
}; };
int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE,
0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS);
if (ret < 0) { if (ret < 0) {
throw new IOException("Error setting baud rate"); throw new IOException("Error setting baud rate");
} }
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
setBaudRate(baudRate); setBaudRate(baudRate);
int configDataBits = 0; int configDataBits = 0;
switch (dataBits) { switch (dataBits) {
case DATABITS_5: case DATABITS_5:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0500; configDataBits |= 0x0500;
break; break;
case DATABITS_6: case DATABITS_6:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0600; configDataBits |= 0x0600;
break; break;
case DATABITS_7: case DATABITS_7:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
configDataBits |= 0x0700; configDataBits |= 0x0700;
break; break;
case DATABITS_8: case DATABITS_8:
configDataBits |= 0x0800; configDataBits |= 0x0800;
break; break;
default: default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits); throw new IllegalArgumentException("Invalid data bits: " + dataBits);
} }
switch (parity) { switch (parity) {
case PARITY_NONE: case PARITY_NONE:
break; break;
case PARITY_ODD: case PARITY_ODD:
configDataBits |= 0x0010; configDataBits |= 0x0010;
break; break;
case PARITY_EVEN: case PARITY_EVEN:
configDataBits |= 0x0020; configDataBits |= 0x0020;
break; break;
case PARITY_MARK: case PARITY_MARK:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported parity: mark"); throw new UnsupportedOperationException("Unsupported parity: mark");
configDataBits |= 0x0030; configDataBits |= 0x0030;
break; break;
case PARITY_SPACE: case PARITY_SPACE:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported parity: space"); throw new UnsupportedOperationException("Unsupported parity: space");
configDataBits |= 0x0040; configDataBits |= 0x0040;
break; break;
default: default:
throw new IllegalArgumentException("Invalid parity: " + parity); throw new IllegalArgumentException("Invalid parity: " + parity);
} }
switch (stopBits) { switch (stopBits) {
case STOPBITS_1: case STOPBITS_1:
break; break;
case STOPBITS_1_5: case STOPBITS_1_5:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
case STOPBITS_2: case STOPBITS_2:
if(mIsRestrictedPort) if(mIsRestrictedPort)
throw new UnsupportedOperationException("Unsupported stop bits: 2"); throw new UnsupportedOperationException("Unsupported stop bits: 2");
configDataBits |= 2; configDataBits |= 2;
break; break;
default: default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits); throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
} }
setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits);
} }
@Override @Override
public boolean getCD() throws IOException { public boolean getCD() throws IOException {
return (getStatus() & STATUS_CD) != 0; return (getStatus() & STATUS_CD) != 0;
} }
@Override @Override
public boolean getCTS() throws IOException { public boolean getCTS() throws IOException {
return (getStatus() & STATUS_CTS) != 0; return (getStatus() & STATUS_CTS) != 0;
} }
@Override @Override
public boolean getDSR() throws IOException { public boolean getDSR() throws IOException {
return (getStatus() & STATUS_DSR) != 0; return (getStatus() & STATUS_DSR) != 0;
} }
@Override @Override
public boolean getDTR() throws IOException { public boolean getDTR() throws IOException {
return dtr; return dtr;
} }
@Override @Override
public void setDTR(boolean value) throws IOException { public void setDTR(boolean value) throws IOException {
dtr = value; dtr = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE);
} }
@Override @Override
public boolean getRI() throws IOException { public boolean getRI() throws IOException {
return (getStatus() & STATUS_RI) != 0; return (getStatus() & STATUS_RI) != 0;
} }
@Override @Override
public boolean getRTS() throws IOException { public boolean getRTS() throws IOException {
return rts; return rts;
} }
@Override @Override
public void setRTS(boolean value) throws IOException { public void setRTS(boolean value) throws IOException {
rts = value; rts = value;
setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE);
} }
@Override @Override
public EnumSet<ControlLine> getControlLines() throws IOException { public EnumSet<ControlLine> getControlLines() throws IOException {
byte status = getStatus(); byte status = getStatus();
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(rts) set.add(ControlLine.RTS); if(rts) set.add(ControlLine.RTS);
if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS);
if(dtr) set.add(ControlLine.DTR); if(dtr) set.add(ControlLine.DTR);
if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR);
if((status & STATUS_CD) != 0) set.add(ControlLine.CD); if((status & STATUS_CD) != 0) set.add(ControlLine.CD);
if((status & STATUS_RI) != 0) set.add(ControlLine.RI); if((status & STATUS_RI) != 0) set.add(ControlLine.RI);
return set; return set;
} }
@Override @Override
public EnumSet<ControlLine> getSupportedControlLines() throws IOException { public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class); return EnumSet.allOf(ControlLine.class);
} }
@Override @Override
// note: only working on some devices, on other devices ignored w/o error // note: only working on some devices, on other devices ignored w/o error
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0)
| (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0);
if (value != 0) { if (value != 0) {
setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value);
} }
} }
@Override @Override
public void setBreak(boolean value) throws IOException { public void setBreak(boolean value) throws IOException {
setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0);
} }
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS, supportedDevices.put(UsbId.VENDOR_SILABS,
new int[] { new int[] {
UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109
UsbId.SILABS_CP2105, UsbId.SILABS_CP2105,
UsbId.SILABS_CP2108, UsbId.SILABS_CP2108,
}); });
return supportedDevices; return supportedDevices;
} }
} }

View file

@ -1,428 +1,430 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* Copyright 2020 kai morich <mail@kai-morich.de> * Copyright 2020 kai morich <mail@kai-morich.de>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.util.Log; import android.util.Log;
import java.io.IOException; import org.emulator.calculator.usbserial.util.MonotonicClock;
import java.util.ArrayList;
import java.util.EnumSet; import java.io.IOException;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.List; import java.util.EnumSet;
import java.util.Map; import java.util.LinkedHashMap;
import java.util.List;
/* import java.util.Map;
* driver is implemented from various information scattered over FTDI documentation
* /*
* baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf * driver is implemented from various information scattered over FTDI documentation
* control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf *
* device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf
* * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf
*/ * device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice
*
public class FtdiSerialDriver implements UsbSerialDriver { */
private static final String TAG = FtdiSerialPort.class.getSimpleName(); public class FtdiSerialDriver implements UsbSerialDriver {
private final UsbDevice mDevice; private static final String TAG = FtdiSerialPort.class.getSimpleName();
private final List<UsbSerialPort> mPorts;
private final UsbDevice mDevice;
public FtdiSerialDriver(UsbDevice device) { private final List<UsbSerialPort> mPorts;
mDevice = device;
mPorts = new ArrayList<>(); public FtdiSerialDriver(UsbDevice device) {
for( int port = 0; port < device.getInterfaceCount(); port++) { mDevice = device;
mPorts.add(new FtdiSerialPort(mDevice, port)); mPorts = new ArrayList<>();
} for( int port = 0; port < device.getInterfaceCount(); port++) {
} mPorts.add(new FtdiSerialPort(mDevice, port));
}
@Override }
public UsbDevice getDevice() {
return mDevice; @Override
} public UsbDevice getDevice() {
return mDevice;
@Override }
public List<UsbSerialPort> getPorts() {
return mPorts; @Override
} public List<UsbSerialPort> getPorts() {
return mPorts;
public class FtdiSerialPort extends CommonUsbSerialPort { }
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; public class FtdiSerialPort extends CommonUsbSerialPort {
private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS
private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;
private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS
private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
private static final int RESET_REQUEST = 0; private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
private static final int MODEM_CONTROL_REQUEST = 1;
private static final int SET_BAUD_RATE_REQUEST = 3; private static final int RESET_REQUEST = 0;
private static final int SET_DATA_REQUEST = 4; private static final int MODEM_CONTROL_REQUEST = 1;
private static final int GET_MODEM_STATUS_REQUEST = 5; private static final int SET_BAUD_RATE_REQUEST = 3;
private static final int SET_LATENCY_TIMER_REQUEST = 9; private static final int SET_DATA_REQUEST = 4;
private static final int GET_LATENCY_TIMER_REQUEST = 10; private static final int GET_MODEM_STATUS_REQUEST = 5;
private static final int SET_LATENCY_TIMER_REQUEST = 9;
private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; private static final int GET_LATENCY_TIMER_REQUEST = 10;
private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100;
private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101;
private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100;
private static final int MODEM_STATUS_CTS = 0x10; private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202;
private static final int MODEM_STATUS_DSR = 0x20; private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200;
private static final int MODEM_STATUS_RI = 0x40; private static final int MODEM_STATUS_CTS = 0x10;
private static final int MODEM_STATUS_CD = 0x80; private static final int MODEM_STATUS_DSR = 0x20;
private static final int RESET_ALL = 0; private static final int MODEM_STATUS_RI = 0x40;
private static final int RESET_PURGE_RX = 1; private static final int MODEM_STATUS_CD = 0x80;
private static final int RESET_PURGE_TX = 2; private static final int RESET_ALL = 0;
private static final int RESET_PURGE_RX = 1;
private boolean baudRateWithPort = false; private static final int RESET_PURGE_TX = 2;
private boolean dtr = false;
private boolean rts = false; private boolean baudRateWithPort = false;
private int breakConfig = 0; private boolean dtr = false;
private boolean rts = false;
public FtdiSerialPort(UsbDevice device, int portNumber) { private int breakConfig = 0;
super(device, portNumber);
} public FtdiSerialPort(UsbDevice device, int portNumber) {
super(device, portNumber);
@Override }
public UsbSerialDriver getDriver() {
return FtdiSerialDriver.this; @Override
} public UsbSerialDriver getDriver() {
return FtdiSerialDriver.this;
}
@Override
protected void openInt(UsbDeviceConnection connection) throws IOException {
if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { @Override
throw new IOException("Could not claim interface " + mPortNumber); protected void openInt(UsbDeviceConnection connection) throws IOException {
} if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) {
if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { throw new IOException("Could not claim interface " + mPortNumber);
throw new IOException("Not enough endpoints"); }
} if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) {
mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); throw new IOException("Not enough endpoints");
mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); }
mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0);
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1);
RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
throw new IOException("Reset failed: result=" + result); RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, throw new IOException("Reset failed: result=" + result);
(dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | }
(rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) |
if (result != 0) { (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE),
throw new IOException("Init RTS,DTR failed: result=" + result); mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
throw new IOException("Init RTS,DTR failed: result=" + result);
// mDevice.getVersion() would require API 23 }
byte[] rawDescriptors = connection.getRawDescriptors();
if(rawDescriptors == null || rawDescriptors.length < 14) { // mDevice.getVersion() would require API 23
throw new IOException("Could not get device descriptors"); byte[] rawDescriptors = connection.getRawDescriptors();
} if(rawDescriptors == null || rawDescriptors.length < 14) {
int deviceType = rawDescriptors[13]; throw new IOException("Could not get device descriptors");
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices }
} int deviceType = rawDescriptors[13];
baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices
@Override }
protected void closeInt() {
try { @Override
mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); protected void closeInt() {
} catch(Exception ignored) {} try {
} mConnection.releaseInterface(mDevice.getInterface(mPortNumber));
} catch(Exception ignored) {}
@Override }
public int read(final byte[] dest, final int timeout) throws IOException {
if(dest.length <= READ_HEADER_LENGTH) { @Override
throw new IllegalArgumentException("Read buffer to small"); public int read(final byte[] dest, final int timeout) throws IOException {
// could allocate larger buffer, including space for 2 header bytes, but this would if(dest.length <= READ_HEADER_LENGTH) {
// result in buffers not being 64 byte aligned any more, causing data loss at continuous throw new IllegalArgumentException("Read buffer to small");
// data transfer at high baud rates when buffers are fully filled. // could allocate larger buffer, including space for 2 header bytes, but this would
} // result in buffers not being 64 byte aligned any more, causing data loss at continuous
int nread; // data transfer at high baud rates when buffers are fully filled.
if (timeout != 0) { }
long endTime = System.currentTimeMillis() + timeout; int nread;
do { if (timeout != 0) {
nread = super.read(dest, Math.max(1, (int)(endTime - System.currentTimeMillis())), false); long endTime = MonotonicClock.millis() + timeout;
} while (nread == READ_HEADER_LENGTH && System.currentTimeMillis() < endTime); do {
if(nread <= 0 && System.currentTimeMillis() < endTime) nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false);
testConnection(); } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime);
} else { if(nread <= 0 && MonotonicClock.millis() < endTime)
do { testConnection();
nread = super.read(dest, timeout, false); } else {
} while (nread == READ_HEADER_LENGTH); do {
} nread = super.read(dest, timeout, false);
return readFilter(dest, nread); } while (nread == READ_HEADER_LENGTH);
} }
return readFilter(dest, nread);
private int readFilter(byte[] buffer, int totalBytesRead) throws IOException { }
final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int destPos = 0; protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); int destPos = 0;
if (length < 0) for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) {
throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH);
System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); if (length < 0)
destPos += length; throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes");
} System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length);
//Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); destPos += length;
return destPos; }
} //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos);
return destPos;
private void setBaudrate(int baudRate) throws IOException { }
int divisor, subdivisor, effectiveBaudRate;
if (baudRate > 3500000) { private void setBaudrate(int baudRate) throws IOException {
throw new UnsupportedOperationException("Baud rate to high"); int divisor, subdivisor, effectiveBaudRate;
} else if(baudRate >= 2500000) { if (baudRate > 3500000) {
divisor = 0; throw new UnsupportedOperationException("Baud rate to high");
subdivisor = 0; } else if(baudRate >= 2500000) {
effectiveBaudRate = 3000000; divisor = 0;
} else if(baudRate >= 1750000) { subdivisor = 0;
divisor = 1; effectiveBaudRate = 3000000;
subdivisor = 0; } else if(baudRate >= 1750000) {
effectiveBaudRate = 2000000; divisor = 1;
} else { subdivisor = 0;
divisor = (24000000 << 1) / baudRate; effectiveBaudRate = 2000000;
divisor = (divisor + 1) >> 1; // round } else {
subdivisor = divisor & 0x07; divisor = (24000000 << 1) / baudRate;
divisor >>= 3; divisor = (divisor + 1) >> 1; // round
if (divisor > 0x3fff) // exceeds bit 13 at 183 baud subdivisor = divisor & 0x07;
throw new UnsupportedOperationException("Baud rate to low"); divisor >>= 3;
effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); if (divisor > 0x3fff) // exceeds bit 13 at 183 baud
effectiveBaudRate = (effectiveBaudRate +1) >> 1; throw new UnsupportedOperationException("Baud rate to low");
} effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor);
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); effectiveBaudRate = (effectiveBaudRate +1) >> 1;
if(baudRateError >= 0.031) // can happen only > 1.5Mbaud }
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
int value = divisor; if(baudRateError >= 0.031) // can happen only > 1.5Mbaud
int index = 0; throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100));
switch(subdivisor) { int value = divisor;
case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 int index = 0;
case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 switch(subdivisor) {
case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0
case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5
case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25
case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625 case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125
case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75 case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375
case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625
} case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75
if(baudRateWithPort) { case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875
index <<= 8; }
index |= mPortNumber+1; if(baudRateWithPort) {
} index <<= 8;
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", index |= mPortNumber+1;
baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); }
Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d",
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor));
value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST,
throw new IOException("Setting baudrate failed: result=" + result); value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
} throw new IOException("Setting baudrate failed: result=" + result);
}
@Override }
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
if(baudRate <= 0) { @Override
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
} if(baudRate <= 0) {
setBaudrate(baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
}
int config = 0; setBaudrate(baudRate);
switch (dataBits) {
case DATABITS_5: int config = 0;
case DATABITS_6: switch (dataBits) {
throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); case DATABITS_5:
case DATABITS_7: case DATABITS_6:
case DATABITS_8: throw new UnsupportedOperationException("Unsupported data bits: " + dataBits);
config |= dataBits; case DATABITS_7:
break; case DATABITS_8:
default: config |= dataBits;
throw new IllegalArgumentException("Invalid data bits: " + dataBits); break;
} default:
throw new IllegalArgumentException("Invalid data bits: " + dataBits);
switch (parity) { }
case PARITY_NONE:
break; switch (parity) {
case PARITY_ODD: case PARITY_NONE:
config |= 0x100; break;
break; case PARITY_ODD:
case PARITY_EVEN: config |= 0x100;
config |= 0x200; break;
break; case PARITY_EVEN:
case PARITY_MARK: config |= 0x200;
config |= 0x300; break;
break; case PARITY_MARK:
case PARITY_SPACE: config |= 0x300;
config |= 0x400; break;
break; case PARITY_SPACE:
default: config |= 0x400;
throw new IllegalArgumentException("Invalid parity: " + parity); break;
} default:
throw new IllegalArgumentException("Invalid parity: " + parity);
switch (stopBits) { }
case STOPBITS_1:
break; switch (stopBits) {
case STOPBITS_1_5: case STOPBITS_1:
throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); break;
case STOPBITS_2: case STOPBITS_1_5:
config |= 0x1000; throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
break; case STOPBITS_2:
default: config |= 0x1000;
throw new IllegalArgumentException("Invalid stop bits: " + stopBits); break;
} default:
throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, }
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
throw new IOException("Setting parameters failed: result=" + result); config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
breakConfig = config; throw new IOException("Setting parameters failed: result=" + result);
} }
breakConfig = config;
private int getStatus() throws IOException { }
byte[] data = new byte[2];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, private int getStatus() throws IOException {
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); byte[] data = new byte[2];
if (result != 2) { int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST,
throw new IOException("Get modem status failed: result=" + result); 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 2) {
return data[0]; throw new IOException("Get modem status failed: result=" + result);
} }
return data[0];
@Override }
public boolean getCD() throws IOException {
return (getStatus() & MODEM_STATUS_CD) != 0; @Override
} public boolean getCD() throws IOException {
return (getStatus() & MODEM_STATUS_CD) != 0;
@Override }
public boolean getCTS() throws IOException {
return (getStatus() & MODEM_STATUS_CTS) != 0; @Override
} public boolean getCTS() throws IOException {
return (getStatus() & MODEM_STATUS_CTS) != 0;
@Override }
public boolean getDSR() throws IOException {
return (getStatus() & MODEM_STATUS_DSR) != 0; @Override
} public boolean getDSR() throws IOException {
return (getStatus() & MODEM_STATUS_DSR) != 0;
@Override }
public boolean getDTR() throws IOException {
return dtr; @Override
} public boolean getDTR() throws IOException {
return dtr;
@Override }
public void setDTR(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, @Override
value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); public void setDTR(boolean value) throws IOException {
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
throw new IOException("Set DTR failed: result=" + result); value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
dtr = value; throw new IOException("Set DTR failed: result=" + result);
} }
dtr = value;
@Override }
public boolean getRI() throws IOException {
return (getStatus() & MODEM_STATUS_RI) != 0; @Override
} public boolean getRI() throws IOException {
return (getStatus() & MODEM_STATUS_RI) != 0;
@Override }
public boolean getRTS() throws IOException {
return rts; @Override
} public boolean getRTS() throws IOException {
return rts;
@Override }
public void setRTS(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, @Override
value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); public void setRTS(boolean value) throws IOException {
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST,
throw new IOException("Set DTR failed: result=" + result); value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
rts = value; throw new IOException("Set DTR failed: result=" + result);
} }
rts = value;
@Override }
public EnumSet<ControlLine> getControlLines() throws IOException {
int status = getStatus(); @Override
EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class); public EnumSet<ControlLine> getControlLines() throws IOException {
if(rts) set.add(ControlLine.RTS); int status = getStatus();
if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); EnumSet<ControlLine> set = EnumSet.noneOf(ControlLine.class);
if(dtr) set.add(ControlLine.DTR); if(rts) set.add(ControlLine.RTS);
if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS);
if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD); if(dtr) set.add(ControlLine.DTR);
if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI); if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR);
return set; if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD);
} if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI);
return set;
@Override }
public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class); @Override
} public EnumSet<ControlLine> getSupportedControlLines() throws IOException {
return EnumSet.allOf(ControlLine.class);
@Override }
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) { @Override
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (purgeWriteBuffers) {
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
throw new IOException("Purge write buffer failed: result=" + result); RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
} throw new IOException("Purge write buffer failed: result=" + result);
}
if (purgeReadBuffers) { }
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); if (purgeReadBuffers) {
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST,
throw new IOException("Purge read buffer failed: result=" + result); RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
} throw new IOException("Purge read buffer failed: result=" + result);
} }
}
@Override }
public void setBreak(boolean value) throws IOException {
int config = breakConfig; @Override
if(value) config |= 0x4000; public void setBreak(boolean value) throws IOException {
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, int config = breakConfig;
config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); if(value) config |= 0x4000;
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST,
throw new IOException("Setting BREAK failed: result=" + result); config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
} throw new IOException("Setting BREAK failed: result=" + result);
}
public void setLatencyTimer(int latencyTime) throws IOException { }
int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); public void setLatencyTimer(int latencyTime) throws IOException {
if (result != 0) { int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST,
throw new IOException("Set latency timer failed: result=" + result); latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 0) {
} throw new IOException("Set latency timer failed: result=" + result);
}
public int getLatencyTimer() throws IOException { }
byte[] data = new byte[1];
int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, public int getLatencyTimer() throws IOException {
0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); byte[] data = new byte[1];
if (result != 1) { int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST,
throw new IOException("Get latency timer failed: result=" + result); 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS);
} if (result != 1) {
return data[0]; throw new IOException("Get latency timer failed: result=" + result);
} }
return data[0];
} }
public static Map<Integer, int[]> getSupportedDevices() { }
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>();
supportedDevices.put(UsbId.VENDOR_FTDI, public static Map<Integer, int[]> getSupportedDevices() {
new int[] { final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
UsbId.FTDI_FT232R, supportedDevices.put(UsbId.VENDOR_FTDI,
UsbId.FTDI_FT232H, new int[] {
UsbId.FTDI_FT2232H, UsbId.FTDI_FT232R,
UsbId.FTDI_FT4232H, UsbId.FTDI_FT232H,
UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD UsbId.FTDI_FT2232H,
}); UsbId.FTDI_FT4232H,
return supportedDevices; UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD
} });
return supportedDevices;
} }
}

View file

@ -1,87 +1,87 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.util.Pair; import android.util.Pair;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.LinkedHashMap; 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.
* *
* @author mike wakerly (opensource@hoho.com) * @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>> mProbeTable =
new LinkedHashMap<Pair<Integer,Integer>, Class<? extends UsbSerialDriver>>(); new LinkedHashMap<>();
/** /**
* Adds or updates a (vendor, product) pair in the table. * Adds or updates a (vendor, product) pair in the table.
* *
* @param vendorId the USB vendor id * @param vendorId the USB vendor id
* @param productId the USB product id * @param productId the USB product id
* @param driverClass the driver class responsible for this pair * @param driverClass the driver class responsible for this pair
* @return {@code this}, for chaining * @return {@code this}, for chaining
*/ */
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); mProbeTable.put(Pair.create(vendorId, productId), driverClass);
return this; return this;
} }
/** /**
* 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
* @return * @return
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) { ProbeTable addDriver(Class<? extends UsbSerialDriver> driverClass) {
final Method method; final Method method;
try { try {
method = driverClass.getMethod("getSupportedDevices"); method = driverClass.getMethod("getSupportedDevices");
} catch (SecurityException | NoSuchMethodException e) { } catch (SecurityException | NoSuchMethodException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
final Map<Integer, int[]> devices; final Map<Integer, int[]> devices;
try { try {
devices = (Map<Integer, int[]>) method.invoke(null); devices = (Map<Integer, int[]>) method.invoke(null);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
for (Map.Entry<Integer, int[]> entry : devices.entrySet()) { for (Map.Entry<Integer, int[]> entry : devices.entrySet()) {
final int vendorId = entry.getKey(); final int vendorId = entry.getKey();
for (int productId : entry.getValue()) { for (int productId : entry.getValue()) {
addProduct(vendorId, productId, driverClass); addProduct(vendorId, productId, driverClass);
} }
} }
return this; return this;
} }
/** /**
* Returns the driver for the given (vendor, product) pair, or {@code null} * Returns the driver for the given (vendor, product) pair, or {@code null}
* if no match. * if no match.
* *
* @param vendorId the USB vendor id * @param vendorId the USB vendor id
* @param productId the USB product id * @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(int vendorId, int productId) {
final Pair<Integer, Integer> pair = Pair.create(vendorId, productId); final Pair<Integer, Integer> pair = Pair.create(vendorId, productId);
return mProbeTable.get(pair); return mProbeTable.get(pair);
} }
} }

View file

@ -0,0 +1,15 @@
package org.emulator.calculator.usbserial.driver;
import java.io.InterruptedIOException;
/**
* Signals that a timeout has occurred on serial write.
* Similar to SocketTimeoutException.
*
* {@see InterruptedIOException#bytesTransferred} may contain bytes transferred
*/
public class SerialTimeoutException extends InterruptedIOException {
public SerialTimeoutException(String s) {
super(s);
}
}

View file

@ -1,67 +1,77 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
/** /**
* Registry of USB vendor/product ID constants. * Registry of USB vendor/product ID constants.
* *
* Culled from various sources; see * Culled from various sources; see
* <a href="http://www.linux-usb.org/usb.ids">usb.ids</a> for one listing. * <a href="http://www.linux-usb.org/usb.ids">usb.ids</a> for one listing.
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public final class UsbId { public final class UsbId {
public static final int VENDOR_FTDI = 0x0403; public static final int VENDOR_FTDI = 0x0403;
public static final int FTDI_FT232R = 0x6001; public static final int FTDI_FT232R = 0x6001;
public static final int FTDI_FT2232H = 0x6010; public static final int FTDI_FT2232H = 0x6010;
public static final int FTDI_FT4232H = 0x6011; public static final int FTDI_FT4232H = 0x6011;
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 VENDOR_ATMEL = 0x03EB;
public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044;
public static final int VENDOR_ARDUINO = 0x2341; public static final int VENDOR_ARDUINO = 0x2341;
public static final int ARDUINO_UNO = 0x0001; public static final int ARDUINO_UNO = 0x0001;
public static final int ARDUINO_MEGA_2560 = 0x0010; public static final int ARDUINO_MEGA_2560 = 0x0010;
public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; public static final int ARDUINO_SERIAL_ADAPTER = 0x003b;
public static final int ARDUINO_MEGA_ADK = 0x003f; public static final int ARDUINO_MEGA_ADK = 0x003f;
public static final int ARDUINO_MEGA_2560_R3 = 0x0042; public static final int ARDUINO_MEGA_2560_R3 = 0x0042;
public static final int ARDUINO_UNO_R3 = 0x0043; public static final int ARDUINO_UNO_R3 = 0x0043;
public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; public static final int ARDUINO_MEGA_ADK_R3 = 0x0044;
public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044;
public static final int ARDUINO_LEONARDO = 0x8036; public static final int ARDUINO_LEONARDO = 0x8036;
public static final int ARDUINO_MICRO = 0x8037; public static final int ARDUINO_MICRO = 0x8037;
public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0;
public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483;
public static final int VENDOR_LEAFLABS = 0x1eaf; public static final int VENDOR_LEAFLABS = 0x1eaf;
public static final int LEAFLABS_MAPLE = 0x0004; 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;
public static final int SILABS_CP2108 = 0xea71; public static final int SILABS_CP2108 = 0xea71;
public static final int VENDOR_PROLIFIC = 0x067b; public static final int VENDOR_PROLIFIC = 0x067b;
public static final int PROLIFIC_PL2303 = 0x2303; public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX
public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN
public static final int VENDOR_QINHENG = 0x1a86; public static final int PROLIFIC_PL2303GB = 0x23b3; // "
public static final int QINHENG_CH340 = 0x7523; public static final int PROLIFIC_PL2303GT = 0x23cd; // "
public static final int QINHENG_CH341A = 0x5523; public static final int PROLIFIC_PL2303GT3 = 0x23c3; // "
public static final int PROLIFIC_PL2303GL = 0x23e3; // "
// 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 PROLIFIC_PL2303GE = 0x23e3; // "
public static final int VENDOR_ARM = 0x0d28; public static final int PROLIFIC_PL2303GS = 0x23f3; // "
public static final int ARM_MBED = 0x0204;
public static final int VENDOR_QINHENG = 0x1a86;
private UsbId() { public static final int QINHENG_CH340 = 0x7523;
throw new IllegalAccessError("Non-instantiable class"); public static final int QINHENG_CH341A = 0x5523;
}
// 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;
private UsbId() {
throw new IllegalAccessError("Non-instantiable class");
}
}

View file

@ -1,33 +1,33 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import java.util.List; import java.util.List;
/** /**
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public interface UsbSerialDriver { public interface UsbSerialDriver {
/** /**
* Returns the raw {@link UsbDevice} backing this port. * Returns the raw {@link UsbDevice} backing this port.
* *
* @return the device * @return the device
*/ */
public UsbDevice getDevice(); UsbDevice getDevice();
/** /**
* Returns all available ports for this device. This list must have at least * Returns all available ports for this device. This list must have at least
* one entry. * one entry.
* *
* @return the ports * @return the ports
*/ */
public List<UsbSerialPort> getPorts(); List<UsbSerialPort> getPorts();
} }

View file

@ -1,263 +1,261 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;
import java.io.Closeable;
import java.io.IOException; import androidx.annotation.IntDef;
import java.util.EnumSet;
import java.io.Closeable;
/** import java.io.IOException;
* Interface for a single serial port. import java.lang.annotation.Retention;
* import java.lang.annotation.RetentionPolicy;
* @author mike wakerly (opensource@hoho.com) import java.util.EnumSet;
*/
public interface UsbSerialPort extends Closeable { /**
* Interface for a single serial port.
/** 5 data bits. */ *
public static final int DATABITS_5 = 5; * @author mike wakerly (opensource@hoho.com)
*/
/** 6 data bits. */ public interface UsbSerialPort extends Closeable {
public static final int DATABITS_6 = 6;
/** 5 data bits. */
/** 7 data bits. */ int DATABITS_5 = 5;
public static final int DATABITS_7 = 7; /** 6 data bits. */
int DATABITS_6 = 6;
/** 8 data bits. */ /** 7 data bits. */
public static final int DATABITS_8 = 8; int DATABITS_7 = 7;
/** 8 data bits. */
/** No flow control. */ int DATABITS_8 = 8;
public static final int FLOWCONTROL_NONE = 0;
/** Values for setParameters(..., parity) */
/** RTS/CTS input flow control. */ @Retention(RetentionPolicy.SOURCE)
public static final int FLOWCONTROL_RTSCTS_IN = 1; @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE})
@interface Parity {}
/** RTS/CTS output flow control. */ /** No parity. */
public static final int FLOWCONTROL_RTSCTS_OUT = 2; int PARITY_NONE = 0;
/** Odd parity. */
/** XON/XOFF input flow control. */ int PARITY_ODD = 1;
public static final int FLOWCONTROL_XONXOFF_IN = 4; /** Even parity. */
int PARITY_EVEN = 2;
/** XON/XOFF output flow control. */ /** Mark parity. */
public static final int FLOWCONTROL_XONXOFF_OUT = 8; int PARITY_MARK = 3;
/** Space parity. */
/** No parity. */ int PARITY_SPACE = 4;
public static final int PARITY_NONE = 0;
/** 1 stop bit. */
/** Odd parity. */ int STOPBITS_1 = 1;
public static final int PARITY_ODD = 1; /** 1.5 stop bits. */
int STOPBITS_1_5 = 3;
/** Even parity. */ /** 2 stop bits. */
public static final int PARITY_EVEN = 2; int STOPBITS_2 = 2;
/** Mark parity. */ /** Values for get[Supported]ControlLines() */
public static final int PARITY_MARK = 3; enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }
/** Space parity. */ /**
public static final int PARITY_SPACE = 4; * Returns the driver used by this port.
*/
/** 1 stop bit. */ UsbSerialDriver getDriver();
public static final int STOPBITS_1 = 1;
/**
/** 1.5 stop bits. */ * Returns the currently-bound USB device.
public static final int STOPBITS_1_5 = 3; */
UsbDevice getDevice();
/** 2 stop bits. */
public static final int STOPBITS_2 = 2; /**
* Port number within driver.
/** values for get[Supported]ControlLines() */ */
public enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }; int getPortNumber();
/** /**
* Returns the driver used by this port. * Returns the write endpoint.
*/ * @return write endpoint
public UsbSerialDriver getDriver(); */
UsbEndpoint getWriteEndpoint();
/**
* Returns the currently-bound USB device. /**
*/ * Returns the read endpoint.
public UsbDevice getDevice(); * @return read endpoint
*/
/** UsbEndpoint getReadEndpoint();
* Port number within driver.
*/ /**
public int getPortNumber(); * The serial number of the underlying UsbDeviceConnection, or {@code null}.
*
/** * @return value from {@link UsbDeviceConnection#getSerial()}
* The serial number of the underlying UsbDeviceConnection, or {@code null}. * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
* */
* @return value from {@link UsbDeviceConnection#getSerial()} String getSerial();
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
*/ /**
public String getSerial(); * Opens and initializes the port. Upon success, caller must ensure that
* {@link #close()} is eventually called.
/** *
* Opens and initializes the port. Upon success, caller must ensure that * @param connection an open device connection, acquired with
* {@link #close()} is eventually called. * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)}
* * @throws IOException on error opening or initializing the port.
* @param connection an open device connection, acquired with */
* {@link UsbManager#openDevice(UsbDevice)} void open(UsbDeviceConnection connection) throws IOException;
* @throws IOException on error opening or initializing the port.
*/ /**
public void open(UsbDeviceConnection connection) throws IOException; * Closes the port and {@link UsbDeviceConnection}
*
/** * @throws IOException on error closing the port.
* Closes the port and {@link UsbDeviceConnection} */
* void close() throws IOException;
* @throws IOException on error closing the port.
*/ /**
public void close() throws IOException; * Reads as many bytes as possible into the destination buffer.
*
/** * @param dest the destination byte buffer
* Reads as many bytes as possible into the destination buffer. * @param timeout the timeout for reading in milliseconds, 0 is infinite
* * @return the actual number of bytes read
* @param dest the destination byte buffer * @throws IOException if an error occurred during reading
* @param timeout the timeout for reading in milliseconds, 0 is infinite */
* @return the actual number of bytes read int read(final byte[] dest, final int timeout) throws IOException;
* @throws IOException if an error occurred during reading
*/ /**
public int read(final byte[] dest, final int timeout) throws IOException; * Writes as many bytes as possible from the source buffer.
*
/** * @param src the source byte buffer
* Writes as many bytes as possible from the source buffer. * @param timeout the timeout for writing in milliseconds, 0 is infinite
* * @throws SerialTimeoutException if timeout reached before sending all data.
* @param src the source byte buffer * ex.bytesTransferred may contain bytes transferred
* @param timeout the timeout for writing in milliseconds, 0 is infinite * @throws IOException if an error occurred during writing
* @return the actual number of bytes written */
* @throws IOException if an error occurred during writing void write(final byte[] src, final int timeout) throws IOException;
*/
public int write(final byte[] src, final int timeout) throws IOException; /**
* Sets various serial port parameters.
/** *
* Sets various serial port parameters. * @param baudRate baud rate as an integer, for example {@code 115200}.
* * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6},
* @param baudRate baud rate as an integer, for example {@code 115200}. * {@link #DATABITS_7}, or {@link #DATABITS_8}.
* @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}.
* {@link #DATABITS_7}, or {@link #DATABITS_8}. * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD},
* @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}.
* @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, * @throws IOException on error setting the port parameters
* {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. * @throws UnsupportedOperationException if values are not supported by a specific device
* @throws IOException on error setting the port parameters */
* @throws UnsupportedOperationException if values are not supported by a specific device void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
*/
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; /**
* Gets the CD (Carrier Detect) bit from the underlying UART.
/** *
* Gets the CD (Carrier Detect) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getCD() throws IOException;
*/
public boolean getCD() throws IOException; /**
* Gets the CTS (Clear To Send) bit from the underlying UART.
/** *
* Gets the CTS (Clear To Send) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getCTS() throws IOException;
*/
public boolean getCTS() throws IOException; /**
* Gets the DSR (Data Set Ready) bit from the underlying UART.
/** *
* Gets the DSR (Data Set Ready) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getDSR() throws IOException;
*/
public boolean getDSR() throws IOException; /**
* Gets the DTR (Data Terminal Ready) bit from the underlying UART.
/** *
* Gets the DTR (Data Terminal Ready) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getDTR() throws IOException;
*/
public boolean getDTR() throws IOException; /**
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported.
/** *
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. * @param value the value to set
* * @throws IOException if an error occurred during writing
* @param value the value to set * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during writing */
* @throws UnsupportedOperationException if not supported void setDTR(boolean value) throws IOException;
*/
public void setDTR(boolean value) throws IOException; /**
* Gets the RI (Ring Indicator) bit from the underlying UART.
/** *
* Gets the RI (Ring Indicator) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getRI() throws IOException;
*/
public boolean getRI() throws IOException; /**
* Gets the RTS (Request To Send) bit from the underlying UART.
/** *
* Gets the RTS (Request To Send) bit from the underlying UART. * @return the current state
* * @throws IOException if an error occurred during reading
* @return the current state * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during reading */
* @throws UnsupportedOperationException if not supported boolean getRTS() throws IOException;
*/
public boolean getRTS() throws IOException; /**
* Sets the RTS (Request To Send) bit on the underlying UART, if supported.
/** *
* Sets the RTS (Request To Send) bit on the underlying UART, if supported. * @param value the value to set
* * @throws IOException if an error occurred during writing
* @param value the value to set * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during writing */
* @throws UnsupportedOperationException if not supported void setRTS(boolean value) throws IOException;
*/
public void setRTS(boolean value) throws IOException; /**
* Gets all control line values from the underlying UART, if supported.
/** * Requires less USB calls than calling getRTS() + ... + getRI() individually.
* Gets all control line values from the underlying UART, if supported. *
* Requires less USB calls than calling getRTS() + ... + getRI() individually. * @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* * @throws IOException if an error occurred during reading
* @return EnumSet.contains(...) is {@code true} if set, else {@code false} */
* @throws IOException if an error occurred during reading EnumSet<ControlLine> getControlLines() throws IOException;
*/
public EnumSet<ControlLine> getControlLines() throws IOException; /**
* Gets all control line supported flags.
/** *
* Gets all control line supported flags. * @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* * @throws IOException if an error occurred during reading
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false} */
* @throws IOException if an error occurred during reading EnumSet<ControlLine> getSupportedControlLines() throws IOException;
*/
public EnumSet<ControlLine> getSupportedControlLines() throws IOException; /**
* Purge non-transmitted output data and / or non-read input data.
/** *
* Purge non-transmitted output data and / or non-read input data. * @param purgeWriteBuffers {@code true} to discard non-transmitted output data
* * @param purgeReadBuffers {@code true} to discard non-read input data
* @param purgeWriteBuffers {@code true} to discard non-transmitted output data * @throws IOException if an error occurred during flush
* @param purgeReadBuffers {@code true} to discard non-read input data * @throws UnsupportedOperationException if not supported
* @throws IOException if an error occurred during flush */
* @throws UnsupportedOperationException if not supported void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
*/
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; /**
* send BREAK condition.
/** *
* send BREAK condition. * @param value set/reset
* */
* @param value set/reset void setBreak(boolean value) throws IOException;
*/
public void setBreak(boolean value) throws IOException; /**
* Returns the current state of the connection.
/** */
* Returns the current state of the connection. boolean isOpen();
*/
public boolean isOpen(); }
}

View file

@ -1,92 +1,92 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.driver; package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public class UsbSerialProber { public class UsbSerialProber {
private final ProbeTable mProbeTable; private final ProbeTable mProbeTable;
public UsbSerialProber(ProbeTable probeTable) { public UsbSerialProber(ProbeTable probeTable) {
mProbeTable = probeTable; mProbeTable = probeTable;
} }
public static UsbSerialProber getDefaultProber() { public static UsbSerialProber getDefaultProber() {
return new UsbSerialProber(getDefaultProbeTable()); return new UsbSerialProber(getDefaultProbeTable());
} }
public static ProbeTable getDefaultProbeTable() { public static ProbeTable getDefaultProbeTable() {
final ProbeTable probeTable = new ProbeTable(); final ProbeTable probeTable = new ProbeTable();
probeTable.addDriver(CdcAcmSerialDriver.class); probeTable.addDriver(CdcAcmSerialDriver.class);
probeTable.addDriver(Cp21xxSerialDriver.class); probeTable.addDriver(Cp21xxSerialDriver.class);
probeTable.addDriver(FtdiSerialDriver.class); probeTable.addDriver(FtdiSerialDriver.class);
probeTable.addDriver(ProlificSerialDriver.class); probeTable.addDriver(ProlificSerialDriver.class);
probeTable.addDriver(Ch34xSerialDriver.class); probeTable.addDriver(Ch34xSerialDriver.class);
return probeTable; return probeTable;
} }
/** /**
* Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers}
* from the currently-attached {@link UsbDevice} hierarchy. This method does * from the currently-attached {@link UsbDevice} hierarchy. This method does
* not require permission from the Android USB system, since it does not * not require permission from the Android USB system, since it does not
* open any of the devices. * open any of the devices.
* *
* @param usbManager * @param usbManager usb manager
* @return a list, possibly empty, of all compatible drivers * @return a list, possibly empty, of all compatible drivers
*/ */
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) { public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
final List<UsbSerialDriver> result = new ArrayList<UsbSerialDriver>(); final List<UsbSerialDriver> result = new ArrayList<>();
for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
final UsbSerialDriver driver = probeDevice(usbDevice); final UsbSerialDriver driver = probeDevice(usbDevice);
if (driver != null) { if (driver != null) {
result.add(driver); result.add(driver);
} }
} }
return result; return result;
} }
/** /**
* Probes a single device for a compatible driver. * Probes a single device for a compatible driver.
* *
* @param usbDevice the usb device to probe * @param usbDevice the usb device to probe
* @return a new {@link UsbSerialDriver} compatible with this device, or * @return a new {@link UsbSerialDriver} compatible with this device, or
* {@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 int vendorId = usbDevice.getVendorId();
final int productId = usbDevice.getProductId(); final int productId = usbDevice.getProductId();
final Class<? extends UsbSerialDriver> driverClass = final Class<? extends UsbSerialDriver> driverClass =
mProbeTable.findDriver(vendorId, productId); mProbeTable.findDriver(vendorId, productId);
if (driverClass != null) { if (driverClass != null) {
final UsbSerialDriver driver; final UsbSerialDriver driver;
try { try {
final Constructor<? extends UsbSerialDriver> ctor = final Constructor<? extends UsbSerialDriver> ctor =
driverClass.getConstructor(UsbDevice.class); driverClass.getConstructor(UsbDevice.class);
driver = ctor.newInstance(usbDevice); driver = ctor.newInstance(usbDevice);
} catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException |
IllegalAccessException | InvocationTargetException e) { IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return driver; return driver;
} }
return null; return null;
} }
} }

View file

@ -0,0 +1,14 @@
package org.emulator.calculator.usbserial.util;
public final class MonotonicClock {
private static final long NS_PER_MS = 1_000_000;
private MonotonicClock() {
}
public static long millis() {
return System.nanoTime() / NS_PER_MS;
}
}

View file

@ -1,239 +1,254 @@
/* Copyright 2011-2013 Google Inc. /* Copyright 2011-2013 Google Inc.
* Copyright 2013 mike wakerly <opensource@hoho.com> * Copyright 2013 mike wakerly <opensource@hoho.com>
* *
* Project home page: https://github.com/mik3y/usb-serial-for-android * Project home page: https://github.com/mik3y/usb-serial-for-android
*/ */
package org.emulator.calculator.usbserial.util; package org.emulator.calculator.usbserial.util;
import android.os.Process; import android.os.Process;
import android.util.Log; import android.util.Log;
import org.emulator.calculator.usbserial.driver.UsbSerialPort; import org.emulator.calculator.usbserial.driver.UsbSerialPort;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method.
* *
* @author mike wakerly (opensource@hoho.com) * @author mike wakerly (opensource@hoho.com)
*/ */
public class SerialInputOutputManager implements Runnable { public class SerialInputOutputManager implements Runnable {
private static final String TAG = SerialInputOutputManager.class.getSimpleName(); private static final String TAG = SerialInputOutputManager.class.getSimpleName();
private static final boolean DEBUG = false; public static boolean DEBUG = false;
private static final int BUFSIZ = 4096; private static final int BUFSIZ = 4096;
/** /**
* default read timeout is infinite, to avoid data loss with bulkTransfer API * default read timeout is infinite, to avoid data loss with bulkTransfer API
*/ */
private int mReadTimeout = 0; private int mReadTimeout = 0;
private int mWriteTimeout = 0; private int mWriteTimeout = 0;
private final Object mReadBufferLock = new Object(); private final Object mReadBufferLock = new Object();
private final Object mWriteBufferLock = new Object(); private final Object mWriteBufferLock = new Object();
private ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
public enum State { public enum State {
STOPPED, STOPPED,
RUNNING, RUNNING,
STOPPING STOPPING
} }
private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO;
private State mState = State.STOPPED; // Synchronized by 'this' private State mState = State.STOPPED; // Synchronized by 'this'
private Listener mListener; // Synchronized by 'this' private Listener mListener; // Synchronized by 'this'
private final UsbSerialPort mSerialPort; private final UsbSerialPort mSerialPort;
public interface Listener { public interface Listener {
/** /**
* Called when new incoming data is available. * Called when new incoming data is available.
*/ */
public void onNewData(byte[] data); void onNewData(byte[] data);
/** /**
* Called when {@link SerialInputOutputManager#run()} aborts due to an error. * Called when {@link SerialInputOutputManager#run()} aborts due to an error.
*/ */
public void onRunError(Exception e); void onRunError(Exception e);
} }
public SerialInputOutputManager(UsbSerialPort serialPort) { public SerialInputOutputManager(UsbSerialPort serialPort) {
mSerialPort = serialPort; mSerialPort = serialPort;
} mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
}
public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
mSerialPort = serialPort; public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
mListener = listener; mSerialPort = serialPort;
} mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
public synchronized void setListener(Listener listener) { }
mListener = listener;
} public synchronized void setListener(Listener listener) {
mListener = listener;
public synchronized Listener getListener() { }
return mListener;
} public synchronized Listener getListener() {
return mListener;
/** }
* setThreadPriority. By default use higher priority than UI thread to prevent data loss
* /**
* @param threadPriority see {@link Process#setThreadPriority(int)} * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss
* */ *
public void setThreadPriority(int threadPriority) { * @param threadPriority see {@link Process#setThreadPriority(int)}
if (mState != State.STOPPED) * */
throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); public void setThreadPriority(int threadPriority) {
mThreadPriority = threadPriority; if (mState != State.STOPPED)
} throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started");
mThreadPriority = threadPriority;
/** }
* read/write timeout
*/ /**
public void setReadTimeout(int timeout) { * read/write timeout
// when set if already running, read already blocks and the new value will not become effective now */
if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) public void setReadTimeout(int timeout) {
throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); // when set if already running, read already blocks and the new value will not become effective now
mReadTimeout = timeout; if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED)
} throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started");
mReadTimeout = timeout;
public int getReadTimeout() { }
return mReadTimeout;
} public int getReadTimeout() {
return mReadTimeout;
public void setWriteTimeout(int timeout) { }
mWriteTimeout = timeout;
} public void setWriteTimeout(int timeout) {
mWriteTimeout = timeout;
public int getWriteTimeout() { }
return mWriteTimeout;
} public int getWriteTimeout() {
return mWriteTimeout;
/** }
* read/write buffer size
*/ /**
public void setReadBufferSize(int bufferSize) { * read/write buffer size
if (getReadBufferSize() == bufferSize) */
return; public void setReadBufferSize(int bufferSize) {
synchronized (mReadBufferLock) { if (getReadBufferSize() == bufferSize)
mReadBuffer = ByteBuffer.allocate(bufferSize); return;
} synchronized (mReadBufferLock) {
} mReadBuffer = ByteBuffer.allocate(bufferSize);
}
public int getReadBufferSize() { }
return mReadBuffer.capacity();
} public int getReadBufferSize() {
return mReadBuffer.capacity();
public void setWriteBufferSize(int bufferSize) { }
if(getWriteBufferSize() == bufferSize)
return; public void setWriteBufferSize(int bufferSize) {
synchronized (mWriteBufferLock) { if(getWriteBufferSize() == bufferSize)
ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); return;
if(mWriteBuffer.position() > 0) synchronized (mWriteBufferLock) {
newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize);
mWriteBuffer = newWriteBuffer; if(mWriteBuffer.position() > 0)
} newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position());
} mWriteBuffer = newWriteBuffer;
}
public int getWriteBufferSize() { }
return mWriteBuffer.capacity();
} public int getWriteBufferSize() {
return mWriteBuffer.capacity();
/* }
* when writeAsync is used, it is recommended to use readTimeout != 0,
* else the write will be delayed until read data is available /**
*/ * when using writeAsync, it is recommended to use readTimeout != 0,
public void writeAsync(byte[] data) { * else the write will be delayed until read data is available
synchronized (mWriteBufferLock) { */
mWriteBuffer.put(data); public void writeAsync(byte[] data) {
} synchronized (mWriteBufferLock) {
} mWriteBuffer.put(data);
}
public synchronized void stop() { }
if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested"); /**
mState = State.STOPPING; * start SerialInputOutputManager in separate thread
} */
} public void start() {
if(mState != State.STOPPED)
public synchronized State getState() { throw new IllegalStateException("already started");
return mState; new Thread(this, this.getClass().getSimpleName()).start();
} }
/** /**
* Continuously services the read and write buffers until {@link #stop()} is * stop SerialInputOutputManager thread
* called, or until a driver exception is raised. *
*/ * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to
@Override * interrupt blocking read
public void run() { */
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) public synchronized void stop() {
setThreadPriority(mThreadPriority); if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested");
synchronized (this) { mState = State.STOPPING;
if (getState() != State.STOPPED) { }
throw new IllegalStateException("Already running"); }
}
mState = State.RUNNING; public synchronized State getState() {
} return mState;
}
Log.i(TAG, "Running ...");
try { /**
while (true) { * Continuously services the read and write buffers until {@link #stop()} is
if (getState() != State.RUNNING) { * called, or until a driver exception is raised.
Log.i(TAG, "Stopping mState=" + getState()); */
break; @Override
} public void run() {
step(); synchronized (this) {
} if (getState() != State.STOPPED) {
} catch (Exception e) { throw new IllegalStateException("Already running");
Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); }
final Listener listener = getListener(); mState = State.RUNNING;
if (listener != null) { }
listener.onRunError(e); Log.i(TAG, "Running ...");
} try {
} finally { if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
synchronized (this) { Process.setThreadPriority(mThreadPriority);
mState = State.STOPPED; while (true) {
Log.i(TAG, "Stopped"); if (getState() != State.RUNNING) {
} Log.i(TAG, "Stopping mState=" + getState());
} break;
} }
step();
private void step() throws IOException { }
// Handle incoming data. } catch (Exception e) {
byte[] buffer = null; Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
synchronized (mReadBufferLock) { final Listener listener = getListener();
buffer = mReadBuffer.array(); if (listener != null) {
} listener.onRunError(e);
int len = mSerialPort.read(buffer, mReadTimeout); }
if (len > 0) { } finally {
if (DEBUG) Log.d(TAG, "Read data len=" + len); synchronized (this) {
final Listener listener = getListener(); mState = State.STOPPED;
if (listener != null) { Log.i(TAG, "Stopped");
final byte[] data = new byte[len]; }
System.arraycopy(buffer, 0, data, 0, len); }
listener.onNewData(data); }
}
} private void step() throws IOException {
// Handle incoming data.
// Handle outgoing data. byte[] buffer;
buffer = null; synchronized (mReadBufferLock) {
synchronized (mWriteBufferLock) { buffer = mReadBuffer.array();
len = mWriteBuffer.position(); }
if (len > 0) { int len = mSerialPort.read(buffer, mReadTimeout);
buffer = new byte[len]; if (len > 0) {
mWriteBuffer.rewind(); if (DEBUG) Log.d(TAG, "Read data len=" + len);
mWriteBuffer.get(buffer, 0, len); final Listener listener = getListener();
mWriteBuffer.clear(); if (listener != null) {
} final byte[] data = new byte[len];
} System.arraycopy(buffer, 0, data, 0, len);
if (buffer != null) { listener.onNewData(data);
if (DEBUG) { }
Log.d(TAG, "Writing data len=" + len); }
}
mSerialPort.write(buffer, mWriteTimeout); // Handle outgoing data.
} buffer = null;
} synchronized (mWriteBufferLock) {
len = mWriteBuffer.position();
} if (len > 0) {
buffer = new byte[len];
mWriteBuffer.rewind();
mWriteBuffer.get(buffer, 0, len);
mWriteBuffer.clear();
}
}
if (buffer != null) {
if (DEBUG) {
Log.d(TAG, "Writing data len=" + len);
}
mSerialPort.write(buffer, mWriteTimeout);
}
}
}

View file

@ -53,7 +53,6 @@ import androidx.core.content.FileProvider;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
@ -2041,11 +2040,18 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
int openSerialPort(String serialPort) { int openSerialPort(String serialPort) {
// Search if this same serial port is not already opened
Integer serialPortId = serialIndex; Integer serialPortId = serialIndex;
Serial serial = new Serial(this, serialPortId); Serial serial = new Serial(this, serialPortId);
if(serial.connect(serialPort)) { if(serial.connect(serialPort)) {
serialsById.put(serialPortId, serial); serialsById.put(serialPortId, serial);
serialIndex++; serialIndex++;
runOnUiThread(() -> {
int resId = Utils.resId(MainActivity.this, "string", "serial_connection_succeeded");
Toast.makeText(MainActivity.this, resId, Toast.LENGTH_SHORT).show();
});
return serialPortId; return serialPortId;
} else { } else {
runOnUiThread(() -> { runOnUiThread(() -> {
@ -2069,6 +2075,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
if(serial != null) { if(serial != null) {
serialsById.remove(serialPortIdInt); serialsById.remove(serialPortIdInt);
serial.disconnect(); serial.disconnect();
runOnUiThread(() -> {
int resId = Utils.resId(MainActivity.this, "string", "serial_disconnection_succeeded");
Toast.makeText(MainActivity.this, resId, Toast.LENGTH_SHORT).show();
});
return 1; return 1;
} }
return 0; return 0;
@ -2088,7 +2098,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
Integer serialPortIdInt = serialPortId; Integer serialPortIdInt = serialPortId;
Serial serial = serialsById.get(serialPortIdInt); Serial serial = serialsById.get(serialPortIdInt);
if(serial != null) if(serial != null)
return serial.receive(nNumberOfBytesToRead); return serial.read(nNumberOfBytesToRead);
return new byte[0]; return new byte[0];
} }
@ -2097,7 +2107,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
Integer serialPortIdInt = serialPortId; Integer serialPortIdInt = serialPortId;
Serial serial = serialsById.get(serialPortIdInt); Serial serial = serialsById.get(serialPortIdInt);
if(serial != null) if(serial != null)
return serial.send(buffer); return serial.write(buffer);
return 0;
}
@SuppressWarnings("UnusedDeclaration")
int serialPortPurgeComm(int serialPortId, int dwFlags) {
Integer serialPortIdInt = serialPortId;
Serial serial = serialsById.get(serialPortIdInt);
if(serial != null)
return serial.purgeComm(dwFlags);
return 0; return 0;
} }

View file

@ -135,6 +135,8 @@
<string name="serial_ports_device_no_driver">No driver</string> <string name="serial_ports_device_no_driver">No driver</string>
<string name="serial_ports_device_item_title">%s, Port %d</string> <string name="serial_ports_device_item_title">%s, Port %d</string>
<string name="serial_ports_device_item_description">Vendor %04X, Product %04X</string> <string name="serial_ports_device_item_description">Vendor %04X, Product %04X</string>
<string name="serial_connection_succeeded">USB serial device connected.</string>
<string name="serial_disconnection_succeeded">USB serial device disconnected.</string>
<string name="serial_connection_failed_device_not_found">Failed to connect the USB serial device: device not found (check the settings).</string> <string name="serial_connection_failed_device_not_found">Failed to connect the USB serial device: device not found (check the settings).</string>
<string name="serial_connection_failed_no_driver_for_device">Failed to connect the USB serial device: no driver for device (check the settings).</string> <string name="serial_connection_failed_no_driver_for_device">Failed to connect the USB serial device: no driver for device (check the settings).</string>
<string name="serial_connection_failed_not_enough_ports_at_device">Failed to connect the USB serial device: not enough ports at device (check the settings).</string> <string name="serial_connection_failed_not_enough_ports_at_device">Failed to connect the USB serial device: not enough ports at device (check the settings).</string>

View file

@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.android.tools.build:gradle:7.0.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip