diff --git a/ReadMe.txt b/ReadMe.txt index b8076e0..edc212b 100644 --- a/ReadMe.txt +++ b/ReadMe.txt @@ -58,9 +58,15 @@ CHANGES Version 2.3 (2021-02-xx) - Add the serial port support (via USB OTG). - TODO: When stop the app, Serial exception seems to delay the save of the calc state!!!! - TODO: Inform if the connection is not possible. - TODO: What's happen if I hot unplug? + FIX: When stop the app, Serial exception seems to delay the save of the calc state!!!! + FIX: Inform if the connection is not possible. + 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) - 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. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 94c7423..d13f580 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.4.1) #add_compile_options(-DDEBUG_ANDROID_PAINT) #add_compile_options(-DDEBUG_ANDROID_THREAD) #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) diff --git a/app/src/main/cpp/emu-jni.c b/app/src/main/cpp/emu-jni.c index 031bf61..862dd2c 100644 --- a/app/src/main/cpp/emu-jni.c +++ b/app/src/main/cpp/emu-jni.c @@ -375,6 +375,20 @@ int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize) { 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 result = 0; JNIEnv *jniEnv = getJNIEnvironment(); @@ -1283,9 +1297,21 @@ JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setConfiguration(J SwitchToState(nOldState); } } 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) { - _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) diff --git a/app/src/main/cpp/win32-layer.c b/app/src/main/cpp/win32-layer.c index d130f48..f183a49 100644 --- a/app/src/main/cpp/win32-layer.c +++ b/app/src/main/cpp/win32-layer.c @@ -330,14 +330,13 @@ BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,LPDWO *lpNumberOfBytesWritten = (DWORD) writenByteCount; return writenByteCount >= 0; } 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); #if defined DEBUG_ANDROID_SERIAL 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); free(hexAsciiDump); #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) *lpNumberOfBytesWritten = (DWORD) writenByteCount; return writenByteCount >= 0; @@ -748,7 +747,7 @@ DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) BOOL WINAPI CloseHandle(HANDLE hObject) { FILE_LOGD("CloseHandle(hObject: %p)", hObject); - if(hObject) + if(!hObject) return FALSE; //https://msdn.microsoft.com/en-us/9b84891d-62ca-4ddc-97b7-c4c79482abd9 // Can be a thread/event/file handle! @@ -3107,6 +3106,7 @@ BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) { hFile->commEventMask = 0; } pthread_mutex_unlock(&commsLock); + SERIAL_LOGD("WaitCommEvent(hFile: %p, lpEvtMask: %p) -> *lpEvtMask=%d", hFile, lpEvtMask, *lpEvtMask); return TRUE; } return FALSE; @@ -3149,8 +3149,9 @@ BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) { return FALSE; } BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) { - SERIAL_LOGD("SetCommState(hFile: %p, dwFlags: 0x%08X) TODO", hFile, dwFlags); - //TODO + SERIAL_LOGD("PurgeComm(hFile: %p, dwFlags: 0x%08X) TODO", hFile, dwFlags); + if(hFile && hFile->handleType == HANDLE_TYPE_COM) + return serialPortPurgeComm(hFile->commId, dwFlags); return FALSE; } BOOL SetCommBreak(HANDLE hFile) { diff --git a/app/src/main/cpp/win32-layer.h b/app/src/main/cpp/win32-layer.h index 634fb9d..9d71091 100644 --- a/app/src/main/cpp/win32-layer.h +++ b/app/src/main/cpp/win32-layer.h @@ -1277,6 +1277,7 @@ extern int closeSerialPort(int serialPortId); extern int setSerialPortParameters(int serialPortId, int baudRate); extern int readSerialPort(int serialPortId, LPBYTE buffer, int nNumberOfBytesToRead); extern int writeSerialPort(int serialPortId, LPBYTE buffer, int bufferSize); +extern int serialPortPurgeComm(int serialPortId, int dwFlags); extern int serialPortSetBreak(int serialPortId); extern int serialPortClearBreak(int serialPortId); extern int showAlert(const TCHAR * messageText, int flags); diff --git a/app/src/main/java/org/emulator/calculator/PanAndScaleView.java b/app/src/main/java/org/emulator/calculator/PanAndScaleView.java index cbe607d..6b23df0 100644 --- a/app/src/main/java/org/emulator/calculator/PanAndScaleView.java +++ b/app/src/main/java/org/emulator/calculator/PanAndScaleView.java @@ -22,6 +22,7 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; import android.os.Handler; +import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; @@ -615,7 +616,7 @@ public class PanAndScaleView extends View { boolean osdAllowed = false; - Handler osdTimerHandler = new Handler(); + Handler osdTimerHandler = new Handler(Looper.getMainLooper()); Runnable osdTimerRunnable = () -> { // OSD should stop now! osdAllowed = false; diff --git a/app/src/main/java/org/emulator/calculator/Serial.java b/app/src/main/java/org/emulator/calculator/Serial.java index 3abb1bf..e7770f8 100644 --- a/app/src/main/java/org/emulator/calculator/Serial.java +++ b/app/src/main/java/org/emulator/calculator/Serial.java @@ -9,9 +9,11 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; import android.os.Handler; import android.os.Looper; +import android.os.SystemClock; import android.util.Log; 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.UsbSerialPort; import org.emulator.calculator.usbserial.driver.UsbSerialProber; @@ -26,13 +28,19 @@ import java.util.regex.Pattern; public class Serial { private static final String TAG = "Serial"; - private final boolean debug = false; + private final boolean debug = true; private final Context context; private final int serialPortId; private static final String INTENT_ACTION_GRANT_USB = "EMU48.GRANT_USB"; 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 SerialInputOutputManager usbIoManager; private UsbSerialPort usbSerialPort; @@ -41,7 +49,7 @@ public class Serial { private boolean connected = false; private String connectionStatus = ""; - private final ArrayDeque reveivedByteQueue = new ArrayDeque<>(); + private final ArrayDeque readByteQueue = new ArrayDeque<>(); public Serial(Context context, int serialPortId) { @@ -62,7 +70,7 @@ public class Serial { mainLooper = new Handler(Looper.getMainLooper()); } - public boolean connect(String serialPort) { + public synchronized boolean connect(String serialPort) { if(debug) Log.d(TAG, "connect( " + serialPort + ")"); Pattern patternSerialPort = Pattern.compile("\\\\.\\\\(\\d+),(\\d+)"); @@ -87,11 +95,11 @@ public class Serial { return false; } - public String getConnectionStatus() { + public synchronized String getConnectionStatus() { 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 + ")"); UsbDevice device = null; @@ -154,10 +162,10 @@ public class Serial { @Override public void onNewData(byte[] data) { if(debug) Log.d(TAG, "onNewData: " + Utils.bytesToHex(data)); - boolean wasEmpty = reveivedByteQueue.isEmpty(); - synchronized (reveivedByteQueue) { + boolean wasEmpty = readByteQueue.isEmpty(); + synchronized (readByteQueue) { for (byte datum : data) - reveivedByteQueue.add(datum); + readByteQueue.add(datum); } if (wasEmpty) onReceivedByteQueueNotEmpty(); @@ -172,14 +180,15 @@ public class Serial { usbIo.setDaemon(true); usbIo.start(); connected = true; + connectionStatus = ""; + if(debug) Log.d(TAG, "connected!"); + purgeComm(PURGE_TXCLEAR | PURGE_RXCLEAR); return true; } catch (Exception e) { connectionStatus = "serial_connection_failed_open_failed"; if(debug) Log.d(TAG, "connectionStatus = " + connectionStatus + ", " + e.getMessage()); disconnect(); } - if(debug) Log.d(TAG, "connected!"); - connectionStatus = ""; return false; } @@ -194,15 +203,16 @@ public class Serial { Log.d(TAG, "onRunError: " + e.getMessage()); 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); } - 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 +")"); try { @@ -214,38 +224,84 @@ public class Serial { 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; - synchronized (reveivedByteQueue) { - int nNumberOfReadBytes = Math.min(nNumberOfBytesToRead, reveivedByteQueue.size()); + synchronized (readByteQueue) { + int nNumberOfReadBytes = Math.min(nNumberOfBytesToRead, readByteQueue.size()); result = new byte[nNumberOfReadBytes]; for (int i = 0; i < nNumberOfReadBytes; i++) { - Byte byteRead = reveivedByteQueue.poll(); + Byte byteRead = readByteQueue.poll(); if(byteRead != null) result[i] = byteRead; } } - if(result == null) { - result = new byte[0]; - } else if(reveivedByteQueue.size() > 0) { + if(readByteQueue.size() > 0) mainLooper.post(() -> NativeLib.commEvent(serialPortId, NativeLib.EV_RXCHAR)); - } return result; } - public int send(byte[] data) { + long maxWritePeriod = 4; //ms + long lastTime = 0; + public synchronized int write(byte[] data) { if(!connected) 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 { - 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) { + if(debug) Log.d(TAG, "write() Exception: " + e.toString()); onReceivedError(e); } 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) return 0; @@ -258,7 +314,7 @@ public class Serial { return 0; } - public int clearBreak() { + public synchronized int clearBreak() { if(!connected) return 0; @@ -271,7 +327,7 @@ public class Serial { return 0; } - public void disconnect() { + public synchronized void disconnect() { if(debug) Log.d(TAG, "disconnect()"); connected = false; @@ -280,7 +336,9 @@ public class Serial { usbIoManager = null; try { usbSerialPort.close(); - } catch (IOException ignored) {} + } catch (IOException ignored) { + + } usbSerialPort = null; } } diff --git a/app/src/main/java/org/emulator/calculator/Utils.java b/app/src/main/java/org/emulator/calculator/Utils.java index 9e2868d..9201a68 100644 --- a/app/src/main/java/org/emulator/calculator/Utils.java +++ b/app/src/main/java/org/emulator/calculator/Utils.java @@ -216,12 +216,15 @@ public class Utils { private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 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++) { int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + hexChars[j * 3] = HEX_ARRAY[v >>> 4]; + 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); } } diff --git a/app/src/main/java/org/emulator/calculator/usbserial/DevicesFragment.java b/app/src/main/java/org/emulator/calculator/usbserial/DevicesFragment.java index bd8b41d..d9a7ebf 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/DevicesFragment.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/DevicesFragment.java @@ -62,7 +62,8 @@ public class DevicesFragment extends ListFragment { else 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; } }; @@ -90,6 +91,7 @@ public class DevicesFragment extends ListFragment { UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber(); UsbSerialProber usbCustomProber = CustomProber.getCustomProber(); listItems.clear(); + listItems.add(new ListItem(null, 0, null)); for(UsbDevice device : usbManager.getDeviceList().values()) { UsbSerialDriver driver = usbDefaultProber.probeDevice(device); if(driver == null) { @@ -98,8 +100,6 @@ public class DevicesFragment extends ListFragment { if(driver != null) { for(int port = 0; port < driver.getPorts().size(); port++) listItems.add(new ListItem(device, port, driver)); - } else { - listItems.add(new ListItem(device, 0, null)); } } listAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/CdcAcmSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/CdcAcmSerialDriver.java index fd9950e..df4d081 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/CdcAcmSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/CdcAcmSerialDriver.java @@ -204,7 +204,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { } @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) { throw new IllegalArgumentException("Invalid baud rate: " + baudRate); } @@ -287,7 +287,7 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { } public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap(); + final Map supportedDevices = new LinkedHashMap<>(); supportedDevices.put(UsbId.VENDOR_ARDUINO, new int[] { UsbId.ARDUINO_UNO, @@ -317,6 +317,10 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { new int[] { UsbId.ARM_MBED, }); + supportedDevices.put(UsbId.VENDOR_ST, + new int[] { + UsbId.ST_CDC, + }); return supportedDevices; } diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/Ch34xSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/Ch34xSerialDriver.java index 7edd424..dd09763 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/Ch34xSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/Ch34xSerialDriver.java @@ -1,374 +1,374 @@ -/* Copyright 2014 Andreas Butti - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; - -import java.io.IOException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Ch34xSerialDriver implements UsbSerialDriver { - - private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); - - private final UsbDevice mDevice; - private final UsbSerialPort mPort; - - private static final int LCR_ENABLE_RX = 0x80; - private static final int LCR_ENABLE_TX = 0x40; - private static final int LCR_MARK_SPACE = 0x20; - private static final int LCR_PAR_EVEN = 0x10; - private static final int LCR_ENABLE_PAR = 0x08; - private static final int LCR_STOP_BITS_2 = 0x04; - private static final int LCR_CS8 = 0x03; - private static final int LCR_CS7 = 0x02; - private static final int LCR_CS6 = 0x01; - private static final int LCR_CS5 = 0x00; - - private static final int GCL_CTS = 0x01; - private static final int GCL_DSR = 0x02; - private static final int GCL_RI = 0x04; - private static final int GCL_CD = 0x08; - private static final int SCL_DTR = 0x20; - private static final int SCL_RTS = 0x40; - - public Ch34xSerialDriver(UsbDevice device) { - mDevice = device; - mPort = new Ch340SerialPort(mDevice, 0); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return Collections.singletonList(mPort); - } - - public class Ch340SerialPort extends CommonUsbSerialPort { - - private static final int USB_TIMEOUT_MILLIS = 5000; - - private final int DEFAULT_BAUD_RATE = 9600; - - private boolean dtr = false; - private boolean rts = false; - - public Ch340SerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return Ch34xSerialDriver.this; - } - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - UsbInterface usbIface = mDevice.getInterface(i); - if (!mConnection.claimInterface(usbIface, true)) { - throw new IOException("Could not claim data interface"); - } - } - - UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); - for (int i = 0; i < dataIface.getEndpointCount(); i++) { - UsbEndpoint ep = dataIface.getEndpoint(i); - if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { - if (ep.getDirection() == UsbConstants.USB_DIR_IN) { - mReadEndpoint = ep; - } else { - mWriteEndpoint = ep; - } - } - } - - initialize(); - setBaudRate(DEFAULT_BAUD_RATE); - } - - @Override - protected void closeInt() { - try { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) - mConnection.releaseInterface(mDevice.getInterface(i)); - } catch(Exception ignored) {} - } - - private int controlOut(int request, int value, int index) { - final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; - return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, - value, index, null, 0, USB_TIMEOUT_MILLIS); - } - - - 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; - return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, - value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); - } - - - private void checkState(String msg, int request, int value, int[] expected) throws IOException { - byte[] buffer = new byte[expected.length]; - int ret = controlIn(request, value, 0, buffer); - - if (ret < 0) { - throw new IOException("Failed send cmd [" + msg + "]"); - } - - if (ret != expected.length) { - throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); - } - - for (int i = 0; i < expected.length; i++) { - if (expected[i] == -1) { - continue; - } - - int current = buffer[i] & 0xff; - if (expected[i] != current) { - throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); - } - } - } - - private void setControlLines() throws IOException { - if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { - throw new IOException("Failed to set control lines"); - } - } - - private byte getStatus() throws IOException { - byte[] buffer = new byte[2]; - int ret = controlIn(0x95, 0x0706, 0, buffer); - if (ret < 0) - throw new IOException("Error getting control lines"); - return buffer[0]; - } - - private void initialize() throws IOException { - checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); - - if (controlOut(0xa1, 0, 0) < 0) { - throw new IOException("Init failed: #2"); - } - - setBaudRate(DEFAULT_BAUD_RATE); - - checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); - - if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { - throw new IOException("Init failed: #5"); - } - - checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); - - if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { - throw new IOException("Init failed: #7"); - } - - setBaudRate(DEFAULT_BAUD_RATE); - - setControlLines(); - - checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); - } - - - private void setBaudRate(int baudRate) throws IOException { - final long CH341_BAUDBASE_FACTOR = 1532620800; - final int CH341_BAUDBASE_DIVMAX = 3; - - long factor = CH341_BAUDBASE_FACTOR / baudRate; - int divisor = CH341_BAUDBASE_DIVMAX; - - while ((factor > 0xfff0) && divisor > 0) { - factor >>= 3; - divisor--; - } - - if (factor > 0xfff0) { - throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); - } - - factor = 0x10000 - factor; - divisor |= 0x0080; // else ch341a waits until buffer full - int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor)); - if (ret < 0) { - throw new IOException("Error setting baud rate: #1)"); - } - - ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff)); - if (ret < 0) { - throw new IOException("Error setting baud rate: #2"); - } - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudRate(baudRate); - - int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; - - switch (dataBits) { - case DATABITS_5: - lcr |= LCR_CS5; - break; - case DATABITS_6: - lcr |= LCR_CS6; - break; - case DATABITS_7: - lcr |= LCR_CS7; - break; - case DATABITS_8: - lcr |= LCR_CS8; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - lcr |= LCR_ENABLE_PAR; - break; - case PARITY_EVEN: - lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; - break; - case PARITY_MARK: - lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; - break; - case PARITY_SPACE: - lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - lcr |= LCR_STOP_BITS_2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - int ret = controlOut(0x9a, 0x2518, lcr); - if (ret < 0) { - throw new IOException("Error setting control byte"); - } - } - - @Override - public boolean getCD() throws IOException { - return (getStatus() & GCL_CD) == 0; - } - - @Override - public boolean getCTS() throws IOException { - return (getStatus() & GCL_CTS) == 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & GCL_DSR) == 0; - } - - @Override - public boolean getDTR() throws IOException { - return dtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - dtr = value; - setControlLines(); - } - - @Override - public boolean getRI() throws IOException { - return (getStatus() & GCL_RI) == 0; - } - - @Override - public boolean getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - rts = value; - setControlLines(); - } - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); - if((status & GCL_CD) == 0) set.add(ControlLine.CD); - if((status & GCL_RI) == 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void setBreak(boolean value) throws IOException { - byte[] req = new byte[2]; - if(controlIn(0x95, 0x1805, 0, req) < 0) { - throw new IOException("Error getting BREAK condition"); - } - if(value) { - req[0] &= ~1; - req[1] &= ~0x40; - } else { - req[0] |= 1; - req[1] |= 0x40; - } - int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); - if(controlOut(0x9a, 0x1805, val) < 0) { - throw new IOException("Error setting BREAK condition"); - } - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ - UsbId.QINHENG_CH340, - UsbId.QINHENG_CH341A, - }); - return supportedDevices; - } - +/* Copyright 2014 Andreas Butti + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Ch34xSerialDriver implements UsbSerialDriver { + + private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + private static final int LCR_ENABLE_RX = 0x80; + private static final int LCR_ENABLE_TX = 0x40; + private static final int LCR_MARK_SPACE = 0x20; + private static final int LCR_PAR_EVEN = 0x10; + private static final int LCR_ENABLE_PAR = 0x08; + private static final int LCR_STOP_BITS_2 = 0x04; + private static final int LCR_CS8 = 0x03; + private static final int LCR_CS7 = 0x02; + private static final int LCR_CS6 = 0x01; + private static final int LCR_CS5 = 0x00; + + private static final int GCL_CTS = 0x01; + private static final int GCL_DSR = 0x02; + private static final int GCL_RI = 0x04; + private static final int GCL_CD = 0x08; + private static final int SCL_DTR = 0x20; + private static final int SCL_RTS = 0x40; + + public Ch34xSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new Ch340SerialPort(mDevice, 0); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + public class Ch340SerialPort extends CommonUsbSerialPort { + + private static final int USB_TIMEOUT_MILLIS = 5000; + + private final int DEFAULT_BAUD_RATE = 9600; + + private boolean dtr = false; + private boolean rts = false; + + public Ch340SerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Ch34xSerialDriver.this; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbIface = mDevice.getInterface(i); + if (!mConnection.claimInterface(usbIface, true)) { + throw new IOException("Could not claim data interface"); + } + } + + UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + initialize(); + setBaudRate(DEFAULT_BAUD_RATE); + } + + @Override + protected void closeInt() { + try { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) + mConnection.releaseInterface(mDevice.getInterface(i)); + } catch(Exception ignored) {} + } + + private int controlOut(int request, int value, int index) { + final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, + value, index, null, 0, USB_TIMEOUT_MILLIS); + } + + + 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; + return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, + value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); + } + + + private void checkState(String msg, int request, int value, int[] expected) throws IOException { + byte[] buffer = new byte[expected.length]; + int ret = controlIn(request, value, 0, buffer); + + if (ret < 0) { + throw new IOException("Failed send cmd [" + msg + "]"); + } + + if (ret != expected.length) { + throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); + } + + for (int i = 0; i < expected.length; i++) { + if (expected[i] == -1) { + continue; + } + + int current = buffer[i] & 0xff; + if (expected[i] != current) { + throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); + } + } + } + + private void setControlLines() throws IOException { + if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { + throw new IOException("Failed to set control lines"); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[2]; + int ret = controlIn(0x95, 0x0706, 0, buffer); + if (ret < 0) + throw new IOException("Error getting control lines"); + return buffer[0]; + } + + private void initialize() throws IOException { + checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); + + if (controlOut(0xa1, 0, 0) < 0) { + throw new IOException("Init failed: #2"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); + + if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { + throw new IOException("Init failed: #5"); + } + + checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); + + if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { + throw new IOException("Init failed: #7"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + setControlLines(); + + checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); + } + + + private void setBaudRate(int baudRate) throws IOException { + final long CH341_BAUDBASE_FACTOR = 1532620800; + final int CH341_BAUDBASE_DIVMAX = 3; + + long factor = CH341_BAUDBASE_FACTOR / baudRate; + int divisor = CH341_BAUDBASE_DIVMAX; + + while ((factor > 0xfff0) && divisor > 0) { + factor >>= 3; + divisor--; + } + + if (factor > 0xfff0) { + throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); + } + + factor = 0x10000 - factor; + divisor |= 0x0080; // else ch341a waits until buffer full + int ret = controlOut(0x9a, 0x1312, (int) ((factor & 0xff00) | divisor)); + if (ret < 0) { + throw new IOException("Error setting baud rate: #1)"); + } + + ret = controlOut(0x9a, 0x0f2c, (int) (factor & 0xff)); + if (ret < 0) { + throw new IOException("Error setting baud rate: #2"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; + + switch (dataBits) { + case DATABITS_5: + lcr |= LCR_CS5; + break; + case DATABITS_6: + lcr |= LCR_CS6; + break; + case DATABITS_7: + lcr |= LCR_CS7; + break; + case DATABITS_8: + lcr |= LCR_CS8; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + lcr |= LCR_ENABLE_PAR; + break; + case PARITY_EVEN: + lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; + break; + case PARITY_MARK: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; + break; + case PARITY_SPACE: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + lcr |= LCR_STOP_BITS_2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int ret = controlOut(0x9a, 0x2518, lcr); + if (ret < 0) { + throw new IOException("Error setting control byte"); + } + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & GCL_CD) == 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & GCL_CTS) == 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & GCL_DSR) == 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setControlLines(); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & GCL_RI) == 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setControlLines(); + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); + if((status & GCL_CD) == 0) set.add(ControlLine.CD); + if((status & GCL_RI) == 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void setBreak(boolean value) throws IOException { + byte[] req = new byte[2]; + if(controlIn(0x95, 0x1805, 0, req) < 0) { + throw new IOException("Error getting BREAK condition"); + } + if(value) { + req[0] &= ~1; + req[1] &= ~0x40; + } else { + req[0] |= 1; + req[1] |= 0x40; + } + int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); + if(controlOut(0x9a, 0x1805, val) < 0) { + throw new IOException("Error setting BREAK condition"); + } + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ + UsbId.QINHENG_CH340, + UsbId.QINHENG_CH341A, + }); + return supportedDevices; + } + } \ No newline at end of file diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/CommonUsbSerialPort.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/CommonUsbSerialPort.java index 3129cc9..0a7e93b 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/CommonUsbSerialPort.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/CommonUsbSerialPort.java @@ -1,270 +1,301 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbRequest; -import android.util.Log; - -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) - */ -public abstract class CommonUsbSerialPort implements UsbSerialPort { - - private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); - private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; - private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit - - protected final UsbDevice mDevice; - protected final int mPortNumber; - - // non-null when open() - protected UsbDeviceConnection mConnection = null; - protected UsbEndpoint mReadEndpoint; - protected UsbEndpoint mWriteEndpoint; - protected UsbRequest mUsbRequest; - - protected final Object mWriteBufferLock = new Object(); - /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ - protected byte[] mWriteBuffer; - - public CommonUsbSerialPort(UsbDevice device, int portNumber) { - mDevice = device; - 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>", - getClass().getSimpleName(), mDevice.getDeviceName(), - mDevice.getDeviceId(), mPortNumber); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public int getPortNumber() { - return mPortNumber; - } - - /** - * Returns the device serial number - * @return serial number - */ - @Override - public String getSerial() { - return mConnection.getSerial(); - } - - /** - * 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. - * - * @param bufferSize the size in bytes - */ - public final void setWriteBufferSize(int bufferSize) { - synchronized (mWriteBufferLock) { - if (bufferSize == mWriteBuffer.length) { - return; - } - mWriteBuffer = new byte[bufferSize]; - } - } - - @Override - public void open(UsbDeviceConnection connection) throws IOException { - if (mConnection != null) { - throw new IOException("Already open"); - } - mConnection = connection; - try { - openInt(connection); - if (mReadEndpoint == null || mWriteEndpoint == null) { - throw new IOException("Could not get read & write endpoints"); - } - mUsbRequest = new UsbRequest(); - mUsbRequest.initialize(mConnection, mReadEndpoint); - } catch(Exception e) { - close(); - throw e; - } - } - - protected abstract void openInt(UsbDeviceConnection connection) throws IOException; - - @Override - public void close() throws IOException { - if (mConnection == null) { - throw new IOException("Already closed"); - } - try { - mUsbRequest.cancel(); - } catch(Exception ignored) {} - mUsbRequest = null; - try { - closeInt(); - } catch(Exception ignored) {} - try { - mConnection.close(); - } catch(Exception ignored) {} - mConnection = null; - } - - protected abstract void closeInt(); - - /** - * use simple USB request supported by all devices to test if connection is still valid - */ - protected void testConnection() throws IOException { - byte[] buf = new byte[2]; - int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); - if(len < 0) - throw new IOException("USB get_status request failed"); - } - - @Override - public int read(final byte[] dest, final int timeout) throws IOException { - return read(dest, timeout, true); - } - - protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { - if(mConnection == null) { - throw new IOException("Connection closed"); - } - if(dest.length <= 0) { - throw new IllegalArgumentException("Read buffer to small"); - } - final int nread; - if (timeout != 0) { - // 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 - // 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) - // /system/lib64/libusbhost.so (usb_request_wait+192) - // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) - // data loss / crashes were observed with timeout up to 200 msec - long endTime = testConnection ? System.currentTimeMillis() + timeout : 0; - int readMax = Math.min(dest.length, MAX_READ_SIZE); - nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); - // Android error propagation is improvable, nread == -1 can be: timeout, connection lost, buffer undersized, ... - if(nread == -1 && testConnection && System.currentTimeMillis() < endTime) - testConnection(); - - } else { - final ByteBuffer buf = ByteBuffer.wrap(dest); - if (!mUsbRequest.queue(buf, dest.length)) { - throw new IOException("Queueing USB request failed"); - } - final UsbRequest response = mConnection.requestWait(); - if (response == null) { - throw new IOException("Waiting for USB request failed"); - } - nread = buf.position(); - } - if (nread > 0) - return nread; - else - return 0; - } - - @Override - public int write(final byte[] src, final int timeout) throws IOException { - int offset = 0; - - if(mConnection == null) { - throw new IOException("Connection closed"); - } - while (offset < src.length) { - final int writeLength; - final int amtWritten; - - synchronized (mWriteBufferLock) { - final byte[] writeBuffer; - - writeLength = Math.min(src.length - offset, mWriteBuffer.length); - if (offset == 0) { - writeBuffer = src; - } else { - // bulkTransfer does not support offsets, make a copy. - System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); - writeBuffer = mWriteBuffer; - } - - amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, timeout); - } - if (amtWritten <= 0) { - throw new IOException("Error writing " + writeLength - + " bytes at offset " + offset + " length=" + src.length); - } - - Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength); - offset += amtWritten; - } - return offset; - } - - @Override - public boolean isOpen() { - return mConnection != null; - } - - @Override - public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; - - @Override - public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getCTS() 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 getControlLines() throws IOException; - - @Override - public abstract EnumSet 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(); } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbRequest; +import android.util.Log; + +import org.emulator.calculator.usbserial.util.MonotonicClock; + +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) + */ +public abstract class CommonUsbSerialPort implements UsbSerialPort { + + private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); + private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024; + private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit + + protected final UsbDevice mDevice; + protected final int mPortNumber; + + // non-null when open() + protected UsbDeviceConnection mConnection = null; + protected UsbEndpoint mReadEndpoint; + protected UsbEndpoint mWriteEndpoint; + protected UsbRequest mUsbRequest; + + protected final Object mWriteBufferLock = new Object(); + /** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */ + protected byte[] mWriteBuffer; + + public CommonUsbSerialPort(UsbDevice device, int portNumber) { + mDevice = device; + 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>", + getClass().getSimpleName(), mDevice.getDeviceName(), + mDevice.getDeviceId(), mPortNumber); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public int getPortNumber() { + return mPortNumber; + } + + @Override + public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; } + + @Override + public UsbEndpoint getReadEndpoint() { return mReadEndpoint; } + + /** + * Returns the device serial number + * @return serial number + */ + @Override + public String getSerial() { + return mConnection.getSerial(); + } + + /** + * 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. + * + * @param bufferSize the size in bytes + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public void open(UsbDeviceConnection connection) throws IOException { + if (mConnection != null) { + throw new IOException("Already open"); + } + if(connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + mConnection = connection; + try { + openInt(connection); + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read & write endpoints"); + } + mUsbRequest = new UsbRequest(); + mUsbRequest.initialize(mConnection, mReadEndpoint); + } catch(Exception e) { + try { + close(); + } catch(Exception ignored) {} + throw e; + } + } + + protected abstract void openInt(UsbDeviceConnection connection) throws IOException; + + @Override + public void close() throws IOException { + if (mConnection == null) { + throw new IOException("Already closed"); + } + try { + mUsbRequest.cancel(); + } catch(Exception ignored) {} + mUsbRequest = null; + try { + closeInt(); + } catch(Exception ignored) {} + try { + mConnection.close(); + } catch(Exception ignored) {} + mConnection = null; + } + + protected abstract void closeInt(); + + /** + * use simple USB request supported by all devices to test if connection is still valid + */ + protected void testConnection() throws IOException { + byte[] buf = new byte[2]; + int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); + if(len < 0) + throw new IOException("USB get_status request failed"); + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + return read(dest, timeout, true); + } + + protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } + if(dest.length <= 0) { + throw new IllegalArgumentException("Read buffer to small"); + } + final int nread; + if (timeout != 0) { + // 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 + // 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) + // /system/lib64/libusbhost.so (usb_request_wait+192) + // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) + // data loss / crashes were observed with timeout up to 200 msec + long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; + int readMax = Math.min(dest.length, MAX_READ_SIZE); + nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); + // Android error propagation is improvable: + // nread == -1 can be: timeout, connection lost, buffer to small, ??? + if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) + testConnection(); + + } else { + final ByteBuffer buf = ByteBuffer.wrap(dest); + if (!mUsbRequest.queue(buf, dest.length)) { + throw new IOException("Queueing USB request failed"); + } + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Waiting for USB request failed"); + } + nread = buf.position(); + // Android error propagation is improvable: + // response != null & nread == 0 can be: connection lost, buffer to small, ??? + if(nread == 0) { + testConnection(); + } + } + return Math.max(nread, 0); + } + + @Override + public void write(final byte[] src, final int timeout) throws IOException { + int offset = 0; + final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); + + if(mConnection == null) { + throw new IOException("Connection closed"); + } + while (offset < src.length) { + int requestTimeout; + final int requestLength; + final int actualLength; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + requestLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, requestLength); + writeBuffer = mWriteBuffer; + } + if (timeout == 0 || offset == 0) { + requestTimeout = timeout; + } else { + requestTimeout = (int)(endTime - MonotonicClock.millis()); + if(requestTimeout == 0) + requestTimeout = -1; + } + if (requestTimeout < 0) { + actualLength = -2; + } else { + actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); + } + } + Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); + if (actualLength <= 0) { + if (timeout != 0 && MonotonicClock.millis() >= endTime) { + SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); + ex.bytesTransferred = offset; + throw ex; + } else { + throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); + } + } + offset += actualLength; + } + } + + @Override + public boolean isOpen() { + return mConnection != null; + } + + @Override + public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; + + @Override + public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getCTS() 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 getControlLines() throws IOException; + + @Override + public abstract EnumSet 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(); } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/Cp21xxSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/Cp21xxSerialDriver.java index d6ff1b9..c8f02c0 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/Cp21xxSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/Cp21xxSerialDriver.java @@ -1,335 +1,335 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Cp21xxSerialDriver implements UsbSerialDriver { - - private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); - - private final UsbDevice mDevice; - private final List mPorts; - - public Cp21xxSerialDriver(UsbDevice device) { - mDevice = device; - mPorts = new ArrayList<>(); - for( int port = 0; port < device.getInterfaceCount(); port++) { - mPorts.add(new Cp21xxSerialPort(mDevice, port)); - } - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return mPorts; - } - - public class Cp21xxSerialPort extends CommonUsbSerialPort { - - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - - /* - * Configuration Request Types - */ - private static final int REQTYPE_HOST_TO_DEVICE = 0x41; - private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; - - /* - * Configuration Request Codes - */ - 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_BREAK_REQUEST_CODE = 0x05; - private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; - private static final int SILABSER_SET_BAUDRATE = 0x1E; - private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; - private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; - - private static final int FLUSH_READ_CODE = 0x0a; - private static final int FLUSH_WRITE_CODE = 0x05; - - /* - * SILABSER_IFC_ENABLE_REQUEST_CODE - */ - private static final int UART_ENABLE = 0x0001; - private static final int UART_DISABLE = 0x0000; - - /* - * SILABSER_SET_MHS_REQUEST_CODE - */ - private static final int DTR_ENABLE = 0x101; - private static final int DTR_DISABLE = 0x100; - private static final int RTS_ENABLE = 0x202; - private static final int RTS_DISABLE = 0x200; - - /* - * SILABSER_GET_MDMSTS_REQUEST_CODE - */ - private static final int STATUS_CTS = 0x10; - private static final int STATUS_DSR = 0x20; - private static final int STATUS_RI = 0x40; - private static final int STATUS_CD = 0x80; - - - private boolean dtr = false; - private boolean rts = false; - - // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity - // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored - private boolean mIsRestrictedPort; - - public Cp21xxSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return Cp21xxSerialDriver.this; - } - - private void setConfigSingle(int request, int value) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, - mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); - } - } - - private byte getStatus() throws IOException { - byte[] buffer = new byte[1]; - int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, - mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 1) { - throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); - } - return buffer[0]; - } - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; - if(mPortNumber >= mDevice.getInterfaceCount()) { - throw new IOException("Unknown port number"); - } - UsbInterface dataIface = mDevice.getInterface(mPortNumber); - if (!mConnection.claimInterface(dataIface, true)) { - throw new IOException("Could not claim interface " + mPortNumber); - } - for (int i = 0; i < dataIface.getEndpointCount(); i++) { - UsbEndpoint ep = dataIface.getEndpoint(i); - if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { - if (ep.getDirection() == UsbConstants.USB_DIR_IN) { - mReadEndpoint = ep; - } else { - mWriteEndpoint = ep; - } - } - } - - setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); - } - - @Override - protected void closeInt() { - try { - setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); - } catch (Exception ignored) {} - try { - mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); - } catch(Exception ignored) {} - } - - private void setBaudRate(int baudRate) throws IOException { - byte[] data = new byte[] { - (byte) ( baudRate & 0xff), - (byte) ((baudRate >> 8 ) & 0xff), - (byte) ((baudRate >> 16) & 0xff), - (byte) ((baudRate >> 24) & 0xff) - }; - int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, - 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); - if (ret < 0) { - throw new IOException("Error setting baud rate"); - } - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudRate(baudRate); - - int configDataBits = 0; - switch (dataBits) { - case DATABITS_5: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0500; - break; - case DATABITS_6: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0600; - break; - case DATABITS_7: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0700; - break; - case DATABITS_8: - configDataBits |= 0x0800; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - configDataBits |= 0x0010; - break; - case PARITY_EVEN: - configDataBits |= 0x0020; - break; - case PARITY_MARK: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported parity: mark"); - configDataBits |= 0x0030; - break; - case PARITY_SPACE: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported parity: space"); - configDataBits |= 0x0040; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported stop bits: 2"); - configDataBits |= 2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); - } - - @Override - public boolean getCD() throws IOException { - return (getStatus() & STATUS_CD) != 0; - } - - @Override - public boolean getCTS() throws IOException { - return (getStatus() & STATUS_CTS) != 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & STATUS_DSR) != 0; - } - - @Override - public boolean getDTR() throws IOException { - return dtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - dtr = value; - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); - } - - @Override - public boolean getRI() throws IOException { - return (getStatus() & STATUS_RI) != 0; - } - - @Override - public boolean getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - rts = value; - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); - } - - @Override - public EnumSet getControlLines() throws IOException { - byte status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); - if((status & STATUS_CD) != 0) set.add(ControlLine.CD); - if((status & STATUS_RI) != 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - // note: only working on some devices, on other devices ignored w/o error - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) - | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); - - if (value != 0) { - setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); - } - } - - @Override - public void setBreak(boolean value) throws IOException { - setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(UsbId.VENDOR_SILABS, - new int[] { - UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 - UsbId.SILABS_CP2105, - UsbId.SILABS_CP2108, - }); - return supportedDevices; - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Cp21xxSerialDriver implements UsbSerialDriver { + + private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public Cp21xxSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new Cp21xxSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class Cp21xxSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + /* + * Configuration Request Types + */ + private static final int REQTYPE_HOST_TO_DEVICE = 0x41; + private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; + + /* + * Configuration Request Codes + */ + 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_BREAK_REQUEST_CODE = 0x05; + private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; + private static final int SILABSER_SET_BAUDRATE = 0x1E; + private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; + private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; + + private static final int FLUSH_READ_CODE = 0x0a; + private static final int FLUSH_WRITE_CODE = 0x05; + + /* + * SILABSER_IFC_ENABLE_REQUEST_CODE + */ + private static final int UART_ENABLE = 0x0001; + private static final int UART_DISABLE = 0x0000; + + /* + * SILABSER_SET_MHS_REQUEST_CODE + */ + private static final int DTR_ENABLE = 0x101; + private static final int DTR_DISABLE = 0x100; + private static final int RTS_ENABLE = 0x202; + private static final int RTS_DISABLE = 0x200; + + /* + * SILABSER_GET_MDMSTS_REQUEST_CODE + */ + private static final int STATUS_CTS = 0x10; + private static final int STATUS_DSR = 0x20; + private static final int STATUS_RI = 0x40; + private static final int STATUS_CD = 0x80; + + + private boolean dtr = false; + private boolean rts = false; + + // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity + // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored + private boolean mIsRestrictedPort; + + public Cp21xxSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Cp21xxSerialDriver.this; + } + + private void setConfigSingle(int request, int value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); + } + return buffer[0]; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; + if(mPortNumber >= mDevice.getInterfaceCount()) { + throw new IOException("Unknown port number"); + } + UsbInterface dataIface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(dataIface, true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); + } + + @Override + protected void closeInt() { + try { + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + private void setBaudRate(int baudRate) throws IOException { + byte[] data = new byte[] { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff) + }; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, + 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); + if (ret < 0) { + throw new IOException("Error setting baud rate"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int configDataBits = 0; + switch (dataBits) { + case DATABITS_5: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0500; + break; + case DATABITS_6: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0600; + break; + case DATABITS_7: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0700; + break; + case DATABITS_8: + configDataBits |= 0x0800; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + configDataBits |= 0x0010; + break; + case PARITY_EVEN: + configDataBits |= 0x0020; + break; + case PARITY_MARK: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: mark"); + configDataBits |= 0x0030; + break; + case PARITY_SPACE: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: space"); + configDataBits |= 0x0040; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported stop bits: 2"); + configDataBits |= 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); + } + + @Override + public EnumSet getControlLines() throws IOException { + byte status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + // note: only working on some devices, on other devices ignored w/o error + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) { + setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_SILABS, + new int[] { + UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 + UsbId.SILABS_CP2105, + UsbId.SILABS_CP2108, + }); + return supportedDevices; + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/FtdiSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/FtdiSerialDriver.java index b36b660..4fa50e0 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/FtdiSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/FtdiSerialDriver.java @@ -1,428 +1,430 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * Copyright 2020 kai morich - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -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 - * 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(); - - private final UsbDevice mDevice; - private final List mPorts; - - public FtdiSerialDriver(UsbDevice device) { - mDevice = device; - 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 List getPorts() { - return mPorts; - } - - public class FtdiSerialPort extends CommonUsbSerialPort { - - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS - - private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; - private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; - - private static final int RESET_REQUEST = 0; - private static final int MODEM_CONTROL_REQUEST = 1; - private static final int SET_BAUD_RATE_REQUEST = 3; - private static final int SET_DATA_REQUEST = 4; - private static final int GET_MODEM_STATUS_REQUEST = 5; - private static final int SET_LATENCY_TIMER_REQUEST = 9; - private static final int GET_LATENCY_TIMER_REQUEST = 10; - - private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; - private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; - private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; - private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; - private static final int MODEM_STATUS_CTS = 0x10; - private static final int MODEM_STATUS_DSR = 0x20; - private static final int MODEM_STATUS_RI = 0x40; - private static final int MODEM_STATUS_CD = 0x80; - private static final int RESET_ALL = 0; - private static final int RESET_PURGE_RX = 1; - private static final int RESET_PURGE_TX = 2; - - private boolean baudRateWithPort = false; - private boolean dtr = false; - private boolean rts = false; - private int breakConfig = 0; - - public FtdiSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return FtdiSerialDriver.this; - } - - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { - throw new IOException("Could not claim interface " + mPortNumber); - } - if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { - throw new IOException("Not enough endpoints"); - } - mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); - mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); - - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, - RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Reset failed: result=" + result); - } - result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, - (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | - (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), - 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) { - throw new IOException("Could not get device descriptors"); - } - int deviceType = rawDescriptors[13]; - baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices - } - - @Override - protected void closeInt() { - 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) { - throw new IllegalArgumentException("Read buffer to small"); - // 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 - // data transfer at high baud rates when buffers are fully filled. - } - int nread; - if (timeout != 0) { - long endTime = System.currentTimeMillis() + timeout; - do { - nread = super.read(dest, Math.max(1, (int)(endTime - System.currentTimeMillis())), false); - } while (nread == READ_HEADER_LENGTH && System.currentTimeMillis() < endTime); - if(nread <= 0 && System.currentTimeMillis() < endTime) - testConnection(); - } else { - do { - nread = super.read(dest, timeout, false); - } 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; - for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { - int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); - if (length < 0) - throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); - System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); - destPos += length; - } - //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); - return destPos; - } - - private void setBaudrate(int baudRate) throws IOException { - int divisor, subdivisor, effectiveBaudRate; - if (baudRate > 3500000) { - throw new UnsupportedOperationException("Baud rate to high"); - } else if(baudRate >= 2500000) { - divisor = 0; - subdivisor = 0; - effectiveBaudRate = 3000000; - } else if(baudRate >= 1750000) { - divisor = 1; - subdivisor = 0; - effectiveBaudRate = 2000000; - } else { - divisor = (24000000 << 1) / baudRate; - divisor = (divisor + 1) >> 1; // round - subdivisor = divisor & 0x07; - divisor >>= 3; - if (divisor > 0x3fff) // exceeds bit 13 at 183 baud - throw new UnsupportedOperationException("Baud rate to low"); - effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); - effectiveBaudRate = (effectiveBaudRate +1) >> 1; - } - double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); - 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)); - int value = divisor; - int index = 0; - switch(subdivisor) { - case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 - case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 - case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 - case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 - case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 - 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 - case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 - } - if(baudRateWithPort) { - index <<= 8; - index |= mPortNumber+1; - } - Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", - baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); - - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, - 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) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudrate(baudRate); - - int config = 0; - switch (dataBits) { - case DATABITS_5: - case DATABITS_6: - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - case DATABITS_7: - case DATABITS_8: - config |= dataBits; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - config |= 0x100; - break; - case PARITY_EVEN: - config |= 0x200; - break; - case PARITY_MARK: - config |= 0x300; - break; - case PARITY_SPACE: - config |= 0x400; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - config |= 0x1000; - 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) { - 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, - 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 2) { - 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 getCTS() throws IOException { - return (getStatus() & MODEM_STATUS_CTS) != 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & MODEM_STATUS_DSR) != 0; - } - - @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, - value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - 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 getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, - value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Set DTR failed: result=" + result); - } - rts = value; - } - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); - 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 getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - if (purgeWriteBuffers) { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, - 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 (result != 0) { - throw new IOException("Purge read buffer failed: result=" + result); - } - } - } - - @Override - public void setBreak(boolean value) throws IOException { - int config = breakConfig; - if(value) config |= 0x4000; - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, - 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); - 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, - 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 1) { - throw new IOException("Get latency timer failed: result=" + result); - } - return data[0]; - } - - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(UsbId.VENDOR_FTDI, - new int[] { - UsbId.FTDI_FT232R, - UsbId.FTDI_FT232H, - UsbId.FTDI_FT2232H, - UsbId.FTDI_FT4232H, - UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD - }); - return supportedDevices; - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * Copyright 2020 kai morich + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import org.emulator.calculator.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +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 + * 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(); + + private final UsbDevice mDevice; + private final List mPorts; + + public FtdiSerialDriver(UsbDevice device) { + mDevice = device; + 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 List getPorts() { + return mPorts; + } + + public class FtdiSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS + + private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + + private static final int RESET_REQUEST = 0; + private static final int MODEM_CONTROL_REQUEST = 1; + private static final int SET_BAUD_RATE_REQUEST = 3; + private static final int SET_DATA_REQUEST = 4; + private static final int GET_MODEM_STATUS_REQUEST = 5; + private static final int SET_LATENCY_TIMER_REQUEST = 9; + private static final int GET_LATENCY_TIMER_REQUEST = 10; + + private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; + private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; + private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; + private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; + private static final int MODEM_STATUS_CTS = 0x10; + private static final int MODEM_STATUS_DSR = 0x20; + private static final int MODEM_STATUS_RI = 0x40; + private static final int MODEM_STATUS_CD = 0x80; + private static final int RESET_ALL = 0; + private static final int RESET_PURGE_RX = 1; + private static final int RESET_PURGE_TX = 2; + + private boolean baudRateWithPort = false; + private boolean dtr = false; + private boolean rts = false; + private int breakConfig = 0; + + public FtdiSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return FtdiSerialDriver.this; + } + + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { + throw new IOException("Not enough endpoints"); + } + mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); + mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Reset failed: result=" + result); + } + result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | + (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), + 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) { + throw new IOException("Could not get device descriptors"); + } + int deviceType = rawDescriptors[13]; + baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9; // ...H devices + } + + @Override + protected void closeInt() { + 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) { + throw new IllegalArgumentException("Read buffer to small"); + // 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 + // data transfer at high baud rates when buffers are fully filled. + } + int nread; + if (timeout != 0) { + long endTime = MonotonicClock.millis() + timeout; + do { + nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); + } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); + if(nread <= 0 && MonotonicClock.millis() < endTime) + testConnection(); + } else { + do { + nread = super.read(dest, timeout, false); + } while (nread == READ_HEADER_LENGTH); + } + return readFilter(dest, nread); + } + + protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException { + final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); + int destPos = 0; + for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { + int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); + if (length < 0) + throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); + System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); + destPos += length; + } + //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); + return destPos; + } + + private void setBaudrate(int baudRate) throws IOException { + int divisor, subdivisor, effectiveBaudRate; + if (baudRate > 3500000) { + throw new UnsupportedOperationException("Baud rate to high"); + } else if(baudRate >= 2500000) { + divisor = 0; + subdivisor = 0; + effectiveBaudRate = 3000000; + } else if(baudRate >= 1750000) { + divisor = 1; + subdivisor = 0; + effectiveBaudRate = 2000000; + } else { + divisor = (24000000 << 1) / baudRate; + divisor = (divisor + 1) >> 1; // round + subdivisor = divisor & 0x07; + divisor >>= 3; + if (divisor > 0x3fff) // exceeds bit 13 at 183 baud + throw new UnsupportedOperationException("Baud rate to low"); + effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); + effectiveBaudRate = (effectiveBaudRate +1) >> 1; + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + 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)); + int value = divisor; + int index = 0; + switch(subdivisor) { + case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 + case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 + case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 + case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 + case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 + 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 + case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 + } + if(baudRateWithPort) { + index <<= 8; + index |= mPortNumber+1; + } + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", + baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, + 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, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudrate(baudRate); + + int config = 0; + switch (dataBits) { + case DATABITS_5: + case DATABITS_6: + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + case DATABITS_7: + case DATABITS_8: + config |= dataBits; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + config |= 0x100; + break; + case PARITY_EVEN: + config |= 0x200; + break; + case PARITY_MARK: + config |= 0x300; + break; + case PARITY_SPACE: + config |= 0x400; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + config |= 0x1000; + 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) { + 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, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 2) { + 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 getCTS() throws IOException { + return (getStatus() & MODEM_STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & MODEM_STATUS_DSR) != 0; + } + + @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, + value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + 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 getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + rts = value; + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); + 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 getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + 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 (result != 0) { + throw new IOException("Purge read buffer failed: result=" + result); + } + } + } + + @Override + public void setBreak(boolean value) throws IOException { + int config = breakConfig; + if(value) config |= 0x4000; + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + 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); + 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, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Get latency timer failed: result=" + result); + } + return data[0]; + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_FTDI, + new int[] { + UsbId.FTDI_FT232R, + UsbId.FTDI_FT232H, + UsbId.FTDI_FT2232H, + UsbId.FTDI_FT4232H, + UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD + }); + return supportedDevices; + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/ProbeTable.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/ProbeTable.java index f6aeff6..b64918e 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/ProbeTable.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/ProbeTable.java @@ -1,87 +1,87 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.util.Pair; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Maps (vendor id, product id) pairs to the corresponding serial driver. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class ProbeTable { - - private final Map, Class> mProbeTable = - new LinkedHashMap, Class>(); - - /** - * Adds or updates a (vendor, product) pair in the table. - * - * @param vendorId the USB vendor id - * @param productId the USB product id - * @param driverClass the driver class responsible for this pair - * @return {@code this}, for chaining - */ - public ProbeTable addProduct(int vendorId, int productId, - Class driverClass) { - mProbeTable.put(Pair.create(vendorId, productId), driverClass); - return this; - } - - /** - * Internal method to add all supported products from - * {@code getSupportedProducts} static method. - * - * @param driverClass - * @return - */ - @SuppressWarnings("unchecked") - ProbeTable addDriver(Class driverClass) { - final Method method; - - try { - method = driverClass.getMethod("getSupportedDevices"); - } catch (SecurityException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - - final Map devices; - try { - devices = (Map) method.invoke(null); - } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - - for (Map.Entry entry : devices.entrySet()) { - final int vendorId = entry.getKey(); - for (int productId : entry.getValue()) { - addProduct(vendorId, productId, driverClass); - } - } - - return this; - } - - /** - * Returns the driver for the given (vendor, product) pair, or {@code null} - * if no match. - * - * @param vendorId the USB vendor id - * @param productId the USB product id - * @return the driver class matching this pair, or {@code null} - */ - public Class findDriver(int vendorId, int productId) { - final Pair pair = Pair.create(vendorId, productId); - return mProbeTable.get(pair); - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.util.Pair; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Maps (vendor id, product id) pairs to the corresponding serial driver. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class ProbeTable { + + private final Map, Class> mProbeTable = + new LinkedHashMap<>(); + + /** + * Adds or updates a (vendor, product) pair in the table. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @param driverClass the driver class responsible for this pair + * @return {@code this}, for chaining + */ + public ProbeTable addProduct(int vendorId, int productId, + Class driverClass) { + mProbeTable.put(Pair.create(vendorId, productId), driverClass); + return this; + } + + /** + * Internal method to add all supported products from + * {@code getSupportedProducts} static method. + * + * @param driverClass + * @return + */ + @SuppressWarnings("unchecked") + ProbeTable addDriver(Class driverClass) { + final Method method; + + try { + method = driverClass.getMethod("getSupportedDevices"); + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + final Map devices; + try { + devices = (Map) method.invoke(null); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + for (Map.Entry entry : devices.entrySet()) { + final int vendorId = entry.getKey(); + for (int productId : entry.getValue()) { + addProduct(vendorId, productId, driverClass); + } + } + + return this; + } + + /** + * Returns the driver for the given (vendor, product) pair, or {@code null} + * if no match. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @return the driver class matching this pair, or {@code null} + */ + public Class findDriver(int vendorId, int productId) { + final Pair pair = Pair.create(vendorId, productId); + return mProbeTable.get(pair); + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/ProlificSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/ProlificSerialDriver.java index aa470be..234215c 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/ProlificSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/ProlificSerialDriver.java @@ -1,507 +1,581 @@ -/* - * Ported to usb-serial-for-android by Felix Hädicke - * - * Based on the pyprolific driver written by Emmanuel Blot - * See https://github.com/eblot/pyftdi - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.util.Log; - -import java.io.IOException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class ProlificSerialDriver implements UsbSerialDriver { - - private final String TAG = ProlificSerialDriver.class.getSimpleName(); - - private final static int[] standardBaudRates = { - 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, - 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, - 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 - }; - private enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_HX}; - - private final UsbDevice mDevice; - private final UsbSerialPort mPort; - - public ProlificSerialDriver(UsbDevice device) { - mDevice = device; - mPort = new ProlificSerialPort(mDevice, 0); - } - - @Override - public List getPorts() { - return Collections.singletonList(mPort); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - class ProlificSerialPort extends CommonUsbSerialPort { - - private static final int USB_READ_TIMEOUT_MILLIS = 1000; - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - - private static final int USB_RECIP_INTERFACE = 0x01; - - private static final int PROLIFIC_VENDOR_READ_REQUEST = 0x01; - private static final int PROLIFIC_VENDOR_WRITE_REQUEST = 0x01; - - private static final int PROLIFIC_VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; - private static final int PROLIFIC_VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR; - private static final int PROLIFIC_CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; - - private static final int WRITE_ENDPOINT = 0x02; - private static final int READ_ENDPOINT = 0x83; - private static final int INTERRUPT_ENDPOINT = 0x81; - - private static final int FLUSH_RX_REQUEST = 0x08; // RX @ Prolific device = write @ usb-serial-for-android library - private static final int FLUSH_TX_REQUEST = 0x09; - - private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING - private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE - private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK - private static final int GET_CONTROL_REQUEST = 0x87; - private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length - - private static final int CONTROL_DTR = 0x01; - private static final int CONTROL_RTS = 0x02; - - private static final int GET_CONTROL_FLAG_CD = 0x02; - private static final int GET_CONTROL_FLAG_DSR = 0x04; - private static final int GET_CONTROL_FLAG_RI = 0x01; - private static final int GET_CONTROL_FLAG_CTS = 0x08; - - private static final int STATUS_FLAG_CD = 0x01; - private static final int STATUS_FLAG_DSR = 0x02; - private static final int STATUS_FLAG_RI = 0x08; - private static final int STATUS_FLAG_CTS = 0x80; - - private static final int STATUS_BUFFER_SIZE = 10; - private static final int STATUS_BYTE_IDX = 8; - - private DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; - private UsbEndpoint mInterruptEndpoint; - private int mControlLinesValue = 0; - private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; - - private int mStatus = 0; - private volatile Thread mReadStatusThread = null; - private final Object mReadStatusThreadLock = new Object(); - private boolean mStopReadStatusThread = false; - private IOException mReadStatusException = null; - - - public ProlificSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return ProlificSerialDriver.this; - } - - private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException { - byte[] buffer = new byte[length]; - int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); - if (result != length) { - throw new IOException(String.format("ControlTransfer 0x%x failed: %d",value, result)); - } - return buffer; - } - - private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException { - int length = (data == null) ? 0 : data.length; - int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); - if (result != length) { - throw new IOException( String.format("ControlTransfer 0x%x failed: %d", value, result)); - } - } - - private byte[] vendorIn(int value, int index, int length) throws IOException { - return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE, PROLIFIC_VENDOR_READ_REQUEST, value, index, length); - } - - private void vendorOut(int value, int index, byte[] data) throws IOException { - outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE, PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data); - } - - private void resetDevice() throws IOException { - purgeHwBuffers(true, true); - } - - private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { - outControlTransfer(PROLIFIC_CTRL_OUT_REQTYPE, request, value, index, data); - } - - private void doBlackMagic() throws IOException { - vendorIn(0x8484, 0, 1); - vendorOut(0x0404, 0, null); - vendorIn(0x8484, 0, 1); - vendorIn(0x8383, 0, 1); - vendorIn(0x8484, 0, 1); - vendorOut(0x0404, 1, null); - vendorIn(0x8484, 0, 1); - vendorIn(0x8383, 0, 1); - vendorOut(0, 1, null); - vendorOut(1, 0, null); - vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_HX) ? 0x44 : 0x24, null); - } - - private void setControlLines(int newControlLinesValue) throws IOException { - ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); - mControlLinesValue = newControlLinesValue; - } - - private void readStatusThreadFunction() { - try { - while (!mStopReadStatusThread) { - byte[] buffer = new byte[STATUS_BUFFER_SIZE]; - long endTime = System.currentTimeMillis() + 500; - int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); - if(readBytesCount == -1 && System.currentTimeMillis() < endTime) - testConnection(); - if (readBytesCount > 0) { - if (readBytesCount != STATUS_BUFFER_SIZE) { - throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); - } else if(buffer[0] != (byte)STATUS_NOTIFICATION ) { - throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]); - } else { - mStatus = buffer[STATUS_BYTE_IDX] & 0xff; - } - } - } - } catch (IOException e) { - mReadStatusException = e; - } - //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); - } - - private int getStatus() throws IOException { - if ((mReadStatusThread == null) && (mReadStatusException == null)) { - synchronized (mReadStatusThreadLock) { - if (mReadStatusThread == null) { - byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1); - mStatus = 0; - if((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; - if((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; - if((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; - if((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; - //Log.d(TAG, "start control line status thread " + mStatus); - mReadStatusThread = new Thread(this::readStatusThreadFunction); - mReadStatusThread.setDaemon(true); - mReadStatusThread.start(); - } - } - } - - /* throw and clear an exception which occured in the status read thread */ - IOException readStatusException = mReadStatusException; - if (mReadStatusException != null) { - mReadStatusException = null; - throw readStatusException; - } - - return mStatus; - } - - private boolean testStatusFlag(int flag) throws IOException { - return ((getStatus() & flag) == flag); - } - - @Override - public void openInt(UsbDeviceConnection connection) throws IOException { - UsbInterface usbInterface = mDevice.getInterface(0); - - if (!connection.claimInterface(usbInterface, true)) { - throw new IOException("Error claiming Prolific interface 0"); - } - - for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { - UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); - - switch (currentEndpoint.getAddress()) { - case READ_ENDPOINT: - mReadEndpoint = currentEndpoint; - break; - - case WRITE_ENDPOINT: - mWriteEndpoint = currentEndpoint; - break; - - case INTERRUPT_ENDPOINT: - mInterruptEndpoint = currentEndpoint; - break; - } - } - - if (mDevice.getDeviceClass() == 0x02) { - mDeviceType = DeviceType.DEVICE_TYPE_01; - } else { - byte[] rawDescriptors = connection.getRawDescriptors(); - if(rawDescriptors == null || rawDescriptors.length <8) { - Log.w(TAG, "Could not get device descriptors, Assuming that it is a HX device"); - mDeviceType = DeviceType.DEVICE_TYPE_HX; - } else { - byte maxPacketSize0 = rawDescriptors[7]; - if (maxPacketSize0 == 64) { - mDeviceType = DeviceType.DEVICE_TYPE_HX; - } else if ((mDevice.getDeviceClass() == 0x00) - || (mDevice.getDeviceClass() == 0xff)) { - mDeviceType = DeviceType.DEVICE_TYPE_01; - } else { - Log.w(TAG, "Could not detect PL2303 subtype, Assuming that it is a HX device"); - mDeviceType = DeviceType.DEVICE_TYPE_HX; - } - } - } - setControlLines(mControlLinesValue); - resetDevice(); - doBlackMagic(); - } - - @Override - public void closeInt() { - try { - synchronized (mReadStatusThreadLock) { - if (mReadStatusThread != null) { - try { - mStopReadStatusThread = true; - mReadStatusThread.join(); - } catch (Exception e) { - Log.w(TAG, "An error occured while waiting for status read thread", e); - } - mStopReadStatusThread = false; - mReadStatusThread = null; - mReadStatusException = null; - } - } - resetDevice(); - } catch(Exception ignored) {} - try { - mConnection.releaseInterface(mDevice.getInterface(0)); - } catch(Exception ignored) {} - } - - private int filterBaudRate(int baudRate) { -// if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) { -// return baudRate & ~(1<<29); // for testing purposes accept without further checks -// } - if (baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - for(int br : standardBaudRates) { - if (br == baudRate) { - return baudRate; - } - } - /* - * Formula taken from Linux + FreeBSD. - * baudrate = baseline / (mantissa * 4^exponent) - * where - * mantissa = buf[8:0] - * exponent = buf[11:9] - * - * Note: The formula does not work for all PL2303 variants. - * Ok for PL2303HX. Not ok for PL2303TA. Other variants unknown. - */ - int baseline, mantissa, exponent; - baseline = 12000000 * 32; - mantissa = baseline / baudRate; - if (mantissa == 0) { // > unrealistic 384 MBaud - throw new UnsupportedOperationException("Baud rate to high"); - } - exponent = 0; - while (mantissa >= 512) { - if (exponent < 7) { - mantissa >>= 2; /* divide by 4 */ - exponent++; - } else { // < 45.8 baud - throw new UnsupportedOperationException("Baud rate to low"); - } - } - int effectiveBaudRate = (baseline / mantissa) >> (exponent << 1); - double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); - if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud - throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); - int buf = mantissa + (exponent<<9) + (1<<31); - - Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d", - baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent)); - return buf; - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { - baudRate = filterBaudRate(baudRate); - if ((mBaudRate == baudRate) && (mDataBits == dataBits) - && (mStopBits == stopBits) && (mParity == parity)) { - // Make sure no action is performed if there is nothing to change - return; - } - - byte[] lineRequestData = new byte[7]; - lineRequestData[0] = (byte) (baudRate & 0xff); - lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); - lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); - lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); - - switch (stopBits) { - case STOPBITS_1: - lineRequestData[4] = 0; - break; - case STOPBITS_1_5: - lineRequestData[4] = 1; - break; - case STOPBITS_2: - lineRequestData[4] = 2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - switch (parity) { - case PARITY_NONE: - lineRequestData[5] = 0; - break; - case PARITY_ODD: - lineRequestData[5] = 1; - break; - case PARITY_EVEN: - lineRequestData[5] = 2; - break; - case PARITY_MARK: - lineRequestData[5] = 3; - break; - case PARITY_SPACE: - lineRequestData[5] = 4; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - lineRequestData[6] = (byte) dataBits; - - ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); - - resetDevice(); - - mBaudRate = baudRate; - mDataBits = dataBits; - mStopBits = stopBits; - mParity = parity; - } - - @Override - public boolean getCD() throws IOException { - return testStatusFlag(STATUS_FLAG_CD); - } - - @Override - public boolean getCTS() throws IOException { - return testStatusFlag(STATUS_FLAG_CTS); - } - - @Override - public boolean getDSR() throws IOException { - return testStatusFlag(STATUS_FLAG_DSR); - } - - @Override - public boolean getDTR() throws IOException { - return (mControlLinesValue & CONTROL_DTR) != 0; - } - - @Override - public void setDTR(boolean value) throws IOException { - int newControlLinesValue; - if (value) { - newControlLinesValue = mControlLinesValue | CONTROL_DTR; - } else { - newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; - } - setControlLines(newControlLinesValue); - } - - @Override - public boolean getRI() throws IOException { - return testStatusFlag(STATUS_FLAG_RI); - } - - @Override - public boolean getRTS() throws IOException { - return (mControlLinesValue & CONTROL_RTS) != 0; - } - - @Override - public void setRTS(boolean value) throws IOException { - int newControlLinesValue; - if (value) { - newControlLinesValue = mControlLinesValue | CONTROL_RTS; - } else { - newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; - } - setControlLines(newControlLinesValue); - } - - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS); - if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS); - if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR); - if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR); - if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD); - if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - if (purgeWriteBuffers) { - vendorOut(FLUSH_RX_REQUEST, 0, null); - } - - if (purgeReadBuffers) { - vendorOut(FLUSH_TX_REQUEST, 0, null); - } - } - - @Override - public void setBreak(boolean value) throws IOException { - ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null); - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap(); - supportedDevices.put(UsbId.VENDOR_PROLIFIC, - new int[] { UsbId.PROLIFIC_PL2303, }); - return supportedDevices; - } -} +/* + * Ported to usb-serial-for-android by Felix Hädicke + * + * Based on the pyprolific driver written by Emmanuel Blot + * See https://github.com/eblot/pyftdi + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +//import org.emulator.calculator.usbserial.BuildConfig; +import org.emulator.calculator.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ProlificSerialDriver implements UsbSerialDriver { + + private final String TAG = ProlificSerialDriver.class.getSimpleName(); + + private final static int[] standardBaudRates = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, + 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, + 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN} + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + public ProlificSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new ProlificSerialPort(mDevice, 0); + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + class ProlificSerialPort extends CommonUsbSerialPort { + + private static final int USB_READ_TIMEOUT_MILLIS = 1000; + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + private static final int USB_RECIP_INTERFACE = 0x01; + + private static final int VENDOR_READ_REQUEST = 0x01; + private static final int VENDOR_WRITE_REQUEST = 0x01; + private static final int VENDOR_READ_HXN_REQUEST = 0x81; + private static final int VENDOR_WRITE_HXN_REQUEST = 0x80; + + private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; + private static final int VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR; + private static final int CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int WRITE_ENDPOINT = 0x02; + private static final int READ_ENDPOINT = 0x83; + private static final int INTERRUPT_ENDPOINT = 0x81; + + private static final int RESET_HXN_REQUEST = 0x07; + private static final int FLUSH_RX_REQUEST = 0x08; + private static final int FLUSH_TX_REQUEST = 0x09; + private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING + private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE + private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK + private static final int GET_CONTROL_HXN_REQUEST = 0x80; + private static final int GET_CONTROL_REQUEST = 0x87; + private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length + + /* RESET_HXN_REQUEST */ + private static final int RESET_HXN_RX_PIPE = 1; + private static final int RESET_HXN_TX_PIPE = 2; + + /* SET_CONTROL_REQUEST */ + private static final int CONTROL_DTR = 0x01; + private static final int CONTROL_RTS = 0x02; + + /* GET_CONTROL_REQUEST */ + private static final int GET_CONTROL_FLAG_CD = 0x02; + private static final int GET_CONTROL_FLAG_DSR = 0x04; + private static final int GET_CONTROL_FLAG_RI = 0x01; + private static final int GET_CONTROL_FLAG_CTS = 0x08; + + /* GET_CONTROL_HXN_REQUEST */ + private static final int GET_CONTROL_HXN_FLAG_CD = 0x40; + private static final int GET_CONTROL_HXN_FLAG_DSR = 0x20; + private static final int GET_CONTROL_HXN_FLAG_RI = 0x80; + private static final int GET_CONTROL_HXN_FLAG_CTS = 0x08; + + /* interrupt endpoint read */ + private static final int STATUS_FLAG_CD = 0x01; + private static final int STATUS_FLAG_DSR = 0x02; + private static final int STATUS_FLAG_RI = 0x08; + private static final int STATUS_FLAG_CTS = 0x80; + + private static final int STATUS_BUFFER_SIZE = 10; + private static final int STATUS_BYTE_IDX = 8; + + protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; + private UsbEndpoint mInterruptEndpoint; + private int mControlLinesValue = 0; + private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; + + private int mStatus = 0; + private volatile Thread mReadStatusThread = null; + private final Object mReadStatusThreadLock = new Object(); + private boolean mStopReadStatusThread = false; + private IOException mReadStatusException = null; + + + public ProlificSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return ProlificSerialDriver.this; + } + + private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException { + byte[] buffer = new byte[length]; + int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException(String.format("ControlTransfer 0x%x failed: %d",value, result)); + } + return buffer; + } + + private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException { + int length = (data == null) ? 0 : data.length; + int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException( String.format("ControlTransfer 0x%x failed: %d", value, result)); + } + } + + private byte[] vendorIn(int value, int index, int length) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST; + return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length); + } + + private void vendorOut(int value, int index, byte[] data) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST; + outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data); + } + + private void resetDevice() throws IOException { + purgeHwBuffers(true, true); + } + + private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { + outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data); + } + + private boolean testHxStatus() { + try { + inControlTransfer(VENDOR_IN_REQTYPE, VENDOR_READ_REQUEST, 0x8080, 0, 1); + return true; + } catch(IOException ignored) { + return false; + } + } + + private void doBlackMagic() throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + return; + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 0, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 1, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorOut(0, 1, null); + vendorOut(1, 0, null); + vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null); + } + + private void setControlLines(int newControlLinesValue) throws IOException { + ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); + mControlLinesValue = newControlLinesValue; + } + + private void readStatusThreadFunction() { + try { + while (!mStopReadStatusThread) { + byte[] buffer = new byte[STATUS_BUFFER_SIZE]; + long endTime = MonotonicClock.millis() + 500; + int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); + if(readBytesCount == -1 && MonotonicClock.millis() < endTime) + testConnection(); + if (readBytesCount > 0) { + if (readBytesCount != STATUS_BUFFER_SIZE) { + throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); + } else if(buffer[0] != (byte)STATUS_NOTIFICATION ) { + throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]); + } else { + mStatus = buffer[STATUS_BYTE_IDX] & 0xff; + } + } + } + } catch (IOException e) { + mReadStatusException = e; + } + //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); + } + + private int getStatus() throws IOException { + if ((mReadStatusThread == null) && (mReadStatusException == null)) { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread == null) { + mStatus = 0; + if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } else { + byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } + //Log.d(TAG, "start control line status thread " + mStatus); + mReadStatusThread = new Thread(this::readStatusThreadFunction); + mReadStatusThread.setDaemon(true); + mReadStatusThread.start(); + } + } + } + + /* throw and clear an exception which occured in the status read thread */ + IOException readStatusException = mReadStatusException; + if (mReadStatusException != null) { + mReadStatusException = null; + throw new IOException(readStatusException); + } + + return mStatus; + } + + private boolean testStatusFlag(int flag) throws IOException { + return ((getStatus() & flag) == flag); + } + + @Override + public void openInt(UsbDeviceConnection connection) throws IOException { + UsbInterface usbInterface = mDevice.getInterface(0); + + if (!connection.claimInterface(usbInterface, true)) { + throw new IOException("Error claiming Prolific interface 0"); + } + + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { + UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); + + switch (currentEndpoint.getAddress()) { + case READ_ENDPOINT: + mReadEndpoint = currentEndpoint; + break; + + case WRITE_ENDPOINT: + mWriteEndpoint = currentEndpoint; + break; + + case INTERRUPT_ENDPOINT: + mInterruptEndpoint = currentEndpoint; + break; + } + } + + byte[] rawDescriptors = connection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2]; + int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12]; + byte maxPacketSize0 = rawDescriptors[7]; + if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) { + mDeviceType = DeviceType.DEVICE_TYPE_01; + } else if(deviceVersion == 0x300 && usbVersion == 0x200) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TA + } else if(deviceVersion == 0x500) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TB + } else if(usbVersion == 0x200 && !testHxStatus()) { + mDeviceType = DeviceType.DEVICE_TYPE_HXN; + } else { + mDeviceType = DeviceType.DEVICE_TYPE_HX; + } + resetDevice(); + doBlackMagic(); + setControlLines(mControlLinesValue); + } + + @Override + public void closeInt() { + try { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread != null) { + try { + mStopReadStatusThread = true; + mReadStatusThread.join(); + } catch (Exception e) { + Log.w(TAG, "An error occured while waiting for status read thread", e); + } + mStopReadStatusThread = false; + mReadStatusThread = null; + mReadStatusException = null; + } + } + resetDevice(); + } catch(Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(0)); + } catch(Exception ignored) {} + } + + private int filterBaudRate(int baudRate) { +// if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) { +// return baudRate & ~(1<<29); // for testing purposes accept without further checks +// } + if (baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + return baudRate; + } + for(int br : standardBaudRates) { + if (br == baudRate) { + return baudRate; + } + } + /* + * Formula taken from Linux + FreeBSD. + * + * For TA+TB devices + * baudrate = baseline / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + * + * For other devices + * baudrate = baseline / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + * + */ + int baseline, mantissa, exponent, buf, effectiveBaudRate; + baseline = 12000000 * 32; + mantissa = baseline / baudRate; + if (mantissa == 0) { // > unrealistic 384 MBaud + throw new UnsupportedOperationException("Baud rate to high"); + } + exponent = 0; + if (mDeviceType == DeviceType.DEVICE_TYPE_T) { + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { // < 7 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + ((exponent & ~1) << 12) + ((exponent & 1) << 16) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> exponent; + } else { + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { // < 45.8 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + (exponent << 9) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> (exponent << 1); + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d", + baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent)); + return buf; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + baudRate = filterBaudRate(baudRate); + if ((mBaudRate == baudRate) && (mDataBits == dataBits) + && (mStopBits == stopBits) && (mParity == parity)) { + // Make sure no action is performed if there is nothing to change + return; + } + + byte[] lineRequestData = new byte[7]; + lineRequestData[0] = (byte) (baudRate & 0xff); + lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); + lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); + lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); + + switch (stopBits) { + case STOPBITS_1: + lineRequestData[4] = 0; + break; + case STOPBITS_1_5: + lineRequestData[4] = 1; + break; + case STOPBITS_2: + lineRequestData[4] = 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + switch (parity) { + case PARITY_NONE: + lineRequestData[5] = 0; + break; + case PARITY_ODD: + lineRequestData[5] = 1; + break; + case PARITY_EVEN: + lineRequestData[5] = 2; + break; + case PARITY_MARK: + lineRequestData[5] = 3; + break; + case PARITY_SPACE: + lineRequestData[5] = 4; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + lineRequestData[6] = (byte) dataBits; + + ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); + + resetDevice(); + + mBaudRate = baudRate; + mDataBits = dataBits; + mStopBits = stopBits; + mParity = parity; + } + + @Override + public boolean getCD() throws IOException { + return testStatusFlag(STATUS_FLAG_CD); + } + + @Override + public boolean getCTS() throws IOException { + return testStatusFlag(STATUS_FLAG_CTS); + } + + @Override + public boolean getDSR() throws IOException { + return testStatusFlag(STATUS_FLAG_DSR); + } + + @Override + public boolean getDTR() throws IOException { + return (mControlLinesValue & CONTROL_DTR) != 0; + } + + @Override + public void setDTR(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_DTR; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; + } + setControlLines(newControlLinesValue); + } + + @Override + public boolean getRI() throws IOException { + return testStatusFlag(STATUS_FLAG_RI); + } + + @Override + public boolean getRTS() throws IOException { + return (mControlLinesValue & CONTROL_RTS) != 0; + } + + @Override + public void setRTS(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_RTS; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; + } + setControlLines(newControlLinesValue); + } + + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS); + if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS); + if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR); + if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + int index = 0; + if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE; + if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE; + if(index != 0) + vendorOut(RESET_HXN_REQUEST, index, null); + } else { + if (purgeWriteBuffers) + vendorOut(FLUSH_RX_REQUEST, 0, null); + if (purgeReadBuffers) + vendorOut(FLUSH_TX_REQUEST, 0, null); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_PROLIFIC, + new int[] { + UsbId.PROLIFIC_PL2303, + UsbId.PROLIFIC_PL2303GC, + UsbId.PROLIFIC_PL2303GB, + UsbId.PROLIFIC_PL2303GT, + UsbId.PROLIFIC_PL2303GT3, + UsbId.PROLIFIC_PL2303GL, + UsbId.PROLIFIC_PL2303GE, + UsbId.PROLIFIC_PL2303GS, + }); + return supportedDevices; + } +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/SerialTimeoutException.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/SerialTimeoutException.java new file mode 100644 index 0000000..c45ebac --- /dev/null +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/SerialTimeoutException.java @@ -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); + } +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbId.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbId.java index 8f37e49..b94206f 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbId.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbId.java @@ -1,67 +1,77 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -/** - * Registry of USB vendor/product ID constants. - * - * Culled from various sources; see - * usb.ids for one listing. - * - * @author mike wakerly (opensource@hoho.com) - */ -public final class UsbId { - - public static final int VENDOR_FTDI = 0x0403; - public static final int FTDI_FT232R = 0x6001; - public static final int FTDI_FT2232H = 0x6010; - public static final int FTDI_FT4232H = 0x6011; - public static final int FTDI_FT232H = 0x6014; - public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD - - public static final int VENDOR_ATMEL = 0x03EB; - public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; - - public static final int VENDOR_ARDUINO = 0x2341; - public static final int ARDUINO_UNO = 0x0001; - public static final int ARDUINO_MEGA_2560 = 0x0010; - public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; - public static final int ARDUINO_MEGA_ADK = 0x003f; - public static final int ARDUINO_MEGA_2560_R3 = 0x0042; - public static final int ARDUINO_UNO_R3 = 0x0043; - public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; - public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; - public static final int ARDUINO_LEONARDO = 0x8036; - public static final int ARDUINO_MICRO = 0x8037; - - public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; - public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; - - public static final int VENDOR_LEAFLABS = 0x1eaf; - public static final int LEAFLABS_MAPLE = 0x0004; - - public static final int VENDOR_SILABS = 0x10c4; - public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 - public static final int SILABS_CP2105 = 0xea70; - public static final int SILABS_CP2108 = 0xea71; - - public static final int VENDOR_PROLIFIC = 0x067b; - public static final int PROLIFIC_PL2303 = 0x2303; - - public static final int VENDOR_QINHENG = 0x1a86; - public static final int QINHENG_CH340 = 0x7523; - 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; - - private UsbId() { - throw new IllegalAccessError("Non-instantiable class"); - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +/** + * Registry of USB vendor/product ID constants. + * + * Culled from various sources; see + * usb.ids for one listing. + * + * @author mike wakerly (opensource@hoho.com) + */ +public final class UsbId { + + public static final int VENDOR_FTDI = 0x0403; + public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_FT2232H = 0x6010; + public static final int FTDI_FT4232H = 0x6011; + public static final int FTDI_FT232H = 0x6014; + public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD + + public static final int VENDOR_ATMEL = 0x03EB; + public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; + + public static final int VENDOR_ARDUINO = 0x2341; + public static final int ARDUINO_UNO = 0x0001; + public static final int ARDUINO_MEGA_2560 = 0x0010; + public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; + public static final int ARDUINO_MEGA_ADK = 0x003f; + public static final int ARDUINO_MEGA_2560_R3 = 0x0042; + public static final int ARDUINO_UNO_R3 = 0x0043; + public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; + public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; + public static final int ARDUINO_LEONARDO = 0x8036; + public static final int ARDUINO_MICRO = 0x8037; + + public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; + public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; + + public static final int VENDOR_LEAFLABS = 0x1eaf; + public static final int LEAFLABS_MAPLE = 0x0004; + + public static final int VENDOR_SILABS = 0x10c4; + public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 + public static final int SILABS_CP2105 = 0xea70; + public static final int SILABS_CP2108 = 0xea71; + + public static final int VENDOR_PROLIFIC = 0x067b; + 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 PROLIFIC_PL2303GB = 0x23b3; // " + public static final int PROLIFIC_PL2303GT = 0x23cd; // " + public static final int PROLIFIC_PL2303GT3 = 0x23c3; // " + public static final int PROLIFIC_PL2303GL = 0x23e3; // " + public static final int PROLIFIC_PL2303GE = 0x23e3; // " + public static final int PROLIFIC_PL2303GS = 0x23f3; // " + + public static final int VENDOR_QINHENG = 0x1a86; + public static final int QINHENG_CH340 = 0x7523; + 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"); + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialDriver.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialDriver.java index 26e5fa2..bbc761a 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialDriver.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialDriver.java @@ -1,33 +1,33 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbDevice; - -import java.util.List; - -/** - * - * @author mike wakerly (opensource@hoho.com) - */ -public interface UsbSerialDriver { - - /** - * Returns the raw {@link UsbDevice} backing this port. - * - * @return the device - */ - public UsbDevice getDevice(); - - /** - * Returns all available ports for this device. This list must have at least - * one entry. - * - * @return the ports - */ - public List getPorts(); -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbDevice; + +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialDriver { + + /** + * Returns the raw {@link UsbDevice} backing this port. + * + * @return the device + */ + UsbDevice getDevice(); + + /** + * Returns all available ports for this device. This list must have at least + * one entry. + * + * @return the ports + */ + List getPorts(); +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialPort.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialPort.java index ab257f6..03f1e6b 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialPort.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialPort.java @@ -1,263 +1,261 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbManager; - -import java.io.Closeable; -import java.io.IOException; -import java.util.EnumSet; - -/** - * Interface for a single serial port. - * - * @author mike wakerly (opensource@hoho.com) - */ -public interface UsbSerialPort extends Closeable { - - /** 5 data bits. */ - public static final int DATABITS_5 = 5; - - /** 6 data bits. */ - public static final int DATABITS_6 = 6; - - /** 7 data bits. */ - public static final int DATABITS_7 = 7; - - /** 8 data bits. */ - public static final int DATABITS_8 = 8; - - /** No flow control. */ - public static final int FLOWCONTROL_NONE = 0; - - /** RTS/CTS input flow control. */ - public static final int FLOWCONTROL_RTSCTS_IN = 1; - - /** RTS/CTS output flow control. */ - public static final int FLOWCONTROL_RTSCTS_OUT = 2; - - /** XON/XOFF input flow control. */ - public static final int FLOWCONTROL_XONXOFF_IN = 4; - - /** XON/XOFF output flow control. */ - public static final int FLOWCONTROL_XONXOFF_OUT = 8; - - /** No parity. */ - public static final int PARITY_NONE = 0; - - /** Odd parity. */ - public static final int PARITY_ODD = 1; - - /** Even parity. */ - public static final int PARITY_EVEN = 2; - - /** Mark parity. */ - public static final int PARITY_MARK = 3; - - /** Space parity. */ - public static final int PARITY_SPACE = 4; - - /** 1 stop bit. */ - public static final int STOPBITS_1 = 1; - - /** 1.5 stop bits. */ - public static final int STOPBITS_1_5 = 3; - - /** 2 stop bits. */ - public static final int STOPBITS_2 = 2; - - /** values for get[Supported]ControlLines() */ - public enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }; - - /** - * Returns the driver used by this port. - */ - public UsbSerialDriver getDriver(); - - /** - * Returns the currently-bound USB device. - */ - public UsbDevice getDevice(); - - /** - * Port number within driver. - */ - public int getPortNumber(); - - /** - * The serial number of the underlying UsbDeviceConnection, or {@code null}. - * - * @return value from {@link UsbDeviceConnection#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. - * - * @param connection an open device connection, acquired with - * {@link UsbManager#openDevice(UsbDevice)} - * @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. - */ - public void close() throws IOException; - - /** - * Reads as many bytes as possible into the destination buffer. - * - * @param dest the destination byte buffer - * @param timeout the timeout for reading in milliseconds, 0 is infinite - * @return the actual number of bytes read - * @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 - * @param timeout the timeout for writing in milliseconds, 0 is infinite - * @return the actual number of bytes written - * @throws IOException if an error occurred during writing - */ - public int write(final byte[] src, final int timeout) throws IOException; - - /** - * 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}, - * {@link #DATABITS_7}, or {@link #DATABITS_8}. - * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. - * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, - * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. - * @throws IOException on error setting the port parameters - * @throws UnsupportedOperationException if values are not supported by a specific device - */ - public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; - - /** - * Gets the CD (Carrier Detect) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getCD() throws IOException; - - /** - * Gets the CTS (Clear To Send) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getCTS() throws IOException; - - /** - * Gets the DSR (Data Set Ready) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getDSR() throws IOException; - - /** - * Gets the DTR (Data Terminal Ready) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getDTR() throws IOException; - - /** - * 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 - * @throws UnsupportedOperationException if not supported - */ - public void setDTR(boolean value) throws IOException; - - /** - * Gets the RI (Ring Indicator) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getRI() throws IOException; - - /** - * Gets the RTS (Request To Send) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - public boolean getRTS() throws IOException; - - /** - * 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 - * @throws UnsupportedOperationException if not supported - */ - 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. - * - * @return EnumSet.contains(...) is {@code true} if set, else {@code false} - * @throws IOException if an error occurred during reading - */ - public EnumSet getControlLines() throws IOException; - - /** - * 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 - */ - public EnumSet getSupportedControlLines() throws IOException; - - /** - * 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 - * @throws IOException if an error occurred during flush - * @throws UnsupportedOperationException if not supported - */ - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; - - /** - * send BREAK condition. - * - * @param value set/reset - */ - public void setBreak(boolean value) throws IOException; - - /** - * Returns the current state of the connection. - */ - public boolean isOpen(); - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; + +import androidx.annotation.IntDef; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.EnumSet; + +/** + * Interface for a single serial port. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialPort extends Closeable { + + /** 5 data bits. */ + int DATABITS_5 = 5; + /** 6 data bits. */ + int DATABITS_6 = 6; + /** 7 data bits. */ + int DATABITS_7 = 7; + /** 8 data bits. */ + int DATABITS_8 = 8; + + /** Values for setParameters(..., parity) */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) + @interface Parity {} + /** No parity. */ + int PARITY_NONE = 0; + /** Odd parity. */ + int PARITY_ODD = 1; + /** Even parity. */ + int PARITY_EVEN = 2; + /** Mark parity. */ + int PARITY_MARK = 3; + /** Space parity. */ + int PARITY_SPACE = 4; + + /** 1 stop bit. */ + int STOPBITS_1 = 1; + /** 1.5 stop bits. */ + int STOPBITS_1_5 = 3; + /** 2 stop bits. */ + int STOPBITS_2 = 2; + + /** Values for get[Supported]ControlLines() */ + enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } + + /** + * Returns the driver used by this port. + */ + UsbSerialDriver getDriver(); + + /** + * Returns the currently-bound USB device. + */ + UsbDevice getDevice(); + + /** + * Port number within driver. + */ + int getPortNumber(); + + /** + * Returns the write endpoint. + * @return write endpoint + */ + UsbEndpoint getWriteEndpoint(); + + /** + * Returns the read endpoint. + * @return read endpoint + */ + UsbEndpoint getReadEndpoint(); + + /** + * The serial number of the underlying UsbDeviceConnection, or {@code null}. + * + * @return value from {@link UsbDeviceConnection#getSerial()} + * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted + */ + String getSerial(); + + /** + * Opens and initializes the port. Upon success, caller must ensure that + * {@link #close()} is eventually called. + * + * @param connection an open device connection, acquired with + * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)} + * @throws IOException on error opening or initializing the port. + */ + void open(UsbDeviceConnection connection) throws IOException; + + /** + * Closes the port and {@link UsbDeviceConnection} + * + * @throws IOException on error closing the port. + */ + void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeout the timeout for reading in milliseconds, 0 is infinite + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + 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 + * @param timeout the timeout for writing in milliseconds, 0 is infinite + * @throws SerialTimeoutException if timeout reached before sending all data. + * ex.bytesTransferred may contain bytes transferred + * @throws IOException if an error occurred during writing + */ + void write(final byte[] src, final int timeout) throws IOException; + + /** + * 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}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. + * @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; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDTR() throws IOException; + + /** + * 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 + * @throws UnsupportedOperationException if not supported + */ + void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRTS() throws IOException; + + /** + * 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 + * @throws UnsupportedOperationException if not supported + */ + 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. + * + * @return EnumSet.contains(...) is {@code true} if set, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getControlLines() throws IOException; + + /** + * 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 + */ + EnumSet getSupportedControlLines() throws IOException; + + /** + * 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 + * @throws IOException if an error occurred during flush + * @throws UnsupportedOperationException if not supported + */ + void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; + + /** + * send BREAK condition. + * + * @param value set/reset + */ + void setBreak(boolean value) throws IOException; + + /** + * Returns the current state of the connection. + */ + boolean isOpen(); + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialProber.java b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialProber.java index b28a56d..6755bb0 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialProber.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/driver/UsbSerialProber.java @@ -1,92 +1,92 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.driver; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author mike wakerly (opensource@hoho.com) - */ -public class UsbSerialProber { - - private final ProbeTable mProbeTable; - - public UsbSerialProber(ProbeTable probeTable) { - mProbeTable = probeTable; - } - - public static UsbSerialProber getDefaultProber() { - return new UsbSerialProber(getDefaultProbeTable()); - } - - public static ProbeTable getDefaultProbeTable() { - final ProbeTable probeTable = new ProbeTable(); - probeTable.addDriver(CdcAcmSerialDriver.class); - probeTable.addDriver(Cp21xxSerialDriver.class); - probeTable.addDriver(FtdiSerialDriver.class); - probeTable.addDriver(ProlificSerialDriver.class); - probeTable.addDriver(Ch34xSerialDriver.class); - return probeTable; - } - - /** - * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} - * from the currently-attached {@link UsbDevice} hierarchy. This method does - * not require permission from the Android USB system, since it does not - * open any of the devices. - * - * @param usbManager - * @return a list, possibly empty, of all compatible drivers - */ - public List findAllDrivers(final UsbManager usbManager) { - final List result = new ArrayList(); - - for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { - final UsbSerialDriver driver = probeDevice(usbDevice); - if (driver != null) { - result.add(driver); - } - } - return result; - } - - /** - * Probes a single device for a compatible driver. - * - * @param usbDevice the usb device to probe - * @return a new {@link UsbSerialDriver} compatible with this device, or - * {@code null} if none available. - */ - public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { - final int vendorId = usbDevice.getVendorId(); - final int productId = usbDevice.getProductId(); - - final Class driverClass = - mProbeTable.findDriver(vendorId, productId); - if (driverClass != null) { - final UsbSerialDriver driver; - try { - final Constructor ctor = - driverClass.getConstructor(UsbDevice.class); - driver = ctor.newInstance(usbDevice); - } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | - IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - return driver; - } - return null; - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public class UsbSerialProber { + + private final ProbeTable mProbeTable; + + public UsbSerialProber(ProbeTable probeTable) { + mProbeTable = probeTable; + } + + public static UsbSerialProber getDefaultProber() { + return new UsbSerialProber(getDefaultProbeTable()); + } + + public static ProbeTable getDefaultProbeTable() { + final ProbeTable probeTable = new ProbeTable(); + probeTable.addDriver(CdcAcmSerialDriver.class); + probeTable.addDriver(Cp21xxSerialDriver.class); + probeTable.addDriver(FtdiSerialDriver.class); + probeTable.addDriver(ProlificSerialDriver.class); + probeTable.addDriver(Ch34xSerialDriver.class); + return probeTable; + } + + /** + * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} + * from the currently-attached {@link UsbDevice} hierarchy. This method does + * not require permission from the Android USB system, since it does not + * open any of the devices. + * + * @param usbManager usb manager + * @return a list, possibly empty, of all compatible drivers + */ + public List findAllDrivers(final UsbManager usbManager) { + final List result = new ArrayList<>(); + + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + final UsbSerialDriver driver = probeDevice(usbDevice); + if (driver != null) { + result.add(driver); + } + } + return result; + } + + /** + * Probes a single device for a compatible driver. + * + * @param usbDevice the usb device to probe + * @return a new {@link UsbSerialDriver} compatible with this device, or + * {@code null} if none available. + */ + public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { + final int vendorId = usbDevice.getVendorId(); + final int productId = usbDevice.getProductId(); + + final Class driverClass = + mProbeTable.findDriver(vendorId, productId); + if (driverClass != null) { + final UsbSerialDriver driver; + try { + final Constructor ctor = + driverClass.getConstructor(UsbDevice.class); + driver = ctor.newInstance(usbDevice); + } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return driver; + } + return null; + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/util/MonotonicClock.java b/app/src/main/java/org/emulator/calculator/usbserial/util/MonotonicClock.java new file mode 100644 index 0000000..ea83865 --- /dev/null +++ b/app/src/main/java/org/emulator/calculator/usbserial/util/MonotonicClock.java @@ -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; + } + +} diff --git a/app/src/main/java/org/emulator/calculator/usbserial/util/SerialInputOutputManager.java b/app/src/main/java/org/emulator/calculator/usbserial/util/SerialInputOutputManager.java index 64df56c..1bb9cf3 100644 --- a/app/src/main/java/org/emulator/calculator/usbserial/util/SerialInputOutputManager.java +++ b/app/src/main/java/org/emulator/calculator/usbserial/util/SerialInputOutputManager.java @@ -1,239 +1,254 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package org.emulator.calculator.usbserial.util; - -import android.os.Process; -import android.util.Log; - -import org.emulator.calculator.usbserial.driver.UsbSerialPort; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class SerialInputOutputManager implements Runnable { - - private static final String TAG = SerialInputOutputManager.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final int BUFSIZ = 4096; - - /** - * default read timeout is infinite, to avoid data loss with bulkTransfer API - */ - private int mReadTimeout = 0; - private int mWriteTimeout = 0; - - private final Object mReadBufferLock = new Object(); - private final Object mWriteBufferLock = new Object(); - - private ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); - private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); - - public enum State { - STOPPED, - RUNNING, - STOPPING - } - - private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; - private State mState = State.STOPPED; // Synchronized by 'this' - private Listener mListener; // Synchronized by 'this' - private final UsbSerialPort mSerialPort; - - public interface Listener { - /** - * Called when new incoming data is available. - */ - public void onNewData(byte[] data); - - /** - * Called when {@link SerialInputOutputManager#run()} aborts due to an error. - */ - public void onRunError(Exception e); - } - - public SerialInputOutputManager(UsbSerialPort serialPort) { - mSerialPort = serialPort; - } - - public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { - mSerialPort = serialPort; - mListener = listener; - } - - public synchronized void setListener(Listener listener) { - mListener = listener; - } - - 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)} - * */ - public void setThreadPriority(int 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) { - // 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) - throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); - mReadTimeout = timeout; - } - - public int getReadTimeout() { - return mReadTimeout; - } - - public void setWriteTimeout(int timeout) { - mWriteTimeout = timeout; - } - - public int getWriteTimeout() { - return mWriteTimeout; - } - - /** - * read/write buffer size - */ - public void setReadBufferSize(int bufferSize) { - if (getReadBufferSize() == bufferSize) - return; - synchronized (mReadBufferLock) { - mReadBuffer = ByteBuffer.allocate(bufferSize); - } - } - - public int getReadBufferSize() { - return mReadBuffer.capacity(); - } - - public void setWriteBufferSize(int bufferSize) { - if(getWriteBufferSize() == bufferSize) - return; - synchronized (mWriteBufferLock) { - ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); - if(mWriteBuffer.position() > 0) - newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); - mWriteBuffer = newWriteBuffer; - } - } - - 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 - */ - 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; - } - } - - public synchronized State getState() { - return mState; - } - - /** - * Continuously services the read and write buffers until {@link #stop()} is - * called, or until a driver exception is raised. - */ - @Override - public void run() { - if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) - setThreadPriority(mThreadPriority); - - synchronized (this) { - if (getState() != State.STOPPED) { - throw new IllegalStateException("Already running"); - } - mState = State.RUNNING; - } - - Log.i(TAG, "Running ..."); - try { - while (true) { - if (getState() != State.RUNNING) { - Log.i(TAG, "Stopping mState=" + getState()); - break; - } - step(); - } - } catch (Exception e) { - Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); - final Listener listener = getListener(); - if (listener != null) { - listener.onRunError(e); - } - } finally { - synchronized (this) { - mState = State.STOPPED; - Log.i(TAG, "Stopped"); - } - } - } - - private void step() throws IOException { - // Handle incoming data. - byte[] buffer = null; - synchronized (mReadBufferLock) { - buffer = mReadBuffer.array(); - } - int len = mSerialPort.read(buffer, mReadTimeout); - if (len > 0) { - if (DEBUG) Log.d(TAG, "Read data len=" + len); - final Listener listener = getListener(); - if (listener != null) { - final byte[] data = new byte[len]; - System.arraycopy(buffer, 0, data, 0, len); - listener.onNewData(data); - } - } - - // 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); - } - } - -} +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package org.emulator.calculator.usbserial.util; + +import android.os.Process; +import android.util.Log; + +import org.emulator.calculator.usbserial.driver.UsbSerialPort; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Utility class which services a {@link UsbSerialPort} in its {@link #run()} method. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class SerialInputOutputManager implements Runnable { + + private static final String TAG = SerialInputOutputManager.class.getSimpleName(); + public static boolean DEBUG = false; + private static final int BUFSIZ = 4096; + + /** + * default read timeout is infinite, to avoid data loss with bulkTransfer API + */ + private int mReadTimeout = 0; + private int mWriteTimeout = 0; + + private final Object mReadBufferLock = new Object(); + private final Object mWriteBufferLock = new Object(); + + private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() + private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); + + public enum State { + STOPPED, + RUNNING, + STOPPING + } + + private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; + private State mState = State.STOPPED; // Synchronized by 'this' + private Listener mListener; // Synchronized by 'this' + private final UsbSerialPort mSerialPort; + + public interface Listener { + /** + * Called when new incoming data is available. + */ + void onNewData(byte[] data); + + /** + * Called when {@link SerialInputOutputManager#run()} aborts due to an error. + */ + void onRunError(Exception e); + } + + public SerialInputOutputManager(UsbSerialPort serialPort) { + mSerialPort = serialPort; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { + mSerialPort = serialPort; + mListener = listener; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public synchronized void setListener(Listener listener) { + mListener = listener; + } + + public synchronized Listener getListener() { + return mListener; + } + + /** + * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss + * + * @param threadPriority see {@link Process#setThreadPriority(int)} + * */ + public void setThreadPriority(int 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) { + // 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) + throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); + mReadTimeout = timeout; + } + + public int getReadTimeout() { + return mReadTimeout; + } + + public void setWriteTimeout(int timeout) { + mWriteTimeout = timeout; + } + + public int getWriteTimeout() { + return mWriteTimeout; + } + + /** + * read/write buffer size + */ + public void setReadBufferSize(int bufferSize) { + if (getReadBufferSize() == bufferSize) + return; + synchronized (mReadBufferLock) { + mReadBuffer = ByteBuffer.allocate(bufferSize); + } + } + + public int getReadBufferSize() { + return mReadBuffer.capacity(); + } + + public void setWriteBufferSize(int bufferSize) { + if(getWriteBufferSize() == bufferSize) + return; + synchronized (mWriteBufferLock) { + ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); + if(mWriteBuffer.position() > 0) + newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); + mWriteBuffer = newWriteBuffer; + } + } + + public int getWriteBufferSize() { + return mWriteBuffer.capacity(); + } + + /** + * when using writeAsync, it is recommended to use readTimeout != 0, + * else the write will be delayed until read data is available + */ + public void writeAsync(byte[] data) { + synchronized (mWriteBufferLock) { + mWriteBuffer.put(data); + } + } + + /** + * start SerialInputOutputManager in separate thread + */ + public void start() { + if(mState != State.STOPPED) + throw new IllegalStateException("already started"); + new Thread(this, this.getClass().getSimpleName()).start(); + } + + /** + * stop SerialInputOutputManager thread + * + * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to + * interrupt blocking read + */ + public synchronized void stop() { + if (getState() == State.RUNNING) { + Log.i(TAG, "Stop requested"); + mState = State.STOPPING; + } + } + + public synchronized State getState() { + return mState; + } + + /** + * Continuously services the read and write buffers until {@link #stop()} is + * called, or until a driver exception is raised. + */ + @Override + public void run() { + synchronized (this) { + if (getState() != State.STOPPED) { + throw new IllegalStateException("Already running"); + } + mState = State.RUNNING; + } + Log.i(TAG, "Running ..."); + try { + if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) + Process.setThreadPriority(mThreadPriority); + while (true) { + if (getState() != State.RUNNING) { + Log.i(TAG, "Stopping mState=" + getState()); + break; + } + step(); + } + } catch (Exception e) { + Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); + final Listener listener = getListener(); + if (listener != null) { + listener.onRunError(e); + } + } finally { + synchronized (this) { + mState = State.STOPPED; + Log.i(TAG, "Stopped"); + } + } + } + + private void step() throws IOException { + // Handle incoming data. + byte[] buffer; + synchronized (mReadBufferLock) { + buffer = mReadBuffer.array(); + } + int len = mSerialPort.read(buffer, mReadTimeout); + if (len > 0) { + if (DEBUG) Log.d(TAG, "Read data len=" + len); + final Listener listener = getListener(); + if (listener != null) { + final byte[] data = new byte[len]; + System.arraycopy(buffer, 0, data, 0, len); + listener.onNewData(data); + } + } + + // 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); + } + } + +} diff --git a/app/src/main/java/org/emulator/forty/eight/MainActivity.java b/app/src/main/java/org/emulator/forty/eight/MainActivity.java index c52df29..b78a68f 100644 --- a/app/src/main/java/org/emulator/forty/eight/MainActivity.java +++ b/app/src/main/java/org/emulator/forty/eight/MainActivity.java @@ -53,7 +53,6 @@ import androidx.core.content.FileProvider; import androidx.core.view.GravityCompat; import androidx.documentfile.provider.DocumentFile; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.Fragment; import com.google.android.material.navigation.NavigationView; @@ -2041,11 +2040,18 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @SuppressWarnings("UnusedDeclaration") int openSerialPort(String serialPort) { + // Search if this same serial port is not already opened + + Integer serialPortId = serialIndex; Serial serial = new Serial(this, serialPortId); if(serial.connect(serialPort)) { serialsById.put(serialPortId, serial); serialIndex++; + runOnUiThread(() -> { + int resId = Utils.resId(MainActivity.this, "string", "serial_connection_succeeded"); + Toast.makeText(MainActivity.this, resId, Toast.LENGTH_SHORT).show(); + }); return serialPortId; } else { runOnUiThread(() -> { @@ -2069,6 +2075,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if(serial != null) { serialsById.remove(serialPortIdInt); 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 0; @@ -2088,7 +2098,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On Integer serialPortIdInt = serialPortId; Serial serial = serialsById.get(serialPortIdInt); if(serial != null) - return serial.receive(nNumberOfBytesToRead); + return serial.read(nNumberOfBytesToRead); return new byte[0]; } @@ -2097,7 +2107,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On Integer serialPortIdInt = serialPortId; Serial serial = serialsById.get(serialPortIdInt); 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; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27bd4fd..bb53dda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,6 +135,8 @@ No driver %s, Port %d Vendor %04X, Product %04X + USB serial device connected. + USB serial device disconnected. Failed to connect the USB serial device: device not found (check the settings). Failed to connect the USB serial device: no driver for device (check the settings). Failed to connect the USB serial device: not enough ports at device (check the settings). diff --git a/build.gradle b/build.gradle index f694bca..5623fbd 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 11e81bf..0a26fdb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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