From 7bfa8841b65707d629b425b306ec4cc3acfc156c Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Mon, 27 Apr 2015 21:20:22 -0700 Subject: [PATCH 1/1] altosdroid: Add USB support for TeleDongle/TeleBT This lets AltosDroid use a USB-connected receiver as well as Bluetooth devices. Signed-off-by: Keith Packard --- altosdroid/AndroidManifest.xml | 23 +- altosdroid/default.properties | 2 +- altosdroid/project.properties | 2 +- altosdroid/res/values/CustomTheme.xml | 7 + altosdroid/res/xml/device_filter.xml | 6 + .../AltosDroid/AltosBluetooth.java | 178 ++----------- .../altusmetrum/AltosDroid/AltosDroid.java | 156 ++++++++++-- .../AltosDroid/AltosDroidLink.java | 224 +++++++++++++++++ .../org/altusmetrum/AltosDroid/AltosUsb.java | 235 ++++++++++++++++++ .../AltosDroid/TelemetryReader.java | 2 + .../AltosDroid/TelemetryService.java | 125 ++++++---- 11 files changed, 733 insertions(+), 227 deletions(-) create mode 100644 altosdroid/res/values/CustomTheme.xml create mode 100644 altosdroid/res/xml/device_filter.xml create mode 100644 altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java create mode 100644 altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml index 19e5a6dc..71c6fb12 100644 --- a/altosdroid/AndroidManifest.xml +++ b/altosdroid/AndroidManifest.xml @@ -19,7 +19,7 @@ package="org.altusmetrum.AltosDroid" android:versionCode="6" android:versionName="1.5"> - + @@ -38,18 +38,35 @@ android:protectionLevel="signature"/> + + + android:allowBackup="true" + android:theme="@style/CustomTheme"> + android:configChanges="orientation|keyboardHidden" + android:launchMode="singleTop"> + + + + + + + + + + + + diff --git a/altosdroid/res/xml/device_filter.xml b/altosdroid/res/xml/device_filter.xml new file mode 100644 index 00000000..84b09c06 --- /dev/null +++ b/altosdroid/res/xml/device_filter.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java index da75ffdd..32b7a65b 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java @@ -33,16 +33,13 @@ import android.util.Log; import org.altusmetrum.altoslib_6.*; -public class AltosBluetooth extends AltosLink { +public class AltosBluetooth extends AltosDroidLink { // Debugging private static final String TAG = "AltosBluetooth"; private static final boolean D = true; private ConnectThread connect_thread = null; - private Thread input_thread = null; - - private Handler handler; private BluetoothAdapter adapter; private BluetoothSocket socket; @@ -51,6 +48,7 @@ public class AltosBluetooth extends AltosLink { // Constructor public AltosBluetooth(BluetoothDevice device, Handler handler) { + super(handler); // set_debug(D); adapter = BluetoothAdapter.getDefaultAdapter(); this.handler = handler; @@ -60,16 +58,7 @@ public class AltosBluetooth extends AltosLink { connect_thread.start(); } - private Object closed_lock = new Object(); - private boolean closed = false; - - private boolean closed() { - synchronized(closed_lock) { - return closed; - } - } - - private void connected() { + void connected() { if (closed()) { if (D) Log.d(TAG, "connected after closed"); return; @@ -80,24 +69,11 @@ public class AltosBluetooth extends AltosLink { if (socket != null) { input = socket.getInputStream(); output = socket.getOutputStream(); - - input_thread = new Thread(this); - input_thread.start(); - - // Configure the newly connected device for telemetry - print("~\nE 0\n"); - set_monitor(false); - if (D) Log.d(TAG, "ConnectThread: connected"); - - /* Let TelemetryService know we're connected - */ - handler.obtainMessage(TelemetryService.MSG_CONNECTED, this).sendToTarget(); - - /* Notify other waiting threads that we're connected now - */ - notifyAll(); + super.connected(); } } + } catch (InterruptedException ie) { + connect_failed(); } catch (IOException io) { connect_failed(); } @@ -109,24 +85,14 @@ public class AltosBluetooth extends AltosLink { return; } - close_socket(); + close_device(); input = null; output = null; handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget(); if (D) Log.e(TAG, "ConnectThread: Failed to establish connection"); } - private void disconnected() { - if (closed()) { - if (D) Log.d(TAG, "disconnected after closed"); - return; - } - - if (D) Log.d(TAG, "Sending disconnected message"); - handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget(); - } - - private void close_socket() { + void close_device() { BluetoothSocket tmp_socket; synchronized(this) { @@ -143,6 +109,12 @@ public class AltosBluetooth extends AltosLink { } } + public void close() { + super.close(); + input = null; + output = null; + } + private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private void create_socket(BluetoothDevice device) { @@ -156,7 +128,7 @@ public class AltosBluetooth extends AltosLink { } if (socket != null) { if (D) Log.d(TAG, String.format("Socket already allocated %s", socket.toString())); - close_socket(); + close_device(); } synchronized (this) { socket = tmp_socket; @@ -205,22 +177,6 @@ public class AltosBluetooth extends AltosLink { } } - public double frequency() { - return frequency; - } - - public int telemetry_rate() { - return telemetry_rate; - } - - public void save_frequency() { - AltosPreferences.set_frequency(0, frequency); - } - - public void save_telemetry_rate() { - AltosPreferences.set_telemetry_rate(0, telemetry_rate); - } - private synchronized void wait_connected() throws InterruptedException, IOException { if (input == null && socket != null) { if (D) Log.d(TAG, "wait_connected..."); @@ -231,109 +187,23 @@ public class AltosBluetooth extends AltosLink { throw new IOException(); } - public void print(String data) { - byte[] bytes = data.getBytes(); - if (D) Log.d(TAG, "print(): begin"); + int write(byte[] buffer, int len) { try { - wait_connected(); - output.write(bytes); - if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'"); - } catch (IOException e) { - disconnected(); - } catch (InterruptedException e) { - disconnected(); + output.write(buffer, 0, len); + } catch (IOException ie) { + return -1; } + return len; } - public void putchar(byte c) { - byte[] bytes = { c }; - if (D) Log.d(TAG, "print(): begin"); + int read(byte[] buffer, int len) { try { - wait_connected(); - output.write(bytes); - if (D) Log.d(TAG, "print(): Wrote byte: '" + c + "'"); - } catch (IOException e) { - disconnected(); - } catch (InterruptedException e) { - disconnected(); - } - } - - private static final int buffer_size = 1024; - - private byte[] buffer = new byte[buffer_size]; - private int buffer_len = 0; - private int buffer_off = 0; - - private byte[] debug_chars = new byte[buffer_size]; - private int debug_off; - - private void debug_input(byte b) { - if (b == '\n') { - Log.d(TAG, " " + new String(debug_chars, 0, debug_off)); - debug_off = 0; - } else { - if (debug_off < buffer_size) - debug_chars[debug_off++] = b; + return input.read(buffer, 0, len); + } catch (IOException ie) { + return -1; } } - public int getchar() { - while (buffer_off == buffer_len) { - try { - wait_connected(); - buffer_len = input.read(buffer); - buffer_off = 0; - } catch (IOException e) { - if (D) Log.d(TAG, "getchar IOException"); - disconnected(); - return AltosLink.ERROR; - } catch (java.lang.InterruptedException e) { - if (D) Log.d(TAG, "getchar Interrupted"); - disconnected(); - return AltosLink.ERROR; - } - } - if (D) - debug_input(buffer[buffer_off]); - return buffer[buffer_off++]; - } - - public void closing() { - synchronized(closed_lock) { - if (D) Log.d(TAG, "Marked closed true"); - closed = true; - } - } - - - public void close() { - if (D) Log.d(TAG, "close(): begin"); - - closing(); - - close_socket(); - - synchronized(this) { - - if (input_thread != null) { - if (D) Log.d(TAG, "close(): stopping input_thread"); - try { - if (D) Log.d(TAG, "close(): input_thread.interrupt()....."); - input_thread.interrupt(); - if (D) Log.d(TAG, "close(): input_thread.join()....."); - input_thread.join(); - } catch (Exception e) {} - input_thread = null; - } - input = null; - output = null; - notifyAll(); - } - } - - //public void flush_output() { super.flush_output(); } - // Stubs of required methods when extending AltosLink public boolean can_cancel_reply() { return false; } public boolean show_reply_timeout() { return true; } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index 4e7bdd6b..27ebf206 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -23,6 +23,7 @@ import java.util.Timer; import java.util.TimerTask; import android.app.Activity; +import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Intent; @@ -51,6 +52,7 @@ import android.widget.RelativeLayout; import android.widget.Toast; import android.app.AlertDialog; import android.location.Location; +import android.hardware.usb.*; import org.altusmetrum.altoslib_6.*; @@ -59,6 +61,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { static final String TAG = "AltosDroid"; static final boolean D = true; + // Actions sent to the telemetry server at startup time + + public static final String ACTION_BLUETOOTH = "org.altusmetrum.AltosDroid.BLUETOOTH"; + public static final String ACTION_USB = "org.altusmetrum.AltosDroid.USB"; + // Message types received by our Handler public static final int MSG_STATE = 1; @@ -101,6 +108,9 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { private Timer timer; AltosState saved_state; + UsbDevice pending_usb_device; + boolean start_with_usb; + // Service private boolean mIsBound = false; private Messenger mService = null; @@ -148,6 +158,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } catch (RemoteException e) { // In this case the service has crashed before we could even do anything with it } + if (pending_usb_device != null) { + try { + mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, pending_usb_device)); + pending_usb_device = null; + } catch (RemoteException e) { + } + } } public void onServiceDisconnected(ComponentName className) { @@ -395,15 +412,6 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { super.onCreate(savedInstanceState); if(D) Log.e(TAG, "+++ ON CREATE +++"); - // Get local Bluetooth adapter - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - // If the adapter is null, then Bluetooth is not supported - if (mBluetoothAdapter == null) { - Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); - finish(); - } - fm = getSupportFragmentManager(); // Set up the window layout @@ -467,23 +475,117 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { mAgeView = (TextView) findViewById(R.id.age_value); } + private boolean ensureBluetooth() { + // Get local Bluetooth adapter + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // If the adapter is null, then Bluetooth is not supported + if (mBluetoothAdapter == null) { + Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); + return false; + } + + if (!mBluetoothAdapter.isEnabled()) { + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT); + } + + return true; + } + + private boolean check_usb() { + UsbDevice device = AltosUsb.find_device(this, AltosLib.product_basestation); + + if (device != null) { + Intent i = new Intent(this, AltosDroid.class); + PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent("hello world", null, this, AltosDroid.class), 0); + + if (AltosUsb.request_permission(this, device, pi)) { + connectUsb(device); + } + start_with_usb = true; + return true; + } + + start_with_usb = false; + + return false; + } + + private void noticeIntent(Intent intent) { + + /* Ok, this is pretty convenient. + * + * When a USB device is plugged in, and our 'hotplug' + * intent registration fires, we get an Intent with + * EXTRA_DEVICE set. + * + * When we start up and see a usb device and request + * permission to access it, that queues a + * PendingIntent, which has the EXTRA_DEVICE added in, + * along with the EXTRA_PERMISSION_GRANTED field as + * well. + * + * So, in both cases, we get the device name using the + * same call. We check to see if access was granted, + * in which case we ignore the device field and do our + * usual startup thing. + */ + + UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + + if (D) Log.e(TAG, "intent " + intent + " device " + device + " granted " + granted); + + if (!granted) + device = null; + + if (device != null) { + if (D) Log.d(TAG, "intent has usb device " + device.toString()); + connectUsb(device); + } else { + + /* 'granted' is only false if this intent came + * from the request_permission call and + * permission was denied. In which case, we + * don't want to loop forever... + */ + if (granted) { + if (D) Log.d(TAG, "check for a USB device at startup"); + if (check_usb()) + return; + } + if (D) Log.d(TAG, "Starting by looking for bluetooth devices"); + if (ensureBluetooth()) + return; + finish(); + } + } + @Override public void onStart() { super.onStart(); if(D) Log.e(TAG, "++ ON START ++"); + noticeIntent(getIntent()); + // Start Telemetry Service - startService(new Intent(AltosDroid.this, TelemetryService.class)); + String action = start_with_usb ? ACTION_USB : ACTION_BLUETOOTH; - if (!mBluetoothAdapter.isEnabled()) { - Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT); - } + startService(new Intent(action, null, AltosDroid.this, TelemetryService.class)); doBindService(); if (mAltosVoice == null) mAltosVoice = new AltosVoice(this); + + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if(D) Log.d(TAG, "onNewIntent"); + noticeIntent(intent); } @Override @@ -519,7 +621,7 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { stop_timer(); } - public void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(D) Log.d(TAG, "onActivityResult " + resultCode); switch (requestCode) { case REQUEST_CONNECT_DEVICE: @@ -544,6 +646,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { } } + private void connectUsb(UsbDevice device) { + if (mService == null) + pending_usb_device = device; + else { + // Attempt to connect to the device + try { + mService.send(Message.obtain(null, TelemetryService.MSG_OPEN_USB, device)); + if (D) Log.d(TAG, "Sent OPEN_USB message"); + } catch (RemoteException e) { + if (D) Log.e(TAG, "connect device message failed"); + } + } + } + private void connectDevice(Intent data) { // Attempt to connect to the device try { @@ -619,12 +735,14 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener { Intent serverIntent = null; switch (item.getItemId()) { case R.id.connect_scan: - // Launch the DeviceListActivity to see devices and do scan - serverIntent = new Intent(this, DeviceListActivity.class); - startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); + if (ensureBluetooth()) { + // Launch the DeviceListActivity to see devices and do scan + serverIntent = new Intent(this, DeviceListActivity.class); + startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); + } return true; case R.id.disconnect: - /* Disconnect the bluetooth device + /* Disconnect the device */ disconnectDevice(); return true; diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java new file mode 100644 index 00000000..badb2caa --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java @@ -0,0 +1,224 @@ +/* + * Copyright © 2015 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; + +import android.os.Handler; +import android.util.Log; + +import org.altusmetrum.altoslib_6.*; + +public abstract class AltosDroidLink extends AltosLink { + + // Debugging + private static final String TAG = "AltosDroidLink"; + private static final boolean D = true; + + Handler handler; + + Thread input_thread = null; + + public double frequency() { + return frequency; + } + + public int telemetry_rate() { + return telemetry_rate; + } + + public void save_frequency() { + AltosPreferences.set_frequency(0, frequency); + } + + public void save_telemetry_rate() { + AltosPreferences.set_telemetry_rate(0, telemetry_rate); + } + + Object closed_lock = new Object(); + boolean closing = false; + boolean closed = false; + + public boolean closed() { + synchronized(closed_lock) { + return closing; + } + } + + void connected() throws InterruptedException { + input_thread = new Thread(this); + input_thread.start(); + + // Configure the newly connected device for telemetry + print("~\nE 0\n"); + set_monitor(false); + if (D) Log.d(TAG, "ConnectThread: connected"); + + /* Let TelemetryService know we're connected + */ + handler.obtainMessage(TelemetryService.MSG_CONNECTED, this).sendToTarget(); + + /* Notify other waiting threads that we're connected now + */ + notifyAll(); + } + + public void closing() { + synchronized(closed_lock) { + if (D) Log.d(TAG, "Marked closing true"); + closing = true; + } + } + + private boolean actually_closed() { + synchronized(closed_lock) { + return closed; + } + } + + abstract void close_device(); + + public void close() { + if (D) Log.d(TAG, "close(): begin"); + + closing(); + + flush_output(); + + synchronized (closed_lock) { + if (D) Log.d(TAG, "Marked closed true"); + closed = true; + } + + close_device(); + + synchronized(this) { + + if (input_thread != null) { + if (D) Log.d(TAG, "close(): stopping input_thread"); + try { + if (D) Log.d(TAG, "close(): input_thread.interrupt()....."); + input_thread.interrupt(); + if (D) Log.d(TAG, "close(): input_thread.join()....."); + input_thread.join(); + } catch (Exception e) {} + input_thread = null; + } + notifyAll(); + } + } + + abstract int write(byte[] buffer, int len); + + abstract int read(byte[] buffer, int len); + + private static final int buffer_size = 64; + + private byte[] in_buffer = new byte[buffer_size]; + private byte[] out_buffer = new byte[buffer_size]; + private int buffer_len = 0; + private int buffer_off = 0; + private int out_buffer_off = 0; + + private byte[] debug_chars = new byte[buffer_size]; + private int debug_off; + + private void debug_input(byte b) { + if (b == '\n') { + Log.d(TAG, " " + new String(debug_chars, 0, debug_off)); + debug_off = 0; + } else { + if (debug_off < buffer_size) + debug_chars[debug_off++] = b; + } + } + + private void disconnected() { + if (closed()) { + if (D) Log.d(TAG, "disconnected after closed"); + return; + } + + if (D) Log.d(TAG, "Sending disconnected message"); + handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget(); + } + + public int getchar() { + + if (actually_closed()) + return ERROR; + + while (buffer_off == buffer_len) { + buffer_len = read(in_buffer, buffer_size); + if (buffer_len < 0) { + Log.d(TAG, "ERROR returned from getchar()"); + disconnected(); + return ERROR; + } + buffer_off = 0; + } + if (D) + debug_input(in_buffer[buffer_off]); + return in_buffer[buffer_off++]; + } + + public void flush_output() { + super.flush_output(); + + if (actually_closed()) { + out_buffer_off = 0; + return; + } + + while (out_buffer_off != 0) { + int sent = write(out_buffer, out_buffer_off); + + if (sent <= 0) { + Log.d(TAG, "flush_output() failed"); + out_buffer_off = 0; + break; + } + + if (sent < out_buffer_off) + System.arraycopy(out_buffer, 0, out_buffer, sent, out_buffer_off - sent); + + out_buffer_off -= sent; + } + } + + public void putchar(byte c) { + out_buffer[out_buffer_off++] = c; + if (out_buffer_off == buffer_size) + flush_output(); + } + + public void print(String data) { + byte[] bytes = data.getBytes(); + if (D) Log.d(TAG, "print(): begin"); + for (byte b : bytes) + putchar(b); + if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'"); + } + + public AltosDroidLink(Handler handler) { + this.handler = handler; + } +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java new file mode 100644 index 00000000..81d50ab3 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java @@ -0,0 +1,235 @@ +/* + * Copyright © 2015 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; +import java.util.HashMap; + +import android.content.Context; +import android.hardware.usb.*; +import android.app.*; +import android.os.Handler; +import android.util.Log; + +import org.altusmetrum.altoslib_6.*; + +public class AltosUsb extends AltosDroidLink { + + // Debugging + private static final String TAG = "AltosUsb"; + private static final boolean D = true; + + private Thread input_thread = null; + + private Handler handler; + + private UsbManager manager; + private UsbDevice device; + private UsbDeviceConnection connection; + private UsbInterface iface; + private UsbEndpoint in, out; + + private InputStream input; + private OutputStream output; + + // Constructor + public AltosUsb(Context context, UsbDevice device, Handler handler) { + super(handler); +// set_debug(D); + this.handler = handler; + + iface = null; + in = null; + out = null; + + int niface = device.getInterfaceCount(); + + for (int i = 0; i < niface; i++) { + + iface = device.getInterface(i); + + in = null; + out = null; + + int nendpoints = iface.getEndpointCount(); + + for (int e = 0; e < nendpoints; e++) { + UsbEndpoint endpoint = iface.getEndpoint(e); + + if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + switch (endpoint.getDirection()) { + case UsbConstants.USB_DIR_OUT: + out = endpoint; + break; + case UsbConstants.USB_DIR_IN: + in = endpoint; + break; + } + } + } + + if (in != null && out != null) + break; + } + + if (in != null && out != null) { + Log.d(TAG, String.format("\tin %s out %s\n", in.toString(), out.toString())); + + manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + + if (manager == null) { + Log.d(TAG, "USB_SERVICE failed"); + return; + } + + connection = manager.openDevice(device); + + if (connection == null) { + Log.d(TAG, "openDevice failed"); + return; + } + + connection.claimInterface(iface, true); + + input_thread = new Thread(this); + input_thread.start(); + + // Configure the newly connected device for telemetry + print("~\nE 0\n"); + set_monitor(false); + } + } + + static private boolean isAltusMetrum(UsbDevice device) { + if (device.getVendorId() != AltosLib.vendor_altusmetrum) + return false; + if (device.getProductId() < AltosLib.product_altusmetrum_min) + return false; + if (device.getProductId() > AltosLib.product_altusmetrum_max) + return false; + return true; + } + + static boolean matchProduct(int want_product, UsbDevice device) { + + if (!isAltusMetrum(device)) + return false; + + if (want_product == AltosLib.product_any) + return true; + + int have_product = device.getProductId(); + + if (want_product == AltosLib.product_basestation) + return have_product == AltosLib.product_teledongle || + have_product == AltosLib.product_teleterra || + have_product == AltosLib.product_telebt || + have_product == AltosLib.product_megadongle; + + if (want_product == AltosLib.product_altimeter) + return have_product == AltosLib.product_telemetrum || + have_product == AltosLib.product_telemega || + have_product == AltosLib.product_easymega || + have_product == AltosLib.product_telegps || + have_product == AltosLib.product_easymini || + have_product == AltosLib.product_telemini; + + if (have_product == AltosLib.product_altusmetrum) /* old devices match any request */ + return true; + + if (want_product == have_product) + return true; + + return false; + } + + static public boolean request_permission(Context context, UsbDevice device, PendingIntent pi) { + UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + +// if (manager.hasPermission(device)) +// return true; + + Log.d(TAG, "request permission for USB device " + device.toString()); + + manager.requestPermission(device, pi); + return false; + } + + static public UsbDevice find_device(Context context, int match_product) { + UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + + HashMap devices = manager.getDeviceList(); + + for (UsbDevice device : devices.values()) { + int vendor = device.getVendorId(); + int product = device.getProductId(); + + if (matchProduct(match_product, device)) { + Log.d(TAG, "found USB device " + device.toString()); + return device; + } + } + + return null; + } + + private void disconnected() { + if (closed()) { + if (D) Log.d(TAG, "disconnected after closed"); + return; + } + + if (D) Log.d(TAG, "Sending disconnected message"); + handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, this).sendToTarget(); + } + + void close_device() { + UsbDeviceConnection tmp_connection; + + synchronized(this) { + tmp_connection = connection; + connection = null; + } + + if (tmp_connection != null) { + if (D) Log.d(TAG, "Closing USB device"); + tmp_connection.close(); + } + } + + int read(byte[] buffer, int len) { + int ret = connection.bulkTransfer(in, buffer, len, -1); + if (D) Log.d(TAG, String.format("read(%d) = %d\n", len, ret)); + return ret; + } + + int write(byte[] buffer, int len) { + int ret = connection.bulkTransfer(out, buffer, len, -1); + if (D) Log.d(TAG, String.format("write(%d) = %d\n", len, ret)); + return ret; + } + + // Stubs of required methods when extending AltosLink + public boolean can_cancel_reply() { return false; } + public boolean show_reply_timeout() { return true; } + public void hide_reply_timeout() { } + +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java index 4e4408d5..bdb2bae4 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -81,6 +81,8 @@ public class TelemetryReader extends Thread { } } catch (InterruptedException ee) { } catch (IOException ie) { + Log.e(TAG, "IO exception in telemetry reader"); + handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, link).sendToTarget(); } finally { close(); } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java index 65eabf11..e7f958b9 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -29,6 +29,7 @@ import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothAdapter; +import android.hardware.usb.*; import android.content.Intent; import android.content.Context; import android.os.Bundle; @@ -56,14 +57,15 @@ public class TelemetryService extends Service implements LocationListener { static final int MSG_REGISTER_CLIENT = 1; static final int MSG_UNREGISTER_CLIENT = 2; static final int MSG_CONNECT = 3; - static final int MSG_CONNECTED = 4; - static final int MSG_CONNECT_FAILED = 5; - static final int MSG_DISCONNECTED = 6; - static final int MSG_TELEMETRY = 7; - static final int MSG_SETFREQUENCY = 8; - static final int MSG_CRC_ERROR = 9; - static final int MSG_SETBAUD = 10; - static final int MSG_DISCONNECT = 11; + static final int MSG_OPEN_USB = 4; + static final int MSG_CONNECTED = 5; + static final int MSG_CONNECT_FAILED = 6; + static final int MSG_DISCONNECTED = 7; + static final int MSG_TELEMETRY = 8; + static final int MSG_SETFREQUENCY = 9; + static final int MSG_CRC_ERROR = 10; + static final int MSG_SETBAUD = 11; + static final int MSG_DISCONNECT = 12; // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. @@ -76,7 +78,7 @@ public class TelemetryService extends Service implements LocationListener { // Name of the connected device DeviceAddress address; - private AltosBluetooth altos_bluetooth = null; + private AltosDroidLink altos_link = null; private TelemetryReader telemetry_reader = null; private TelemetryLogger telemetry_logger = null; @@ -94,7 +96,7 @@ public class TelemetryService extends Service implements LocationListener { @Override public void handleMessage(Message msg) { TelemetryService s = service.get(); - AltosBluetooth bt = null; + AltosDroidLink bt = null; if (s == null) return; switch (msg.what) { @@ -112,18 +114,23 @@ public class TelemetryService extends Service implements LocationListener { AltosDroidPreferences.set_active_device(address); s.start_altos_bluetooth(address, false); break; + case MSG_OPEN_USB: + if (D) Log.d(TAG, "Open USB command received"); + UsbDevice device = (UsbDevice) msg.obj; + s.start_usb(device); + break; case MSG_DISCONNECT: if (D) Log.d(TAG, "Disconnect command received"); s.address = null; - s.stop_altos_bluetooth(true); + s.disconnect(true); break; case MSG_SETFREQUENCY: if (D) Log.d(TAG, "MSG_SETFREQUENCY"); s.telemetry_state.frequency = (Double) msg.obj; if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) { try { - s.altos_bluetooth.set_radio_frequency(s.telemetry_state.frequency); - s.altos_bluetooth.save_frequency(); + s.altos_link.set_radio_frequency(s.telemetry_state.frequency); + s.altos_link.save_frequency(); } catch (InterruptedException e) { } catch (TimeoutException e) { } @@ -134,8 +141,8 @@ public class TelemetryService extends Service implements LocationListener { if (D) Log.d(TAG, "MSG_SETBAUD"); s.telemetry_state.telemetry_rate = (Integer) msg.obj; if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) { - s.altos_bluetooth.set_telemetry_rate(s.telemetry_state.telemetry_rate); - s.altos_bluetooth.save_telemetry_rate(); + s.altos_link.set_telemetry_rate(s.telemetry_state.telemetry_rate); + s.altos_link.save_telemetry_rate(); } s.send_to_clients(); break; @@ -145,9 +152,9 @@ public class TelemetryService extends Service implements LocationListener { */ case MSG_CONNECTED: Log.d(TAG, "MSG_CONNECTED"); - bt = (AltosBluetooth) msg.obj; + bt = (AltosDroidLink) msg.obj; - if (bt != s.altos_bluetooth) { + if (bt != s.altos_link) { if (D) Log.d(TAG, "Stale message"); break; } @@ -159,9 +166,9 @@ public class TelemetryService extends Service implements LocationListener { break; case MSG_CONNECT_FAILED: Log.d(TAG, "MSG_CONNECT_FAILED"); - bt = (AltosBluetooth) msg.obj; + bt = (AltosDroidLink) msg.obj; - if (bt != s.altos_bluetooth) { + if (bt != s.altos_link) { if (D) Log.d(TAG, "Stale message"); break; } @@ -169,14 +176,16 @@ public class TelemetryService extends Service implements LocationListener { if (D) Log.d(TAG, "Connection failed... retrying"); s.start_altos_bluetooth(s.address, true); } else { - s.stop_altos_bluetooth(true); + s.disconnect(true); } break; case MSG_DISCONNECTED: + + /* This can be sent by either AltosDroidLink or TelemetryReader */ Log.d(TAG, "MSG_DISCONNECTED"); - bt = (AltosBluetooth) msg.obj; + bt = (AltosDroidLink) msg.obj; - if (bt != s.altos_bluetooth) { + if (bt != s.altos_link) { if (D) Log.d(TAG, "Stale message"); break; } @@ -184,7 +193,7 @@ public class TelemetryService extends Service implements LocationListener { if (D) Log.d(TAG, "Connection lost... retrying"); s.start_altos_bluetooth(s.address, true); } else { - s.stop_altos_bluetooth(true); + s.disconnect(true); } break; @@ -275,16 +284,17 @@ public class TelemetryService extends Service implements LocationListener { send_to_client(client, m); } - private void stop_altos_bluetooth(boolean notify) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): begin"); + private void disconnect(boolean notify) { + if (D) Log.d(TAG, "disconnect(): begin"); + telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED; telemetry_state.address = null; - if (altos_bluetooth != null) - altos_bluetooth.closing(); + if (altos_link != null) + altos_link.closing(); if (telemetry_reader != null) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): stopping TelemetryReader"); + if (D) Log.d(TAG, "disconnect(): stopping TelemetryReader"); telemetry_reader.interrupt(); try { telemetry_reader.join(); @@ -293,31 +303,44 @@ public class TelemetryService extends Service implements LocationListener { telemetry_reader = null; } if (telemetry_logger != null) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): stopping TelemetryLogger"); + if (D) Log.d(TAG, "disconnect(): stopping TelemetryLogger"); telemetry_logger.stop(); telemetry_logger = null; } - if (altos_bluetooth != null) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): stopping AltosBluetooth"); - altos_bluetooth.close(); - altos_bluetooth = null; + if (altos_link != null) { + if (D) Log.d(TAG, "disconnect(): stopping AltosDroidLink"); + altos_link.close(); + altos_link = null; } telemetry_state.config = null; if (notify) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): send message to clients"); + if (D) Log.d(TAG, "disconnect(): send message to clients"); send_to_clients(); if (clients.isEmpty()) { - if (D) Log.d(TAG, "stop_altos_bluetooth(): no clients, terminating"); + if (D) Log.d(TAG, "disconnect(): no clients, terminating"); stopSelf(); } } } + private void start_usb(UsbDevice device) { + AltosUsb d = new AltosUsb(this, device, handler); + + if (d != null) { + disconnect(false); + altos_link = d; + try { + connected(); + } catch (InterruptedException ie) { + } + } + } + private void start_altos_bluetooth(DeviceAddress address, boolean pause) { // Get the BLuetoothDevice object BluetoothDevice device = bluetooth_adapter.getRemoteDevice(address.address); - stop_altos_bluetooth(false); + disconnect(false); if (pause) { try { Thread.sleep(4000); @@ -326,7 +349,7 @@ public class TelemetryService extends Service implements LocationListener { } this.address = address; if (D) Log.d(TAG, String.format("start_altos_bluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress())); - altos_bluetooth = new AltosBluetooth(device, handler); + altos_link = new AltosBluetooth(device, handler); telemetry_state.connect = TelemetryState.CONNECT_CONNECTING; telemetry_state.address = address; send_to_clients(); @@ -335,11 +358,11 @@ public class TelemetryService extends Service implements LocationListener { private void connected() throws InterruptedException { if (D) Log.d(TAG, "connected top"); try { - if (altos_bluetooth == null) + if (altos_link == null) throw new InterruptedException("no bluetooth"); - telemetry_state.config = altos_bluetooth.config_data(); - altos_bluetooth.set_radio_frequency(telemetry_state.frequency); - altos_bluetooth.set_telemetry_rate(telemetry_state.telemetry_rate); + telemetry_state.config = altos_link.config_data(); + altos_link.set_radio_frequency(telemetry_state.frequency); + altos_link.set_telemetry_rate(telemetry_state.telemetry_rate); } catch (TimeoutException e) { // If this timed out, then we really want to retry it, but // probably safer to just retry the connection from scratch. @@ -349,7 +372,7 @@ public class TelemetryService extends Service implements LocationListener { start_altos_bluetooth(address, true); } else { handler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget(); - stop_altos_bluetooth(true); + disconnect(true); } return; } @@ -358,12 +381,12 @@ public class TelemetryService extends Service implements LocationListener { telemetry_state.connect = TelemetryState.CONNECT_CONNECTED; telemetry_state.address = address; - telemetry_reader = new TelemetryReader(altos_bluetooth, handler, telemetry_state.state); + telemetry_reader = new TelemetryReader(altos_link, handler, telemetry_state.state); telemetry_reader.start(); if (D) Log.d(TAG, "connected TelemetryReader started"); - telemetry_logger = new TelemetryLogger(this, altos_bluetooth); + telemetry_logger = new TelemetryLogger(this, altos_link); if (D) Log.d(TAG, "Notify UI of connection"); @@ -403,10 +426,6 @@ public class TelemetryService extends Service implements LocationListener { LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this); - - DeviceAddress address = AltosDroidPreferences.active_device(); - if (address != null) - start_altos_bluetooth(address, false); } @Override @@ -431,6 +450,14 @@ public class TelemetryService extends Service implements LocationListener { // Move us into the foreground. startForeground(NOTIFICATION, notification); + String action = intent.getAction(); + + if (action.equals(AltosDroid.ACTION_BLUETOOTH)) { + DeviceAddress address = AltosDroidPreferences.active_device(); + if (address != null && !address.address.startsWith("USB")) + start_altos_bluetooth(address, false); + } + // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; @@ -443,7 +470,7 @@ public class TelemetryService extends Service implements LocationListener { ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this); // Stop the bluetooth Comms threads - stop_altos_bluetooth(true); + disconnect(true); // Demote us from the foreground, and cancel the persistent notification. stopForeground(true); -- 2.30.2