Update the usb serial stack and fix several serial issues.

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -220,7 +220,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -363,7 +363,7 @@ public class Ch34xSerialDriver implements UsbSerialDriver {
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
UsbId.QINHENG_CH340, UsbId.QINHENG_CH340,
UsbId.QINHENG_CH341A, UsbId.QINHENG_CH341A,

View file

@ -12,6 +12,8 @@ import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest; import android.hardware.usb.UsbRequest;
import android.util.Log; import android.util.Log;
import org.emulator.calculator.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.EnumSet; import java.util.EnumSet;
@ -64,6 +66,12 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
return mPortNumber; return mPortNumber;
} }
@Override
public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; }
@Override
public UsbEndpoint getReadEndpoint() { return mReadEndpoint; }
/** /**
* Returns the device serial number * Returns the device serial number
* @return serial number * @return serial number
@ -93,6 +101,9 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
if (mConnection != null) { if (mConnection != null) {
throw new IOException("Already open"); throw new IOException("Already open");
} }
if(connection == null) {
throw new IllegalArgumentException("Connection is null");
}
mConnection = connection; mConnection = connection;
try { try {
openInt(connection); openInt(connection);
@ -102,7 +113,9 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
mUsbRequest = new UsbRequest(); mUsbRequest = new UsbRequest();
mUsbRequest.initialize(mConnection, mReadEndpoint); mUsbRequest.initialize(mConnection, mReadEndpoint);
} catch(Exception e) { } catch(Exception e) {
close(); try {
close();
} catch(Exception ignored) {}
throw e; throw e;
} }
} }
@ -161,11 +174,12 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
// /system/lib64/libusbhost.so (usb_request_wait+192) // /system/lib64/libusbhost.so (usb_request_wait+192)
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) // /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 // data loss / crashes were observed with timeout up to 200 msec
long endTime = testConnection ? System.currentTimeMillis() + timeout : 0; long endTime = testConnection ? MonotonicClock.millis() + timeout : 0;
int readMax = Math.min(dest.length, MAX_READ_SIZE); int readMax = Math.min(dest.length, MAX_READ_SIZE);
nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout);
// Android error propagation is improvable, nread == -1 can be: timeout, connection lost, buffer undersized, ... // Android error propagation is improvable:
if(nread == -1 && testConnection && System.currentTimeMillis() < endTime) // nread == -1 can be: timeout, connection lost, buffer to small, ???
if(nread == -1 && testConnection && MonotonicClock.millis() < endTime)
testConnection(); testConnection();
} else { } else {
@ -178,47 +192,64 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
throw new IOException("Waiting for USB request failed"); throw new IOException("Waiting for USB request failed");
} }
nread = buf.position(); nread = buf.position();
// Android error propagation is improvable:
// response != null & nread == 0 can be: connection lost, buffer to small, ???
if(nread == 0) {
testConnection();
}
} }
if (nread > 0) return Math.max(nread, 0);
return nread;
else
return 0;
} }
@Override @Override
public int write(final byte[] src, final int timeout) throws IOException { public void write(final byte[] src, final int timeout) throws IOException {
int offset = 0; int offset = 0;
final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout);
if(mConnection == null) { if(mConnection == null) {
throw new IOException("Connection closed"); throw new IOException("Connection closed");
} }
while (offset < src.length) { while (offset < src.length) {
final int writeLength; int requestTimeout;
final int amtWritten; final int requestLength;
final int actualLength;
synchronized (mWriteBufferLock) { synchronized (mWriteBufferLock) {
final byte[] writeBuffer; final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length); requestLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) { if (offset == 0) {
writeBuffer = src; writeBuffer = src;
} else { } else {
// bulkTransfer does not support offsets, make a copy. // bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength); System.arraycopy(src, offset, mWriteBuffer, 0, requestLength);
writeBuffer = mWriteBuffer; writeBuffer = mWriteBuffer;
} }
if (timeout == 0 || offset == 0) {
amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength, timeout); 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);
}
} }
if (amtWritten <= 0) { Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout);
throw new IOException("Error writing " + writeLength if (actualLength <= 0) {
+ " bytes at offset " + offset + " length=" + src.length); 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;
Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
} }
return offset;
} }
@Override @Override
@ -227,7 +258,7 @@ public abstract class CommonUsbSerialPort implements UsbSerialPort {
} }
@Override @Override
public abstract void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
@Override @Override
public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } public boolean getCD() throws IOException { throw new UnsupportedOperationException(); }

View file

@ -175,7 +175,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -322,7 +322,7 @@ public class Cp21xxSerialDriver implements UsbSerialDriver {
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_SILABS, supportedDevices.put(UsbId.VENDOR_SILABS,
new int[] { new int[] {
UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109

View file

@ -12,6 +12,8 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.util.Log; import android.util.Log;
import org.emulator.calculator.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
@ -146,11 +148,11 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
int nread; int nread;
if (timeout != 0) { if (timeout != 0) {
long endTime = System.currentTimeMillis() + timeout; long endTime = MonotonicClock.millis() + timeout;
do { do {
nread = super.read(dest, Math.max(1, (int)(endTime - System.currentTimeMillis())), false); nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false);
} while (nread == READ_HEADER_LENGTH && System.currentTimeMillis() < endTime); } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime);
if(nread <= 0 && System.currentTimeMillis() < endTime) if(nread <= 0 && MonotonicClock.millis() < endTime)
testConnection(); testConnection();
} else { } else {
do { do {
@ -160,7 +162,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
return readFilter(dest, nread); return readFilter(dest, nread);
} }
private int readFilter(byte[] buffer, int totalBytesRead) throws IOException { protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException {
final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); final int maxPacketSize = mReadEndpoint.getMaxPacketSize();
int destPos = 0; int destPos = 0;
for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) {
@ -226,7 +228,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
if(baudRate <= 0) { if(baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
@ -413,7 +415,7 @@ public class FtdiSerialDriver implements UsbSerialDriver {
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_FTDI, supportedDevices.put(UsbId.VENDOR_FTDI,
new int[] { new int[] {
UsbId.FTDI_FT232R, UsbId.FTDI_FT232R,

View file

@ -21,7 +21,7 @@ import java.util.Map;
public class ProbeTable { public class ProbeTable {
private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable = private final Map<Pair<Integer, Integer>, Class<? extends UsbSerialDriver>> mProbeTable =
new LinkedHashMap<Pair<Integer,Integer>, Class<? extends UsbSerialDriver>>(); new LinkedHashMap<>();
/** /**
* Adds or updates a (vendor, product) pair in the table. * Adds or updates a (vendor, product) pair in the table.

View file

@ -16,6 +16,9 @@ import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbInterface;
import android.util.Log; import android.util.Log;
//import org.emulator.calculator.usbserial.BuildConfig;
import org.emulator.calculator.usbserial.util.MonotonicClock;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
@ -32,7 +35,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800,
403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000
}; };
private enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_HX}; protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN}
private final UsbDevice mDevice; private final UsbDevice mDevice;
private final UsbSerialPort mPort; private final UsbSerialPort mPort;
@ -59,34 +62,50 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private static final int USB_RECIP_INTERFACE = 0x01; private static final int USB_RECIP_INTERFACE = 0x01;
private static final int PROLIFIC_VENDOR_READ_REQUEST = 0x01; private static final int VENDOR_READ_REQUEST = 0x01;
private static final int PROLIFIC_VENDOR_WRITE_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 PROLIFIC_VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; private static final int 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 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 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 WRITE_ENDPOINT = 0x02;
private static final int READ_ENDPOINT = 0x83; private static final int READ_ENDPOINT = 0x83;
private static final int INTERRUPT_ENDPOINT = 0x81; 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 RESET_HXN_REQUEST = 0x07;
private static final int FLUSH_RX_REQUEST = 0x08;
private static final int FLUSH_TX_REQUEST = 0x09; 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_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 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 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 GET_CONTROL_REQUEST = 0x87;
private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length 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_DTR = 0x01;
private static final int CONTROL_RTS = 0x02; 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_CD = 0x02;
private static final int GET_CONTROL_FLAG_DSR = 0x04; 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_RI = 0x01;
private static final int GET_CONTROL_FLAG_CTS = 0x08; 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_CD = 0x01;
private static final int STATUS_FLAG_DSR = 0x02; private static final int STATUS_FLAG_DSR = 0x02;
private static final int STATUS_FLAG_RI = 0x08; private static final int STATUS_FLAG_RI = 0x08;
@ -95,7 +114,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
private static final int STATUS_BUFFER_SIZE = 10; private static final int STATUS_BUFFER_SIZE = 10;
private static final int STATUS_BYTE_IDX = 8; private static final int STATUS_BYTE_IDX = 8;
private DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX;
private UsbEndpoint mInterruptEndpoint; private UsbEndpoint mInterruptEndpoint;
private int mControlLinesValue = 0; private int mControlLinesValue = 0;
private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1;
@ -134,11 +153,13 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
private byte[] vendorIn(int value, int index, int length) throws IOException { private byte[] vendorIn(int value, int index, int length) throws IOException {
return inControlTransfer(PROLIFIC_VENDOR_IN_REQTYPE, PROLIFIC_VENDOR_READ_REQUEST, value, index, length); 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 { private void vendorOut(int value, int index, byte[] data) throws IOException {
outControlTransfer(PROLIFIC_VENDOR_OUT_REQTYPE, PROLIFIC_VENDOR_WRITE_REQUEST, value, index, data); 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 { private void resetDevice() throws IOException {
@ -146,10 +167,21 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { private void ctrlOut(int request, int value, int index, byte[] data) throws IOException {
outControlTransfer(PROLIFIC_CTRL_OUT_REQTYPE, request, value, index, data); 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 { private void doBlackMagic() throws IOException {
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN)
return;
vendorIn(0x8484, 0, 1); vendorIn(0x8484, 0, 1);
vendorOut(0x0404, 0, null); vendorOut(0x0404, 0, null);
vendorIn(0x8484, 0, 1); vendorIn(0x8484, 0, 1);
@ -160,7 +192,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
vendorIn(0x8383, 0, 1); vendorIn(0x8383, 0, 1);
vendorOut(0, 1, null); vendorOut(0, 1, null);
vendorOut(1, 0, null); vendorOut(1, 0, null);
vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_HX) ? 0x44 : 0x24, null); vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null);
} }
private void setControlLines(int newControlLinesValue) throws IOException { private void setControlLines(int newControlLinesValue) throws IOException {
@ -172,9 +204,9 @@ public class ProlificSerialDriver implements UsbSerialDriver {
try { try {
while (!mStopReadStatusThread) { while (!mStopReadStatusThread) {
byte[] buffer = new byte[STATUS_BUFFER_SIZE]; byte[] buffer = new byte[STATUS_BUFFER_SIZE];
long endTime = System.currentTimeMillis() + 500; long endTime = MonotonicClock.millis() + 500;
int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500);
if(readBytesCount == -1 && System.currentTimeMillis() < endTime) if(readBytesCount == -1 && MonotonicClock.millis() < endTime)
testConnection(); testConnection();
if (readBytesCount > 0) { if (readBytesCount > 0) {
if (readBytesCount != STATUS_BUFFER_SIZE) { if (readBytesCount != STATUS_BUFFER_SIZE) {
@ -196,12 +228,20 @@ public class ProlificSerialDriver implements UsbSerialDriver {
if ((mReadStatusThread == null) && (mReadStatusException == null)) { if ((mReadStatusThread == null) && (mReadStatusException == null)) {
synchronized (mReadStatusThreadLock) { synchronized (mReadStatusThreadLock) {
if (mReadStatusThread == null) { if (mReadStatusThread == null) {
byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1);
mStatus = 0; mStatus = 0;
if((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
if((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1);
if((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS;
if((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; 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); //Log.d(TAG, "start control line status thread " + mStatus);
mReadStatusThread = new Thread(this::readStatusThreadFunction); mReadStatusThread = new Thread(this::readStatusThreadFunction);
mReadStatusThread.setDaemon(true); mReadStatusThread.setDaemon(true);
@ -214,7 +254,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
IOException readStatusException = mReadStatusException; IOException readStatusException = mReadStatusException;
if (mReadStatusException != null) { if (mReadStatusException != null) {
mReadStatusException = null; mReadStatusException = null;
throw readStatusException; throw new IOException(readStatusException);
} }
return mStatus; return mStatus;
@ -250,29 +290,27 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
} }
if (mDevice.getDeviceClass() == 0x02) { byte[] rawDescriptors = connection.getRawDescriptors();
mDeviceType = DeviceType.DEVICE_TYPE_01; if(rawDescriptors == null || rawDescriptors.length < 14) {
} else { throw new IOException("Could not get device descriptors");
byte[] rawDescriptors = connection.getRawDescriptors(); }
if(rawDescriptors == null || rawDescriptors.length <8) { int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2];
Log.w(TAG, "Could not get device descriptors, Assuming that it is a HX device"); int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12];
mDeviceType = DeviceType.DEVICE_TYPE_HX; byte maxPacketSize0 = rawDescriptors[7];
} else { if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) {
byte maxPacketSize0 = rawDescriptors[7]; mDeviceType = DeviceType.DEVICE_TYPE_01;
if (maxPacketSize0 == 64) { } else if(deviceVersion == 0x300 && usbVersion == 0x200) {
mDeviceType = DeviceType.DEVICE_TYPE_HX; mDeviceType = DeviceType.DEVICE_TYPE_T; // TA
} else if ((mDevice.getDeviceClass() == 0x00) } else if(deviceVersion == 0x500) {
|| (mDevice.getDeviceClass() == 0xff)) { mDeviceType = DeviceType.DEVICE_TYPE_T; // TB
mDeviceType = DeviceType.DEVICE_TYPE_01; } else if(usbVersion == 0x200 && !testHxStatus()) {
} else { mDeviceType = DeviceType.DEVICE_TYPE_HXN;
Log.w(TAG, "Could not detect PL2303 subtype, Assuming that it is a HX device"); } else {
mDeviceType = DeviceType.DEVICE_TYPE_HX; mDeviceType = DeviceType.DEVICE_TYPE_HX;
}
}
} }
setControlLines(mControlLinesValue);
resetDevice(); resetDevice();
doBlackMagic(); doBlackMagic();
setControlLines(mControlLinesValue);
} }
@Override @Override
@ -305,6 +343,9 @@ public class ProlificSerialDriver implements UsbSerialDriver {
if (baudRate <= 0) { if (baudRate <= 0) {
throw new IllegalArgumentException("Invalid baud rate: " + baudRate); throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
} }
if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
return baudRate;
}
for(int br : standardBaudRates) { for(int br : standardBaudRates) {
if (br == baudRate) { if (br == baudRate) {
return baudRate; return baudRate;
@ -312,34 +353,53 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
/* /*
* Formula taken from Linux + FreeBSD. * 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) * baudrate = baseline / (mantissa * 4^exponent)
* where * where
* mantissa = buf[8:0] * mantissa = buf[8:0]
* exponent = buf[11:9] * 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; int baseline, mantissa, exponent, buf, effectiveBaudRate;
baseline = 12000000 * 32; baseline = 12000000 * 32;
mantissa = baseline / baudRate; mantissa = baseline / baudRate;
if (mantissa == 0) { // > unrealistic 384 MBaud if (mantissa == 0) { // > unrealistic 384 MBaud
throw new UnsupportedOperationException("Baud rate to high"); throw new UnsupportedOperationException("Baud rate to high");
} }
exponent = 0; exponent = 0;
while (mantissa >= 512) { if (mDeviceType == DeviceType.DEVICE_TYPE_T) {
if (exponent < 7) { while (mantissa >= 2048) {
mantissa >>= 2; /* divide by 4 */ if (exponent < 15) {
exponent++; mantissa >>= 1; /* divide by 2 */
} else { // < 45.8 baud exponent++;
throw new UnsupportedOperationException("Baud rate to low"); } 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);
} }
int effectiveBaudRate = (baseline / mantissa) >> (exponent << 1);
double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate));
if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud
throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); 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", 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)); baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent));
@ -347,7 +407,7 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
@Override @Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException {
baudRate = filterBaudRate(baudRate); baudRate = filterBaudRate(baudRate);
if ((mBaudRate == baudRate) && (mDataBits == dataBits) if ((mBaudRate == baudRate) && (mDataBits == dataBits)
&& (mStopBits == stopBits) && (mParity == parity)) { && (mStopBits == stopBits) && (mParity == parity)) {
@ -483,12 +543,17 @@ public class ProlificSerialDriver implements UsbSerialDriver {
@Override @Override
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException {
if (purgeWriteBuffers) { if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) {
vendorOut(FLUSH_RX_REQUEST, 0, null); int index = 0;
} if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE;
if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE;
if (purgeReadBuffers) { if(index != 0)
vendorOut(FLUSH_TX_REQUEST, 0, null); vendorOut(RESET_HXN_REQUEST, index, null);
} else {
if (purgeWriteBuffers)
vendorOut(FLUSH_RX_REQUEST, 0, null);
if (purgeReadBuffers)
vendorOut(FLUSH_TX_REQUEST, 0, null);
} }
} }
@ -499,9 +564,18 @@ public class ProlificSerialDriver implements UsbSerialDriver {
} }
public static Map<Integer, int[]> getSupportedDevices() { public static Map<Integer, int[]> getSupportedDevices() {
final Map<Integer, int[]> supportedDevices = new LinkedHashMap<Integer, int[]>(); final Map<Integer, int[]> supportedDevices = new LinkedHashMap<>();
supportedDevices.put(UsbId.VENDOR_PROLIFIC, supportedDevices.put(UsbId.VENDOR_PROLIFIC,
new int[] { UsbId.PROLIFIC_PL2303, }); 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; return supportedDevices;
} }
} }

View file

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

View file

@ -50,7 +50,14 @@ public final class UsbId {
public static final int SILABS_CP2108 = 0xea71; public static final int SILABS_CP2108 = 0xea71;
public static final int VENDOR_PROLIFIC = 0x067b; public static final int VENDOR_PROLIFIC = 0x067b;
public static final int PROLIFIC_PL2303 = 0x2303; public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX
public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN
public static final int 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 VENDOR_QINHENG = 0x1a86;
public static final int QINHENG_CH340 = 0x7523; public static final int QINHENG_CH340 = 0x7523;
@ -60,6 +67,9 @@ public final class UsbId {
public static final int VENDOR_ARM = 0x0d28; public static final int VENDOR_ARM = 0x0d28;
public static final int ARM_MBED = 0x0204; public static final int ARM_MBED = 0x0204;
public static final int VENDOR_ST = 0x0483;
public static final int ST_CDC = 0x5740;
private UsbId() { private UsbId() {
throw new IllegalAccessError("Non-instantiable class"); throw new IllegalAccessError("Non-instantiable class");
} }

View file

@ -21,7 +21,7 @@ public interface UsbSerialDriver {
* *
* @return the device * @return the device
*/ */
public UsbDevice getDevice(); UsbDevice getDevice();
/** /**
* Returns all available ports for this device. This list must have at least * Returns all available ports for this device. This list must have at least
@ -29,5 +29,5 @@ public interface UsbSerialDriver {
* *
* @return the ports * @return the ports
*/ */
public List<UsbSerialPort> getPorts(); List<UsbSerialPort> getPorts();
} }

View file

@ -8,10 +8,15 @@ package org.emulator.calculator.usbserial.driver;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import androidx.annotation.IntDef;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet; import java.util.EnumSet;
/** /**
@ -22,73 +27,65 @@ import java.util.EnumSet;
public interface UsbSerialPort extends Closeable { public interface UsbSerialPort extends Closeable {
/** 5 data bits. */ /** 5 data bits. */
public static final int DATABITS_5 = 5; int DATABITS_5 = 5;
/** 6 data bits. */ /** 6 data bits. */
public static final int DATABITS_6 = 6; int DATABITS_6 = 6;
/** 7 data bits. */ /** 7 data bits. */
public static final int DATABITS_7 = 7; int DATABITS_7 = 7;
/** 8 data bits. */ /** 8 data bits. */
public static final int DATABITS_8 = 8; 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;
/** Values for setParameters(..., parity) */
@Retention(RetentionPolicy.SOURCE)
@IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE})
@interface Parity {}
/** No parity. */ /** No parity. */
public static final int PARITY_NONE = 0; int PARITY_NONE = 0;
/** Odd parity. */ /** Odd parity. */
public static final int PARITY_ODD = 1; int PARITY_ODD = 1;
/** Even parity. */ /** Even parity. */
public static final int PARITY_EVEN = 2; int PARITY_EVEN = 2;
/** Mark parity. */ /** Mark parity. */
public static final int PARITY_MARK = 3; int PARITY_MARK = 3;
/** Space parity. */ /** Space parity. */
public static final int PARITY_SPACE = 4; int PARITY_SPACE = 4;
/** 1 stop bit. */ /** 1 stop bit. */
public static final int STOPBITS_1 = 1; int STOPBITS_1 = 1;
/** 1.5 stop bits. */ /** 1.5 stop bits. */
public static final int STOPBITS_1_5 = 3; int STOPBITS_1_5 = 3;
/** 2 stop bits. */ /** 2 stop bits. */
public static final int STOPBITS_2 = 2; int STOPBITS_2 = 2;
/** values for get[Supported]ControlLines() */ /** Values for get[Supported]ControlLines() */
public enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }; enum ControlLine { RTS, CTS, DTR, DSR, CD, RI }
/** /**
* Returns the driver used by this port. * Returns the driver used by this port.
*/ */
public UsbSerialDriver getDriver(); UsbSerialDriver getDriver();
/** /**
* Returns the currently-bound USB device. * Returns the currently-bound USB device.
*/ */
public UsbDevice getDevice(); UsbDevice getDevice();
/** /**
* Port number within driver. * Port number within driver.
*/ */
public int getPortNumber(); 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}. * The serial number of the underlying UsbDeviceConnection, or {@code null}.
@ -96,24 +93,24 @@ public interface UsbSerialPort extends Closeable {
* @return value from {@link UsbDeviceConnection#getSerial()} * @return value from {@link UsbDeviceConnection#getSerial()}
* @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted
*/ */
public String getSerial(); String getSerial();
/** /**
* Opens and initializes the port. Upon success, caller must ensure that * Opens and initializes the port. Upon success, caller must ensure that
* {@link #close()} is eventually called. * {@link #close()} is eventually called.
* *
* @param connection an open device connection, acquired with * @param connection an open device connection, acquired with
* {@link UsbManager#openDevice(UsbDevice)} * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)}
* @throws IOException on error opening or initializing the port. * @throws IOException on error opening or initializing the port.
*/ */
public void open(UsbDeviceConnection connection) throws IOException; void open(UsbDeviceConnection connection) throws IOException;
/** /**
* Closes the port and {@link UsbDeviceConnection} * Closes the port and {@link UsbDeviceConnection}
* *
* @throws IOException on error closing the port. * @throws IOException on error closing the port.
*/ */
public void close() throws IOException; void close() throws IOException;
/** /**
* Reads as many bytes as possible into the destination buffer. * Reads as many bytes as possible into the destination buffer.
@ -123,17 +120,18 @@ public interface UsbSerialPort extends Closeable {
* @return the actual number of bytes read * @return the actual number of bytes read
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
*/ */
public int read(final byte[] dest, final int timeout) throws IOException; int read(final byte[] dest, final int timeout) throws IOException;
/** /**
* Writes as many bytes as possible from the source buffer. * Writes as many bytes as possible from the source buffer.
* *
* @param src the source byte buffer * @param src the source byte buffer
* @param timeout the timeout for writing in milliseconds, 0 is infinite * @param timeout the timeout for writing in milliseconds, 0 is infinite
* @return the actual number of bytes written * @throws SerialTimeoutException if timeout reached before sending all data.
* ex.bytesTransferred may contain bytes transferred
* @throws IOException if an error occurred during writing * @throws IOException if an error occurred during writing
*/ */
public int write(final byte[] src, final int timeout) throws IOException; void write(final byte[] src, final int timeout) throws IOException;
/** /**
* Sets various serial port parameters. * Sets various serial port parameters.
@ -147,7 +145,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException on error setting the port parameters * @throws IOException on error setting the port parameters
* @throws UnsupportedOperationException if values are not supported by a specific device * @throws UnsupportedOperationException if values are not supported by a specific device
*/ */
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException; void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException;
/** /**
* Gets the CD (Carrier Detect) bit from the underlying UART. * Gets the CD (Carrier Detect) bit from the underlying UART.
@ -156,7 +154,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getCD() throws IOException; boolean getCD() throws IOException;
/** /**
* Gets the CTS (Clear To Send) bit from the underlying UART. * Gets the CTS (Clear To Send) bit from the underlying UART.
@ -165,7 +163,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getCTS() throws IOException; boolean getCTS() throws IOException;
/** /**
* Gets the DSR (Data Set Ready) bit from the underlying UART. * Gets the DSR (Data Set Ready) bit from the underlying UART.
@ -174,7 +172,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getDSR() throws IOException; boolean getDSR() throws IOException;
/** /**
* Gets the DTR (Data Terminal Ready) bit from the underlying UART. * Gets the DTR (Data Terminal Ready) bit from the underlying UART.
@ -183,7 +181,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getDTR() throws IOException; boolean getDTR() throws IOException;
/** /**
* Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported.
@ -192,7 +190,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during writing * @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public void setDTR(boolean value) throws IOException; void setDTR(boolean value) throws IOException;
/** /**
* Gets the RI (Ring Indicator) bit from the underlying UART. * Gets the RI (Ring Indicator) bit from the underlying UART.
@ -201,7 +199,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getRI() throws IOException; boolean getRI() throws IOException;
/** /**
* Gets the RTS (Request To Send) bit from the underlying UART. * Gets the RTS (Request To Send) bit from the underlying UART.
@ -210,7 +208,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public boolean getRTS() throws IOException; boolean getRTS() throws IOException;
/** /**
* Sets the RTS (Request To Send) bit on the underlying UART, if supported. * Sets the RTS (Request To Send) bit on the underlying UART, if supported.
@ -219,7 +217,7 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during writing * @throws IOException if an error occurred during writing
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public void setRTS(boolean value) throws IOException; void setRTS(boolean value) throws IOException;
/** /**
* Gets all control line values from the underlying UART, if supported. * Gets all control line values from the underlying UART, if supported.
@ -228,7 +226,7 @@ public interface UsbSerialPort extends Closeable {
* @return EnumSet.contains(...) is {@code true} if set, else {@code false} * @return EnumSet.contains(...) is {@code true} if set, else {@code false}
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
*/ */
public EnumSet<ControlLine> getControlLines() throws IOException; EnumSet<ControlLine> getControlLines() throws IOException;
/** /**
* Gets all control line supported flags. * Gets all control line supported flags.
@ -236,7 +234,7 @@ public interface UsbSerialPort extends Closeable {
* @return EnumSet.contains(...) is {@code true} if supported, else {@code false} * @return EnumSet.contains(...) is {@code true} if supported, else {@code false}
* @throws IOException if an error occurred during reading * @throws IOException if an error occurred during reading
*/ */
public EnumSet<ControlLine> getSupportedControlLines() throws IOException; EnumSet<ControlLine> getSupportedControlLines() throws IOException;
/** /**
* Purge non-transmitted output data and / or non-read input data. * Purge non-transmitted output data and / or non-read input data.
@ -246,18 +244,18 @@ public interface UsbSerialPort extends Closeable {
* @throws IOException if an error occurred during flush * @throws IOException if an error occurred during flush
* @throws UnsupportedOperationException if not supported * @throws UnsupportedOperationException if not supported
*/ */
public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException;
/** /**
* send BREAK condition. * send BREAK condition.
* *
* @param value set/reset * @param value set/reset
*/ */
public void setBreak(boolean value) throws IOException; void setBreak(boolean value) throws IOException;
/** /**
* Returns the current state of the connection. * Returns the current state of the connection.
*/ */
public boolean isOpen(); boolean isOpen();
} }

View file

@ -46,11 +46,11 @@ public class UsbSerialProber {
* not require permission from the Android USB system, since it does not * not require permission from the Android USB system, since it does not
* open any of the devices. * open any of the devices.
* *
* @param usbManager * @param usbManager usb manager
* @return a list, possibly empty, of all compatible drivers * @return a list, possibly empty, of all compatible drivers
*/ */
public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) { public List<UsbSerialDriver> findAllDrivers(final UsbManager usbManager) {
final List<UsbSerialDriver> result = new ArrayList<UsbSerialDriver>(); final List<UsbSerialDriver> result = new ArrayList<>();
for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) {
final UsbSerialDriver driver = probeDevice(usbDevice); final UsbSerialDriver driver = probeDevice(usbDevice);

View file

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

View file

@ -22,7 +22,7 @@ import java.nio.ByteBuffer;
public class SerialInputOutputManager implements Runnable { public class SerialInputOutputManager implements Runnable {
private static final String TAG = SerialInputOutputManager.class.getSimpleName(); private static final String TAG = SerialInputOutputManager.class.getSimpleName();
private static final boolean DEBUG = false; public static boolean DEBUG = false;
private static final int BUFSIZ = 4096; private static final int BUFSIZ = 4096;
/** /**
@ -34,7 +34,7 @@ public class SerialInputOutputManager implements Runnable {
private final Object mReadBufferLock = new Object(); private final Object mReadBufferLock = new Object();
private final Object mWriteBufferLock = new Object(); private final Object mWriteBufferLock = new Object();
private ByteBuffer mReadBuffer = ByteBuffer.allocate(BUFSIZ); private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize()
private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ);
public enum State { public enum State {
@ -52,21 +52,23 @@ public class SerialInputOutputManager implements Runnable {
/** /**
* Called when new incoming data is available. * Called when new incoming data is available.
*/ */
public void onNewData(byte[] data); void onNewData(byte[] data);
/** /**
* Called when {@link SerialInputOutputManager#run()} aborts due to an error. * Called when {@link SerialInputOutputManager#run()} aborts due to an error.
*/ */
public void onRunError(Exception e); void onRunError(Exception e);
} }
public SerialInputOutputManager(UsbSerialPort serialPort) { public SerialInputOutputManager(UsbSerialPort serialPort) {
mSerialPort = serialPort; mSerialPort = serialPort;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
} }
public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) {
mSerialPort = serialPort; mSerialPort = serialPort;
mListener = listener; mListener = listener;
mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
} }
public synchronized void setListener(Listener listener) { public synchronized void setListener(Listener listener) {
@ -78,7 +80,7 @@ public class SerialInputOutputManager implements Runnable {
} }
/** /**
* setThreadPriority. By default use higher priority than UI thread to prevent data loss * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss
* *
* @param threadPriority see {@link Process#setThreadPriority(int)} * @param threadPriority see {@link Process#setThreadPriority(int)}
* */ * */
@ -140,8 +142,8 @@ public class SerialInputOutputManager implements Runnable {
return mWriteBuffer.capacity(); return mWriteBuffer.capacity();
} }
/* /**
* when writeAsync is used, it is recommended to use readTimeout != 0, * when using writeAsync, it is recommended to use readTimeout != 0,
* else the write will be delayed until read data is available * else the write will be delayed until read data is available
*/ */
public void writeAsync(byte[] data) { public void writeAsync(byte[] data) {
@ -150,6 +152,21 @@ public class SerialInputOutputManager implements Runnable {
} }
} }
/**
* 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() { public synchronized void stop() {
if (getState() == State.RUNNING) { if (getState() == State.RUNNING) {
Log.i(TAG, "Stop requested"); Log.i(TAG, "Stop requested");
@ -167,18 +184,16 @@ public class SerialInputOutputManager implements Runnable {
*/ */
@Override @Override
public void run() { public void run() {
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
setThreadPriority(mThreadPriority);
synchronized (this) { synchronized (this) {
if (getState() != State.STOPPED) { if (getState() != State.STOPPED) {
throw new IllegalStateException("Already running"); throw new IllegalStateException("Already running");
} }
mState = State.RUNNING; mState = State.RUNNING;
} }
Log.i(TAG, "Running ..."); Log.i(TAG, "Running ...");
try { try {
if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
Process.setThreadPriority(mThreadPriority);
while (true) { while (true) {
if (getState() != State.RUNNING) { if (getState() != State.RUNNING) {
Log.i(TAG, "Stopping mState=" + getState()); Log.i(TAG, "Stopping mState=" + getState());
@ -202,7 +217,7 @@ public class SerialInputOutputManager implements Runnable {
private void step() throws IOException { private void step() throws IOException {
// Handle incoming data. // Handle incoming data.
byte[] buffer = null; byte[] buffer;
synchronized (mReadBufferLock) { synchronized (mReadBufferLock) {
buffer = mReadBuffer.array(); buffer = mReadBuffer.array();
} }

View file

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

View file

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

View file

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

View file

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