Commit 9593ff3d authored by Don Gagne's avatar Don Gagne

Use FTDI driver provided by ftdicjip.com

- Fixes lost connections with FTDI radios
- Modified code to only test vendor id, all product ids are let through
- Modified code to pass log output through to C++ side
QGCLoggingCategory system
parent 81564629
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Everything -->
<!-- Allow anything connected -->
<usb-device />
<!-- 0x26AC / 0x11: PX4 FMU Pixhawk -->
<!--
<usb-device vendor-id="9900" product-id="17" />
-->
<!-- 0x0403 / 0x6001: FTDI FT232R UART -->
<!--
<usb-device vendor-id="1027" product-id="24577" />
-->
<!-- 0x0403 / 0x6015: FTDI FT231X -->
<!--
<usb-device vendor-id="1027" product-id="24597" />
-->
<!-- 0x2341 / Arduino -->
<!--
<usb-device vendor-id="9025" />
-->
<!-- 0x16C0 / 0x0483: Teensyduino -->
<!--
<usb-device vendor-id="5824" product-id="1155" />
-->
<!-- 0x10C4 / 0xEA60: CP210x UART Bridge -->
<!--
<usb-device vendor-id="4292" product-id="60000" />
-->
<!-- 0x067B / 0x2303: Prolific PL2303 -->
<!--
<usb-device vendor-id="1659" product-id="8963" />
-->
</resources>
......@@ -18,6 +18,10 @@
* Project home page: http://code.google.com/p/usb-serial-for-android/
*/
// IMPORTANT NOTE:
// This code has been modified from the original source. It now uses the FTDI driver provided by
// ftdichip.com to communicate with an FTDI device. The previous code did not work with all FTDI
// devices.
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbConstants;
......@@ -27,12 +31,16 @@ import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbRequest;
import android.util.Log;
import com.ftdi.j2xx.D2xxManager;
import com.ftdi.j2xx.FT_Device;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
import org.qgroundcontrol.qgchelper.UsbDeviceJNI;
/**
* A {@link CommonUsbSerialDriver} implementation for a variety of FTDI devices
* <p>
......@@ -167,6 +175,8 @@ public class FtdiSerialDriver extends CommonUsbSerialDriver {
*/
private static final boolean ENABLE_ASYNC_READS = false;
FT_Device m_ftDev;
/**
* Filter FTDI status bytes from buffer
* @param src The source buffer (which contains status bytes)
......@@ -219,261 +229,140 @@ public class FtdiSerialDriver extends CommonUsbSerialDriver {
@Override
public void open() throws IOException {
boolean opened = false;
D2xxManager ftD2xx = null;
try {
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
if (mConnection.claimInterface(mDevice.getInterface(i), true)) {
Log.d(TAG, "claimInterface " + i + " SUCCESS");
} else {
throw new IOException("Error claiming interface " + i);
}
}
reset();
opened = true;
ftD2xx = D2xxManager.getInstance(UsbDeviceJNI.m_context);
} catch (D2xxManager.D2xxException ex) {
UsbDeviceJNI.qgcLogDebug("D2xxManager.getInstance threw exception: " + ex.getMessage());
}
if (ftD2xx == null) {
String errMsg = "Unable to retrieve D2xxManager instance.";
UsbDeviceJNI.qgcLogWarning(errMsg);
throw new IOException(errMsg);
}
UsbDeviceJNI.qgcLogDebug("Opened D2xxManager");
int DevCount = ftD2xx.createDeviceInfoList(UsbDeviceJNI.m_context);
UsbDeviceJNI.qgcLogDebug("Found " + DevCount + " ftdi devices.");
if (DevCount < 1) {
throw new IOException("No FTDI Devices found");
}
m_ftDev = null;
try {
m_ftDev = ftD2xx.openByIndex(UsbDeviceJNI.m_context, 0);
} catch (NullPointerException e) {
UsbDeviceJNI.qgcLogDebug("ftD2xx.openByIndex exception: " + e.getMessage());
} finally {
if (!opened) {
close();
if (m_ftDev == null) {
throw new IOException("No FTDI Devices found");
}
}
UsbDeviceJNI.qgcLogDebug("Opened FTDI device.");
}
@Override
public void close() {
mConnection.close();
if (m_ftDev != null) {
try {
m_ftDev.close();
} catch (Exception e) {
UsbDeviceJNI.qgcLogWarning("close exception: " + e.getMessage());
}
m_ftDev = null;
}
}
@Override
public int read(byte[] dest, int timeoutMillis) throws IOException {
final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(0);
if (ENABLE_ASYNC_READS) {
final int readAmt;
synchronized (mReadBufferLock) {
// mReadBuffer is only used for maximum read size.
readAmt = Math.min(dest.length, mReadBuffer.length);
}
final UsbRequest request = new UsbRequest();
request.initialize(mConnection, endpoint);
final ByteBuffer buf = ByteBuffer.wrap(dest);
if (!request.queue(buf, readAmt)) {
throw new IOException("Error queueing request.");
}
final UsbRequest response = mConnection.requestWait();
if (response == null) {
throw new IOException("Null response");
}
final int payloadBytesRead = buf.position() - MODEM_STATUS_HEADER_LENGTH;
if (payloadBytesRead > 0) {
return payloadBytesRead;
} else {
return 0;
}
} else {
final int totalBytesRead;
synchronized (mReadBufferLock) {
final int readAmt = Math.min(dest.length, mReadBuffer.length);
totalBytesRead = mConnection.bulkTransfer(endpoint, mReadBuffer,
readAmt, timeoutMillis);
if (totalBytesRead < MODEM_STATUS_HEADER_LENGTH) {
throw new IOException("Expected at least " + MODEM_STATUS_HEADER_LENGTH + " bytes");
}
return filterStatusBytes(mReadBuffer, dest, totalBytesRead, endpoint.getMaxPacketSize());
int totalBytesRead = 0;
int bytesAvailable = m_ftDev.getQueueStatus();
if (bytesAvailable > 0) {
bytesAvailable = Math.min(4096, bytesAvailable);
try {
totalBytesRead = m_ftDev.read(dest, bytesAvailable, timeoutMillis);
} catch (NullPointerException e) {
final String errorMsg = "Error reading: " + e.getMessage();
UsbDeviceJNI.qgcLogWarning(errorMsg);
throw new IOException(errorMsg, e);
}
}
return totalBytesRead;
}
@Override
public int write(byte[] src, int timeoutMillis) throws IOException {
final UsbEndpoint endpoint = mDevice.getInterface(0).getEndpoint(1);
int offset = 0;
while (offset < src.length) {
final int writeLength;
final int amtWritten;
synchronized (mWriteBufferLock) {
final byte[] writeBuffer;
writeLength = Math.min(src.length - offset, mWriteBuffer.length);
if (offset == 0) {
writeBuffer = src;
} else {
// bulkTransfer does not support offsets, make a copy.
System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
writeBuffer = mWriteBuffer;
}
amtWritten = mConnection.bulkTransfer(endpoint, writeBuffer, writeLength,
timeoutMillis);
}
if (amtWritten <= 0) {
throw new IOException("Error writing " + writeLength
+ " bytes at offset " + offset + " length=" + src.length);
}
//Log.d(TAG, "Wrote amtWritten=" + amtWritten + " attempted=" + writeLength);
offset += amtWritten;
try {
m_ftDev.write(src);
return src.length;
} catch (Exception e) {
UsbDeviceJNI.qgcLogWarning("Error writing: " + e.getMessage());
}
return offset;
return 0;
}
private int setBaudRate(int baudRate) throws IOException {
long[] vals = convertBaudrate(baudRate);
long actualBaudrate = vals[0];
long index = vals[1];
long value = vals[2];
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_BAUD_RATE_REQUEST, (int) value, (int) index,
null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting baudrate failed: result=" + result);
try {
m_ftDev.setBaudRate(baudRate);
return baudRate;
} catch (Exception e) {
UsbDeviceJNI.qgcLogWarning("Error setting baud rate: " + e.getMessage());
}
return (int) actualBaudrate;
return 0;
}
@Override
public void setParameters(int baudRate, int dataBits, int stopBits, int parity)
throws IOException {
public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException {
setBaudRate(baudRate);
int config = dataBits;
switch (parity) {
case PARITY_NONE:
config |= (0x00 << 8);
break;
case PARITY_ODD:
config |= (0x01 << 8);
break;
case PARITY_EVEN:
config |= (0x02 << 8);
break;
case PARITY_MARK:
config |= (0x03 << 8);
break;
case PARITY_SPACE:
config |= (0x04 << 8);
break;
default:
throw new IllegalArgumentException("Unknown parity value: " + parity);
switch (dataBits) {
case 7:
dataBits = D2xxManager.FT_DATA_BITS_7;
break;
case 8:
default:
dataBits = D2xxManager.FT_DATA_BITS_8;
break;
}
switch (stopBits) {
case STOPBITS_1:
config |= (0x00 << 11);
break;
case STOPBITS_1_5:
config |= (0x01 << 11);
break;
case STOPBITS_2:
config |= (0x02 << 11);
break;
default:
throw new IllegalArgumentException("Unknown stopBits value: " + stopBits);
}
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_DATA_REQUEST, config, 0 /* index */,
null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Setting parameters failed: result=" + result);
default:
case 0:
stopBits = D2xxManager.FT_STOP_BITS_1;
break;
case 1:
stopBits = D2xxManager.FT_STOP_BITS_2;
break;
}
}
private long[] convertBaudrate(int baudrate) {
// TODO(mikey): Braindead transcription of libfti method. Clean up,
// using more idiomatic Java where possible.
int divisor = 24000000 / baudrate;
int bestDivisor = 0;
int bestBaud = 0;
int bestBaudDiff = 0;
int fracCode[] = {
0, 3, 2, 4, 1, 5, 6, 7
};
for (int i = 0; i < 2; i++) {
int tryDivisor = divisor + i;
int baudEstimate;
int baudDiff;
if (tryDivisor <= 8) {
// Round up to minimum supported divisor
tryDivisor = 8;
} else if (mType != DeviceType.TYPE_AM && tryDivisor < 12) {
// BM doesn't support divisors 9 through 11 inclusive
tryDivisor = 12;
} else if (divisor < 16) {
// AM doesn't support divisors 9 through 15 inclusive
tryDivisor = 16;
} else {
if (mType == DeviceType.TYPE_AM) {
// TODO
} else {
if (tryDivisor > 0x1FFFF) {
// Round down to maximum supported divisor value (for
// BM)
tryDivisor = 0x1FFFF;
}
}
}
// Get estimated baud rate (to nearest integer)
baudEstimate = (24000000 + (tryDivisor / 2)) / tryDivisor;
// Get absolute difference from requested baud rate
if (baudEstimate < baudrate) {
baudDiff = baudrate - baudEstimate;
} else {
baudDiff = baudEstimate - baudrate;
}
if (i == 0 || baudDiff < bestBaudDiff) {
// Closest to requested baud rate so far
bestDivisor = tryDivisor;
bestBaud = baudEstimate;
bestBaudDiff = baudDiff;
if (baudDiff == 0) {
// Spot on! No point trying
break;
}
}
}
// Encode the best divisor value
long encodedDivisor = (bestDivisor >> 3) | (fracCode[bestDivisor & 7] << 14);
// Deal with special cases for encoded value
if (encodedDivisor == 1) {
encodedDivisor = 0; // 3000000 baud
} else if (encodedDivisor == 0x4001) {
encodedDivisor = 1; // 2000000 baud (BM only)
switch (parity) {
default:
case 0:
parity = D2xxManager.FT_PARITY_NONE;
break;
case 1:
parity = D2xxManager.FT_PARITY_ODD;
break;
case 2:
parity = D2xxManager.FT_PARITY_EVEN;
break;
case 3:
parity = D2xxManager.FT_PARITY_MARK;
break;
case 4:
parity = D2xxManager.FT_PARITY_SPACE;
break;
}
// Split into "value" and "index" values
long value = encodedDivisor & 0xFFFF;
long index;
if (mType == DeviceType.TYPE_2232C || mType == DeviceType.TYPE_2232H
|| mType == DeviceType.TYPE_4232H) {
index = (encodedDivisor >> 8) & 0xffff;
index &= 0xFF00;
index |= 0 /* TODO mIndex */;
} else {
index = (encodedDivisor >> 16) & 0xffff;
try {
m_ftDev.setDataCharacteristics((byte)dataBits, (byte)stopBits, (byte)parity);
} catch (Exception e) {
UsbDeviceJNI.qgcLogWarning("Error setDataCharacteristics: " + e.getMessage());
}
// Return the nearest baud rate
return new long[] {
bestBaud, index, value
};
}
@Override
public boolean getCD() throws IOException {
return false;
......@@ -515,18 +404,22 @@ public class FtdiSerialDriver extends CommonUsbSerialDriver {
@Override
public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException {
if (purgeReadBuffers) {
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_RX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Flushing RX failed: result=" + result);
try {
m_ftDev.purge(D2xxManager.FT_PURGE_RX);
} catch (Exception e) {
String errMsg = "Error purgeHwBuffers(RX): "+ e.getMessage();
UsbDeviceJNI.qgcLogWarning(errMsg);
throw new IOException(errMsg);
}
}
if (purgeWriteBuffers) {
int result = mConnection.controlTransfer(FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_TX, 0 /* index */, null, 0, USB_WRITE_TIMEOUT_MILLIS);
if (result != 0) {
throw new IOException("Flushing RX failed: result=" + result);
try {
m_ftDev.purge(D2xxManager.FT_PURGE_TX);
} catch (Exception e) {
String errMsg = "Error purgeHwBuffers(TX): " + e.getMessage();
UsbDeviceJNI.qgcLogWarning(errMsg);
throw new IOException(errMsg);
}
}
......
......@@ -18,6 +18,10 @@
* Project home page: http://code.google.com/p/usb-serial-for-android/
*/
// IMPORTANT NOTE:
// This source has been modified from the original such that testIfSupported only tests for a vendor id
// match. If that matches it allows all product ids through. This provides for better match on unknown boards.
package com.hoho.android.usbserial.driver;
import android.hardware.usb.UsbDevice;
......@@ -230,21 +234,7 @@ public enum UsbSerialProber {
* @param supportedDevices map of vendor IDs to product ID(s)
* @return {@code true} if supported
*/
private static boolean testIfSupported(final UsbDevice usbDevice,
final Map<Integer, int[]> supportedDevices) {
final int[] supportedProducts = supportedDevices.get(
Integer.valueOf(usbDevice.getVendorId()));
if (supportedProducts == null) {
return false;
}
final int productId = usbDevice.getProductId();
for (int supportedProductId : supportedProducts) {
if (productId == supportedProductId) {
return true;
}
}
return false;
private static boolean testIfSupported(final UsbDevice usbDevice, final Map<Integer, int[]> supportedDevices) {
return supportedDevices.containsKey(usbDevice.getVendorId());
}
}
......@@ -68,6 +68,8 @@ public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListe
private static TextToSpeech m_tts;
private static PowerManager.WakeLock m_wl;
public static Context m_context;
private final static UsbIoManager.Listener m_Listener =
new UsbIoManager.Listener()
{
......@@ -90,6 +92,10 @@ public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListe
private static native void nativeDeviceException(int userDataA, String messageA);
private static native void nativeDeviceNewData(int userDataA, byte[] dataA);
// Native C++ functions called to log output
public static native void qgcLogDebug(String message);
public static native void qgcLogWarning(String message);
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Constructor. Only used once to create the initial instance for the static functions.
......@@ -252,7 +258,7 @@ public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListe
tempL = tempL + Integer.toString(deviceL.getVendorId()) + ":";
listL[countL] = tempL;
countL++;
//Log.i(TAG, "Found " + tempL);
qgcLogDebug("Found " + tempL);
}
}
......@@ -273,11 +279,13 @@ public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListe
// calls like close(), read(), and write().
//
/////////////////////////////////////////////////////////////////////////////////////////////////
public static int open(String nameA, int userDataA)
public static int open(Context parentContext, String nameA, int userDataA)
{
int idL = BAD_PORT;
Log.i(TAG, "Getting device list");
m_context = parentContext;
//qgcLogDebug("Getting device list");
if (!getCurrentDevices())
return BAD_PORT;
......@@ -366,7 +374,7 @@ public class UsbDeviceJNI extends QtActivity implements TextToSpeech.OnInitListe
m_ioManager.remove(idL);
}
Log.e(TAG, "Port open exception");
qgcLogWarning("Port open exception: " + exA.getMessage());
return BAD_PORT;
}
}
......
......@@ -46,10 +46,10 @@
#include <QtAndroidExtras/QtAndroidExtras>
#include <QtAndroidExtras/QAndroidJniObject>
#include <android/log.h>
#include "qserialport_android_p.h"
QGC_LOGGING_CATEGORY(AndroidSerialPortLog, "AndroidSerialPortLog")
QT_BEGIN_NAMESPACE
#define BAD_PORT 0
......@@ -91,6 +91,30 @@ static void jniDeviceException(JNIEnv *envA, jobject thizA, jint userDataA, jstr
}
}
static void jniLogDebug(JNIEnv *envA, jobject thizA, jstring messageA)
{
Q_UNUSED(thizA);
const char *stringL = envA->GetStringUTFChars(messageA, NULL);
QString logMessage = QString::fromUtf8(stringL);
envA->ReleaseStringUTFChars(messageA, stringL);
if (envA->ExceptionCheck())
envA->ExceptionClear();
qCDebug(AndroidSerialPortLog) << logMessage;
}
static void jniLogWarning(JNIEnv *envA, jobject thizA, jstring messageA)
{
Q_UNUSED(thizA);
const char *stringL = envA->GetStringUTFChars(messageA, NULL);
QString logMessage = QString::fromUtf8(stringL);
envA->ReleaseStringUTFChars(messageA, stringL);
if (envA->ExceptionCheck())
envA->ExceptionClear();
qWarning() << logMessage;
}
void cleanJavaException()
{
QAndroidJniEnvironment env;
......@@ -116,13 +140,15 @@ QSerialPortPrivate::QSerialPortPrivate(QSerialPort *q)
void QSerialPortPrivate::setNativeMethods(void)
{
__android_log_print(ANDROID_LOG_INFO, kJTag, "Registering Native Functions");
qCDebug(AndroidSerialPortLog) << "Registering Native Functions";
// REGISTER THE C++ FUNCTION WITH JNI
JNINativeMethod javaMethods[] {
{"nativeDeviceHasDisconnected", "(I)V", reinterpret_cast<void *>(jniDeviceHasDisconnected)},
{"nativeDeviceNewData", "(I[B)V", reinterpret_cast<void *>(jniDeviceNewData)},
{"nativeDeviceException", "(ILjava/lang/String;)V", reinterpret_cast<void *>(jniDeviceException)}
{"nativeDeviceHasDisconnected", "(I)V", reinterpret_cast<void *>(jniDeviceHasDisconnected)},
{"nativeDeviceNewData", "(I[B)V", reinterpret_cast<void *>(jniDeviceNewData)},
{"nativeDeviceException", "(ILjava/lang/String;)V", reinterpret_cast<void *>(jniDeviceException)},
{"qgcLogDebug", "(Ljava/lang/String;)V", reinterpret_cast<void *>(jniLogDebug)},
{"qgcLogWarning", "(Ljava/lang/String;)V", reinterpret_cast<void *>(jniLogWarning)}
};
QAndroidJniEnvironment jniEnv;
......@@ -133,36 +159,36 @@ void QSerialPortPrivate::setNativeMethods(void)
jclass objectClass = jniEnv->FindClass(kJniClassName);
if(!objectClass) {
__android_log_print(ANDROID_LOG_ERROR, kJTag, "Couldn't find class: %s", kJniClassName);
qWarning() << "Couldn't find class:" << kJniClassName;
return;
}
jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
__android_log_print(ANDROID_LOG_INFO, kJTag, "Native Functions Registered");
if (val < 0) {
qWarning() << "Error registering methods: " << val;
} else {
qCDebug(AndroidSerialPortLog) << "Native Functions Registered";
}
if (jniEnv->ExceptionCheck()) {
jniEnv->ExceptionDescribe();
jniEnv->ExceptionClear();
}
if (val < 0) {
__android_log_print(ANDROID_LOG_ERROR, kJTag, "Error registering methods");
}
}
bool QSerialPortPrivate::open(QIODevice::OpenMode mode)
{
rwMode = mode;
__android_log_print(ANDROID_LOG_INFO, kJTag, "Opening %s", systemLocation.toLatin1().data());
qCDebug(AndroidSerialPortLog) << "Opening" << systemLocation.toLatin1().data();
__android_log_print(ANDROID_LOG_INFO, kJTag, "Calling Java Open");
QAndroidJniObject jnameL = QAndroidJniObject::fromString(systemLocation);
cleanJavaException();
deviceId = QAndroidJniObject::callStaticMethod<jint>(
kJniClassName,
"open",
"(Ljava/lang/String;I)I",
"(Landroid/content/Context;Ljava/lang/String;I)I",
QtAndroid::androidActivity().object(),
jnameL.object<jstring>(),
(jint)this);
cleanJavaException();
......@@ -171,20 +197,11 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode)
if (deviceId == BAD_PORT)
{
__android_log_print(ANDROID_LOG_ERROR, kJTag, "Error opening %s", systemLocation.toLatin1().data());
qWarning() << "Error opening %s" << systemLocation.toLatin1().data();
q_ptr->setError(QSerialPort::DeviceNotFoundError);
return false;
}
__android_log_print(ANDROID_LOG_INFO, kJTag, "Calling Java getDeviceHandle");
cleanJavaException();
descriptor = QAndroidJniObject::callStaticMethod<jint>(
kJniClassName,
"getDeviceHandle",
"(I)I",
deviceId);
cleanJavaException();
if (rwMode == QIODevice::WriteOnly)
stopReadThread();
......@@ -196,7 +213,7 @@ void QSerialPortPrivate::close()
if (deviceId == BAD_PORT)
return;
__android_log_print(ANDROID_LOG_INFO, kJTag, "Closing %s", systemLocation.toLatin1().data());
qCDebug(AndroidSerialPortLog) << "Closing" << systemLocation.toLatin1().data();
cleanJavaException();
jboolean resultL = QAndroidJniObject::callStaticMethod<jboolean>(
kJniClassName,
......
......@@ -49,6 +49,9 @@
#include <QtCore/qstringlist.h>
#include <QtCore/qthread.h>
#include "QGCLoggingCategory.h"
Q_DECLARE_LOGGING_CATEGORY(AndroidSerialPortLog)
QT_BEGIN_NAMESPACE
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment