Commit cf8b2675 authored by Matej Frančeškin's avatar Matej Frančeškin

Support for TaiSync on Android

parent 4515b8d4
......@@ -15,7 +15,8 @@ OTHER_FILES += \
$$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java \
$$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/QGCActivity.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/UsbIoManager.java
$$PWD/android/src/org/mavlink/qgroundcontrol/UsbIoManager.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/TaiSync.java
DISTFILES += \
$$PWD/android/gradle/wrapper/gradle-wrapper.jar \
......
......@@ -7,10 +7,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"/>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
</intent-filter>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"/>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
<!-- Rest of Standard Manifest -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
......@@ -60,13 +62,14 @@
<!-- Support devices without USB host mode since there are other connection types -->
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
<!-- Support devices without Bluetooth since there are other connection types -->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<!-- Support devices without Bluetooth since there are other connection types -->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<!-- Support devices that don't have location services -->
<!-- Support devices that don't have location services -->
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.usb.accessory"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
......
......@@ -2,5 +2,6 @@
<resources>
<!-- Allow anything connected -->
<usb-device />
<usb-accessory />
</resources>
......@@ -29,10 +29,13 @@ package org.mavlink.qgroundcontrol;
//
////////////////////////////////////////////////////////////////////////////////////////////
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Timer;
import java.util.TimerTask;
import java.io.IOException;
import android.app.Activity;
......@@ -40,12 +43,16 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.*;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.widget.Toast;
import android.util.Log;
import android.os.PowerManager;
import android.view.WindowManager;
import android.os.Bundle;
import android.app.PendingIntent;
import android.view.WindowManager;
import com.hoho.android.usbserial.driver.*;
import org.qtproject.qt5.android.bindings.QtActivity;
......@@ -65,6 +72,8 @@ public class QGCActivity extends QtActivity
private final static ExecutorService m_Executor = Executors.newSingleThreadExecutor();
private static final String TAG = "QGC_QGCActivity";
private static PowerManager.WakeLock m_wl;
private static String USB_ACTION = "org.mavlink.qgroundcontrol.action.USB_PERMISSION";
private TaiSync taiSync = null;
public static Context m_context;
......@@ -85,6 +94,26 @@ public class QGCActivity extends QtActivity
}
};
private final BroadcastReceiver mOpenAccessoryReceiver =
new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (USB_ACTION.equals(action)) {
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
openAccessory(accessory);
}
} else if( UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null) {
closeAccessory(accessory);
}
}
}
};
// NATIVE C++ FUNCTION THAT WILL BE CALLED IF THE DEVICE IS UNPLUGGED
private static native void nativeDeviceHasDisconnected(int userDataA);
private static native void nativeDeviceException(int userDataA, String messageA);
......@@ -120,10 +149,27 @@ public class QGCActivity extends QtActivity
Log.i(TAG, "SCREEN_BRIGHT_WAKE_LOCK not acquired!!!");
}
m_instance.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (m_manager == null) {
try {
m_manager = (UsbManager)m_instance.getSystemService(Context.USB_SERVICE);
taiSync = new TaiSync();
IntentFilter filter = new IntentFilter(USB_ACTION);
filter.addAction( UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mOpenAccessoryReceiver, filter);
probeAccessories();
} catch(Exception e) {
Log.e(TAG, "Exception getCurrentDevices(): " + e);
}
}
}
@Override
protected void onDestroy() {
protected void onDestroy()
{
unregisterReceiver(mOpenAccessoryReceiver);
try {
if(m_wl != null) {
m_wl.release();
......@@ -149,9 +195,6 @@ public class QGCActivity extends QtActivity
if (m_instance == null)
return false;
if (m_manager == null)
m_manager = (UsbManager)m_instance.getSystemService(Context.USB_SERVICE);
if (m_devices != null)
m_devices.clear();
......@@ -193,7 +236,7 @@ public class QGCActivity extends QtActivity
int countL = 0;
int iL;
// CHECK FOR ALREADY OPENED DEVICES AND DON"T INCLUDE THEM IN THE COUNT
// CHECK FOR ALREADY OPENED DEVICES AND DON'T INCLUDE THEM IN THE COUNT
for (iL=0; iL<m_devices.size(); iL++)
{
if (m_openedDevices.get(m_devices.get(iL).getDevice().getDeviceId()) != null)
......@@ -668,5 +711,61 @@ public class QGCActivity extends QtActivity
{
return m_openedDevices.get(idA);
}
UsbAccessory openUsbAccessory = null;
Object openAccessoryLock = new Object();
private void openAccessory(UsbAccessory usbAccessory)
{
Log.i(TAG, "openAccessory: " + usbAccessory.getSerial());
try {
synchronized(openAccessoryLock) {
if ((openUsbAccessory != null && !taiSync.isRunning()) || openUsbAccessory == null) {
openUsbAccessory = usbAccessory;
taiSync.open(m_manager.openAccessory(usbAccessory));
}
}
} catch (IOException e) {
Log.e(TAG, "openAccessory exception: " + e);
taiSync.close();
closeAccessory(openUsbAccessory);
}
}
private void closeAccessory(UsbAccessory usbAccessory)
{
Log.i(TAG, "closeAccessory");
synchronized(openAccessoryLock) {
if (openUsbAccessory != null && usbAccessory == openUsbAccessory && taiSync.isRunning()) {
taiSync.close();
openUsbAccessory = null;
}
}
}
private void probeAccessories()
{
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(USB_ACTION), 0);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run()
{
// Log.i(TAG, "probeAccessories");
UsbAccessory[] accessories = m_manager.getAccessoryList();
if (accessories != null) {
for (UsbAccessory usbAccessory : accessories) {
if (m_manager.hasPermission(usbAccessory)) {
openAccessory(usbAccessory);
} else {
Log.i(TAG, "requestPermission");
m_manager.requestPermission(usbAccessory, pendingIntent);
}
}
}
}
}, 0, 3000);
}
}
package org.mavlink.qgroundcontrol;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class TaiSync
{
private static final int PROTOCOL_VERSION = 0x1;
private static final int PROTOCOL_CHANNEL = 0x02;
private static final int PROTOCOL_DATA = 0x03;
private static final int TELEM_PORT = 14550;
private static final int VIDEO_PORT = 5000;
private boolean running = false;
private DatagramSocket socket = null;
private ParcelFileDescriptor mParcelFileDescriptor;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
private ExecutorService mThreadPool;
private byte[] mBytes = new byte[32 * 1024];
private byte vMaj = 0;
public TaiSync()
{
mThreadPool = Executors.newFixedThreadPool(2);
}
public boolean isRunning()
{
synchronized (this)
{
return running;
}
}
public void open(ParcelFileDescriptor parcelFileDescriptor) throws IOException
{
// Log.i("QGC_TaiSync", "Open");
synchronized (this)
{
if (running) {
return;
}
running = true;
}
mParcelFileDescriptor = parcelFileDescriptor;
if (mParcelFileDescriptor == null) {
throw new IOException("parcelFileDescriptor is null");
}
FileDescriptor fileDescriptor = mParcelFileDescriptor.getFileDescriptor();
mFileInputStream = new FileInputStream(fileDescriptor);
mFileOutputStream = new FileOutputStream(fileDescriptor);
socket = new DatagramSocket();
final InetAddress address = InetAddress.getByName("localhost");
// Request connection packet
byte[] conn = { 0x00, 0x00, 0x00, 0x00, // uint32 - protocol
0x00, 0x00, 0x00, 0x00, // uint32 - dport
0x00, 0x00, 0x00, 0x1C, // uint32 - length
0x00, 0x00, 0x00, 0x00, // uint32 - magic
0x00, 0x00, 0x00, 0x00, // uint32 - version major
0x00, 0x00, 0x00, 0x00, // uint32 - version minor
0x00, 0x00, 0x00, 0x00 }; // uint32 - padding
mFileOutputStream.write(conn);
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
int bytesRead = 0;
try {
while (bytesRead >= 0) {
synchronized (this)
{
if (!running) {
break;
}
}
bytesRead = mFileInputStream.read(mBytes);
if (bytesRead > 0)
{
if (mBytes[3] == PROTOCOL_VERSION)
{
vMaj = mBytes[19];
Log.i("QGC_TaiSync", "Got protocol version message vMaj = " + mBytes[19]);
byte[] data = { 0x00, 0x00, 0x00, 0x01, // uint32 - protocol
0x00, 0x00, 0x00, 0x00, // uint32 - dport
0x00, 0x00, 0x00, 0x1C, // uint32 - length
0x00, 0x00, 0x00, 0x00, // uint32 - magic
0x00, 0x00, 0x00, vMaj, // uint32 - version major
0x00, 0x00, 0x00, 0x00, // uint32 - version minor
0x00, 0x00, 0x00, 0x00 }; // uint32 - padding
mFileOutputStream.write(data);
}
else if (mBytes[3] == PROTOCOL_CHANNEL) {
int dPort = ((mBytes[4] & 0xff)<< 24) | ((mBytes[5]&0xff) << 16) | ((mBytes[6]&0xff) << 8) | (mBytes[7] &0xff);
int dLength = ((mBytes[8] & 0xff)<< 24) | ((mBytes[9]&0xff) << 16) | ((mBytes[10]&0xff) << 8) | (mBytes[11] &0xff);
Log.i("QGC_TaiSync", "Read 2 port = " + dPort + " length = " + dLength);
byte[] data = { 0x00, 0x00, 0x00, 0x02, // uint32 - protocol
0x00, 0x00, mBytes[6], mBytes[7], // uint32 - dport
0x00, 0x00, 0x00, 0x1C, // uint32 - length
0x00, 0x00, 0x00, 0x00, // uint32 - magic
0x00, 0x00, 0x00, vMaj, // uint32 - version major
0x00, 0x00, 0x00, 0x00, // uint32 - version minor
0x00, 0x00, 0x00, 0x00 }; // uint32 - padding
mFileOutputStream.write(data);
}
else if (mBytes[3] == PROTOCOL_DATA) {
int dPort = ((mBytes[4] & 0xff)<< 24) | ((mBytes[5]&0xff) << 16) | ((mBytes[6]&0xff) << 8) | (mBytes[7] &0xff);
int dLength = ((mBytes[8] & 0xff)<< 24) | ((mBytes[9]&0xff) << 16) | ((mBytes[10]&0xff) << 8) | (mBytes[11] &0xff);
int payloadOffset = 28;
int payloadLength = bytesRead - payloadOffset;
if (dPort == 8000) { // Video
byte[] sBytes = new byte[payloadLength];
System.arraycopy(mBytes, payloadOffset, sBytes, 0, payloadLength);
DatagramPacket packet = new DatagramPacket(sBytes, sBytes.length, address, VIDEO_PORT);
socket.send(packet);
} else if (dPort == 8200) { // Command
/*
StringBuilder sb = new StringBuilder();
for (int i = 0; i < dLength; i++) {
sb.append(String.format("%02X ", mBytes[i]));
}
Log.i("QGC_TaiSync", "Read 3 port = " + dPort + " length = " + dLength + " - " + sb.toString());
*/
} else if (dPort == 8400) { // Telemetry
byte[] sBytes = new byte[payloadLength];
System.arraycopy(mBytes, payloadOffset, sBytes, 0, payloadLength);
DatagramPacket packet = new DatagramPacket(sBytes, sBytes.length, address, TELEM_PORT);
socket.send(packet);
}
}
}
}
} catch (IOException e) {
Log.e("QGC_TaiSync", "Exception: " + e);
e.printStackTrace();
} finally {
close();
}
}
});
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
byte[] inbuf = new byte[256];
try {
while (true) {
synchronized (this)
{
if (!running) {
break;
}
}
DatagramPacket packet = new DatagramPacket(inbuf, inbuf.length);
socket.receive(packet);
int dataPort = 8400;
byte portMSB = (byte)((dataPort >> 8) & 0xFF);
byte portLSB = (byte)(dataPort & 0xFF);
byte[] lA = new byte[4];
int len = 28 + packet.getLength();
byte[] buffer = new byte[len];
for (int i = 3; i >= 0; i--) {
lA[i] = (byte)(len & 0xFF);
len >>= 8;
}
byte[] header = { 0x00, 0x00, 0x00, PROTOCOL_DATA, // uint32 - protocol
0x00, 0x00, portMSB, portLSB, // uint32 - dport
lA[0], lA[1], lA[2], lA[3], // uint32 - length
0x00, 0x00, 0x00, 0x00, // uint32 - magic
0x00, 0x00, 0x00, vMaj, // uint32 - version major
0x00, 0x00, 0x00, 0x00, // uint32 - version minor
0x00, 0x00, 0x00, 0x00 }; // uint32 - padding
System.arraycopy(header, 0, buffer, 0, header.length);
System.arraycopy(packet.getData(), 0, buffer, header.length, packet.getLength());
mFileOutputStream.write(buffer);
}
} catch (IOException e) {
Log.e("QGC_TaiSync", "Exception: " + e);
e.printStackTrace();
} finally {
close();
}
}
});
}
public void close()
{
// Log.i("QGC_TaiSync", "Close");
synchronized (this)
{
running = false;
}
try {
if (socket != null) {
socket.close();
socket = null;
}
if (mParcelFileDescriptor != null) {
mParcelFileDescriptor.close();
mParcelFileDescriptor = null;
}
} catch (Exception e) {
}
socket = null;
}
}
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