mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Since the Android USB stack and subsequently the usb-serial-for-android driver have problems with read-timeouts, the read-timeout is now implemented in AndroidSerial.java. Also, DC_STATUS_TIMEOUT is returned if there are less bytes returned than expected. Different chipsets seem to behave differently with usb-serial-for-android. On CP210x the read blocks until there is some data here, but on FTDI the chip seems to return whatever is currently in the buffer (so 0 bytes if the buffer is empty). This different behaviour should be mitigated by the changes by this commit. Signed-off-by: Christof Arnosti <charno@charno.ch>
297 lines
11 KiB
Java
297 lines
11 KiB
Java
package org.subsurfacedivelog.mobile;
|
|
|
|
import com.hoho.android.usbserial.driver.*;
|
|
|
|
import android.hardware.usb.UsbDeviceConnection;
|
|
import android.hardware.usb.UsbManager;
|
|
import android.hardware.usb.UsbDevice;
|
|
|
|
import android.content.Context;
|
|
import android.util.Log;
|
|
import android.app.PendingIntent;
|
|
import android.content.Intent;
|
|
|
|
import org.subsurfacedivelog.mobile.SubsurfaceMobileActivity;
|
|
|
|
import java.lang.System;
|
|
import java.lang.Thread;
|
|
import java.util.Queue;
|
|
import java.util.List;
|
|
import java.util.LinkedList;
|
|
import java.lang.Math;
|
|
import java.util.Date;
|
|
|
|
public class AndroidSerial {
|
|
|
|
private static final String TAG = "AndroidSerial";
|
|
|
|
private static final int DC_STATUS_SUCCESS = 0;
|
|
private static final int DC_STATUS_DONE = 1;
|
|
private static final int DC_STATUS_UNSUPPORTED = -1;
|
|
private static final int DC_STATUS_INVALIDARGS = -2;
|
|
private static final int DC_STATUS_NOMEMORY = -3;
|
|
private static final int DC_STATUS_NODEVICE = -4;
|
|
private static final int DC_STATUS_NOACCESS = -5;
|
|
private static final int DC_STATUS_IO = -6;
|
|
private static final int DC_STATUS_TIMEOUT = -7;
|
|
private static final int DC_STATUS_PROTOCOL = -8;
|
|
private static final int DC_STATUS_DATAFORMAT = -9;
|
|
private static final int DC_STATUS_CANCELLED = -1;
|
|
|
|
/**
|
|
* The parity checking scheme.
|
|
* Matches us-serial-for-android
|
|
*/
|
|
private static final int DC_PARITY_NONE = 0;
|
|
private static final int DC_PARITY_ODD = 1;
|
|
private static final int DC_PARITY_EVEN = 2;
|
|
private static final int DC_PARITY_MARK = 3;
|
|
private static final int DC_PARITY_SPACE = 4;
|
|
|
|
/**
|
|
* The number of stop bits.
|
|
* Has to be translated for usb-serial-for-android
|
|
*/
|
|
private static final int DC_STOPBITS_ONE = 0; /**< 1 stop bit */
|
|
private static final int DC_STOPBITS_ONEPOINTFIVE = 1; /**< 1.5 stop bits*/
|
|
private static final int DC_STOPBITS_TWO = 2; /**< 2 stop bits */
|
|
|
|
/**
|
|
* The direction of the data transmission.
|
|
*/
|
|
private static final int DC_DIRECTION_INPUT = 1; /**< Input direction */
|
|
private static final int DC_DIRECTION_OUTPUT = 2; /**< Output direction */
|
|
private static final int DC_DIRECTION_ALL = 3; /**< All directions */
|
|
|
|
|
|
private UsbSerialPort usbSerialPort;
|
|
private int timeout = 0;
|
|
private LinkedList<Byte> readBuffer = new LinkedList<Byte>();
|
|
|
|
private static String printQueue(LinkedList<Byte> readBuffer)
|
|
{
|
|
String str = "" + readBuffer.size() + " elements: ";
|
|
for (byte element : readBuffer) {
|
|
str = str + String.format("%02X", element);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
private AndroidSerial(UsbSerialPort usbSerialPort)
|
|
{
|
|
this.usbSerialPort = usbSerialPort;
|
|
}
|
|
|
|
public static AndroidSerial open_android_serial()
|
|
{
|
|
try {
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
// Find all available drivers from attached devices.
|
|
Context context = SubsurfaceMobileActivity.getAppContext();
|
|
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
|
ProbeTable usbSerialProbetable = UsbSerialProber.getDefaultProbeTable();
|
|
|
|
usbSerialProbetable.addProduct(0x0403, 0xf460, FtdiSerialDriver.class); // Oceanic Custom PID
|
|
usbSerialProbetable.addProduct(0x0403, 0xf680, FtdiSerialDriver.class); // Suunto Custom PID
|
|
usbSerialProbetable.addProduct(0x0403, 0x87d0, FtdiSerialDriver.class); // Cressi (Leonardo) Custom PID
|
|
|
|
usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Mares (Nemo Sport) / Cressi Custom PID
|
|
usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Zeagle Custom PID
|
|
usbSerialProbetable.addProduct(0xFFFF, 0x0005, CdcAcmSerialDriver.class); // Mares Icon HD Custom PID
|
|
|
|
UsbSerialProber usbSerialProber = new UsbSerialProber(usbSerialProbetable);
|
|
|
|
List<UsbSerialDriver> availableDrivers = usbSerialProber.findAllDrivers(manager);
|
|
if (availableDrivers.isEmpty()) {
|
|
Log.w(TAG, "no usb-to-serial devices found!");
|
|
return null;
|
|
}
|
|
|
|
// Open a connection to the first available driver.
|
|
UsbSerialDriver driver = availableDrivers.get(0);
|
|
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
|
|
|
|
if (connection == null) {
|
|
manager.requestPermission(driver.getDevice(), PendingIntent.getBroadcast(context, 0, new Intent("org.subsurfacedivelog.mobile.USB_PERMISSION"), 0));
|
|
Log.w(TAG, "Could not open device!");
|
|
return null;
|
|
}
|
|
|
|
Log.i(TAG, "Num ports: " + driver.getPorts().size());
|
|
|
|
UsbSerialPort usbSerialPort = driver.getPorts().get(0); // Most devices have just one port (port 0)
|
|
usbSerialPort.open(connection);
|
|
Log.i(TAG, "Opened serial device " + usbSerialPort);
|
|
|
|
return new AndroidSerial(usbSerialPort);
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/* dc_status_t */
|
|
public int set_timeout(int timeout)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
this.timeout = timeout;
|
|
return AndroidSerial.DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* dc_status_t */
|
|
public int set_dtr(boolean value)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
usbSerialPort.setDTR(value);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
return AndroidSerial.DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* dc_status_t */
|
|
public int set_rts(boolean value)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
usbSerialPort.setRTS(value);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
return AndroidSerial.DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* length of read data, or dc_status_t if not successful */
|
|
/*
|
|
// Currently commented out since a non-blocking get_available is not possible due to usb-serial-for-android limitations
|
|
public int get_available() {
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
byte[] readData = new byte[64]; // TODO magic number
|
|
int len = usbSerialPort.read(readData, 250); // TODO magic number
|
|
for (int i = 0; i < len; i++) {
|
|
readBuffer.add(readData[i]);
|
|
}
|
|
return readBuffer.size();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Flow control is not implemented in usb-serial-for-android. It's not used by any dive computer anyways...
|
|
/* dc_status_t */
|
|
public int configure(int baudrate, int databits, /*dc_parity_t*/ int parity, /*dc_stopbits_t*/ int stopbits)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName() + ", baudrate=" + baudrate + ", databits=" + databits + ", parity=" + parity + ", stopbits=" + stopbits);
|
|
int translated_stopbits = 0;
|
|
switch(stopbits) {
|
|
case AndroidSerial.DC_STOPBITS_ONE:
|
|
translated_stopbits = UsbSerialPort.STOPBITS_1;
|
|
break;
|
|
case AndroidSerial.DC_STOPBITS_ONEPOINTFIVE:
|
|
translated_stopbits = UsbSerialPort.STOPBITS_1_5;
|
|
break;
|
|
case AndroidSerial.DC_STOPBITS_TWO:
|
|
translated_stopbits = UsbSerialPort.STOPBITS_2;
|
|
break;
|
|
}
|
|
|
|
try {
|
|
usbSerialPort.setParameters(baudrate, databits, translated_stopbits, parity);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
return AndroidSerial.DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
/* length of read data, or dc_status_t if not successful */
|
|
public int read(byte[] data)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
|
|
try {
|
|
|
|
Log.d(TAG, "read length: " + data.length);
|
|
|
|
int toReadFromHwLength = data.length - readBuffer.size();
|
|
int arraylength = (toReadFromHwLength % 64) != 0 ? toReadFromHwLength + (64 - (toReadFromHwLength % 64)): toReadFromHwLength; // use blocks of 64 for reading
|
|
|
|
long startTime = (new Date()).getTime();
|
|
|
|
// while we don't have enough in the buffer, try to read from HW until there is enough or timeout is reached.
|
|
while (toReadFromHwLength > 0 && (startTime + timeout > (new Date()).getTime() || timeout == 0)) {
|
|
// Read and append to buffer
|
|
byte[] readFromHwData = new byte[arraylength];
|
|
int actuallyReadFromHwLength = usbSerialPort.read(readFromHwData, 0); // This behaves differently on different chipsets. CP210x blocks, FTDI seems to return instantly.
|
|
for (int i = 0; i < actuallyReadFromHwLength; i++ ) {
|
|
readBuffer.add(readFromHwData[i]);
|
|
}
|
|
toReadFromHwLength = data.length - readBuffer.size();
|
|
arraylength = (toReadFromHwLength % 64) != 0 ? toReadFromHwLength + (64 - (toReadFromHwLength % 64)): toReadFromHwLength; // use blocks of 64 for reading
|
|
}
|
|
|
|
//Log.d(TAG, "read buffer: " + printQueue(readBuffer));
|
|
int returnLength = 0;
|
|
for (returnLength = 0; returnLength < data.length; returnLength++) {
|
|
if (readBuffer.isEmpty()) break;
|
|
data[returnLength] = readBuffer.remove();
|
|
}
|
|
|
|
Log.d(TAG, "return length: " + returnLength);
|
|
return returnLength;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
}
|
|
|
|
/* number of bytes written, or dc_status_t */
|
|
public int write (byte[] data)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
Log.d(TAG, "write length: " + data.length);
|
|
int retval = usbSerialPort.write(data, timeout);
|
|
Log.d(TAG, "actual write length: " + retval);
|
|
return retval;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
}
|
|
|
|
/* dc_status_t */
|
|
public int purge (/*dc_direction_t*/ int direction)
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
if ((direction | AndroidSerial.DC_DIRECTION_INPUT) > 0) readBuffer.clear();
|
|
boolean retval = this.usbSerialPort.purgeHwBuffers((direction | AndroidSerial.DC_DIRECTION_OUTPUT) > 0, (direction | AndroidSerial.DC_DIRECTION_INPUT) > 0);
|
|
return retval ? AndroidSerial.DC_STATUS_SUCCESS : AndroidSerial.DC_STATUS_IO;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
}
|
|
|
|
/*dc_status_t*/
|
|
public int close ()
|
|
{
|
|
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
|
|
try {
|
|
usbSerialPort.close();
|
|
return AndroidSerial.DC_STATUS_SUCCESS;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in " + Thread.currentThread().getStackTrace()[2].getMethodName(), e);
|
|
return AndroidSerial.DC_STATUS_IO;
|
|
}
|
|
}
|
|
}
|