diff options
| author | Keith Packard <keithp@keithp.com> | 2015-04-27 21:20:22 -0700 | 
|---|---|---|
| committer | Keith Packard <keithp@keithp.com> | 2015-04-27 21:20:22 -0700 | 
| commit | 7bfa8841b65707d629b425b306ec4cc3acfc156c (patch) | |
| tree | a5265c9aae11ca7dcae8fb70e3a64a8d843eb6bb | |
| parent | 356617a3476e237311b8bbcefd6beda8271b120d (diff) | |
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 <keithp@keithp.com>
| -rw-r--r-- | altosdroid/AndroidManifest.xml | 23 | ||||
| -rw-r--r-- | altosdroid/default.properties | 2 | ||||
| -rw-r--r-- | altosdroid/project.properties | 2 | ||||
| -rw-r--r-- | altosdroid/res/values/CustomTheme.xml | 7 | ||||
| -rw-r--r-- | altosdroid/res/xml/device_filter.xml | 6 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java | 178 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java | 156 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java | 224 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java | 235 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java | 2 | ||||
| -rw-r--r-- | altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java | 125 | 
11 files changed, 733 insertions, 227 deletions
| 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"> -    <uses-sdk android:targetSdkVersion="10" android:minSdkVersion="10"/> +    <uses-sdk android:targetSdkVersion="12" android:minSdkVersion="12"/>      <!-- Google Maps -->      <uses-feature android:glEsVersion="0x00020000" android:required="true"/> @@ -38,18 +38,35 @@                  android:protectionLevel="signature"/>      <uses-permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"/> +    <!-- Permissions needed to access USB OTG --> +    <uses-feature android:name="android.hardware.usb.host" />      <application android:label="@string/app_name"                   android:icon="@drawable/app_icon" -                 android:allowBackup="true" > +                 android:allowBackup="true" +		 android:theme="@style/CustomTheme">          <activity android:name="org.altusmetrum.AltosDroid.AltosDroid"                    android:label="@string/app_name" -                  android:configChanges="orientation|keyboardHidden" > +                  android:configChanges="orientation|keyboardHidden" +		  android:launchMode="singleTop">              <intent-filter>                  <action android:name="android.intent.action.MAIN" />                  <category android:name="android.intent.category.LAUNCHER" />              </intent-filter>          </activity> + +	<activity android:name="org.altusmetrum.AltosDroid.AltosDroid" +                  android:configChanges="orientation|keyboardHidden" +		  android:launchMode="singleTop"> +	  <intent-filter> +	    <action +		android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/> +	  </intent-filter> +	  <meta-data +	      android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" +	      android:resource="@xml/device_filter" /> +	</activity> +          <activity android:name=".DeviceListActivity"                    android:label="@string/select_device"                    android:theme="@android:style/Theme.Dialog" diff --git a/altosdroid/default.properties b/altosdroid/default.properties index 66db0d15..3ac25234 100644 --- a/altosdroid/default.properties +++ b/altosdroid/default.properties @@ -8,4 +8,4 @@  # project structure.  # Project target. -target=android-10 +target=android-12 diff --git a/altosdroid/project.properties b/altosdroid/project.properties index 96b9551c..d178f98a 100644 --- a/altosdroid/project.properties +++ b/altosdroid/project.properties @@ -8,5 +8,5 @@  # project structure.  # Project target. -target=android-10 +target=android-12  android.library.reference.1=google-play-services_lib/ diff --git a/altosdroid/res/values/CustomTheme.xml b/altosdroid/res/values/CustomTheme.xml new file mode 100644 index 00000000..4daed1f8 --- /dev/null +++ b/altosdroid/res/values/CustomTheme.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> +  <style name="CustomTheme" parent="android:Theme"> +    <item name="android:windowNoTitle">false</item> +  </style> +</resources> 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 @@ +<?xml version="1.0" encoding="utf-8"?> + +<resources> +  <usb-device vendor-id="65534" /> +  <usb-device vendor-id="1027" /> +</resources> 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 <keithp@keithp.com> + * + * 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 <keithp@keithp.com> + * + * 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<String,UsbDevice>	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); | 
