summaryrefslogtreecommitdiff
path: root/altosdroid/src
diff options
context:
space:
mode:
Diffstat (limited to 'altosdroid/src')
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java301
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java77
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java636
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java219
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java33
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java44
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java10
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java62
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java517
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java327
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java230
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java21
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java388
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java28
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java40
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java13
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java84
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java418
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java117
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java124
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java127
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java203
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java243
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabRecover.java (renamed from altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java)34
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java5
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java14
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java37
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java520
-rw-r--r--altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java21
29 files changed, 3697 insertions, 1196 deletions
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java
index 3740f55d..976e64bb 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java
@@ -29,236 +29,191 @@ import android.bluetooth.BluetoothSocket;
//import android.os.Bundle;
import android.os.Handler;
//import android.os.Message;
-import android.util.Log;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
-public class AltosBluetooth extends AltosLink {
-
- // Debugging
- private static final String TAG = "AltosBluetooth";
- private static final boolean D = true;
+public class AltosBluetooth extends AltosDroidLink {
private ConnectThread connect_thread = null;
- private Thread input_thread = null;
-
- private Handler handler;
- private BluetoothAdapter adapter;
- private BluetoothDevice device;
+ private BluetoothDevice device;
private BluetoothSocket socket;
private InputStream input;
private OutputStream output;
+ private boolean pause;
// Constructor
- public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) {
-// set_debug(D);
- adapter = BluetoothAdapter.getDefaultAdapter();
- device = in_device;
- handler = in_handler;
+ public AltosBluetooth(BluetoothDevice device, Handler handler, boolean pause) {
+ super(handler);
+ this.device = device;
+ this.handler = handler;
+ this.pause = pause;
- connect_thread = new ConnectThread(device);
+ connect_thread = new ConnectThread();
connect_thread.start();
-
}
- private class ConnectThread extends Thread {
- private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
-
- public ConnectThread(BluetoothDevice device) {
- BluetoothSocket tmp_socket = null;
-
- try {
- tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
- } catch (IOException e) {
- e.printStackTrace();
- }
- socket = tmp_socket;
+ void connected() {
+ if (closed()) {
+ AltosDebug.debug("connected after closed");
+ return;
}
- public void run() {
- if (D) Log.d(TAG, "ConnectThread: BEGIN");
- setName("ConnectThread");
-
- // Always cancel discovery because it will slow down a connection
- adapter.cancelDiscovery();
-
- synchronized (AltosBluetooth.this) {
- // Make a connection to the BluetoothSocket
- try {
- // This is a blocking call and will only return on a
- // successful connection or an exception
- socket.connect();
-
+ AltosDebug.check_ui("connected\n");
+ try {
+ synchronized(this) {
+ if (socket != null) {
input = socket.getInputStream();
output = socket.getOutputStream();
- } catch (IOException e) {
- // Close the socket
- try {
- socket.close();
- } catch (IOException e2) {
- if (D) Log.e(TAG, "ConnectThread: Failed to close() socket after failed connection");
- }
- input = null;
- output = null;
- AltosBluetooth.this.notifyAll();
- handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED).sendToTarget();
- if (D) Log.e(TAG, "ConnectThread: Failed to establish connection");
- return;
+ super.connected();
}
+ }
+ } catch (InterruptedException ie) {
+ connect_failed();
+ } catch (IOException io) {
+ connect_failed();
+ }
+ }
- input_thread = new Thread(AltosBluetooth.this);
- input_thread.start();
-
- // Configure the newly connected device for telemetry
- print("~\nE 0\n");
- set_monitor(false);
-
- // Let TelemetryService know we're connected
- handler.obtainMessage(TelemetryService.MSG_CONNECTED).sendToTarget();
+ private void connect_failed() {
+ if (closed()) {
+ AltosDebug.debug("connect_failed after closed");
+ return;
+ }
- // Notify other waiting threads, now that we're connected
- AltosBluetooth.this.notifyAll();
+ close_device();
+ input = null;
+ output = null;
+ handler.obtainMessage(TelemetryService.MSG_CONNECT_FAILED, this).sendToTarget();
+ AltosDebug.error("ConnectThread: Failed to establish connection");
+ }
- // Reset the ConnectThread because we're done
- connect_thread = null;
+ void close_device() {
+ BluetoothSocket tmp_socket;
- if (D) Log.d(TAG, "ConnectThread: Connect completed");
- }
+ synchronized(this) {
+ tmp_socket = socket;
+ socket = null;
}
- public void cancel() {
+ if (tmp_socket != null) {
try {
- if (socket != null)
- socket.close();
+ tmp_socket.close();
} catch (IOException e) {
- if (D) Log.e(TAG, "ConnectThread: close() of connect socket failed", e);
+ AltosDebug.error("close_socket failed");
}
}
}
- public double frequency() {
- return frequency;
- }
-
- public int telemetry_rate() {
- return telemetry_rate;
- }
-
- public void save_frequency() {
- AltosPreferences.set_frequency(0, frequency);
+ public void close() {
+ super.close();
+ input = null;
+ output = null;
}
- public void save_telemetry_rate() {
- AltosPreferences.set_telemetry_rate(0, telemetry_rate);
- }
+ private final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
- private synchronized void wait_connected() throws InterruptedException, IOException {
- if (input == null) {
- if (D) Log.d(TAG, "wait_connected...");
- wait();
- if (D) Log.d(TAG, "wait_connected done");
- if (input == null) throw new IOException();
- }
- }
+ private void create_socket(BluetoothDevice device) {
- private void connection_lost() {
- if (D) Log.e(TAG, "Connection lost during I/O");
- handler.obtainMessage(TelemetryService.MSG_DISCONNECTED).sendToTarget();
- }
+ BluetoothSocket tmp_socket = null;
- public void print(String data) {
- byte[] bytes = data.getBytes();
- if (D) Log.d(TAG, "print(): begin");
+ AltosDebug.check_ui("create_socket\n");
try {
- wait_connected();
- output.write(bytes);
- if (D) Log.d(TAG, "print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
+ tmp_socket = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
- connection_lost();
- } catch (InterruptedException e) {
- connection_lost();
+ e.printStackTrace();
}
- }
-
- public void putchar(byte c) {
- byte[] bytes = { c };
- if (D) Log.d(TAG, "print(): begin");
- try {
- wait_connected();
- output.write(bytes);
- if (D) Log.d(TAG, "print(): Wrote byte: '" + c + "'");
- } catch (IOException e) {
- connection_lost();
- } catch (InterruptedException e) {
- connection_lost();
+ if (socket != null) {
+ AltosDebug.debug("Socket already allocated %s", socket.toString());
+ close_device();
+ }
+ synchronized (this) {
+ socket = tmp_socket;
}
}
- private static final int buffer_size = 1024;
+ private class ConnectThread extends Thread {
- private byte[] buffer = new byte[buffer_size];
- private int buffer_len = 0;
- private int buffer_off = 0;
+ public void run() {
+ AltosDebug.debug("ConnectThread: BEGIN (pause %b)", pause);
+ setName("ConnectThread");
- public int getchar() {
- while (buffer_off == buffer_len) {
+ if (pause) {
+ try {
+ Thread.sleep(4000);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ create_socket(device);
+ // Always cancel discovery because it will slow down a connection
try {
- wait_connected();
- buffer_len = input.read(buffer);
- buffer_off = 0;
- } catch (IOException e) {
- connection_lost();
- return AltosLink.ERROR;
- } catch (java.lang.InterruptedException e) {
- connection_lost();
- return AltosLink.ERROR;
+ BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
+ } catch (Exception e) {
+ AltosDebug.debug("cancelDiscovery exception %s", e.toString());
}
- }
- return buffer[buffer_off++];
- }
- public void close() {
- if (D) Log.d(TAG, "close(): begin");
- synchronized(this) {
- if (D) Log.d(TAG, "close(): synched");
+ BluetoothSocket local_socket = null;
- if (connect_thread != null) {
- if (D) Log.d(TAG, "close(): stopping connect_thread");
- connect_thread.cancel();
- connect_thread = null;
- }
- if (D) Log.d(TAG, "close(): Closing socket");
- try {
- socket.close();
- } catch (IOException e) {
- if (D) Log.e(TAG, "close(): unable to close() socket");
+ synchronized (AltosBluetooth.this) {
+ if (!closed())
+ local_socket = socket;
}
- if (input_thread != null) {
- if (D) Log.d(TAG, "close(): stopping input_thread");
+
+ if (local_socket != null) {
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;
+ // Make a connection to the BluetoothSocket
+ // This is a blocking call and will only return on a
+ // successful connection or an exception
+ local_socket.connect();
+ } catch (Exception e) {
+ AltosDebug.debug("Connect exception %s", e.toString());
+ try {
+ local_socket.close();
+ } catch (Exception ce) {
+ AltosDebug.debug("Close exception %s", ce.toString());
+ }
+ local_socket = null;
+ }
+ }
+
+ if (local_socket != null) {
+ connected();
+ } else {
+ connect_failed();
}
- input = null;
- output = null;
- notifyAll();
+
+ AltosDebug.debug("ConnectThread: completed");
}
}
+ private synchronized void wait_connected() throws InterruptedException, IOException {
+ AltosDebug.check_ui("wait_connected\n");
+ if (input == null && socket != null) {
+ AltosDebug.debug("wait_connected...");
+ wait();
+ AltosDebug.debug("wait_connected done");
+ }
+ if (socket == null)
+ throw new IOException();
+ }
- // We override this method so that we can add some debugging. Not 100% elegant, but more useful
- // than debugging one char at a time above in getchar()!
- public void add_reply(AltosLine line) throws InterruptedException {
- if (D) Log.d(TAG, String.format("Got REPLY: %s", line.line));
- super.add_reply(line);
+ int write(byte[] buffer, int len) {
+ try {
+ output.write(buffer, 0, len);
+ } catch (IOException ie) {
+ return -1;
+ }
+ return len;
}
- //public void flush_output() { super.flush_output(); }
+ int read(byte[] buffer, int len) {
+ try {
+ return input.read(buffer, 0, len);
+ } catch (IOException ie) {
+ return -1;
+ }
+ }
// Stubs of required methods when extending AltosLink
public boolean can_cancel_reply() { return false; }
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java
new file mode 100644
index 00000000..db63d810
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDebug.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util.Arrays;
+import java.io.*;
+import java.lang.*;
+
+import org.altusmetrum.altoslib_8.*;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.*;
+import android.widget.*;
+import android.location.Location;
+import android.content.*;
+import android.util.Log;
+import android.os.*;
+import android.content.pm.*;
+
+public class AltosDebug {
+ // Debugging
+ static final String TAG = "AltosDroid";
+
+ static boolean D = true;
+
+ static void init(Context context) {
+ ApplicationInfo app_info = context.getApplicationInfo();
+
+ if ((app_info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ Log.d(TAG, "Enable debugging\n");
+ D = true;
+ } else {
+ Log.d(TAG, "Disable debugging\n");
+ D = false;
+ }
+ }
+
+
+ static void info(String format, Object ... arguments) {
+ Log.i(TAG, String.format(format, arguments));
+ }
+
+ static void debug(String format, Object ... arguments) {
+ if (D)
+ Log.d(TAG, String.format(format, arguments));
+ }
+
+ static void error(String format, Object ... arguments) {
+ Log.e(TAG, String.format(format, arguments));
+ }
+
+ static void check_ui(String format, Object ... arguments) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Log.e(TAG, String.format("ON UI THREAD " + format, arguments));
+ for (StackTraceElement el : Thread.currentThread().getStackTrace())
+ Log.e(TAG, "\t" + el.toString() + "\n");
+ }
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
index 53963f25..3a07212a 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
@@ -18,11 +18,12 @@
package org.altusmetrum.AltosDroid;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.text.*;
+import java.util.*;
+import java.io.*;
import android.app.Activity;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
@@ -36,28 +37,26 @@ import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.content.res.Resources;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.Window;
-import android.view.View;
-import android.widget.TabHost;
-import android.widget.TextView;
-import android.widget.RelativeLayout;
-import android.widget.Toast;
+import android.view.*;
+import android.widget.*;
import android.app.AlertDialog;
import android.location.Location;
+import android.hardware.usb.*;
+import android.graphics.*;
+import android.graphics.drawable.*;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
- // Debugging
- 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
@@ -67,14 +66,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
// Intent request codes
public static final int REQUEST_CONNECT_DEVICE = 1;
public static final int REQUEST_ENABLE_BT = 2;
+ public static final int REQUEST_PRELOAD_MAPS = 3;
+ public static final int REQUEST_MAP_TYPE = 4;
+
+ public int map_type = AltosMap.maptype_hybrid;
public static FragmentManager fm;
private BluetoothAdapter mBluetoothAdapter = null;
- // Layout Views
- private TextView mTitle;
-
// Flight state values
private TextView mCallsignView;
private TextView mRSSIView;
@@ -83,6 +83,14 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
private RelativeLayout mStateLayout;
private TextView mStateView;
private TextView mAgeView;
+ private boolean mAgeViewOld;
+ private int mAgeNewColor;
+ private int mAgeOldColor;
+
+ public static final String tab_pad_name = "pad";
+ public static final String tab_flight_name = "flight";
+ public static final String tab_recover_name = "recover";
+ public static final String tab_map_name = "map";
// field to display the version at the bottom of the screen
private TextView mVersion;
@@ -100,6 +108,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
// Timer and Saved flight state for Age calculation
private Timer timer;
AltosState saved_state;
+ TelemetryState telemetry_state;
+ Integer[] serials;
+
+ UsbDevice pending_usb_device;
+ boolean start_with_usb;
// Service
private boolean mIsBound = false;
@@ -120,17 +133,13 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
switch (msg.what) {
case MSG_STATE:
- if(D) Log.d(TAG, "MSG_STATE");
- TelemetryState telemetry_state = (TelemetryState) msg.obj;
- if (telemetry_state == null) {
- Log.d(TAG, "telemetry_state null!");
+ if (msg.obj == null) {
+ AltosDebug.debug("telemetry_state null!");
return;
}
-
- ad.update_state(telemetry_state);
+ ad.update_state((TelemetryState) msg.obj);
break;
case MSG_UPDATE_AGE:
- if(D) Log.d(TAG, "MSG_UPDATE_AGE");
ad.update_age();
break;
}
@@ -148,6 +157,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) {
@@ -201,17 +217,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
str = str.concat(String.format(" %d bps",
AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
- mTitle.setText(str);
+ setTitle(str);
} else {
- mTitle.setText(R.string.title_connected_to);
+ setTitle(R.string.title_connected_to);
}
break;
case TelemetryState.CONNECT_CONNECTING:
- mTitle.setText(R.string.title_connecting);
+ if (telemetry_state.address != null)
+ setTitle(String.format("Connecting to %s...", telemetry_state.address.name));
+ else
+ setTitle("Connecting to something...");
break;
- case TelemetryState.CONNECT_READY:
+ case TelemetryState.CONNECT_DISCONNECTED:
case TelemetryState.CONNECT_NONE:
- mTitle.setText(R.string.title_not_connected);
+ setTitle(R.string.title_not_connected);
break;
}
}
@@ -231,21 +250,73 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
}
}
+ int selected_serial = 0;
+ int current_serial;
+ long switch_time;
+
+ void set_switch_time() {
+ switch_time = System.currentTimeMillis();
+ selected_serial = 0;
+ }
+
boolean registered_units_listener;
- void update_state(TelemetryState telemetry_state) {
+ void update_state(TelemetryState new_telemetry_state) {
+
+ if (new_telemetry_state != null)
+ telemetry_state = new_telemetry_state;
+
+ if (selected_serial != 0)
+ current_serial = selected_serial;
+
+ if (current_serial == 0)
+ current_serial = telemetry_state.latest_serial;
if (!registered_units_listener) {
registered_units_listener = true;
AltosPreferences.register_units_listener(this);
}
+ serials = telemetry_state.states.keySet().toArray(new Integer[0]);
+ Arrays.sort(serials);
+
update_title(telemetry_state);
- update_ui(telemetry_state.state, telemetry_state.location);
- if (telemetry_state.connect == TelemetryState.CONNECT_CONNECTED)
- start_timer();
- else
- stop_timer();
+
+ AltosState state = null;
+ boolean aged = true;
+
+ if (telemetry_state.states.containsKey(current_serial)) {
+ state = telemetry_state.states.get(current_serial);
+ int age = state_age(state);
+ if (age < 20)
+ aged = false;
+ if (current_serial == selected_serial)
+ aged = false;
+ else if (switch_time != 0 && (switch_time - state.received_time) > 0)
+ aged = true;
+ }
+
+ if (aged) {
+ AltosState newest_state = null;
+ int newest_age = 0;
+
+ for (int serial : telemetry_state.states.keySet()) {
+ AltosState existing = telemetry_state.states.get(serial);
+ int existing_age = state_age(existing);
+
+ if (newest_state == null || existing_age < newest_age) {
+ newest_state = existing;
+ newest_age = existing_age;
+ }
+ }
+
+ if (newest_state != null)
+ state = newest_state;
+ }
+
+ update_ui(telemetry_state, state, telemetry_state.location);
+
+ start_timer();
}
boolean same_string(String a, String b) {
@@ -260,14 +331,55 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
}
}
- void update_age() {
- if (saved_state != null)
- mAgeView.setText(String.format("%d", (System.currentTimeMillis() - saved_state.received_time + 500) / 1000));
+
+ private int blend_component(int a, int b, double r, int shift, int mask) {
+ return ((int) (((a >> shift) & mask) * r + ((b >> shift) & mask) * (1 - r)) & mask) << shift;
}
+ private int blend_color(int a, int b, double r) {
+ return (blend_component(a, b, r, 0, 0xff) |
+ blend_component(a, b, r, 8, 0xff) |
+ blend_component(a, b, r, 16, 0xff) |
+ blend_component(a, b, r, 24, 0xff));
+ }
+
+ int state_age(AltosState state) {
+ return (int) ((System.currentTimeMillis() - state.received_time + 500) / 1000);
+ }
+
+ void set_screen_on(int age) {
+ if (age < 60)
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ else
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ void update_age() {
+ if (saved_state != null) {
+ int age = state_age(saved_state);
+
+ double age_scale = age / 100.0;
+
+ if (age_scale > 1.0)
+ age_scale = 1.0;
- void update_ui(AltosState state, Location location) {
+ mAgeView.setTextColor(blend_color(mAgeOldColor, mAgeNewColor, age_scale));
- Log.d(TAG, "update_ui");
+ set_screen_on(age);
+
+ String text;
+ if (age < 60)
+ text = String.format("%ds", age);
+ else if (age < 60 * 60)
+ text = String.format("%dm", age / 60);
+ else if (age < 60 * 60 * 24)
+ text = String.format("%dh", age / (60 * 60));
+ else
+ text = String.format("%dd", age / (24 * 60 * 60));
+ mAgeView.setText(text);
+ }
+ }
+
+ void update_ui(TelemetryState telem_state, AltosState state, Location location) {
int prev_state = AltosLib.ao_flight_invalid;
@@ -277,7 +389,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
prev_state = saved_state.state;
if (state != null) {
- Log.d(TAG, String.format("prev state %d new state %d\n", prev_state, state.state));
+ set_screen_on(state_age(state));
+
if (state.state == AltosLib.ao_flight_stateless) {
boolean prev_locked = false;
boolean locked = false;
@@ -289,27 +402,23 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
if (prev_locked != locked) {
String currentTab = mTabHost.getCurrentTabTag();
if (locked) {
- if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
+ if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
} else {
- if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("pad");
+ if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_pad_name);
}
}
} else {
if (prev_state != state.state) {
String currentTab = mTabHost.getCurrentTabTag();
- Log.d(TAG, "switch state");
switch (state.state) {
case AltosLib.ao_flight_boost:
- if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent");
- break;
- case AltosLib.ao_flight_drogue:
- if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent");
+ if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
break;
case AltosLib.ao_flight_landed:
- if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed");
+ if (currentTab.equals(tab_flight_name)) mTabHost.setCurrentTabByTag(tab_recover_name);
break;
case AltosLib.ao_flight_stateless:
- if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
+ if (currentTab.equals(tab_pad_name)) mTabHost.setCurrentTabByTag(tab_flight_name);
break;
}
}
@@ -328,22 +437,18 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
}
if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
- Log.d(TAG, "update callsign");
mCallsignView.setText(state.callsign);
}
if (saved_state == null || state.serial != saved_state.serial) {
- Log.d(TAG, "update serial");
mSerialView.setText(String.format("%d", state.serial));
}
if (saved_state == null || state.flight != saved_state.flight) {
- Log.d(TAG, "update flight");
if (state.flight == AltosLib.MISSING)
mFlightView.setText("");
else
mFlightView.setText(String.format("%d", state.flight));
}
if (saved_state == null || state.state != saved_state.state) {
- Log.d(TAG, "update state");
if (state.state == AltosLib.ao_flight_stateless) {
mStateLayout.setVisibility(View.GONE);
} else {
@@ -352,16 +457,15 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
}
}
if (saved_state == null || state.rssi != saved_state.rssi) {
- Log.d(TAG, "update rssi");
mRSSIView.setText(String.format("%d", state.rssi));
}
}
for (AltosDroidTab mTab : mTabs)
- mTab.update_ui(state, from_receiver, location, mTab == mTabsAdapter.currentItem());
+ mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
- if (state != null)
- mAltosVoice.tell(state, from_receiver);
+ if (mAltosVoice != null)
+ mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem());
saved_state = state;
}
@@ -398,26 +502,32 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
return String.format(format, value);
}
+ private View create_tab_view(String label) {
+ LayoutInflater inflater = (LayoutInflater) this.getLayoutInflater();
+ View tab_view = inflater.inflate(R.layout.tab_layout, null);
+ TextView text_view = (TextView) tab_view.findViewById (R.id.tabLabel);
+ text_view.setText(label);
+ return tab_view;
+ }
+
+ public void set_map_source(int source) {
+ for (AltosDroidTab mTab : mTabs)
+ mTab.set_map_source(source);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if(D) Log.e(TAG, "+++ ON CREATE +++");
+ AltosDebug.init(this);
+ AltosDebug.debug("+++ 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();
- }
+ // Initialise preferences
+ AltosDroidPreferences.init(this);
fm = getSupportFragmentManager();
// Set up the window layout
- requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.altosdroid);
- getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
// Create the Tabs and ViewPager
mTabHost = (TabHost)findViewById(android.R.id.tabhost);
@@ -428,37 +538,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
- mTabsAdapter.addTab(mTabHost.newTabSpec("pad").setIndicator("Pad"), TabPad.class, null);
- mTabsAdapter.addTab(mTabHost.newTabSpec("ascent").setIndicator("Ascent"), TabAscent.class, null);
- mTabsAdapter.addTab(mTabHost.newTabSpec("descent").setIndicator("Descent"), TabDescent.class, null);
- mTabsAdapter.addTab(mTabHost.newTabSpec("landed").setIndicator("Landed"), TabLanded.class, null);
- mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator("Map"), TabMap.class, null);
-
-
- // Scale the size of the Tab bar for different screen densities
- // This probably won't be needed when we start supporting ICS+ tabs.
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
- int density = metrics.densityDpi;
-
- if (density==DisplayMetrics.DENSITY_XHIGH)
- tabHeight = 65;
- else if (density==DisplayMetrics.DENSITY_HIGH)
- tabHeight = 45;
- else if (density==DisplayMetrics.DENSITY_MEDIUM)
- tabHeight = 35;
- else if (density==DisplayMetrics.DENSITY_LOW)
- tabHeight = 25;
- else
- tabHeight = 65;
-
- for (int i = 0; i < 5; i++)
- mTabHost.getTabWidget().getChildAt(i).getLayoutParams().height = tabHeight;
-
- // Set up the custom title
- mTitle = (TextView) findViewById(R.id.title_left_text);
- mTitle.setText(R.string.app_name);
- mTitle = (TextView) findViewById(R.id.title_right_text);
+ mTabsAdapter.addTab(mTabHost.newTabSpec(tab_pad_name).setIndicator(create_tab_view("Pad")), TabPad.class, null);
+ mTabsAdapter.addTab(mTabHost.newTabSpec(tab_flight_name).setIndicator(create_tab_view("Flight")), TabFlight.class, null);
+ mTabsAdapter.addTab(mTabHost.newTabSpec(tab_recover_name).setIndicator(create_tab_view("Recover")), TabRecover.class, null);
+ mTabsAdapter.addTab(mTabHost.newTabSpec(tab_map_name).setIndicator(create_tab_view("Map")), TabMap.class, null);
// Display the Version
mVersion = (TextView) findViewById(R.id.version);
@@ -473,58 +556,158 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
mStateLayout = (RelativeLayout) findViewById(R.id.state_container);
mStateView = (TextView) findViewById(R.id.state_value);
mAgeView = (TextView) findViewById(R.id.age_value);
+ mAgeNewColor = mAgeView.getTextColors().getDefaultColor();
+ mAgeOldColor = getResources().getColor(R.color.old_color);
+ }
+
+ 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);
- mAltosVoice = new AltosVoice(this);
+ 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);
+
+ AltosDebug.debug("intent %s device %s granted %s", intent, device, granted);
+
+ if (!granted)
+ device = null;
+
+ if (device != null) {
+ AltosDebug.debug("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) {
+ AltosDebug.debug("check for a USB device at startup");
+ if (check_usb())
+ return;
+ }
+ AltosDebug.debug("Starting by looking for bluetooth devices");
+ if (ensureBluetooth())
+ return;
+ finish();
+ }
}
@Override
public void onStart() {
super.onStart();
- if(D) Log.e(TAG, "++ ON START ++");
+ AltosDebug.debug("++ ON START ++");
+
+ set_switch_time();
+
+ 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 synchronized void onResume() {
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ AltosDebug.debug("onNewIntent");
+ noticeIntent(intent);
+ }
+
+ @Override
+ public void onResume() {
super.onResume();
- if(D) Log.e(TAG, "+ ON RESUME +");
+ AltosDebug.debug("+ ON RESUME +");
}
@Override
- public synchronized void onPause() {
+ public void onPause() {
super.onPause();
- if(D) Log.e(TAG, "- ON PAUSE -");
+ AltosDebug.debug("- ON PAUSE -");
}
@Override
public void onStop() {
super.onStop();
- if(D) Log.e(TAG, "-- ON STOP --");
-
- doUnbindService();
+ AltosDebug.debug("-- ON STOP --");
}
@Override
public void onDestroy() {
super.onDestroy();
- if(D) Log.e(TAG, "--- ON DESTROY ---");
+ AltosDebug.debug("--- ON DESTROY ---");
- if (mAltosVoice != null) mAltosVoice.stop();
+ doUnbindService();
+ if (mAltosVoice != null) {
+ mAltosVoice.stop();
+ mAltosVoice = null;
+ }
stop_timer();
}
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if(D) Log.d(TAG, "onActivityResult " + resultCode);
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ AltosDebug.debug("onActivityResult " + resultCode);
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect to
@@ -539,28 +722,64 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
//setupChat();
} else {
// User did not enable Bluetooth or an error occured
- Log.e(TAG, "BT not enabled");
+ AltosDebug.error("BT not enabled");
stopService(new Intent(AltosDroid.this, TelemetryService.class));
Toast.makeText(this, R.string.bt_not_enabled, Toast.LENGTH_SHORT).show();
finish();
}
break;
+ case REQUEST_MAP_TYPE:
+ if (resultCode == Activity.RESULT_OK)
+ set_map_type(data);
+ break;
}
}
- private void connectDevice(String address) {
+ 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));
+ AltosDebug.debug("Sent OPEN_USB message");
+ } catch (RemoteException e) {
+ AltosDebug.debug("connect device message failed");
+ }
+ }
+ }
+
+ private void connectDevice(Intent data) {
// Attempt to connect to the device
try {
- if (D) Log.d(TAG, "Connecting to " + address);
- mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, address));
+ String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
+ String name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
+
+ AltosDebug.debug("Connecting to " + address + " " + name);
+ DeviceAddress a = new DeviceAddress(address, name);
+ mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, a));
+ AltosDebug.debug("Sent connecting message");
} catch (RemoteException e) {
+ AltosDebug.debug("connect device message failed");
}
}
- private void connectDevice(Intent data) {
- // Get the device MAC address
- String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
- connectDevice(address);
+ private void disconnectDevice() {
+ try {
+ mService.send(Message.obtain(null, TelemetryService.MSG_DISCONNECT, null));
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void set_map_type(Intent data) {
+ int type = data.getIntExtra(MapTypeActivity.EXTRA_MAP_TYPE, -1);
+
+ AltosDebug.debug("intent set_map_type %d\n", type);
+ if (type != -1) {
+ map_type = type;
+ for (AltosDroidTab mTab : mTabs)
+ mTab.set_map_type(map_type);
+ }
}
@Override
@@ -573,20 +792,22 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
void setFrequency(double freq) {
try {
mService.send(Message.obtain(null, TelemetryService.MSG_SETFREQUENCY, freq));
+ set_switch_time();
} catch (RemoteException e) {
}
}
void setFrequency(String freq) {
try {
- setFrequency (Double.parseDouble(freq.substring(11, 17)));
- } catch (NumberFormatException e) {
+ setFrequency (AltosParse.parse_double_net(freq.substring(11, 17)));
+ } catch (ParseException e) {
}
}
void setBaud(int baud) {
try {
mService.send(Message.obtain(null, TelemetryService.MSG_SETBAUD, baud));
+ set_switch_time();
} catch (RemoteException e) {
}
}
@@ -611,18 +832,77 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
}
}
+ void select_tracker(int serial) {
+ int i;
+
+ AltosDebug.debug("select tracker %d\n", serial);
+
+ if (serial == selected_serial) {
+ AltosDebug.debug("%d already selected\n", serial);
+ return;
+ }
+
+ if (serial != 0) {
+ for (i = 0; i < serials.length; i++)
+ if (serials[i] == serial)
+ break;
+
+ if (i == serials.length) {
+ AltosDebug.debug("attempt to select unknown tracker %d\n", serial);
+ return;
+ }
+ }
+
+ current_serial = selected_serial = serial;
+ update_state(null);
+ }
+
+ void touch_trackers(Integer[] serials) {
+ AlertDialog.Builder builder_tracker = new AlertDialog.Builder(this);
+ builder_tracker.setTitle("Select Tracker");
+ final String[] trackers = new String[serials.length + 1];
+ trackers[0] = "Auto";
+ for (int i = 0; i < serials.length; i++)
+ trackers[i+1] = String.format("%d", serials[i]);
+ builder_tracker.setItems(trackers,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if (item == 0)
+ select_tracker(0);
+ else
+ select_tracker(Integer.parseInt(trackers[item]));
+ }
+ });
+ AlertDialog alert_tracker = builder_tracker.create();
+ alert_tracker.show();
+ }
+
+ void delete_track(int serial) {
+ try {
+ mService.send(Message.obtain(null, TelemetryService.MSG_DELETE_SERIAL, (Integer) serial));
+ } catch (Exception ex) {
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
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 device
+ */
+ disconnectDevice();
return true;
case R.id.quit:
- Log.d(TAG, "R.id.quit");
- stopService(new Intent(AltosDroid.this, TelemetryService.class));
+ AltosDebug.debug("R.id.quit");
+ disconnectDevice();
finish();
return true;
case R.id.select_freq:
@@ -676,8 +956,92 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
boolean imperial = AltosPreferences.imperial_units();
AltosPreferences.set_imperial_units(!imperial);
return true;
+ case R.id.preload_maps:
+ serverIntent = new Intent(this, PreloadMapActivity.class);
+ startActivityForResult(serverIntent, REQUEST_PRELOAD_MAPS);
+ return true;
+ case R.id.map_type:
+ serverIntent = new Intent(this, MapTypeActivity.class);
+ startActivityForResult(serverIntent, REQUEST_MAP_TYPE);
+ return true;
+ case R.id.map_source:
+ int source = AltosDroidPreferences.map_source();
+ int new_source = source == AltosDroidPreferences.MAP_SOURCE_ONLINE ? AltosDroidPreferences.MAP_SOURCE_OFFLINE : AltosDroidPreferences.MAP_SOURCE_ONLINE;
+ AltosDroidPreferences.set_map_source(new_source);
+ set_map_source(new_source);
+ return true;
+ case R.id.select_tracker:
+ if (serials != null) {
+ String[] trackers = new String[serials.length+1];
+ trackers[0] = "Auto";
+ for (int i = 0; i < serials.length; i++)
+ trackers[i+1] = String.format("%d", serials[i]);
+ AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
+ builder_serial.setTitle("Select a tracker");
+ builder_serial.setItems(trackers,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if (item == 0)
+ select_tracker(0);
+ else
+ select_tracker(serials[item-1]);
+ }
+ });
+ AlertDialog alert_serial = builder_serial.create();
+ alert_serial.show();
+
+ }
+ return true;
+ case R.id.delete_track:
+ if (serials != null) {
+ String[] trackers = new String[serials.length];
+ for (int i = 0; i < serials.length; i++)
+ trackers[i] = String.format("%d", serials[i]);
+ AlertDialog.Builder builder_serial = new AlertDialog.Builder(this);
+ builder_serial.setTitle("Delete a track");
+ builder_serial.setItems(trackers,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ delete_track(serials[item]);
+ }
+ });
+ AlertDialog alert_serial = builder_serial.create();
+ alert_serial.show();
+
+ }
+ return true;
}
return false;
}
+ static String direction(AltosGreatCircle from_receiver,
+ Location receiver) {
+ if (from_receiver == null)
+ return null;
+
+ if (receiver == null)
+ return null;
+
+ if (!receiver.hasBearing())
+ return null;
+
+ float bearing = receiver.getBearing();
+ float heading = (float) from_receiver.bearing - bearing;
+
+ while (heading <= -180.0f)
+ heading += 360.0f;
+ while (heading > 180.0f)
+ heading -= 360.0f;
+
+ int iheading = (int) (heading + 0.5f);
+
+ if (-1 < iheading && iheading < 1)
+ return "ahead";
+ else if (iheading < -179 || 179 < iheading)
+ return "backwards";
+ else if (iheading < 0)
+ return String.format("left %d°", -iheading);
+ else
+ return String.format("right %d°", iheading);
+ }
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java
new file mode 100644
index 00000000..7cbba794
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java
@@ -0,0 +1,219 @@
+/*
+ * 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 org.altusmetrum.altoslib_8.*;
+
+public abstract class AltosDroidLink extends AltosLink {
+
+ 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);
+ AltosDebug.debug("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) {
+ AltosDebug.debug("Marked closing true");
+ closing = true;
+ }
+ }
+
+ private boolean actually_closed() {
+ synchronized(closed_lock) {
+ return closed;
+ }
+ }
+
+ abstract void close_device();
+
+ public void close() {
+ AltosDebug.debug("close(): begin");
+
+ closing();
+
+ flush_output();
+
+ synchronized (closed_lock) {
+ AltosDebug.debug("Marked closed true");
+ closed = true;
+ }
+
+ close_device();
+
+ synchronized(this) {
+
+ if (input_thread != null) {
+ AltosDebug.debug("close(): stopping input_thread");
+ try {
+ AltosDebug.debug("close(): input_thread.interrupt().....");
+ input_thread.interrupt();
+ AltosDebug.debug("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') {
+ AltosDebug.debug(" " + 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()) {
+ AltosDebug.debug("disconnected after closed");
+ return;
+ }
+
+ AltosDebug.debug("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) {
+ AltosDebug.debug("ERROR returned from getchar()");
+ disconnected();
+ return ERROR;
+ }
+ buffer_off = 0;
+ }
+ if (AltosDebug.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) {
+ AltosDebug.debug("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();
+ AltosDebug.debug("print(): begin");
+ for (byte b : bytes)
+ putchar(b);
+ AltosDebug.debug("print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
+ }
+
+ public AltosDroidLink(Handler handler) {
+ this.handler = handler;
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java
new file mode 100644
index 00000000..5f6ff198
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidMapInterface.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util.*;
+import java.io.*;
+import android.location.Location;
+import org.altusmetrum.altoslib_8.*;
+
+public interface AltosDroidMapInterface {
+ public void onCreateView(AltosDroid altos_droid);
+
+ public void set_visible(boolean visible);
+
+ public void center(double lat, double lon, double accuracy);
+
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java
index 7ab70147..a4e27006 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java
@@ -17,14 +17,23 @@
package org.altusmetrum.AltosDroid;
import android.content.Context;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
public class AltosDroidPreferences extends AltosPreferences {
/* Active device preference name */
- final static String activeDevicePreference = "ACTIVE-DEVICE";
+ final static String activeDeviceAddressPreference = "ACTIVE-DEVICE-ADDRESS";
+ final static String activeDeviceNamePreference = "ACTIVE-DEVICE-NAME";
- static String active_device_address;
+ static DeviceAddress active_device_address;
+
+ /* Map source preference name */
+ final static String mapSourcePreference = "MAP-SOURCE";
+
+ static final int MAP_SOURCE_OFFLINE = 0;
+ static final int MAP_SOURCE_ONLINE = 1;
+
+ static int map_source;
public static void init(Context context) {
if (backend != null)
@@ -32,20 +41,41 @@ public class AltosDroidPreferences extends AltosPreferences {
AltosPreferences.init(new AltosDroidPreferencesBackend(context));
- active_device_address = backend.getString(activeDevicePreference, null);
+ String address = backend.getString(activeDeviceAddressPreference, null);
+ String name = backend.getString(activeDeviceNamePreference, null);
+
+ if (address != null && name != null)
+ active_device_address = new DeviceAddress (address, name);
+
+ map_source = backend.getInt(mapSourcePreference, MAP_SOURCE_ONLINE);
}
- public static void set_active_device(String address) {
+ public static void set_active_device(DeviceAddress address) {
synchronized(backend) {
active_device_address = address;
- backend.putString(activeDevicePreference, active_device_address);
+ backend.putString(activeDeviceAddressPreference, active_device_address.address);
+ backend.putString(activeDeviceNamePreference, active_device_address.name);
flush_preferences();
}
}
- public static String active_device() {
+ public static DeviceAddress active_device() {
synchronized(backend) {
return active_device_address;
}
}
+
+ public static void set_map_source(int map_source) {
+ synchronized(backend) {
+ AltosDroidPreferences.map_source = map_source;
+ backend.putInt(mapSourcePreference, map_source);
+ flush_preferences();
+ }
+ }
+
+ public static int map_source() {
+ synchronized(backend) {
+ return map_source;
+ }
+ }
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java
index bc5300fd..2ff711f5 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java
@@ -24,7 +24,7 @@ import android.content.SharedPreferences;
import android.os.Environment;
import android.util.*;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {
public final static String NAME = "org.altusmetrum.AltosDroid";
@@ -44,7 +44,12 @@ public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {
public String[] keys() {
Map<String, ?> all = prefs.getAll();
- return (String[])all.keySet().toArray();
+ Object[] ao = all.keySet().toArray();
+
+ String[] as = new String[ao.length];
+ for (int i = 0; i < ao.length; i++)
+ as[i] = (String) ao[i];
+ return as;
}
public AltosPreferencesBackend node(String key) {
@@ -104,6 +109,7 @@ public class AltosDroidPreferencesBackend implements AltosPreferencesBackend {
}
public void remove(String key) {
+ AltosDebug.debug("remove preference %s\n", key);
editor.remove(key);
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java
index cbb20045..9d612a1e 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java
@@ -17,7 +17,7 @@
package org.altusmetrum.AltosDroid;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
import android.location.Location;
import android.app.Activity;
import android.graphics.Color;
@@ -26,21 +26,28 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.FragmentManager;
import android.location.Location;
-import android.util.Log;
import android.widget.TextView;
public abstract class AltosDroidTab extends Fragment implements AltosUnitsListener {
+ TelemetryState last_telem_state;
AltosState last_state;
AltosGreatCircle last_from_receiver;
Location last_receiver;
+ AltosDroid altos_droid;
- public abstract void show(AltosState state, AltosGreatCircle from_receiver, Location receiver);
+ public abstract void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver);
public abstract String tab_name();
+ public void set_map_type(int map_type) {
+ }
+
+ public void set_map_source(int map_source) {
+ }
+
public void units_changed(boolean imperial_units) {
- if (!isHidden() && last_state != null)
- show(last_state, last_from_receiver, last_receiver);
+ if (!isHidden())
+ show(last_telem_state, last_state, last_from_receiver, last_receiver);
}
public void set_value(TextView text_view,
@@ -55,29 +62,46 @@ public abstract class AltosDroidTab extends Fragment implements AltosUnitsListen
public void set_visible(boolean visible) {
FragmentTransaction ft = AltosDroid.fm.beginTransaction();
+ AltosDebug.debug("set visible %b %s\n", visible, tab_name());
if (visible) {
- AltosState state = last_state;
- AltosGreatCircle from_receiver = last_from_receiver;
- Location receiver = last_receiver;
-
- show(state, from_receiver, receiver);
ft.show(this);
+ show(last_telem_state, last_state, last_from_receiver, last_receiver);
} else
ft.hide(this);
- ft.commit();
+ ft.commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ altos_droid = (AltosDroid) activity;
+ altos_droid.registerTab(this);
}
- public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver, boolean is_current) {
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ altos_droid.unregisterTab(this);
+ altos_droid = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ AltosDebug.debug("onResume tab %s\n", tab_name());
+ set_visible(true);
+ }
+
+ public void update_ui(TelemetryState telem_state, AltosState state,
+ AltosGreatCircle from_receiver, Location receiver, boolean is_current)
+ {
+ last_telem_state = telem_state;
last_state = state;
last_from_receiver = from_receiver;
last_receiver = receiver;
- if (is_current) {
- if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: visible, performing update", tab_name()));
-
- show(state, from_receiver, receiver);
- } else {
- if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: not visible, skipping update", tab_name()));
+ if (is_current)
+ show(telem_state, state, from_receiver, receiver);
+ else
return;
- }
}
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java
new file mode 100644
index 00000000..eb059901
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOffline.java
@@ -0,0 +1,517 @@
+/*
+ * 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.util.*;
+import java.io.*;
+
+import org.altusmetrum.altoslib_8.*;
+
+import android.app.Activity;
+import android.graphics.*;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.*;
+import android.widget.*;
+import android.location.Location;
+import android.content.*;
+import android.util.*;
+
+class Rocket implements Comparable {
+ AltosLatLon position;
+ String name;
+ int serial;
+ long last_packet;
+ boolean active;
+ AltosMapOffline map_offline;
+
+ void paint() {
+ map_offline.draw_bitmap(position, map_offline.rocket_bitmap, map_offline.rocket_off_x, map_offline.rocket_off_y);
+ map_offline.draw_text(position, name, 0, 3*map_offline.rocket_bitmap.getHeight()/4);
+ }
+
+ void set_position(AltosLatLon position, long last_packet) {
+ this.position = position;
+ this.last_packet = last_packet;
+ }
+
+ void set_active(boolean active) {
+ this.active = active;
+ }
+
+ public int compareTo(Object o) {
+ Rocket other = (Rocket) o;
+
+ if (active && !other.active)
+ return 1;
+ if (other.active && !active)
+ return -1;
+
+ long diff = last_packet - other.last_packet;
+
+ if (diff > 0)
+ return 1;
+ if (diff < 0)
+ return -1;
+ return 0;
+ }
+
+ Rocket(int serial, AltosMapOffline map_offline) {
+ this.serial = serial;
+ this.name = String.format("%d", serial);
+ this.map_offline = map_offline;
+ }
+}
+
+public class AltosMapOffline extends View implements ScaleGestureDetector.OnScaleGestureListener, AltosMapInterface, AltosDroidMapInterface {
+ ScaleGestureDetector scale_detector;
+ boolean scaling;
+ AltosMap map;
+ AltosDroid altos_droid;
+
+ AltosLatLon here;
+ AltosLatLon there;
+ AltosLatLon pad;
+
+ Canvas canvas;
+ Paint paint;
+
+ Bitmap pad_bitmap;
+ int pad_off_x, pad_off_y;
+ Bitmap rocket_bitmap;
+ int rocket_off_x, rocket_off_y;
+ Bitmap here_bitmap;
+ int here_off_x, here_off_y;
+
+ static final int WHITE = 0xffffffff;
+ static final int RED = 0xffff0000;
+ static final int PINK = 0xffff8080;
+ static final int YELLOW= 0xffffff00;
+ static final int CYAN = 0xff00ffff;
+ static final int BLUE = 0xff0000ff;
+ static final int BLACK = 0xff000000;
+
+ public static final int stateColors[] = {
+ WHITE, // startup
+ WHITE, // idle
+ WHITE, // pad
+ RED, // boost
+ PINK, // fast
+ YELLOW, // coast
+ CYAN, // drogue
+ BLUE, // main
+ BLACK, // landed
+ BLACK, // invalid
+ CYAN, // stateless
+ };
+
+ /* AltosMapInterface */
+ public void debug(String format, Object ... arguments) {
+ AltosDebug.debug(format, arguments);
+ }
+
+ class MapTile extends AltosMapTile {
+ public void paint(AltosMapTransform t) {
+ AltosPointInt pt = new AltosPointInt(t.screen(upper_left));
+
+ if (canvas.quickReject(pt.x, pt.y, pt.x + px_size, pt.y + px_size, Canvas.EdgeType.AA))
+ return;
+
+ AltosImage altos_image = cache.get(this, store, px_size, px_size);
+
+ MapImage map_image = (MapImage) altos_image;
+
+ Bitmap bitmap = null;
+
+ if (map_image != null)
+ bitmap = map_image.bitmap;
+
+ if (bitmap != null) {
+ canvas.drawBitmap(bitmap, pt.x, pt.y, paint);
+ } else {
+ paint.setColor(0xff808080);
+ canvas.drawRect(pt.x, pt.y, pt.x + px_size, pt.y + px_size, paint);
+ if (t.has_location()) {
+ String message = null;
+ switch (status) {
+ case AltosMapTile.loading:
+ message = "Loading...";
+ break;
+ case AltosMapTile.bad_request:
+ message = "Internal error";
+ break;
+ case AltosMapTile.failed:
+ message = "Network error, check connection";
+ break;
+ case AltosMapTile.forbidden:
+ message = "Too many requests, try later";
+ break;
+ }
+ if (message != null) {
+ Rect bounds = new Rect();
+ paint.getTextBounds(message, 0, message.length(), bounds);
+
+ int width = bounds.right - bounds.left;
+ int height = bounds.bottom - bounds.top;
+
+ float x = pt.x + px_size / 2.0f;
+ float y = pt.y + px_size / 2.0f;
+ x = x - width / 2.0f;
+ y = y + height / 2.0f;
+ paint.setColor(0xff000000);
+ canvas.drawText(message, 0, message.length(), x, y, paint);
+ }
+ }
+ }
+ }
+
+ public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+ super(listener, upper_left, center, zoom, maptype, px_size, 2);
+ }
+
+ }
+
+ public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+ return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
+ }
+
+ public AltosMapPath new_path() {
+ return null;
+ }
+
+ public AltosMapLine new_line() {
+ return null;
+ }
+
+ class MapImage implements AltosImage {
+ public Bitmap bitmap;
+
+ public void flush() {
+ if (bitmap != null) {
+ bitmap.recycle();
+ bitmap = null;
+ }
+ }
+
+ public MapImage(File file) {
+ bitmap = BitmapFactory.decodeFile(file.getPath());
+ }
+ }
+
+ public AltosImage load_image(File file) throws Exception {
+ return new MapImage(file);
+ }
+
+ class MapMark extends AltosMapMark {
+ public void paint(AltosMapTransform t) {
+ }
+
+ MapMark(double lat, double lon, int state) {
+ super(lat, lon, state);
+ }
+ }
+
+ public AltosMapMark new_mark(double lat, double lon, int state) {
+ return new MapMark(lat, lon, state);
+ }
+
+ public int width() {
+ return getWidth();
+ }
+
+ public int height() {
+ return getHeight();
+ }
+
+ public void repaint() {
+ postInvalidate();
+ }
+
+ public void repaint(AltosRectangle damage) {
+ postInvalidate(damage.x, damage.y, damage.x + damage.width, damage.y + damage.height);
+ }
+
+ public void set_zoom_label(String label) {
+ }
+
+ public void select_object(AltosLatLon latlon) {
+ if (map.transform == null)
+ return;
+ ArrayList<Integer> near = new ArrayList<Integer>();
+
+ for (Rocket rocket : sorted_rockets()) {
+ if (rocket.position == null) {
+ debug("rocket %d has no position\n", rocket.serial);
+ continue;
+ }
+ double distance = map.transform.hypot(latlon, rocket.position);
+ debug("check select %d distance %g width %d\n", rocket.serial, distance, rocket_bitmap.getWidth());
+ if (distance < rocket_bitmap.getWidth() * 2.0) {
+ debug("selecting %d\n", rocket.serial);
+ near.add(rocket.serial);
+ }
+ }
+ if (near.size() != 0)
+ altos_droid.touch_trackers(near.toArray(new Integer[0]));
+ }
+
+ class Line {
+ AltosLatLon a, b;
+
+ void paint() {
+ if (a != null && b != null) {
+ AltosPointDouble a_screen = map.transform.screen(a);
+ AltosPointDouble b_screen = map.transform.screen(b);
+ paint.setColor(0xff8080ff);
+ canvas.drawLine((float) a_screen.x, (float) a_screen.y,
+ (float) b_screen.x, (float) b_screen.y,
+ paint);
+ }
+ }
+
+ void set_a(AltosLatLon a) {
+ this.a = a;
+ }
+
+ void set_b(AltosLatLon b) {
+ this.b = b;
+ }
+
+ Line() {
+ }
+ }
+
+ Line line = new Line();
+
+ int stroke_width = 20;
+
+ void draw_text(AltosLatLon lat_lon, String text, int off_x, int off_y) {
+ if (lat_lon != null && map != null && map.transform != null) {
+ AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
+
+ Rect bounds = new Rect();
+ paint.getTextBounds(text, 0, text.length(), bounds);
+
+ int width = bounds.right - bounds.left;
+ int height = bounds.bottom - bounds.top;
+
+ float x = pt.x;
+ float y = pt.y;
+ x = x - width / 2.0f - off_x;
+ y = y + height / 2.0f - off_y;
+ paint.setColor(0xff000000);
+ canvas.drawText(text, 0, text.length(), x, y, paint);
+ }
+ }
+
+ HashMap<Integer,Rocket> rockets = new HashMap<Integer,Rocket>();
+
+ void draw_bitmap(AltosLatLon lat_lon, Bitmap bitmap, int off_x, int off_y) {
+ if (lat_lon != null && map != null && map.transform != null) {
+ AltosPointInt pt = new AltosPointInt(map.transform.screen(lat_lon));
+
+ canvas.drawBitmap(bitmap, pt.x - off_x, pt.y - off_y, paint);
+ }
+ }
+
+ private Rocket[] sorted_rockets() {
+ Rocket[] rocket_array = rockets.values().toArray(new Rocket[0]);
+
+ Arrays.sort(rocket_array);
+ return rocket_array;
+ }
+
+ private void draw_positions() {
+ line.set_a(there);
+ line.set_b(here);
+ line.paint();
+ draw_bitmap(pad, pad_bitmap, pad_off_x, pad_off_y);
+
+ for (Rocket rocket : sorted_rockets())
+ rocket.paint();
+ draw_bitmap(here, here_bitmap, here_off_x, here_off_y);
+ }
+
+ @Override public void invalidate() {
+ Rect r = new Rect();
+ getDrawingRect(r);
+ super.invalidate();
+ }
+
+ @Override public void invalidate(int l, int t, int r, int b) {
+ Rect rect = new Rect();
+ getDrawingRect(rect);
+ super.invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas view_canvas) {
+ if (map == null) {
+ debug("MapView draw without map\n");
+ return;
+ }
+ canvas = view_canvas;
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setStrokeWidth(stroke_width);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setTextSize(40);
+ map.paint();
+ draw_positions();
+ canvas = null;
+ }
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ float f = detector.getScaleFactor();
+
+ if (f <= 0.8) {
+ map.set_zoom_centre(map.get_zoom() - 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
+ return true;
+ }
+ if (f >= 1.2) {
+ map.set_zoom_centre(map.get_zoom() + 1, new AltosPointInt((int) detector.getFocusX(), (int) detector.getFocusY()));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ scale_detector.onTouchEvent(event);
+
+ if (scale_detector.isInProgress()) {
+ scaling = true;
+ }
+
+ if (scaling) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ scaling = false;
+ }
+ return true;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ map.touch_start((int) event.getX(), (int) event.getY(), true);
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ map.touch_continue((int) event.getX(), (int) event.getY(), true);
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ map.touch_stop((int) event.getX(), (int) event.getY(), true);
+ }
+ return true;
+ }
+
+ double mapAccuracy;
+
+ public void center(double lat, double lon, double accuracy) {
+ if (mapAccuracy <= 0 || accuracy < mapAccuracy/10 || (map != null && !map.has_centre())) {
+ if (map != null)
+ map.maybe_centre(lat, lon);
+ mapAccuracy = accuracy;
+ }
+ }
+
+ public void set_visible(boolean visible) {
+ if (visible)
+ setVisibility(VISIBLE);
+ else
+ setVisibility(GONE);
+ }
+
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+ if (state != null) {
+ map.show(state, null);
+ if (state.pad_lat != AltosLib.MISSING && pad == null)
+ pad = new AltosLatLon(state.pad_lat, state.pad_lon);
+ }
+
+ if (telem_state != null) {
+ Integer[] old_serial = rockets.keySet().toArray(new Integer[0]);
+ Integer[] new_serial = telem_state.states.keySet().toArray(new Integer[0]);
+
+ /* remove deleted keys */
+ for (int serial : old_serial) {
+ if (!telem_state.states.containsKey(serial))
+ rockets.remove(serial);
+ }
+
+ /* set remaining keys */
+
+ for (int serial : new_serial) {
+ Rocket rocket;
+ AltosState t_state = telem_state.states.get(serial);
+ if (rockets.containsKey(serial))
+ rocket = rockets.get(serial);
+ else {
+ rocket = new Rocket(serial, this);
+ rockets.put(serial, rocket);
+ }
+ if (t_state.gps != null) {
+ AltosLatLon latlon = new AltosLatLon(t_state.gps.lat, t_state.gps.lon);
+ rocket.set_position(latlon, t_state.received_time);
+ if (state.serial == serial)
+ there = latlon;
+ }
+ if (state != null)
+ rocket.set_active(state.serial == serial);
+ }
+ }
+ if (receiver != null) {
+ here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
+ }
+ }
+
+ public void onCreateView(AltosDroid altos_droid) {
+ this.altos_droid = altos_droid;
+ map = new AltosMap(this);
+ map.set_maptype(altos_droid.map_type);
+
+ pad_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pad);
+ /* arrow at the bottom of the launchpad image */
+ pad_off_x = pad_bitmap.getWidth() / 2;
+ pad_off_y = pad_bitmap.getHeight();
+
+ rocket_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rocket);
+ /* arrow at the bottom of the rocket image */
+ rocket_off_x = rocket_bitmap.getWidth() / 2;
+ rocket_off_y = rocket_bitmap.getHeight();
+
+ here_bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_maps_indicator_current_position);
+ /* Center of the dot */
+ here_off_x = here_bitmap.getWidth() / 2;
+ here_off_y = here_bitmap.getHeight() / 2;
+ }
+
+ public void set_map_type(int map_type) {
+ if (map != null)
+ map.set_maptype(map_type);
+ }
+
+ public AltosMapOffline(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.altos_droid = altos_droid;
+ scale_detector = new ScaleGestureDetector(context, this);
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java
new file mode 100644
index 00000000..4ac95c0b
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosMapOnline.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright © 2013 Mike Beattie <mike@ethernal.org>
+ *
+ * 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.util.*;
+
+import org.altusmetrum.altoslib_8.*;
+
+import com.google.android.gms.maps.*;
+import com.google.android.gms.maps.model.*;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.*;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+//import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.location.Location;
+import android.content.*;
+
+class RocketOnline implements Comparable {
+ Marker marker;
+ int serial;
+ long last_packet;
+ int size;
+
+ void set_position(AltosLatLon position, long last_packet) {
+ marker.setPosition(new LatLng(position.lat, position.lon));
+ this.last_packet = last_packet;
+ }
+
+ private Bitmap rocket_bitmap(Context context, String text) {
+
+ /* From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/
+ */
+ Bitmap orig_bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocket);
+ Bitmap bitmap = orig_bitmap.copy(Bitmap.Config.ARGB_8888, true);
+
+ Canvas canvas = new Canvas(bitmap);
+ Paint paint = new Paint();
+ paint.setTextSize(40);
+ paint.setColor(0xff000000);
+
+ Rect bounds = new Rect();
+ paint.getTextBounds(text, 0, text.length(), bounds);
+
+ int width = bounds.right - bounds.left;
+ int height = bounds.bottom - bounds.top;
+
+ float x = bitmap.getWidth() / 2.0f - width / 2.0f;
+ float y = bitmap.getHeight() / 2.0f - height / 2.0f;
+
+ size = bitmap.getWidth();
+
+ canvas.drawText(text, 0, text.length(), x, y, paint);
+ return bitmap;
+ }
+
+ public void remove() {
+ marker.remove();
+ }
+
+ public int compareTo(Object o) {
+ RocketOnline other = (RocketOnline) o;
+
+ long diff = last_packet - other.last_packet;
+
+ if (diff > 0)
+ return 1;
+ if (diff < 0)
+ return -1;
+ return 0;
+ }
+
+ RocketOnline(Context context, int serial, GoogleMap map, double lat, double lon, long last_packet) {
+ this.serial = serial;
+ String name = String.format("%d", serial);
+ this.marker = map.addMarker(new MarkerOptions()
+ .icon(BitmapDescriptorFactory.fromBitmap(rocket_bitmap(context, name)))
+ .position(new LatLng(lat, lon))
+ .visible(true));
+ this.last_packet = last_packet;
+ }
+}
+
+public class AltosMapOnline implements AltosDroidMapInterface, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapClickListener {
+ public SupportMapFragment mMapFragment;
+ private GoogleMap mMap;
+ private boolean mapLoaded = false;
+ Context context;
+
+ private HashMap<Integer,RocketOnline> rockets = new HashMap<Integer,RocketOnline>();
+ private Marker mPadMarker;
+ private boolean pad_set;
+ private Polyline mPolyline;
+
+ private View map_view;
+
+ private double mapAccuracy = -1;
+
+ private AltosLatLon my_position = null;
+ private AltosLatLon target_position = null;
+
+ private AltosDroid altos_droid;
+
+ public void onCreateView(AltosDroid altos_droid) {
+ this.altos_droid = altos_droid;
+ final int map_type = altos_droid.map_type;
+ mMapFragment = new SupportMapFragment() {
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setupMap(map_type);
+ }
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ map_view = super.onCreateView(inflater, container, savedInstanceState);
+ return map_view;
+ }
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ map_view = null;
+ }
+ };
+ }
+
+// public void onActivityCreated() {
+// getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit();
+// }
+
+ private double pixel_distance(LatLng a, LatLng b) {
+ Projection projection = mMap.getProjection();
+
+ Point a_pt = projection.toScreenLocation(a);
+ Point b_pt = projection.toScreenLocation(b);
+
+ return Math.hypot((double) (a_pt.x - b_pt.x), (double) (a_pt.y - b_pt.y));
+ }
+
+ private RocketOnline[] sorted_rockets() {
+ RocketOnline[] rocket_array = rockets.values().toArray(new RocketOnline[0]);
+
+ Arrays.sort(rocket_array);
+ return rocket_array;
+ }
+
+ public void onMapClick(LatLng lat_lng) {
+ ArrayList<Integer> near = new ArrayList<Integer>();
+
+ for (RocketOnline rocket : sorted_rockets()) {
+ LatLng pos = rocket.marker.getPosition();
+
+ if (pos == null)
+ continue;
+
+ double distance = pixel_distance(lat_lng, pos);
+ if (distance < rocket.size * 2)
+ near.add(rocket.serial);
+ }
+
+ if (near.size() != 0)
+ altos_droid.touch_trackers(near.toArray(new Integer[0]));
+ }
+
+ public boolean onMarkerClick(Marker marker) {
+ onMapClick(marker.getPosition());
+ return true;
+ }
+
+ public void setupMap(int map_type) {
+ mMap = mMapFragment.getMap();
+ if (mMap != null) {
+ set_map_type(map_type);
+ mMap.setMyLocationEnabled(true);
+ mMap.getUiSettings().setTiltGesturesEnabled(false);
+ mMap.getUiSettings().setZoomControlsEnabled(false);
+ mMap.setOnMarkerClickListener(this);
+ mMap.setOnMapClickListener(this);
+
+ mPadMarker = mMap.addMarker(
+ new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
+ .position(new LatLng(0,0))
+ .visible(false)
+ );
+
+ mPolyline = mMap.addPolyline(
+ new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
+ .width(20)
+ .color(Color.BLUE)
+ .visible(false)
+ );
+
+ mapLoaded = true;
+ }
+ }
+
+ public void center(double lat, double lon, double accuracy) {
+ if (mMap == null)
+ return;
+
+ if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
+ mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14));
+ mapAccuracy = accuracy;
+ }
+ }
+
+ private void set_rocket(int serial, AltosState state) {
+ RocketOnline rocket;
+
+ if (state.gps == null || state.gps.lat == AltosLib.MISSING)
+ return;
+
+ if (mMap == null)
+ return;
+
+ if (rockets.containsKey(serial)) {
+ rocket = rockets.get(serial);
+ rocket.set_position(new AltosLatLon(state.gps.lat, state.gps.lon), state.received_time);
+ } else {
+ rocket = new RocketOnline(context,
+ serial,
+ mMap, state.gps.lat, state.gps.lon,
+ state.received_time);
+ rockets.put(serial, rocket);
+ }
+ }
+
+ private void remove_rocket(int serial) {
+ RocketOnline rocket = rockets.get(serial);
+ rocket.remove();
+ rockets.remove(serial);
+ }
+
+ public void set_visible(boolean visible) {
+ if (map_view == null)
+ return;
+ if (visible)
+ map_view.setVisibility(View.VISIBLE);
+ else
+ map_view.setVisibility(View.GONE);
+ }
+
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+
+ if (telem_state != null) {
+ for (int serial : rockets.keySet()) {
+ if (!telem_state.states.containsKey(serial))
+ remove_rocket(serial);
+ }
+
+ for (int serial : telem_state.states.keySet()) {
+ set_rocket(serial, telem_state.states.get(serial));
+ }
+ }
+
+ if (state != null) {
+ if (mapLoaded) {
+ if (!pad_set && state.pad_lat != AltosLib.MISSING) {
+ pad_set = true;
+ mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon));
+ mPadMarker.setVisible(true);
+ }
+ }
+ if (state.gps != null) {
+
+ target_position = new AltosLatLon(state.gps.lat, state.gps.lon);
+ if (state.gps.locked && state.gps.nsat >= 4)
+ center (state.gps.lat, state.gps.lon, 10);
+ }
+ }
+
+ if (receiver != null) {
+ double accuracy;
+
+ if (receiver.hasAccuracy())
+ accuracy = receiver.getAccuracy();
+ else
+ accuracy = 1000;
+
+ my_position = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
+ center (my_position.lat, my_position.lon, accuracy);
+ }
+
+ if (my_position != null && target_position != null && mPolyline != null) {
+ mPolyline.setPoints(Arrays.asList(new LatLng(my_position.lat, my_position.lon), new LatLng(target_position.lat, target_position.lon)));
+ mPolyline.setVisible(true);
+ }
+
+ }
+
+ public void set_map_type(int map_type) {
+ if (mMap != null) {
+ if (map_type == AltosMap.maptype_hybrid)
+ mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
+ else if (map_type == AltosMap.maptype_satellite)
+ mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
+ else if (map_type == AltosMap.maptype_terrain)
+ mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
+ else
+ mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
+ }
+ }
+
+ public AltosMapOnline(Context context) {
+ this.context = context;
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java
new file mode 100644
index 00000000..b7eb76a5
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosUsb.java
@@ -0,0 +1,230 @@
+/*
+ * 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 org.altusmetrum.altoslib_8.*;
+
+public class AltosUsb extends AltosDroidLink {
+
+ 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) {
+ AltosDebug.debug("\tin %s out %s\n", in.toString(), out.toString());
+
+ manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+
+ if (manager == null) {
+ AltosDebug.debug("USB_SERVICE failed");
+ return;
+ }
+
+ connection = manager.openDevice(device);
+
+ if (connection == null) {
+ AltosDebug.debug("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;
+
+ AltosDebug.debug("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)) {
+ AltosDebug.debug("found USB device " + device.toString());
+ return device;
+ }
+ }
+
+ return null;
+ }
+
+ private void disconnected() {
+ if (closed()) {
+ AltosDebug.debug("disconnected after closed");
+ return;
+ }
+
+ AltosDebug.debug("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) {
+ AltosDebug.debug("Closing USB device");
+ tmp_connection.close();
+ }
+ }
+
+ int read(byte[] buffer, int len) {
+ int ret = connection.bulkTransfer(in, buffer, len, -1);
+ AltosDebug.debug("read(%d) = %d\n", len, ret);
+ return ret;
+ }
+
+ int write(byte[] buffer, int len) {
+ int ret = connection.bulkTransfer(out, buffer, len, -1);
+ AltosDebug.debug("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/AltosViewPager.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java
index 223ae75a..f22298d7 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosViewPager.java
@@ -34,14 +34,19 @@ public class AltosViewPager extends ViewPager {
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
- if(v.getClass() != null &&
- v.getClass().getPackage() != null &&
- v.getClass().getPackage().getName() != null &&
- v.getClass().getPackage().getName().startsWith("maps."))
- {
- return true;
- }
- return super.canScroll(v, checkV, dx, x, y);
+
+ if (v.getClass() != null &&
+ v.getClass().getName() != null &&
+ v.getClass().getName().endsWith("MapOffline"))
+ return true;
+
+ if(v.getClass() != null &&
+ v.getClass().getPackage() != null &&
+ v.getClass().getPackage().getName() != null &&
+ v.getClass().getPackage().getName().startsWith("maps."))
+ return true;
+
+ return super.canScroll(v, checkV, dx, x, y);
}
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java
index 2d32dc07..325b89d2 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java
@@ -20,201 +20,305 @@ package org.altusmetrum.AltosDroid;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
+import android.location.Location;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
public class AltosVoice {
private TextToSpeech tts = null;
private boolean tts_enabled = false;
- private IdleThread idle_thread = null;
+ static final int TELL_MODE_NONE = 0;
+ static final int TELL_MODE_PAD = 1;
+ static final int TELL_MODE_FLIGHT = 2;
+ static final int TELL_MODE_RECOVER = 3;
- private AltosState old_state = null;
+ static final int TELL_FLIGHT_NONE = 0;
+ static final int TELL_FLIGHT_STATE = 1;
+ static final int TELL_FLIGHT_SPEED = 2;
+ static final int TELL_FLIGHT_HEIGHT = 3;
+ static final int TELL_FLIGHT_TRACK = 4;
- public AltosVoice(AltosDroid a) {
+ private int last_tell_mode;
+ private int last_tell_serial = AltosLib.MISSING;
+ private int last_state;
+ private AltosGPS last_gps;
+ private double last_height = AltosLib.MISSING;
+ private Location last_receiver;
+ private long last_speak_time;
+ private int last_flight_tell = TELL_FLIGHT_NONE;
+
+ private long now() {
+ return System.currentTimeMillis();
+ }
+
+ private void reset_last() {
+ last_tell_mode = TELL_MODE_NONE;
+ last_speak_time = now() - 100 * 1000;
+ last_gps = null;
+ last_height = AltosLib.MISSING;
+ last_receiver = null;
+ last_state = AltosLib.ao_flight_invalid;
+ last_flight_tell = TELL_FLIGHT_NONE;
+ }
+ public AltosVoice(AltosDroid a) {
tts = new TextToSpeech(a, new OnInitListener() {
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) tts_enabled = true;
- if (tts_enabled) {
- idle_thread = new IdleThread();
- }
}
});
+ reset_last();
+ }
+ public synchronized void set_enable(boolean enable) {
+ tts_enabled = enable;
}
public synchronized void speak(String s) {
if (!tts_enabled) return;
+ last_speak_time = now();
tts.speak(s, TextToSpeech.QUEUE_ADD, null);
}
+ public synchronized long time_since_speak() {
+ return now() - last_speak_time;
+ }
+
+ public synchronized void speak(String format, Object ... arguments) {
+ speak(String.format(format, arguments));
+ }
+
+ public synchronized boolean is_speaking() {
+ return tts.isSpeaking();
+ }
+
public void stop() {
- if (tts != null) tts.shutdown();
- if (idle_thread != null) {
- idle_thread.interrupt();
- idle_thread = null;
+ if (tts != null) {
+ tts.stop();
+ tts.shutdown();
}
}
- public void tell(AltosState state, AltosGreatCircle from_receiver) {
- if (!tts_enabled) return;
+ private boolean last_apogee_good;
+ private boolean last_main_good;
+ private boolean last_gps_good;
- boolean spoke = false;
- if (old_state == null || old_state.state != state.state) {
- if (state.state != AltosLib.ao_flight_stateless)
- speak(state.state_name());
- if ((old_state == null || old_state.state <= AltosLib.ao_flight_boost) &&
- state.state > AltosLib.ao_flight_boost) {
- if (state.max_speed() != AltosLib.MISSING)
- speak(String.format("Max speed: %s.",
- AltosConvert.speed.say_units(state.max_speed())));
- spoke = true;
- } else if ((old_state == null || old_state.state < AltosLib.ao_flight_drogue) &&
- state.state >= AltosLib.ao_flight_drogue) {
- if (state.max_height() != AltosLib.MISSING)
- speak(String.format("Max height: %s.",
- AltosConvert.height.say_units(state.max_height())));
- spoke = true;
- }
+ private boolean tell_gonogo(String name,
+ boolean current,
+ boolean previous,
+ boolean new_mode) {
+ if (current != previous || new_mode)
+ speak("%s %s.", name, current ? "ready" : "not ready");
+ return current;
+ }
+
+ private boolean tell_pad(TelemetryState telem_state, AltosState state,
+ AltosGreatCircle from_receiver, Location receiver) {
+
+ if (state == null)
+ return false;
+
+ if (state.apogee_voltage != AltosLib.MISSING)
+ last_apogee_good = tell_gonogo("apogee",
+ state.apogee_voltage >= AltosLib.ao_igniter_good,
+ last_apogee_good,
+ last_tell_mode != TELL_MODE_PAD);
+
+ if (state.main_voltage != AltosLib.MISSING)
+ last_main_good = tell_gonogo("main",
+ state.main_voltage >= AltosLib.ao_igniter_good,
+ last_main_good,
+ last_tell_mode != TELL_MODE_PAD);
+
+ if (state.gps != null)
+ last_gps_good = tell_gonogo("G P S",
+ state.gps_ready,
+ last_gps_good,
+ last_tell_mode != TELL_MODE_PAD);
+ return true;
+ }
+
+
+ private boolean descending(int state) {
+ return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
+ }
+
+ private boolean target_moved(AltosState state) {
+ if (last_gps != null && state != null && state.gps != null) {
+ AltosGreatCircle moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
+ state.gps.lat, state.gps.lon, state.gps.alt);
+ double height_change = 0;
+ double height = state.height();
+
+ if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
+ height_change = Math.abs(last_height - height);
+
+ if (moved.range < 10 && height_change < 10)
+ return false;
}
- if (old_state == null || old_state.gps_ready != state.gps_ready) {
- if (state.gps_ready) {
- speak("GPS ready");
- spoke = true;
- } else if (old_state != null) {
- speak("GPS lost");
- spoke = true;
- }
+ return true;
+ }
+
+ private boolean receiver_moved(Location receiver) {
+ if (last_receiver != null && receiver != null) {
+ AltosGreatCircle moved = new AltosGreatCircle(last_receiver.getLatitude(),
+ last_receiver.getLongitude(),
+ last_receiver.getAltitude(),
+ receiver.getLatitude(),
+ receiver.getLongitude(),
+ receiver.getAltitude());
+ if (moved.range < 10)
+ return false;
}
- old_state = state;
- if (idle_thread != null)
- idle_thread.notice(state, from_receiver, spoke);
+ return true;
}
+ private boolean tell_flight(TelemetryState telem_state, AltosState state,
+ AltosGreatCircle from_receiver, Location receiver) {
- class IdleThread extends Thread {
- boolean started;
- private AltosState state;
- private AltosGreatCircle from_receiver;
- int reported_landing;
- int report_interval;
- long report_time;
+ boolean spoken = false;
- public synchronized void report(boolean last) {
- if (state == null)
- return;
+ if (state == null)
+ return false;
- /* reset the landing count once we hear about a new flight */
- if (state.state < AltosLib.ao_flight_drogue)
- reported_landing = 0;
+ if (last_tell_mode != TELL_MODE_FLIGHT)
+ last_flight_tell = TELL_FLIGHT_NONE;
- /* Shut up once the rocket is on the ground */
- if (reported_landing > 2) {
- return;
+ if (state.state != last_state && AltosLib.ao_flight_boost <= state.state && state.state <= AltosLib.ao_flight_landed) {
+ speak(state.state_name());
+ if (descending(state.state) && !descending(last_state)) {
+ if (state.max_height() != AltosLib.MISSING) {
+ speak("max height: %s.",
+ AltosConvert.height.say_units(state.max_height()));
+ }
}
+ last_flight_tell = TELL_FLIGHT_STATE;
+ return true;
+ }
- /* If the rocket isn't on the pad, then report location */
- if ((AltosLib.ao_flight_drogue <= state.state &&
- state.state < AltosLib.ao_flight_landed) ||
- state.state == AltosLib.ao_flight_stateless)
- {
- AltosGreatCircle position;
-
- if (from_receiver != null)
- position = from_receiver;
- else
- position = state.from_pad;
-
- if (position != null) {
- speak(String.format("Height %s, bearing %s %d, elevation %d, range %s.\n",
- AltosConvert.height.say_units(state.height()),
- position.bearing_words(
- AltosGreatCircle.BEARING_VOICE),
- (int) (position.bearing + 0.5),
- (int) (position.elevation + 0.5),
- AltosConvert.distance.say_units(position.range)));
- }
- } else if (state.state > AltosLib.ao_flight_pad) {
- if (state.height() != AltosLib.MISSING)
- speak(AltosConvert.height.say_units(state.height()));
+ if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
+ if (time_since_speak() < 10 * 1000)
+ return false;
+ if (!target_moved(state) && !receiver_moved(receiver))
+ return false;
+ }
+
+ double speed;
+ double height;
+
+ if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
+ last_flight_tell = TELL_FLIGHT_SPEED;
+
+ if (state.state <= AltosLib.ao_flight_coast) {
+ speed = state.speed();
} else {
- reported_landing = 0;
+ speed = state.gps_speed();
+ if (speed == AltosLib.MISSING)
+ speed = state.speed();
}
- /* If the rocket is coming down, check to see if it has landed;
- * either we've got a landed report or we haven't heard from it in
- * a long time
- */
- if (state.state >= AltosLib.ao_flight_drogue &&
- (last ||
- System.currentTimeMillis() - state.received_time >= 15000 ||
- state.state == AltosLib.ao_flight_landed))
- {
- if (Math.abs(state.speed()) < 20 && state.height() < 100)
- speak("rocket landed safely");
- else
- speak("rocket may have crashed");
- if (state.from_pad != null)
- speak(String.format("Bearing %d degrees, range %s.",
- (int) (state.from_pad.bearing + 0.5),
- AltosConvert.distance.say_units(state.from_pad.distance)));
- ++reported_landing;
+ if (speed != AltosLib.MISSING) {
+ speak("speed: %s.", AltosConvert.speed.say_units(speed));
+ return true;
}
}
- long now () {
- return System.currentTimeMillis();
- }
+ if (last_flight_tell == TELL_FLIGHT_SPEED) {
+ last_flight_tell = TELL_FLIGHT_HEIGHT;
+ height = state.height();
- void set_report_time() {
- report_time = now() + report_interval;
+ if (height != AltosLib.MISSING) {
+ speak("height: %s.", AltosConvert.height.say_units(height));
+ return true;
+ }
}
- public void run () {
- try {
- for (;;) {
- set_report_time();
- for (;;) {
- synchronized (this) {
- long sleep_time = report_time - now();
- if (sleep_time <= 0)
- break;
- wait(sleep_time);
- }
- }
- report(false);
- }
- } catch (InterruptedException ie) {
+ if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
+ last_flight_tell = TELL_FLIGHT_TRACK;
+ if (from_receiver != null) {
+ speak("bearing %s %d, elevation %d, range %s.",
+ from_receiver.bearing_words(
+ AltosGreatCircle.BEARING_VOICE),
+ (int) (from_receiver.bearing + 0.5),
+ (int) (from_receiver.elevation + 0.5),
+ AltosConvert.distance.say(from_receiver.range));
+ return true;
}
}
- public synchronized void notice(AltosState new_state, AltosGreatCircle new_from_receiver, boolean spoken) {
- AltosState old_state = state;
- state = new_state;
- from_receiver = new_from_receiver;
- if (!started && state.state > AltosLib.ao_flight_pad) {
- started = true;
- start();
- }
+ return spoken;
+ }
- if (state.state < AltosLib.ao_flight_drogue)
- report_interval = 10000;
- else
- report_interval = 20000;
- if (old_state != null && old_state.state != state.state) {
- report_time = now();
- this.notify();
- } else if (spoken)
- set_report_time();
- }
+ private boolean tell_recover(TelemetryState telem_state, AltosState state,
+ AltosGreatCircle from_receiver, Location receiver) {
+
+ if (from_receiver == null)
+ return false;
- public IdleThread() {
- state = null;
- reported_landing = 0;
- report_interval = 10000;
+ if (last_tell_mode == TELL_MODE_RECOVER) {
+ if (!target_moved(state) && !receiver_moved(receiver))
+ return false;
+ if (time_since_speak() <= 10 * 1000)
+ return false;
}
+
+ String direction = AltosDroid.direction(from_receiver, receiver);
+ if (direction == null)
+ direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
+
+ speak("%s, range %s.", direction,
+ AltosConvert.distance.say_units(from_receiver.distance));
+
+ return true;
}
+ public void tell(TelemetryState telem_state, AltosState state,
+ AltosGreatCircle from_receiver, Location receiver,
+ AltosDroidTab tab) {
+
+ boolean spoken = false;
+
+ if (!tts_enabled) return;
+
+ if (is_speaking()) return;
+
+ int tell_serial = last_tell_serial;
+
+ if (state != null)
+ tell_serial = state.serial;
+
+ if (tell_serial != last_tell_serial)
+ reset_last();
+
+ int tell_mode = TELL_MODE_NONE;
+
+ if (tab.tab_name().equals(AltosDroid.tab_pad_name))
+ tell_mode = TELL_MODE_PAD;
+ else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
+ tell_mode = TELL_MODE_FLIGHT;
+ else
+ tell_mode = TELL_MODE_RECOVER;
+
+ if (tell_mode == TELL_MODE_PAD)
+ spoken = tell_pad(telem_state, state, from_receiver, receiver);
+ else if (tell_mode == TELL_MODE_FLIGHT)
+ spoken = tell_flight(telem_state, state, from_receiver, receiver);
+ else
+ spoken = tell_recover(telem_state, state, from_receiver, receiver);
+
+ if (spoken) {
+ last_tell_mode = tell_mode;
+ last_tell_serial = tell_serial;
+ if (state != null) {
+ last_state = state.state;
+ last_height = state.height();
+ if (state.gps != null)
+ last_gps = state.gps;
+ }
+ if (receiver != null)
+ last_receiver = receiver;
+ }
+ }
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java
new file mode 100644
index 00000000..673d72dd
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceAddress.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public class DeviceAddress {
+ public String address;
+ public String name;
+
+ public DeviceAddress(String address, String name) {
+ this.address = address;
+ this.name = name;
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java
index 71692122..f36ef267 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java
@@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
@@ -45,12 +44,10 @@ import android.widget.AdapterView.OnItemClickListener;
* Activity in the result Intent.
*/
public class DeviceListActivity extends Activity {
- // Debugging
- private static final String TAG = "DeviceListActivity";
- private static final boolean D = true;
// Return Intent extra
- public static String EXTRA_DEVICE_ADDRESS = "device_address";
+ public static final String EXTRA_DEVICE_ADDRESS = "device_address";
+ public static final String EXTRA_DEVICE_NAME = "device_name";
// Member fields
private BluetoothAdapter mBtAdapter;
@@ -136,7 +133,7 @@ public class DeviceListActivity extends Activity {
* Start device discover with the BluetoothAdapter
*/
private void doDiscovery() {
- if (D) Log.d(TAG, "doDiscovery()");
+ AltosDebug.debug("doDiscovery()");
// Indicate scanning in the title
setProgressBarIndeterminateVisibility(true);
@@ -164,9 +161,20 @@ public class DeviceListActivity extends Activity {
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17);
+ int newline = info.indexOf('\n');
+
+ String name = null;
+ if (newline > 0)
+ name = info.substring(0, newline);
+ else
+ name = info;
+
+ AltosDebug.debug("******* selected item '%s'", info);
+
// Create the result Intent and include the MAC address
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
+ intent.putExtra(EXTRA_DEVICE_NAME, name);
// Set result and finish this Activity
setResult(Activity.RESULT_OK, intent);
@@ -183,14 +191,22 @@ public class DeviceListActivity extends Activity {
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
- // Get the BluetoothDevice object from the Intent
+
+ /* Get the BluetoothDevice object from the Intent
+ */
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- // If it's already paired, skip it, because it's been listed already
- if ( device.getBondState() != BluetoothDevice.BOND_BONDED
- && device.getName().startsWith("TeleBT") ) {
- mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
+
+ /* If it's already paired, skip it, because it's been listed already
+ */
+ if (device != null && device.getBondState() != BluetoothDevice.BOND_BONDED)
+ {
+ String name = device.getName();
+ if (name != null && name.startsWith("TeleBT"))
+ mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
- // When discovery is finished, change the Activity title
+
+ /* When discovery is finished, change the Activity title
+ */
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.select_device);
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java
index 267c90f8..6cecbdf1 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java
@@ -52,27 +52,14 @@ public class GoNoGoLights {
missing = m;
set = true;
if (missing) {
- hide();
red.setImageDrawable(dGray);
green.setImageDrawable(dGray);
} else if (state) {
red.setImageDrawable(dGray);
green.setImageDrawable(dGreen);
- show();
} else {
red.setImageDrawable(dRed);
green.setImageDrawable(dGray);
- show();
}
}
-
- public void show() {
- red.setVisibility(View.VISIBLE);
- green.setVisibility(View.VISIBLE);
- }
-
- public void hide() {
- red.setVisibility(View.GONE);
- green.setVisibility(View.GONE);
- }
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java
new file mode 100644
index 00000000..8846e56c
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/MapTypeActivity.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.*;
+import org.altusmetrum.AltosDroid.R;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.*;
+import android.widget.AdapterView.*;
+
+import org.altusmetrum.altoslib_8.*;
+
+public class MapTypeActivity extends Activity {
+ private Button hybrid;
+ private Button satellite;
+ private Button roadmap;
+ private Button terrain;
+ private int selected_type;
+
+ public static final String EXTRA_MAP_TYPE = "map_type";
+
+ private void done(int type) {
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_MAP_TYPE, type);
+ setResult(Activity.RESULT_OK, intent);
+ finish();
+ }
+
+ public void selectType(View view) {
+ AltosDebug.debug("selectType %s", view.toString());
+ if (view == hybrid)
+ done(AltosMap.maptype_hybrid);
+ if (view == satellite)
+ done(AltosMap.maptype_satellite);
+ if (view == roadmap)
+ done(AltosMap.maptype_roadmap);
+ if (view == terrain)
+ done(AltosMap.maptype_terrain);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Setup the window
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setContentView(R.layout.map_type);
+
+ hybrid = (Button) findViewById(R.id.map_type_hybrid);
+ satellite = (Button) findViewById(R.id.map_type_satellite);
+ roadmap = (Button) findViewById(R.id.map_type_roadmap);
+ terrain = (Button) findViewById(R.id.map_type_terrain);
+
+ // Set result CANCELED incase the user backs out
+ setResult(Activity.RESULT_CANCELED);
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java
new file mode 100644
index 00000000..d7462089
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/PreloadMapActivity.java
@@ -0,0 +1,418 @@
+/*
+ * 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.util.*;
+import java.io.*;
+import java.text.*;
+
+import org.altusmetrum.AltosDroid.R;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.*;
+import android.widget.AdapterView.*;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationListener;
+import android.location.Criteria;
+
+import org.altusmetrum.altoslib_8.*;
+
+/**
+ * This Activity appears as a dialog. It lists any paired devices and
+ * devices detected in the area after discovery. When a device is chosen
+ * by the user, the MAC address of the device is sent back to the parent
+ * Activity in the result Intent.
+ */
+public class PreloadMapActivity extends Activity implements AltosLaunchSiteListener, AltosMapInterface, AltosMapLoaderListener, LocationListener {
+
+ private ArrayAdapter<AltosLaunchSite> known_sites_adapter;
+
+ private CheckBox hybrid;
+ private CheckBox satellite;
+ private CheckBox roadmap;
+ private CheckBox terrain;
+
+ private Spinner known_sites_spinner;
+ private Spinner min_zoom;
+ private Spinner max_zoom;
+ private TextView radius_label;
+ private Spinner radius;
+
+ private EditText latitude;
+ private EditText longitude;
+
+ private ProgressBar progress;
+
+ /* AltosMapLoaderListener interfaces */
+ public void loader_start(final int max) {
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ progress.setMax(max);
+ progress.setProgress(0);
+ }
+ });
+ }
+
+ public void loader_notify(final int cur, final int max, final String name) {
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ progress.setProgress(cur);
+ }
+ });
+ }
+
+ public void loader_done(int max) {
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ progress.setProgress(0);
+ finish();
+ }
+ });
+ }
+
+ /* AltosLaunchSiteListener interface */
+ public void notify_launch_sites(final List<AltosLaunchSite> sites) {
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ for (AltosLaunchSite site : sites)
+ known_sites_adapter.add(site);
+ }
+ });
+ }
+
+ AltosMap map;
+ AltosMapLoader loader;
+
+ class PreloadMapImage implements AltosImage {
+ public void flush() {
+ }
+
+ public PreloadMapImage(File file) {
+ }
+ }
+
+ public AltosMapPath new_path() {
+ return null;
+ }
+
+ public AltosMapLine new_line() {
+ return null;
+ }
+
+ public AltosImage load_image(File file) throws Exception {
+ return new PreloadMapImage(file);
+ }
+
+ public AltosMapMark new_mark(double lat, double lon, int state) {
+ return null;
+ }
+
+ class PreloadMapTile extends AltosMapTile {
+ public void paint(AltosMapTransform t) {
+ }
+
+ public PreloadMapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+ super(listener, upper_left, center, zoom, maptype, px_size, 2);
+ }
+
+ }
+
+ public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
+ return new PreloadMapTile(listener, upper_left, center, zoom, maptype, px_size);
+ }
+
+ public int width() {
+ return AltosMap.px_size;
+ }
+
+ public int height() {
+ return AltosMap.px_size;
+ }
+
+ public void repaint() {
+ }
+
+ public void repaint(AltosRectangle damage) {
+ }
+
+ public void set_zoom_label(String label) {
+ }
+
+ public void select_object(AltosLatLon latlon) {
+ }
+
+ public void debug(String format, Object ... arguments) {
+ AltosDebug.debug(format, arguments);
+ }
+
+ /* LocationProvider interface */
+
+ AltosLaunchSite current_location_site;
+
+ public void onLocationChanged(Location location) {
+ AltosDebug.debug("location changed");
+ if (current_location_site == null) {
+ AltosLaunchSite selected_item = (AltosLaunchSite) known_sites_spinner.getSelectedItem();
+
+ current_location_site = new AltosLaunchSite("Current Location", location.getLatitude(), location.getLongitude());
+ known_sites_adapter.insert(current_location_site, 0);
+
+ if (selected_item != null)
+ known_sites_spinner.setSelection(known_sites_adapter.getPosition(selected_item));
+ else {
+ latitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.latitude)));
+ longitude.setText(new StringBuffer(String.format("%12.6f", current_location_site.longitude)));
+ }
+ } else {
+ current_location_site.latitude = location.getLatitude();
+ current_location_site.longitude = location.getLongitude();
+ }
+ }
+
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+
+ public void onProviderEnabled(String provider) {
+ }
+
+ public void onProviderDisabled(String provider) {
+ }
+
+ private double text(EditText view) throws ParseException {
+ return AltosParse.parse_double_locale(view.getEditableText().toString());
+ }
+
+ private double latitude() throws ParseException {
+ return text(latitude);
+ }
+
+ private double longitude() throws ParseException {
+ return text(longitude);
+ }
+
+ private int value(Spinner spinner) {
+ return (Integer) spinner.getSelectedItem();
+ }
+
+ private int min_z() {
+ return value(min_zoom);
+ }
+
+ private int max_z() {
+ return value(max_zoom);
+ }
+
+ private double value_distance(Spinner spinner) {
+ return (Double) spinner.getSelectedItem();
+ }
+
+ private double radius() {
+ double r = value_distance(radius);
+ if (AltosPreferences.imperial_units())
+ r = AltosConvert.distance.inverse(r);
+ else
+ r = r * 1000;
+ return r;
+ }
+
+ private int bit(CheckBox box, int value) {
+ if (box.isChecked())
+ return 1 << value;
+ return 0;
+ }
+
+ private int types() {
+ return (bit(hybrid, AltosMap.maptype_hybrid) |
+ bit(satellite, AltosMap.maptype_satellite) |
+ bit(roadmap, AltosMap.maptype_roadmap) |
+ bit(terrain, AltosMap.maptype_terrain));
+ }
+
+ private void load() {
+ try {
+ double lat = latitude();
+ double lon = longitude();
+ int min = min_z();
+ int max = max_z();
+ double r = radius();
+ int t = types();
+
+ AltosDebug.debug("PreloadMap load %f %f %d %d %f %d\n",
+ lat, lon, min, max, r, t);
+ loader.load(lat, lon, min, max, r, t);
+ } catch (ParseException e) {
+ AltosDebug.debug("PreloadMap load raised exception %s", e.toString());
+ }
+ }
+
+ private void add_numbers(Spinner spinner, int min, int max, int def) {
+
+ ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this, android.R.layout.simple_spinner_item);
+
+ int spinner_def = 0;
+ int pos = 0;
+
+ for (int i = min; i <= max; i++) {
+ adapter.add(new Integer(i));
+ if (i == def)
+ spinner_def = pos;
+ pos++;
+ }
+
+ spinner.setAdapter(adapter);
+ spinner.setSelection(spinner_def);
+ }
+
+
+ private void add_distance(Spinner spinner, double[] distances_km, double def_km, double[] distances_mi, double def_mi) {
+
+ ArrayAdapter<Double> adapter = new ArrayAdapter<Double>(this, android.R.layout.simple_spinner_item);
+
+ int spinner_def = 0;
+ int pos = 0;
+
+ double[] distances;
+ double def;
+ if (AltosPreferences.imperial_units()) {
+ distances = distances_mi;
+ def = def_mi;
+ } else {
+ distances = distances_km;
+ def = def_km;
+ }
+
+ for (int i = 0; i < distances.length; i++) {
+ adapter.add(distances[i]);
+ if (distances[i] == def)
+ spinner_def = pos;
+ pos++;
+ }
+
+ spinner.setAdapter(adapter);
+ spinner.setSelection(spinner_def);
+ }
+
+
+
+ class SiteListListener implements OnItemSelectedListener {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ AltosLaunchSite site = (AltosLaunchSite) parent.getItemAtPosition(pos);
+ latitude.setText(new StringBuffer(String.format("%12.6f", site.latitude)));
+ longitude.setText(new StringBuffer(String.format("%12.6f", site.longitude)));
+ }
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+ public SiteListListener() {
+ }
+ }
+
+ double[] radius_mi = { 1, 2, 5, 10, 20 };
+ double radius_def_mi = 2;
+ double[] radius_km = { 1, 2, 5, 10, 20, 30 };
+ double radius_def_km = 2;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Setup the window
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setContentView(R.layout.map_preload);
+
+ // Set result CANCELED incase the user backs out
+ setResult(Activity.RESULT_CANCELED);
+
+ // Initialize the button to perform device discovery
+ Button loadButton = (Button) findViewById(R.id.preload_load);
+ loadButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ load();
+ }
+ });
+
+ latitude = (EditText) findViewById(R.id.preload_latitude);
+ longitude = (EditText) findViewById(R.id.preload_longitude);
+
+ hybrid = (CheckBox) findViewById(R.id.preload_hybrid);
+ satellite = (CheckBox) findViewById(R.id.preload_satellite);
+ roadmap = (CheckBox) findViewById(R.id.preload_roadmap);
+ terrain = (CheckBox) findViewById(R.id.preload_terrain);
+
+ hybrid.setChecked(true);
+
+ min_zoom = (Spinner) findViewById(R.id.preload_min_zoom);
+ add_numbers(min_zoom,
+ AltosMap.min_zoom - AltosMap.default_zoom,
+ AltosMap.max_zoom - AltosMap.default_zoom, -2);
+ max_zoom = (Spinner) findViewById(R.id.preload_max_zoom);
+ add_numbers(max_zoom,
+ AltosMap.min_zoom - AltosMap.default_zoom,
+ AltosMap.max_zoom - AltosMap.default_zoom, 2);
+ radius_label = (TextView) findViewById(R.id.preload_radius_label);
+ radius = (Spinner) findViewById(R.id.preload_radius);
+ if (AltosPreferences.imperial_units())
+ radius_label.setText("Radius (miles)");
+ else
+ radius_label.setText("Radius (km)");
+ add_distance(radius, radius_km, radius_def_km, radius_mi, radius_def_mi);
+
+ progress = (ProgressBar) findViewById(R.id.preload_progress);
+
+ // Initialize array adapters. One for already paired devices and
+ // one for newly discovered devices
+ known_sites_spinner = (Spinner) findViewById(R.id.preload_site_list);
+
+ known_sites_adapter = new ArrayAdapter<AltosLaunchSite>(this, android.R.layout.simple_spinner_item);
+
+ known_sites_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ known_sites_spinner.setAdapter(known_sites_adapter);
+ known_sites_spinner.setOnItemSelectedListener(new SiteListListener());
+
+ map = new AltosMap(this);
+
+ loader = new AltosMapLoader(map, this);
+
+ // Listen for GPS and Network position updates
+ LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
+
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
+
+ new AltosLaunchSites(this);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // Stop listening for location updates
+ ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
+ }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java
deleted file mode 100644
index 23de9622..00000000
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright © 2013 Mike Beattie <mike@ethernal.org>
- *
- * 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 org.altusmetrum.altoslib_6.*;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.location.Location;
-
-public class TabAscent extends AltosDroidTab {
- AltosDroid mAltosDroid;
-
- private TextView mHeightView;
- private TextView mMaxHeightView;
- private TextView mSpeedView;
- private TextView mMaxSpeedView;
- private TextView mAccelView;
- private TextView mMaxAccelView;
- private TextView mLatitudeView;
- private TextView mLongitudeView;
- private TextView mApogeeVoltageView;
- private GoNoGoLights mApogeeLights;
- private TextView mMainVoltageView;
- private GoNoGoLights mMainLights;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mAltosDroid = (AltosDroid) activity;
- mAltosDroid.registerTab(this);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.tab_ascent, container, false);
-
- mHeightView = (TextView) v.findViewById(R.id.height_value);
- mMaxHeightView = (TextView) v.findViewById(R.id.max_height_value);
- mSpeedView = (TextView) v.findViewById(R.id.speed_value);
- mMaxSpeedView = (TextView) v.findViewById(R.id.max_speed_value);
- mAccelView = (TextView) v.findViewById(R.id.accel_value);
- mMaxAccelView = (TextView) v.findViewById(R.id.max_accel_value);
- mLatitudeView = (TextView) v.findViewById(R.id.lat_value);
- mLongitudeView = (TextView) v.findViewById(R.id.lon_value);
-
- mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value);
- mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
- (ImageView) v.findViewById(R.id.apogee_greenled),
- getResources());
-
- mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value);
- mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
- (ImageView) v.findViewById(R.id.main_greenled),
- getResources());
-
- return v;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mAltosDroid.unregisterTab(this);
- mAltosDroid = null;
- }
-
- public String tab_name() {
- return "ascent";
- }
-
- public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
- if (state != null) {
- set_value(mHeightView, AltosConvert.height, 6, state.height());
- set_value(mHeightView, AltosConvert.height, 6, state.height());
- set_value(mMaxHeightView, AltosConvert.height, 6, state.max_height());
- set_value(mSpeedView, AltosConvert.speed, 6, state.speed());
- set_value(mMaxSpeedView, AltosConvert.speed, 6, state.max_speed());
- set_value(mAccelView, AltosConvert.accel, 6, state.acceleration());
- set_value(mMaxAccelView, AltosConvert.accel, 6, state.max_acceleration());
-
- if (state.gps != null) {
- mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
- mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
- } else {
- mLatitudeView.setText("");
- mLongitudeView.setText("");
- }
-
- mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage));
- mApogeeLights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING);
-
- mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage));
- mMainLights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING);
- }
- }
-}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java
deleted file mode 100644
index 4ec6f409..00000000
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright © 2013 Mike Beattie <mike@ethernal.org>
- *
- * 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 org.altusmetrum.altoslib_6.*;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.location.Location;
-
-public class TabDescent extends AltosDroidTab {
- AltosDroid mAltosDroid;
-
- private TextView mSpeedView;
- private TextView mHeightView;
- private TextView mElevationView;
- private TextView mRangeView;
- private TextView mBearingView;
- private TextView mCompassView;
- private TextView mDistanceView;
- private TextView mLatitudeView;
- private TextView mLongitudeView;
- private TextView mApogeeVoltageView;
- private GoNoGoLights mApogeeLights;
- private TextView mMainVoltageView;
- private GoNoGoLights mMainLights;
-
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mAltosDroid = (AltosDroid) activity;
- mAltosDroid.registerTab(this);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.tab_descent, container, false);
-
- mSpeedView = (TextView) v.findViewById(R.id.speed_value);
- mHeightView = (TextView) v.findViewById(R.id.height_value);
- mElevationView = (TextView) v.findViewById(R.id.elevation_value);
- mRangeView = (TextView) v.findViewById(R.id.range_value);
- mBearingView = (TextView) v.findViewById(R.id.bearing_value);
- mCompassView = (TextView) v.findViewById(R.id.compass_value);
- mDistanceView = (TextView) v.findViewById(R.id.distance_value);
- mLatitudeView = (TextView) v.findViewById(R.id.lat_value);
- mLongitudeView = (TextView) v.findViewById(R.id.lon_value);
-
- mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value);
- mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
- (ImageView) v.findViewById(R.id.apogee_greenled),
- getResources());
-
- mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value);
- mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
- (ImageView) v.findViewById(R.id.main_greenled),
- getResources());
-
- return v;
- }
-
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mAltosDroid.unregisterTab(this);
- mAltosDroid = null;
- }
-
- public String tab_name() { return "descent"; }
-
- public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
- if (state != null) {
- set_value(mSpeedView, AltosConvert.speed, 6, state.speed());
- set_value(mHeightView, AltosConvert.height, 6, state.height());
- if (from_receiver != null) {
- mElevationView.setText(AltosDroid.number("%3.0f°", from_receiver.elevation));
- set_value(mRangeView, AltosConvert.distance, 6, from_receiver.range);
- mBearingView.setText(AltosDroid.number("%3.0f°", from_receiver.bearing));
- mCompassView.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG));
- set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
- } else {
- mElevationView.setText("<unknown>");
- mRangeView.setText("<unknown>");
- mBearingView.setText("<unknown>");
- mCompassView.setText("<unknown>");
- mDistanceView.setText("<unknown>");
- }
- if (state.gps != null) {
- mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
- mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
- }
-
- mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage));
- mApogeeLights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING);
-
- mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage));
- mMainLights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING);
- }
- }
-
-}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java
new file mode 100644
index 00000000..a503f1bc
--- /dev/null
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabFlight.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright © 2013 Mike Beattie <mike@ethernal.org>
+ *
+ * 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 org.altusmetrum.altoslib_8.*;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.*;
+import android.widget.*;
+import android.location.Location;
+
+public class TabFlight extends AltosDroidTab {
+ private TextView speed_view;
+ private TextView height_view;
+ private TextView max_speed_view;
+ private TextView max_height_view;
+ private TextView elevation_view;
+ private TextView range_view;
+ private TextView bearing_view;
+ private TextView compass_view;
+ private TextView distance_view;
+ private TextView latitude_view;
+ private TextView longitude_view;
+ private View apogee_view;
+ private TextView apogee_voltage_view;
+ private TextView apogee_voltage_label;
+ private GoNoGoLights apogee_lights;
+ private View main_view;
+ private TextView main_voltage_view;
+ private TextView main_voltage_label;
+ private GoNoGoLights main_lights;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.tab_flight, container, false);
+
+ speed_view = (TextView) v.findViewById(R.id.speed_value);
+ height_view = (TextView) v.findViewById(R.id.height_value);
+ max_speed_view = (TextView) v.findViewById(R.id.max_speed_value);
+ max_height_view= (TextView) v.findViewById(R.id.max_height_value);
+ elevation_view = (TextView) v.findViewById(R.id.elevation_value);
+ range_view = (TextView) v.findViewById(R.id.range_value);
+ bearing_view = (TextView) v.findViewById(R.id.bearing_value);
+ compass_view = (TextView) v.findViewById(R.id.compass_value);
+ distance_view = (TextView) v.findViewById(R.id.distance_value);
+ latitude_view = (TextView) v.findViewById(R.id.lat_value);
+ longitude_view = (TextView) v.findViewById(R.id.lon_value);
+
+ apogee_view = v.findViewById(R.id.apogee_view);
+ apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value);
+ apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
+ (ImageView) v.findViewById(R.id.apogee_greenled),
+ getResources());
+ apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label);
+
+ main_view = v.findViewById(R.id.main_view);
+ main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value);
+ main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
+ (ImageView) v.findViewById(R.id.main_greenled),
+ getResources());
+ main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label);
+
+ return v;
+ }
+
+ public String tab_name() { return AltosDroid.tab_flight_name; }
+
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+ if (state != null) {
+ set_value(speed_view, AltosConvert.speed, 6, state.speed());
+ set_value(height_view, AltosConvert.height, 6, state.height());
+ set_value(max_speed_view, AltosConvert.speed, 6, state.max_speed());
+ set_value(max_height_view, AltosConvert.speed, 6, state.max_height());
+ if (from_receiver != null) {
+ elevation_view.setText(AltosDroid.number("%3.0f°", from_receiver.elevation));
+ set_value(range_view, AltosConvert.distance, 6, from_receiver.range);
+ bearing_view.setText(AltosDroid.number("%3.0f°", from_receiver.bearing));
+ compass_view.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG));
+ set_value(distance_view, AltosConvert.distance, 6, from_receiver.distance);
+ } else {
+ elevation_view.setText("<unknown>");
+ range_view.setText("<unknown>");
+ bearing_view.setText("<unknown>");
+ compass_view.setText("<unknown>");
+ distance_view.setText("<unknown>");
+ }
+ if (state.gps != null) {
+ latitude_view.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
+ longitude_view.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
+ }
+
+ if (state.apogee_voltage == AltosLib.MISSING) {
+ apogee_view.setVisibility(View.GONE);
+ } else {
+ apogee_voltage_view.setText(AltosDroid.number("%4.2f V", state.apogee_voltage));
+ apogee_lights.set(state.apogee_voltage > 3.2, state.apogee_voltage == AltosLib.MISSING);
+ apogee_view.setVisibility(View.VISIBLE);
+ }
+
+ if (state.main_voltage == AltosLib.MISSING) {
+ main_view.setVisibility(View.GONE);
+ } else {
+ main_voltage_view.setText(AltosDroid.number("%4.2f V", state.main_voltage));
+ main_lights.set(state.main_voltage > 3.2, state.main_voltage == AltosLib.MISSING);
+ main_view.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java
index 871b94a1..54ccd18f 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java
@@ -17,186 +17,159 @@
package org.altusmetrum.AltosDroid;
-import java.util.Arrays;
+import java.util.*;
+import java.io.*;
-import org.altusmetrum.altoslib_6.*;
-
-import com.google.android.gms.maps.CameraUpdateFactory;
-import com.google.android.gms.maps.GoogleMap;
-import com.google.android.gms.maps.SupportMapFragment;
-import com.google.android.gms.maps.model.BitmapDescriptorFactory;
-import com.google.android.gms.maps.model.LatLng;
-import com.google.android.gms.maps.model.Marker;
-import com.google.android.gms.maps.model.MarkerOptions;
-import com.google.android.gms.maps.model.Polyline;
-import com.google.android.gms.maps.model.PolylineOptions;
+import org.altusmetrum.altoslib_8.*;
import android.app.Activity;
-import android.graphics.Color;
+import android.graphics.*;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-//import android.support.v4.app.FragmentTransaction;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
+import android.support.v4.app.FragmentTransaction;
+import android.view.*;
+import android.widget.*;
import android.location.Location;
+import android.content.*;
public class TabMap extends AltosDroidTab {
- AltosDroid mAltosDroid;
-
- private SupportMapFragment mMapFragment;
- private GoogleMap mMap;
- private boolean mapLoaded = false;
- private Marker mRocketMarker;
- private Marker mPadMarker;
- private boolean pad_set;
- private Polyline mPolyline;
+ AltosLatLon here;
private TextView mDistanceView;
+ private TextView mBearingLabel;
private TextView mBearingView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
private TextView mReceiverLatitudeView;
private TextView mReceiverLongitudeView;
-
- private double mapAccuracy = -1;
+ private AltosMapOffline map_offline;
+ private AltosMapOnline map_online;
+ private View view;
+ private int map_source;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
- mAltosDroid = (AltosDroid) activity;
- mAltosDroid.registerTab(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- mMapFragment = new SupportMapFragment() {
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- setupMap();
- }
- };
-
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.tab_map, container, false);
- mDistanceView = (TextView)v.findViewById(R.id.distance_value);
- mBearingView = (TextView)v.findViewById(R.id.bearing_value);
- mTargetLatitudeView = (TextView)v.findViewById(R.id.target_lat_value);
- mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value);
- mReceiverLatitudeView = (TextView)v.findViewById(R.id.receiver_lat_value);
- mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value);
- return v;
+ view = inflater.inflate(R.layout.tab_map, container, false);
+ int map_source = AltosDroidPreferences.map_source();
+
+ mDistanceView = (TextView)view.findViewById(R.id.distance_value);
+ mBearingLabel = (TextView)view.findViewById(R.id.bearing_label);
+ mBearingView = (TextView)view.findViewById(R.id.bearing_value);
+ mTargetLatitudeView = (TextView)view.findViewById(R.id.target_lat_value);
+ mTargetLongitudeView = (TextView)view.findViewById(R.id.target_lon_value);
+ mReceiverLatitudeView = (TextView)view.findViewById(R.id.receiver_lat_value);
+ mReceiverLongitudeView = (TextView)view.findViewById(R.id.receiver_lon_value);
+ map_offline = (AltosMapOffline)view.findViewById(R.id.map_offline);
+ map_offline.onCreateView(altos_droid);
+ map_online = new AltosMapOnline(view.getContext());
+ map_online.onCreateView(altos_droid);
+ set_map_source(AltosDroidPreferences.map_source());
+ return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- getChildFragmentManager().beginTransaction().add(R.id.map, mMapFragment).commit();
+ if (map_online != null)
+ getChildFragmentManager().beginTransaction().add(R.id.map_online, map_online.mMapFragment).commit();
}
@Override
public void onDestroyView() {
super.onDestroyView();
-
- mAltosDroid.unregisterTab(this);
- mAltosDroid = null;
-
- //Fragment fragment = (getFragmentManager().findFragmentById(R.id.map));
- //FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
- //ft.remove(fragment);
- //ft.commit();
}
- private void setupMap() {
- mMap = mMapFragment.getMap();
- if (mMap != null) {
- mMap.setMyLocationEnabled(true);
- mMap.getUiSettings().setTiltGesturesEnabled(false);
- mMap.getUiSettings().setZoomControlsEnabled(false);
-
- mRocketMarker = mMap.addMarker(
- // From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/
- new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.rocket))
- .position(new LatLng(0,0))
- .visible(false)
- );
-
- mPadMarker = mMap.addMarker(
- new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.pad))
- .position(new LatLng(0,0))
- .visible(false)
- );
-
- mPolyline = mMap.addPolyline(
- new PolylineOptions().add(new LatLng(0,0), new LatLng(0,0))
- .width(3)
- .color(Color.BLUE)
- .visible(false)
- );
-
- mapLoaded = true;
- }
- }
+ public String tab_name() { return AltosDroid.tab_map_name; }
private void center(double lat, double lon, double accuracy) {
- if (mapAccuracy < 0 || accuracy < mapAccuracy/10) {
- mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14));
- mapAccuracy = accuracy;
- }
+ if (map_offline != null)
+ map_offline.center(lat, lon, accuracy);
+ if (map_online != null)
+ map_online.center(lat, lon, accuracy);
}
- public String tab_name() { return "map"; }
-
- public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
- mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
+ String direction = AltosDroid.direction(from_receiver, receiver);
+ if (direction != null) {
+ mBearingLabel.setText("Direction");
+ mBearingView.setText(direction);
+ } else {
+ mBearingLabel.setText("Bearing");
+ mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
+ }
set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
+ } else {
+ mBearingLabel.setText("Bearing");
+ mBearingView.setText("");
+ set_value(mDistanceView, AltosConvert.distance, 6, AltosLib.MISSING);
}
if (state != null) {
- if (mapLoaded) {
- if (state.gps != null) {
- mRocketMarker.setPosition(new LatLng(state.gps.lat, state.gps.lon));
- mRocketMarker.setVisible(true);
-
- mPolyline.setPoints(Arrays.asList(new LatLng(state.pad_lat, state.pad_lon), new LatLng(state.gps.lat, state.gps.lon)));
- mPolyline.setVisible(true);
- }
-
- if (!pad_set && state.pad_lat != AltosLib.MISSING) {
- pad_set = true;
- mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon));
- mPadMarker.setVisible(true);
- }
- }
if (state.gps != null) {
mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "E", "W"));
- if (state.gps.locked && state.gps.nsat >= 4)
- center (state.gps.lat, state.gps.lon, 10);
}
}
if (receiver != null) {
double accuracy;
+ here = new AltosLatLon(receiver.getLatitude(), receiver.getLongitude());
if (receiver.hasAccuracy())
accuracy = receiver.getAccuracy();
else
accuracy = 1000;
- mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
- mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
- center (receiver.getLatitude(), receiver.getLongitude(), accuracy);
+ mReceiverLatitudeView.setText(AltosDroid.pos(here.lat, "N", "S"));
+ mReceiverLongitudeView.setText(AltosDroid.pos(here.lon, "E", "W"));
+ center (here.lat, here.lon, accuracy);
+ }
+ if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
+ if (map_offline != null)
+ map_offline.show(telem_state, state, from_receiver, receiver);
+ } else {
+ if (map_online != null)
+ map_online.show(telem_state, state, from_receiver, receiver);
}
+ }
+ @Override
+ public void set_map_type(int map_type) {
+ if (map_offline != null)
+ map_offline.set_map_type(map_type);
+ if (map_online != null)
+ map_online.set_map_type(map_type);
+ }
+
+ @Override
+ public void set_map_source(int map_source) {
+ this.map_source = map_source;
+ if (map_source == AltosDroidPreferences.MAP_SOURCE_OFFLINE) {
+ if (map_online != null)
+ map_online.set_visible(false);
+ if (map_offline != null) {
+ map_offline.set_visible(true);
+ map_offline.show(last_telem_state, last_state, last_from_receiver, last_receiver);
+ }
+ } else {
+ if (map_offline != null)
+ map_offline.set_visible(false);
+ if (map_online != null) {
+ map_online.set_visible(true);
+ map_online.show(last_telem_state, last_state, last_from_receiver, last_receiver);
+ }
+ }
}
public TabMap() {
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java
index 0ac78219..4d04316f 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java
@@ -17,155 +17,222 @@
package org.altusmetrum.AltosDroid;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.view.*;
+import android.widget.*;
import android.location.Location;
public class TabPad extends AltosDroidTab {
- AltosDroid mAltosDroid;
-
- private TextView mBatteryVoltageView;
- private TextView mBatteryVoltageLabel;
- private GoNoGoLights mBatteryLights;
- private TextView mApogeeVoltageView;
- private TextView mApogeeVoltageLabel;
- private GoNoGoLights mApogeeLights;
- private TextView mMainVoltageView;
- private TextView mMainVoltageLabel;
- private GoNoGoLights mMainLights;
- private TextView mDataLoggingView;
- private GoNoGoLights mDataLoggingLights;
- private TextView mGPSLockedView;
- private GoNoGoLights mGPSLockedLights;
- private TextView mGPSReadyView;
- private GoNoGoLights mGPSReadyLights;
- private TextView mPadLatitudeView;
- private TextView mPadLongitudeView;
- private TextView mPadAltitudeView;
+ private TextView battery_voltage_view;
+ private GoNoGoLights battery_lights;
+
+ private TableRow receiver_row;
+ private TextView receiver_voltage_view;
+ private TextView receiver_voltage_label;
+ private GoNoGoLights receiver_voltage_lights;
+
+ private TableRow apogee_row;
+ private TextView apogee_voltage_view;
+ private TextView apogee_voltage_label;
+ private GoNoGoLights apogee_lights;
+
+ private TableRow main_row;
+ private TextView main_voltage_view;
+ private TextView main_voltage_label;
+ private GoNoGoLights main_lights;
+
+ private TextView data_logging_view;
+ private GoNoGoLights data_logging_lights;
+
+ private TextView gps_locked_view;
+ private GoNoGoLights gps_locked_lights;
+
+ private TextView gps_ready_view;
+ private GoNoGoLights gps_ready_lights;
+
+ private TextView receiver_latitude_view;
+ private TextView receiver_longitude_view;
+ private TextView receiver_altitude_view;
+
+ private TableRow[] ignite_row = new TableRow[4];
+ private TextView[] ignite_voltage_view = new TextView[4];
+ private TextView[] ignite_voltage_label = new TextView[4];
+ private GoNoGoLights[] ignite_lights = new GoNoGoLights[4];
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mAltosDroid = (AltosDroid) activity;
- mAltosDroid.registerTab(this);
- }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.tab_pad, container, false);
- mBatteryVoltageView = (TextView) v.findViewById(R.id.battery_voltage_value);
- mBatteryVoltageLabel = (TextView) v.findViewById(R.id.battery_voltage_label);
- mBatteryLights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),
+ battery_voltage_view = (TextView) v.findViewById(R.id.battery_voltage_value);
+ battery_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled),
(ImageView) v.findViewById(R.id.battery_greenled),
getResources());
- mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value);
- mApogeeVoltageLabel = (TextView) v.findViewById(R.id.apogee_voltage_label);
- mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
+ receiver_row = (TableRow) v.findViewById(R.id.receiver_row);
+ receiver_voltage_view = (TextView) v.findViewById(R.id.receiver_voltage_value);
+ receiver_voltage_label = (TextView) v.findViewById(R.id.receiver_voltage_label);
+ receiver_voltage_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.receiver_redled),
+ (ImageView) v.findViewById(R.id.receiver_greenled),
+ getResources());
+
+ apogee_row = (TableRow) v.findViewById(R.id.apogee_row);
+ apogee_voltage_view = (TextView) v.findViewById(R.id.apogee_voltage_value);
+ apogee_voltage_label = (TextView) v.findViewById(R.id.apogee_voltage_label);
+ apogee_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled),
(ImageView) v.findViewById(R.id.apogee_greenled),
getResources());
- mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value);
- mMainVoltageLabel = (TextView) v.findViewById(R.id.main_voltage_label);
- mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
+ main_row = (TableRow) v.findViewById(R.id.main_row);
+ main_voltage_view = (TextView) v.findViewById(R.id.main_voltage_value);
+ main_voltage_label = (TextView) v.findViewById(R.id.main_voltage_label);
+ main_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled),
(ImageView) v.findViewById(R.id.main_greenled),
getResources());
- mDataLoggingView = (TextView) v.findViewById(R.id.logging_value);
- mDataLoggingLights = new GoNoGoLights((ImageView) v.findViewById(R.id.logging_redled),
+ data_logging_view = (TextView) v.findViewById(R.id.logging_value);
+ data_logging_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.logging_redled),
(ImageView) v.findViewById(R.id.logging_greenled),
getResources());
- mGPSLockedView = (TextView) v.findViewById(R.id.gps_locked_value);
- mGPSLockedLights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_locked_redled),
+ gps_locked_view = (TextView) v.findViewById(R.id.gps_locked_value);
+ gps_locked_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_locked_redled),
(ImageView) v.findViewById(R.id.gps_locked_greenled),
getResources());
- mGPSReadyView = (TextView) v.findViewById(R.id.gps_ready_value);
- mGPSReadyLights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_ready_redled),
+ gps_ready_view = (TextView) v.findViewById(R.id.gps_ready_value);
+ gps_ready_lights = new GoNoGoLights((ImageView) v.findViewById(R.id.gps_ready_redled),
(ImageView) v.findViewById(R.id.gps_ready_greenled),
getResources());
- mPadLatitudeView = (TextView) v.findViewById(R.id.pad_lat_value);
- mPadLongitudeView = (TextView) v.findViewById(R.id.pad_lon_value);
- mPadAltitudeView = (TextView) v.findViewById(R.id.pad_alt_value);
- return v;
- }
+ for (int i = 0; i < 4; i++) {
+ int row_id, view_id, label_id, lights_id;
+ int red_id, green_id;
+ switch (i) {
+ case 0:
+ default:
+ row_id = R.id.ignite_a_row;
+ view_id = R.id.ignite_a_voltage_value;
+ label_id = R.id.ignite_a_voltage_label;
+ red_id = R.id.ignite_a_redled;
+ green_id = R.id.ignite_a_greenled;
+ break;
+ case 1:
+ row_id = R.id.ignite_b_row;
+ view_id = R.id.ignite_b_voltage_value;
+ label_id = R.id.ignite_b_voltage_label;
+ red_id = R.id.ignite_b_redled;
+ green_id = R.id.ignite_b_greenled;
+ break;
+ case 2:
+ row_id = R.id.ignite_c_row;
+ view_id = R.id.ignite_c_voltage_value;
+ label_id = R.id.ignite_c_voltage_label;
+ red_id = R.id.ignite_c_redled;
+ green_id = R.id.ignite_c_greenled;
+ break;
+ case 3:
+ row_id = R.id.ignite_d_row;
+ view_id = R.id.ignite_d_voltage_value;
+ label_id = R.id.ignite_d_voltage_label;
+ red_id = R.id.ignite_d_redled;
+ green_id = R.id.ignite_d_greenled;
+ break;
+ }
+ ignite_row[i] = (TableRow) v.findViewById(row_id);
+ ignite_voltage_view[i] = (TextView) v.findViewById(view_id);
+ ignite_voltage_label[i] = (TextView) v.findViewById(label_id);
+ ignite_lights[i] = new GoNoGoLights((ImageView) v.findViewById(red_id),
+ (ImageView) v.findViewById(green_id),
+ getResources());
+ }
- @Override
- public void onDestroy() {
- super.onDestroy();
- mAltosDroid.unregisterTab(this);
- mAltosDroid = null;
+ receiver_latitude_view = (TextView) v.findViewById(R.id.receiver_lat_value);
+ receiver_longitude_view = (TextView) v.findViewById(R.id.receiver_lon_value);
+ receiver_altitude_view = (TextView) v.findViewById(R.id.receiver_alt_value);
+ return v;
}
- public String tab_name() { return "pad"; }
+ public String tab_name() { return AltosDroid.tab_pad_name; }
- public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (state != null) {
- mBatteryVoltageView.setText(AltosDroid.number("%4.2f V", state.battery_voltage));
- mBatteryLights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);
+ battery_voltage_view.setText(AltosDroid.number(" %4.2f V", state.battery_voltage));
+ battery_lights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING);
if (state.apogee_voltage == AltosLib.MISSING) {
- mApogeeVoltageView.setVisibility(View.GONE);
- mApogeeVoltageLabel.setVisibility(View.GONE);
+ apogee_row.setVisibility(View.GONE);
} else {
- mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage));
- mApogeeVoltageView.setVisibility(View.VISIBLE);
- mApogeeVoltageLabel.setVisibility(View.VISIBLE);
+ apogee_voltage_view.setText(AltosDroid.number(" %4.2f V", state.apogee_voltage));
+ apogee_row.setVisibility(View.VISIBLE);
}
- mApogeeLights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING);
+ apogee_lights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING);
if (state.main_voltage == AltosLib.MISSING) {
- mMainVoltageView.setVisibility(View.GONE);
- mMainVoltageLabel.setVisibility(View.GONE);
+ main_row.setVisibility(View.GONE);
} else {
- mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage));
- mMainVoltageView.setVisibility(View.VISIBLE);
- mMainVoltageLabel.setVisibility(View.VISIBLE);
+ main_voltage_view.setText(AltosDroid.number(" %4.2f V", state.main_voltage));
+ main_row.setVisibility(View.VISIBLE);
+ }
+ main_lights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING);
+
+ int num_igniter = state.ignitor_voltage == null ? 0 : state.ignitor_voltage.length;
+
+ for (int i = 0; i < 4; i++) {
+ double voltage = i >= num_igniter ? AltosLib.MISSING : state.ignitor_voltage[i];
+ if (voltage == AltosLib.MISSING) {
+ ignite_row[i].setVisibility(View.GONE);
+ } else {
+ ignite_voltage_view[i].setText(AltosDroid.number(" %4.2f V", voltage));
+ ignite_row[i].setVisibility(View.VISIBLE);
+ }
+ ignite_lights[i].set(voltage >= AltosLib.ao_igniter_good, voltage == AltosLib.MISSING);
}
- mMainLights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING);
if (state.flight != 0) {
if (state.state <= AltosLib.ao_flight_pad)
- mDataLoggingView.setText("Ready to record");
+ data_logging_view.setText("Ready to record");
else if (state.state < AltosLib.ao_flight_landed)
- mDataLoggingView.setText("Recording data");
+ data_logging_view.setText("Recording data");
else
- mDataLoggingView.setText("Recorded data");
+ data_logging_view.setText("Recorded data");
} else {
- mDataLoggingView.setText("Storage full");
+ data_logging_view.setText("Storage full");
}
- mDataLoggingLights.set(state.flight != 0, state.flight == AltosLib.MISSING);
+ data_logging_lights.set(state.flight != 0, state.flight == AltosLib.MISSING);
if (state.gps != null) {
int soln = state.gps.nsat;
int nsat = state.gps.cc_gps_sat != null ? state.gps.cc_gps_sat.length : 0;
- mGPSLockedView.setText(String.format("%4d in soln, %4d in view", soln, nsat));
- mGPSLockedLights.set(state.gps.locked && state.gps.nsat >= 4, false);
+ gps_locked_view.setText(String.format("%d in soln, %d in view", soln, nsat));
+ gps_locked_lights.set(state.gps.locked && state.gps.nsat >= 4, false);
if (state.gps_ready)
- mGPSReadyView.setText("Ready");
+ gps_ready_view.setText("Ready");
else
- mGPSReadyView.setText(AltosDroid.integer("Waiting %d", state.gps_waiting));
+ gps_ready_view.setText(AltosDroid.integer("Waiting %d", state.gps_waiting));
} else
- mGPSLockedLights.set(false, true);
- mGPSReadyLights.set(state.gps_ready, state.gps == null);
+ gps_locked_lights.set(false, true);
+ gps_ready_lights.set(state.gps_ready, state.gps == null);
+ }
+
+ if (telem_state != null) {
+ if (telem_state.receiver_battery == AltosLib.MISSING) {
+ receiver_row.setVisibility(View.GONE);
+ } else {
+ receiver_voltage_view.setText(AltosDroid.number(" %4.2f V", telem_state.receiver_battery));
+ receiver_row.setVisibility(View.VISIBLE);
+ }
+ receiver_voltage_lights.set(telem_state.receiver_battery >= AltosLib.ao_battery_good, telem_state.receiver_battery == AltosLib.MISSING);
}
if (receiver != null) {
double altitude = AltosLib.MISSING;
if (receiver.hasAltitude())
altitude = receiver.getAltitude();
- mPadLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
- mPadLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
- set_value(mPadAltitudeView, AltosConvert.height, 6, altitude);
+ receiver_latitude_view.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S"));
+ receiver_longitude_view.setText(AltosDroid.pos(receiver.getLongitude(), "E", "W"));
+ set_value(receiver_altitude_view, AltosConvert.height, 1, altitude);
}
}
-
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabRecover.java
index 4c69d869..19bb79d3 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabRecover.java
@@ -17,7 +17,7 @@
package org.altusmetrum.AltosDroid;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
import android.app.Activity;
import android.os.Bundle;
@@ -28,10 +28,9 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.location.Location;
-public class TabLanded extends AltosDroidTab {
- AltosDroid mAltosDroid;
-
+public class TabRecover extends AltosDroidTab {
private TextView mBearingView;
+ private TextView mDirectionView;
private TextView mDistanceView;
private TextView mTargetLatitudeView;
private TextView mTargetLongitudeView;
@@ -41,19 +40,12 @@ public class TabLanded extends AltosDroidTab {
private TextView mMaxSpeedView;
private TextView mMaxAccelView;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mAltosDroid = (AltosDroid) activity;
- mAltosDroid.registerTab(this);
- }
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.tab_landed, container, false);
+ View v = inflater.inflate(R.layout.tab_recover, container, false);
mBearingView = (TextView) v.findViewById(R.id.bearing_value);
+ mDirectionView = (TextView) v.findViewById(R.id.direction_value);
mDistanceView = (TextView) v.findViewById(R.id.distance_value);
mTargetLatitudeView = (TextView) v.findViewById(R.id.target_lat_value);
mTargetLongitudeView = (TextView) v.findViewById(R.id.target_lon_value);
@@ -66,19 +58,17 @@ public class TabLanded extends AltosDroidTab {
return v;
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- mAltosDroid.unregisterTab(this);
- mAltosDroid = null;
- }
-
- public String tab_name() { return "landed"; }
+ public String tab_name() { return AltosDroid.tab_recover_name; }
- public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) {
+ public void show(TelemetryState telem_state, AltosState state, AltosGreatCircle from_receiver, Location receiver) {
if (from_receiver != null) {
mBearingView.setText(String.format("%3.0f°", from_receiver.bearing));
set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance);
+ String direction = AltosDroid.direction(from_receiver, receiver);
+ if (direction == null)
+ mDirectionView.setText("");
+ else
+ mDirectionView.setText(direction);
}
if (state != null && state.gps != null) {
mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S"));
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java
index 1ac34f9d..b34a25b6 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabsAdapter.java
@@ -29,7 +29,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.TabWidget;
-import android.util.Log;
/**
* This is a helper class that implements the management of tabs and all
@@ -106,7 +105,7 @@ public class TabsAdapter extends FragmentPagerAdapter
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
- Log.d(AltosDroid.TAG, String.format("TabsAdapter.getItem(%d)", position));
+ AltosDebug.debug("TabsAdapter.getItem(%d)", position);
info.fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args);
return info.fragment;
}
@@ -131,7 +130,7 @@ public class TabsAdapter extends FragmentPagerAdapter
if (cur_frag != null) {
cur_frag.set_visible(true);
}
- Log.d(AltosDroid.TAG, String.format("TabsAdapter.onTabChanged(%s) = %d", tabId, position));
+ AltosDebug.debug("TabsAdapter.onTabChanged(%s) = %d", tabId, position);
mViewPager.setCurrentItem(position);
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java
index 9764ab72..79020c16 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryLogger.java
@@ -1,18 +1,14 @@
package org.altusmetrum.AltosDroid;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
-import android.util.Log;
public class TelemetryLogger {
- private static final String TAG = "TelemetryLogger";
- private static final boolean D = true;
-
private Context context = null;
private AltosLink link = null;
private AltosLog logger = null;
@@ -33,21 +29,21 @@ public class TelemetryLogger {
private void close() {
if (logger != null) {
- if (D) Log.d(TAG, "Shutting down Telemetry Logging");
+ AltosDebug.debug("Shutting down Telemetry Logging");
logger.close();
logger = null;
}
}
-
+
void handleExternalStorageState() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (logger == null) {
- if (D) Log.d(TAG, "Starting up Telemetry Logging");
+ AltosDebug.debug("Starting up Telemetry Logging");
logger = new AltosLog(link);
}
} else {
- if (D) Log.d(TAG, "External Storage not present - stopping");
+ AltosDebug.debug("External Storage not present - stopping");
close();
}
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java
index 4e4408d5..3199f252 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java
@@ -21,42 +21,32 @@ package org.altusmetrum.AltosDroid;
import java.text.*;
import java.io.*;
+import java.util.*;
import java.util.concurrent.*;
-import android.util.Log;
import android.os.Handler;
-import org.altusmetrum.altoslib_6.*;
+import org.altusmetrum.altoslib_8.*;
public class TelemetryReader extends Thread {
- private static final String TAG = "TelemetryReader";
- private static final boolean D = true;
-
int crc_errors;
Handler handler;
AltosLink link;
- AltosState state = null;
LinkedBlockingQueue<AltosLine> telemQueue;
- public AltosState read() throws ParseException, AltosCRCException, InterruptedException, IOException {
+ public AltosTelemetry read() throws ParseException, AltosCRCException, InterruptedException, IOException {
AltosLine l = telemQueue.take();
if (l.line == null)
throw new IOException("IO error");
AltosTelemetry telem = AltosTelemetryLegacy.parse(l.line);
- if (state == null)
- state = new AltosState();
- else
- state = state.clone();
- telem.update_state(state);
- return state;
+ return telem;
}
public void close() {
- state = null;
link.remove_monitor(telemQueue);
link = null;
telemQueue.clear();
@@ -64,16 +54,14 @@ public class TelemetryReader extends Thread {
}
public void run() {
- AltosState state = null;
-
try {
- if (D) Log.d(TAG, "starting loop");
+ AltosDebug.debug("starting loop");
while (telemQueue != null) {
try {
- state = read();
- handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();
+ AltosTelemetry telem = read();
+ handler.obtainMessage(TelemetryService.MSG_TELEMETRY, telem).sendToTarget();
} catch (ParseException pp) {
- Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));
+ AltosDebug.error("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage());
} catch (AltosCRCException ce) {
++crc_errors;
handler.obtainMessage(TelemetryService.MSG_CRC_ERROR, new Integer(crc_errors)).sendToTarget();
@@ -81,21 +69,22 @@ public class TelemetryReader extends Thread {
}
} catch (InterruptedException ee) {
} catch (IOException ie) {
+ AltosDebug.error("IO exception in telemetry reader");
+ handler.obtainMessage(TelemetryService.MSG_DISCONNECTED, link).sendToTarget();
} finally {
close();
}
}
- public TelemetryReader (AltosLink in_link, Handler in_handler, AltosState in_state) {
- if (D) Log.d(TAG, "connected TelemetryReader create started");
+ public TelemetryReader (AltosLink in_link, Handler in_handler) {
+ AltosDebug.debug("connected TelemetryReader create started");
link = in_link;
handler = in_handler;
- state = in_state;
telemQueue = new LinkedBlockingQueue<AltosLine>();
link.add_monitor(telemQueue);
link.set_telemetry(AltosLib.ao_telemetry_standard);
- if (D) Log.d(TAG, "connected TelemetryReader created");
+ AltosDebug.debug("connected TelemetryReader created");
}
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java
index d4ac66aa..4a056d95 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java
@@ -18,10 +18,8 @@
package org.altusmetrum.AltosDroid;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
import java.util.concurrent.TimeoutException;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.*;
import android.app.Notification;
//import android.app.NotificationManager;
@@ -29,6 +27,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;
@@ -38,55 +37,50 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.Looper;
-import android.util.Log;
import android.widget.Toast;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import android.location.Criteria;
-import org.altusmetrum.altoslib_6.*;
-
+import org.altusmetrum.altoslib_8.*;
public class TelemetryService extends Service implements LocationListener {
- private static final String TAG = "TelemetryService";
- private static final boolean D = true;
-
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_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;
+ static final int MSG_DELETE_SERIAL = 13;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.telemetry_service_label;
//private NotificationManager mNM;
- // Timer - we wake up every now and then to decide if the service should stop
- private Timer timer = new Timer();
-
- ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
- final Handler mHandler = new IncomingHandler(this);
- final Messenger mMessenger = new Messenger(mHandler); // Target we publish for clients to send messages to IncomingHandler.
+ ArrayList<Messenger> clients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
+ final Handler handler = new IncomingHandler(this);
+ final Messenger messenger = new Messenger(handler); // Target we publish for clients to send messages to IncomingHandler.
// Name of the connected device
- String address;
- private AltosBluetooth mAltosBluetooth = null;
- private TelemetryReader mTelemetryReader = null;
- private TelemetryLogger mTelemetryLogger = null;
- // Local Bluetooth adapter
- private BluetoothAdapter mBluetoothAdapter = null;
+ DeviceAddress address;
+ private AltosDroidLink altos_link = null;
+ private TelemetryReader telemetry_reader = null;
+ private TelemetryLogger telemetry_logger = null;
- private TelemetryState telemetry_state;
+ // Local Bluetooth adapter
+ private BluetoothAdapter bluetooth_adapter = null;
// Last data seen; send to UI when it starts
+ private TelemetryState telemetry_state;
// Handler of incoming messages from clients.
static class IncomingHandler extends Handler {
@@ -96,83 +90,121 @@ public class TelemetryService extends Service implements LocationListener {
@Override
public void handleMessage(Message msg) {
TelemetryService s = service.get();
+ AltosDroidLink bt = null;
if (s == null)
return;
switch (msg.what) {
+
+ /* Messages from application */
case MSG_REGISTER_CLIENT:
- s.mClients.add(msg.replyTo);
- try {
- // Now we try to send the freshly connected UI any relavant information about what
- // we're talking to
- msg.replyTo.send(s.message());
- } catch (RemoteException e) {
- s.mClients.remove(msg.replyTo);
- }
- if (D) Log.d(TAG, "Client bound to service");
+ s.add_client(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
- s.mClients.remove(msg.replyTo);
- if (D) Log.d(TAG, "Client unbound from service");
+ s.remove_client(msg.replyTo);
break;
case MSG_CONNECT:
- if (D) Log.d(TAG, "Connect command received");
- String address = (String) msg.obj;
+ AltosDebug.debug("Connect command received");
+ DeviceAddress address = (DeviceAddress) msg.obj;
AltosDroidPreferences.set_active_device(address);
- s.startAltosBluetooth(address);
- break;
- case MSG_CONNECTED:
- if (D) Log.d(TAG, "Connected to device");
- try {
- s.connected();
- } catch (InterruptedException ie) {
- }
+ s.start_altos_bluetooth(address, false);
break;
- case MSG_CONNECT_FAILED:
- if (D) Log.d(TAG, "Connection failed... retrying");
- if (s.address != null)
- s.startAltosBluetooth(s.address);
+ case MSG_OPEN_USB:
+ AltosDebug.debug("Open USB command received");
+ UsbDevice device = (UsbDevice) msg.obj;
+ s.start_usb(device);
break;
- case MSG_DISCONNECTED:
- Log.d(TAG, "MSG_DISCONNECTED");
- s.stopAltosBluetooth();
+ case MSG_DISCONNECT:
+ AltosDebug.debug("Disconnect command received");
+ s.address = null;
+ s.disconnect(true);
break;
- case MSG_TELEMETRY:
- // forward telemetry messages
- s.telemetry_state.state = (AltosState) msg.obj;
- if (s.telemetry_state.state != null) {
- if (D) Log.d(TAG, "Save state");
- AltosPreferences.set_state(0, s.telemetry_state.state, null);
- }
- if (D) Log.d(TAG, "MSG_TELEMETRY");
- s.sendMessageToClients();
- break;
- case MSG_CRC_ERROR:
- // forward crc error messages
- s.telemetry_state.crc_errors = (Integer) msg.obj;
- if (D) Log.d(TAG, "MSG_CRC_ERROR");
- s.sendMessageToClients();
+ case MSG_DELETE_SERIAL:
+ AltosDebug.debug("Delete Serial command received");
+ s.delete_serial((Integer) msg.obj);
break;
case MSG_SETFREQUENCY:
- if (D) Log.d(TAG, "MSG_SETFREQUENCY");
+ AltosDebug.debug("MSG_SETFREQUENCY");
s.telemetry_state.frequency = (Double) msg.obj;
if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
try {
- s.mAltosBluetooth.set_radio_frequency(s.telemetry_state.frequency);
- s.mAltosBluetooth.save_frequency();
+ s.altos_link.set_radio_frequency(s.telemetry_state.frequency);
+ s.altos_link.save_frequency();
} catch (InterruptedException e) {
} catch (TimeoutException e) {
}
}
- s.sendMessageToClients();
+ s.send_to_clients();
break;
case MSG_SETBAUD:
- if (D) Log.d(TAG, "MSG_SETBAUD");
+ AltosDebug.debug("MSG_SETBAUD");
s.telemetry_state.telemetry_rate = (Integer) msg.obj;
if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
- s.mAltosBluetooth.set_telemetry_rate(s.telemetry_state.telemetry_rate);
- s.mAltosBluetooth.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;
+
+ /*
+ *Messages from AltosBluetooth
+ */
+ case MSG_CONNECTED:
+ AltosDebug.debug("MSG_CONNECTED");
+ bt = (AltosDroidLink) msg.obj;
+
+ if (bt != s.altos_link) {
+ AltosDebug.debug("Stale message");
+ break;
+ }
+ AltosDebug.debug("Connected to device");
+ try {
+ s.connected();
+ } catch (InterruptedException ie) {
+ }
+ break;
+ case MSG_CONNECT_FAILED:
+ AltosDebug.debug("MSG_CONNECT_FAILED");
+ bt = (AltosDroidLink) msg.obj;
+
+ if (bt != s.altos_link) {
+ AltosDebug.debug("Stale message");
+ break;
+ }
+ if (s.address != null) {
+ AltosDebug.debug("Connection failed... retrying");
+ s.start_altos_bluetooth(s.address, true);
+ } else {
+ s.disconnect(true);
}
- s.sendMessageToClients();
+ break;
+ case MSG_DISCONNECTED:
+
+ /* This can be sent by either AltosDroidLink or TelemetryReader */
+ AltosDebug.debug("MSG_DISCONNECTED");
+ bt = (AltosDroidLink) msg.obj;
+
+ if (bt != s.altos_link) {
+ AltosDebug.debug("Stale message");
+ break;
+ }
+ if (s.address != null) {
+ AltosDebug.debug("Connection lost... retrying");
+ s.start_altos_bluetooth(s.address, true);
+ } else {
+ s.disconnect(true);
+ }
+ break;
+
+ /*
+ * Messages from TelemetryReader
+ */
+ case MSG_TELEMETRY:
+ s.telemetry((AltosTelemetry) msg.obj);
+ break;
+ case MSG_CRC_ERROR:
+ // forward crc error messages
+ s.telemetry_state.crc_errors = (Integer) msg.obj;
+ s.send_to_clients();
break;
default:
super.handleMessage(msg);
@@ -180,161 +212,288 @@ public class TelemetryService extends Service implements LocationListener {
}
}
+ /* Handle telemetry packet
+ */
+ private void telemetry(AltosTelemetry telem) {
+ AltosState state;
+
+ if (telemetry_state.states.containsKey(telem.serial))
+ state = telemetry_state.states.get(telem.serial).clone();
+ else
+ state = new AltosState();
+ telem.update_state(state);
+ telemetry_state.states.put(telem.serial, state);
+ if (state != null) {
+ AltosPreferences.set_state(telem.serial, state, null);
+ }
+ send_to_clients();
+ }
+
+ /* Construct the message to deliver to clients
+ */
private Message message() {
if (telemetry_state == null)
- Log.d(TAG, "telemetry_state null!");
- if (telemetry_state.state == null)
- Log.d(TAG, "telemetry_state.state null!");
+ AltosDebug.debug("telemetry_state null!");
+ if (telemetry_state.states == null)
+ AltosDebug.debug("telemetry_state.states null!");
return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);
}
- private void sendMessageToClients() {
- Message m = message();
- if (D) Log.d(TAG, String.format("Send message to %d clients", mClients.size()));
- for (int i=mClients.size()-1; i>=0; i--) {
- try {
- if (D) Log.d(TAG, String.format("Send message to client %d", i));
- mClients.get(i).send(m);
- } catch (RemoteException e) {
- mClients.remove(i);
- }
+ /* A new friend has connected
+ */
+ private void add_client(Messenger client) {
+
+ clients.add(client);
+ AltosDebug.debug("Client bound to service");
+
+ /* On connect, send the current state to the new client
+ */
+ send_to_client(client, message());
+
+ /* If we've got an address from a previous session, then
+ * go ahead and try to reconnect to the device
+ */
+ if (address != null && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) {
+ AltosDebug.debug("Reconnecting now...");
+ start_altos_bluetooth(address, false);
}
}
- private void stopAltosBluetooth() {
- if (D) Log.d(TAG, "stopAltosBluetooth(): begin");
- telemetry_state.connect = TelemetryState.CONNECT_READY;
- if (mTelemetryReader != null) {
- if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryReader");
- mTelemetryReader.interrupt();
+ /* A client has disconnected, clean up
+ */
+ private void remove_client(Messenger client) {
+ clients.remove(client);
+ AltosDebug.debug("Client unbound from service");
+
+ /* When the list of clients is empty, stop the service if
+ * we have no current telemetry source
+ */
+
+ if (clients.isEmpty() && telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED) {
+ AltosDebug.debug("No clients, no connection. Stopping\n");
+ stopSelf();
+ }
+ }
+
+ private void send_to_client(Messenger client, Message m) {
+ try {
+ client.send(m);
+ } catch (RemoteException e) {
+ AltosDebug.error("Client %s disappeared", client.toString());
+ remove_client(client);
+ }
+ }
+
+ private void send_to_clients() {
+ Message m = message();
+ for (Messenger client : clients)
+ send_to_client(client, m);
+ }
+
+ private void disconnect(boolean notify) {
+ AltosDebug.debug("disconnect(): begin");
+
+ telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
+ telemetry_state.address = null;
+
+ if (altos_link != null)
+ altos_link.closing();
+
+ stop_receiver_voltage_timer();
+
+ if (telemetry_reader != null) {
+ AltosDebug.debug("disconnect(): stopping TelemetryReader");
+ telemetry_reader.interrupt();
try {
- mTelemetryReader.join();
+ telemetry_reader.join();
} catch (InterruptedException e) {
}
- mTelemetryReader = null;
+ telemetry_reader = null;
}
- if (mTelemetryLogger != null) {
- if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryLogger");
- mTelemetryLogger.stop();
- mTelemetryLogger = null;
+ if (telemetry_logger != null) {
+ AltosDebug.debug("disconnect(): stopping TelemetryLogger");
+ telemetry_logger.stop();
+ telemetry_logger = null;
}
- if (mAltosBluetooth != null) {
- if (D) Log.d(TAG, "stopAltosBluetooth(): stopping AltosBluetooth");
- mAltosBluetooth.close();
- mAltosBluetooth = null;
+ if (altos_link != null) {
+ AltosDebug.debug("disconnect(): stopping AltosDroidLink");
+ altos_link.close();
+ altos_link = null;
}
telemetry_state.config = null;
- if (D) Log.d(TAG, "stopAltosBluetooth(): send message to clients");
- sendMessageToClients();
+ if (notify) {
+ AltosDebug.debug("disconnect(): send message to clients");
+ send_to_clients();
+ if (clients.isEmpty()) {
+ AltosDebug.debug("disconnect(): no clients, terminating");
+ stopSelf();
+ }
+ }
}
- private void startAltosBluetooth(String address) {
+ 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 delete_serial(int serial) {
+ telemetry_state.states.remove((Integer) serial);
+ AltosPreferences.remove_state(serial);
+ send_to_clients();
+ }
+
+ private void start_altos_bluetooth(DeviceAddress address, boolean pause) {
// Get the BLuetoothDevice object
- BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
+ BluetoothDevice device = bluetooth_adapter.getRemoteDevice(address.address);
+ disconnect(false);
this.address = address;
- if (mAltosBluetooth == null) {
- if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress()));
- mAltosBluetooth = new AltosBluetooth(device, mHandler);
- telemetry_state.connect = TelemetryState.CONNECT_CONNECTING;
- sendMessageToClients();
- } else {
- // This is a bit of a hack - if it appears we're still connected, we treat this as a restart.
- // So, to give a suitable delay to teardown/bringup, we just schedule a resend of a message
- // to ourselves in a few seconds time that will ultimately call this method again.
- // ... then we tear down the existing connection.
- // We do it this way around so that we don't lose a reference to the device when this method
- // is called on reception of MSG_CONNECT_FAILED in the handler above.
- mHandler.sendMessageDelayed(Message.obtain(null, MSG_CONNECT, address), 3000);
- stopAltosBluetooth();
+ AltosDebug.debug("start_altos_bluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress());
+ altos_link = new AltosBluetooth(device, handler, pause);
+ telemetry_state.connect = TelemetryState.CONNECT_CONNECTING;
+ telemetry_state.address = address;
+ send_to_clients();
+ }
+
+ // Timer for receiver battery voltage monitoring
+ Timer receiver_voltage_timer;
+
+ private void update_receiver_voltage() {
+ if (altos_link != null) {
+ try {
+ double voltage = altos_link.monitor_battery();
+ telemetry_state.receiver_battery = voltage;
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+
+ private void stop_receiver_voltage_timer() {
+ if (receiver_voltage_timer != null) {
+ receiver_voltage_timer.cancel();
+ receiver_voltage_timer.purge();
+ receiver_voltage_timer = null;
+ }
+ }
+
+ private void start_receiver_voltage_timer() {
+ if (receiver_voltage_timer == null && altos_link.has_monitor_battery()) {
+ receiver_voltage_timer = new Timer();
+ receiver_voltage_timer.scheduleAtFixedRate(new TimerTask() { public void run() {update_receiver_voltage();}}, 1000L, 10000L);
}
}
private void connected() throws InterruptedException {
- if (D) Log.d(TAG, "connected top");
+ AltosDebug.debug("connected top");
+ AltosDebug.check_ui("connected\n");
try {
- if (mAltosBluetooth == null)
+ if (altos_link == null)
throw new InterruptedException("no bluetooth");
- telemetry_state.config = mAltosBluetooth.config_data();
- mAltosBluetooth.set_radio_frequency(telemetry_state.frequency);
- mAltosBluetooth.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.
- mHandler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget();
+ AltosDebug.debug("connected timeout");
+ if (address != null) {
+ AltosDebug.debug("connected timeout, retrying");
+ start_altos_bluetooth(address, true);
+ } else {
+ handler.obtainMessage(MSG_CONNECT_FAILED).sendToTarget();
+ disconnect(true);
+ }
return;
}
- if (D) Log.d(TAG, "connected bluetooth configured");
+ AltosDebug.debug("connected bluetooth configured");
telemetry_state.connect = TelemetryState.CONNECT_CONNECTED;
+ telemetry_state.address = address;
- mTelemetryReader = new TelemetryReader(mAltosBluetooth, mHandler, telemetry_state.state);
- mTelemetryReader.start();
+ telemetry_reader = new TelemetryReader(altos_link, handler);
+ telemetry_reader.start();
- if (D) Log.d(TAG, "connected TelemetryReader started");
+ AltosDebug.debug("connected TelemetryReader started");
- mTelemetryLogger = new TelemetryLogger(this, mAltosBluetooth);
+ telemetry_logger = new TelemetryLogger(this, altos_link);
- if (D) Log.d(TAG, "Notify UI of connection");
+ start_receiver_voltage_timer();
- sendMessageToClients();
- }
+ AltosDebug.debug("Notify UI of connection");
- private void onTimerTick() {
- if (D) Log.d(TAG, "Timer wakeup");
- try {
- if (mClients.size() <= 0 && telemetry_state.connect != TelemetryState.CONNECT_CONNECTED) {
- stopSelf();
- }
- } catch (Throwable t) {
- Log.e(TAG, "Timer failed: ", t);
- }
+ send_to_clients();
}
@Override
public void onCreate() {
+
+ AltosDebug.init(this);
+
+ // Initialise preferences
+ AltosDroidPreferences.init(this);
+
// Get local Bluetooth adapter
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ bluetooth_adapter = BluetoothAdapter.getDefaultAdapter();
// If the adapter is null, then Bluetooth is not supported
- if (mBluetoothAdapter == null) {
+ if (bluetooth_adapter == null) {
Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
}
- // Initialise preferences
- AltosDroidPreferences.init(this);
-
telemetry_state = new TelemetryState();
// Create a reference to the NotificationManager so that we can update our notifcation text later
//mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
- telemetry_state.connect = TelemetryState.CONNECT_READY;
+ telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
+ telemetry_state.address = null;
- AltosSavedState saved_state = AltosPreferences.state(0);
+ /* Pull the saved state information out of the preferences database
+ */
+ ArrayList<Integer> serials = AltosPreferences.list_states();
- if (saved_state != null) {
- if (D) Log.d(TAG, String.format("recovered old state flight %d\n", saved_state.state.flight));
- telemetry_state.state = saved_state.state;
- }
+ telemetry_state.latest_serial = AltosPreferences.latest_state();
- // Start our timer - first event in 10 seconds, then every 10 seconds after that.
- timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L);
+ for (int serial : serials) {
+ AltosSavedState saved_state = AltosPreferences.state(serial);
+ if (saved_state != null) {
+ if (serial == 0) {
+ serial = saved_state.state.serial;
+ AltosPreferences.set_state(serial, saved_state.state, saved_state.listener_state);
+ AltosPreferences.remove_state(0);
+ }
+ if (telemetry_state.latest_serial == 0)
+ telemetry_state.latest_serial = serial;
+
+ AltosDebug.debug("recovered old state serial %d flight %d\n",
+ serial,
+ saved_state.state.flight);
+ if (saved_state.state.gps != null)
+ AltosDebug.debug("\tposition %f,%f\n",
+ saved_state.state.gps.lat,
+ saved_state.state.gps.lon);
+ telemetry_state.states.put(serial, saved_state.state);
+ }
+ }
// Listen for GPS and Network position updates
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
-
- String address = AltosDroidPreferences.active_device();
- if (address != null)
- startAltosBluetooth(address);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("TelemetryService", "Received start id " + startId + ": " + intent);
+ AltosDebug.debug("Received start id %d: %s", startId, intent);
CharSequence text = getText(R.string.telemetry_service_started);
@@ -354,6 +513,20 @@ public class TelemetryService extends Service implements LocationListener {
// Move us into the foreground.
startForeground(NOTIFICATION, notification);
+ /* Start bluetooth if we don't have a connection already */
+ if (intent != null &&
+ (telemetry_state.connect == TelemetryState.CONNECT_NONE ||
+ telemetry_state.connect == TelemetryState.CONNECT_DISCONNECTED))
+ {
+ 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;
@@ -366,28 +539,25 @@ public class TelemetryService extends Service implements LocationListener {
((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this);
// Stop the bluetooth Comms threads
- stopAltosBluetooth();
+ disconnect(true);
// Demote us from the foreground, and cancel the persistent notification.
stopForeground(true);
- // Stop our timer
- if (timer != null) {timer.cancel();}
-
// Tell the user we stopped.
Toast.makeText(this, R.string.telemetry_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
+ return messenger.getBinder();
}
public void onLocationChanged(Location location) {
telemetry_state.location = location;
- if (D) Log.d(TAG, "location changed");
- sendMessageToClients();
+ AltosDebug.debug("location changed");
+ send_to_clients();
}
public void onStatusChanged(String provider, int status, Bundle extras) {
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java
index 862847d2..f9191a32 100644
--- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java
+++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java
@@ -17,29 +17,36 @@
package org.altusmetrum.AltosDroid;
-import org.altusmetrum.altoslib_6.*;
+import java.util.*;
+import org.altusmetrum.altoslib_8.*;
import android.location.Location;
public class TelemetryState {
- public static final int CONNECT_NONE = 0;
- public static final int CONNECT_READY = 1;
- public static final int CONNECT_CONNECTING = 2;
- public static final int CONNECT_CONNECTED = 3;
+ public static final int CONNECT_NONE = 0;
+ public static final int CONNECT_DISCONNECTED = 1;
+ public static final int CONNECT_CONNECTING = 2;
+ public static final int CONNECT_CONNECTED = 3;
int connect;
+ DeviceAddress address;
AltosConfigData config;
- AltosState state;
Location location;
int crc_errors;
+ double receiver_battery;
double frequency;
int telemetry_rate;
+ HashMap<Integer,AltosState> states;
+
+ int latest_serial;
+
public TelemetryState() {
connect = CONNECT_NONE;
config = null;
- state = null;
+ states = new HashMap<Integer,AltosState>();
location = null;
crc_errors = 0;
+ receiver_battery = AltosLib.MISSING;
frequency = AltosPreferences.frequency(0);
telemetry_rate = AltosPreferences.telemetry_rate(0);
}