altosdroid: Add idle mode monitoring, reboot. Start igniters
authorKeith Packard <keithp@keithp.com>
Tue, 26 Apr 2016 03:12:34 +0000 (23:12 -0400)
committerKeith Packard <keithp@keithp.com>
Tue, 26 Apr 2016 03:12:34 +0000 (23:12 -0400)
This adds three idle mode operations -- monitor idle, reboot flight
computer and test igniters. The igniter test isn't quite wired up.

Signed-off-by: Keith Packard <keithp@keithp.com>
16 files changed:
altosdroid/AndroidManifest.xml.in
altosdroid/Makefile.am
altosdroid/res/layout/device_list.xml
altosdroid/res/layout/idle_mode.xml [new file with mode: 0644]
altosdroid/res/layout/igniter_status.xml [new file with mode: 0644]
altosdroid/res/layout/igniters.xml [new file with mode: 0644]
altosdroid/res/menu/option_menu.xml
altosdroid/res/values/strings.xml
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidLink.java
altosdroid/src/org/altusmetrum/AltosDroid/IdleModeActivity.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/IgniterActivity.java [new file with mode: 0644]
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java
altoslib/AltosIdleMonitor.java
altoslib/AltosIgnite.java
altoslib/AltosLink.java

index 2403579642957d4af2cb195fa84e0a2e92c83790..15b044456dc15d25932bc810bf88a3a4b99d772d 100644 (file)
@@ -39,7 +39,7 @@
     <uses-permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE"/>
 
     <!-- Permissions needed to access USB OTG -->
-    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-feature android:name="android.hardware.usb.host" android:required="false" />
 
     <application android:label="@string/app_name"
                  android:icon="@drawable/app_icon"
                   android:theme="@android:style/Theme.Dialog"
                   android:configChanges="orientation|keyboardHidden" />
 
+        <activity android:name=".IdleModeActivity"
+                  android:label="@string/idle_mode"
+                  android:theme="@android:style/Theme.Dialog"
+                  android:configChanges="orientation|keyboardHidden" />
+
+        <activity android:name=".IgniterActivity"
+                  android:label="@string/igniters"
+                  android:theme="@android:style/Theme.Dialog"
+                  android:configChanges="orientation|keyboardHidden" />
+
         <service android:name=".TelemetryService" />
 
         <meta-data android:name="com.google.android.maps.v2.API_KEY"
index 26e14ee7edaa5879525d0f6ffec72267243aa300..295abbc57347ca611fafe3b5890020b03fe2392e 100644 (file)
@@ -47,7 +47,7 @@ DRAWABLES=\
 LAYOUTS=$(LAYOUT_DIR)/*.xml
 MENUS=$(MENU_DIR)/*.xml
 VALUES=$(VALUES_DIR)/*.xml
-XMLS=$(XML_DIR)/*.xml
+XMLS=$(XML_DIR)/*.xml AndroidManifest.xml
 
 RES=$(LAYOUTS) $(MENUS) $(VALUES) $(XMLS)
 
index bf295e4c5e0fc8eba7a146cefb66c8d1902d78c5..57e5501e1aaecb886c95cf64f0586aad719fb782 100644 (file)
@@ -1,17 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
+ Copyright © 2016 Keith Packard <keithp@keithp.com>
 
-          http://www.apache.org/licenses/LICENSE-2.0
+ 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.
 
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
diff --git a/altosdroid/res/layout/idle_mode.xml b/altosdroid/res/layout/idle_mode.xml
new file mode 100644 (file)
index 0000000..d6a05fb
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Copyright © 2016 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.
+
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+      <TextView android:id="@+id/set_callsign_label"
+               android:layout_width="fill_parent"
+               android:layout_height="wrap_content"
+               android:text="@string/set_callsign_label"
+               />
+      <EditText android:id="@+id/set_callsign"
+               android:layout_width="fill_parent"
+               android:layout_height="wrap_content"
+               android:hint="@string/set_callsign_label"/>
+      <Button android:id="@+id/connect_idle"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/connect_idle"
+             />
+      <Button android:id="@+id/disconnect_idle"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/disconnect_idle"
+             />
+      <Button android:id="@+id/reboot_idle"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/reboot_idle"
+             />
+      <Button android:id="@+id/igniters_idle"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/igniters_idle"
+             />
+</LinearLayout>
diff --git a/altosdroid/res/layout/igniter_status.xml b/altosdroid/res/layout/igniter_status.xml
new file mode 100644 (file)
index 0000000..298f915
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Copyright © 2016 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.
+
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+  <TextView
+      android:id="@+id/igniter_status"
+      android:layout_width="wrap_content"
+      android:layout_height="fill_parent"
+      android:padding="10dp"
+      android:layout_alignParentRight="true"
+      />
+  <TextView
+      android:id="@+id/igniter_name"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent"  
+      android:padding="10dp"
+      android:layout_alignParentLeft="@+id/igniter_status"
+      />
+</RelativeLayout>
diff --git a/altosdroid/res/layout/igniters.xml b/altosdroid/res/layout/igniters.xml
new file mode 100644 (file)
index 0000000..350a1e5
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Copyright © 2016 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.
+
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+
+    <ListView android:id="@+id/igniters"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+       android:fadeScrollbars="false"
+       android:scrollbars="vertical"
+       android:choiceMode="singleChoice"
+       />
+
+    <ToggleButton android:id="@+id/igniter_arm"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textOn="@string/igniter_armed"
+            android:textOff="@string/igniter_arm"
+           />
+    
+    <Button android:id="@+id/igniter_fire"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/igniter_fire"
+           />
+    
+</LinearLayout>
index 7e08c80333c30a3c8958f52533e4cb8d9a68fe00..4de4a16e83a736c5a41f84bdd6f1397f6ed8f638 100644 (file)
@@ -46,6 +46,9 @@
     <item android:id="@+id/delete_track"
          android:icon="@android:drawable/ic_notification_clear_all"
          android:title="@string/delete_track"/>
+    <item android:id="@+id/idle_mode"
+          android:icon="@android:drawable/ic_menu_preferences"
+          android:title="@string/idle_mode" />
     <item android:id="@+id/quit"
           android:icon="@android:drawable/ic_menu_close_clear_cancel"
           android:title="@string/quit" />
index e7014fc934db5f76555d064c2f9ff6ce2cf65083..6f761a58e7be5fd96d9558b7c9fd2fa537ac51db 100644 (file)
        <string name="preload_radius">Radius</string>
        
        <string name="preload_load">Load Map</string>
+
+       <!-- Idle mode -->
+       <string name="idle_mode">Idle Mode</string>
+       <string name="set_callsign_label">Callsign</string>
+       <string name="connect_idle">Connect</string>
+       <string name="disconnect_idle">Disconnect</string>
+       <string name="reboot_idle">Reboot</string>
+       <string name="igniters_idle">Fire Igniters</string>
+
+       <!-- igniters -->
+       <string name="igniters">Igniters</string>
+       <string name="igniter_arm">Arm</string>
+       <string name="igniter_armed">Armed</string>
+       <string name="igniter_fire">Fire</string>
+       
 </resources>
index 4f1dcb1a3b441cee951af66fd9728ba060e6eaf8..e5f56a5a9688893c36688b51c854148da1704995 100644 (file)
@@ -64,12 +64,20 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
        public static final int MSG_STATE           = 1;
        public static final int MSG_UPDATE_AGE      = 2;
+       public static final int MSG_IDLE_MODE       = 3;
+       public static final int MSG_IGNITER_STATUS  = 4;
 
        // 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 static final int REQUEST_IDLE_MODE      = 5;
+       public static final int REQUEST_IGNITERS       = 6;
+
+       public static final String EXTRA_IDLE_MODE = "idle_mode";
+       public static final String EXTRA_IDLE_RESULT = "idle_result";
+       public static final String EXTRA_TELEMETRY_SERVICE = "telemetry_service";
 
        public int map_type = AltosMap.maptype_hybrid;
 
@@ -100,6 +108,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
        private double frequency;
        private int telemetry_rate;
 
+       private boolean idle_mode = false;
+
        public Location location = null;
 
        // Tabs
@@ -146,6 +156,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        case MSG_UPDATE_AGE:
                                ad.update_age();
                                break;
+                       case MSG_IDLE_MODE:
+                               ad.idle_mode = (Boolean) msg.obj;
+                               ad.update_state(null);
+                               break;
                        }
                }
        };
@@ -216,8 +230,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                switch (telemetry_state.connect) {
                case TelemetryState.CONNECT_CONNECTED:
                        if (telemetry_state.config != null) {
-                               String str = String.format("S/N %d %6.3f MHz", telemetry_state.config.serial,
-                                                          telemetry_state.frequency);
+                               String str = String.format("S/N %d %6.3f MHz%s", telemetry_state.config.serial,
+                                                          telemetry_state.frequency, idle_mode ? " (idle)" : "");
                                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]));
@@ -444,7 +458,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                mCallsignView.setText(state.callsign);
                        }
                        if (saved_state == null || state.serial != saved_state.serial) {
-                               mSerialView.setText(String.format("%d", state.serial));
+                               if (state.serial == AltosLib.MISSING)
+                                       mSerialView.setText("");
+                               else
+                                       mSerialView.setText(String.format("%d", state.serial));
                        }
                        if (saved_state == null || state.flight != saved_state.flight) {
                                if (state.flight == AltosLib.MISSING)
@@ -461,7 +478,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                                }
                        }
                        if (saved_state == null || state.rssi != saved_state.rssi) {
-                               mRSSIView.setText(String.format("%d", state.rssi));
+                               if (state.rssi == AltosLib.MISSING)
+                                       mRSSIView.setText("");
+                               else
+                                       mRSSIView.setText(String.format("%d", state.rssi));
                        }
                }
 
@@ -681,9 +701,10 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
 
-               AltosDebug.debug("Resume, location is %f,%f\n",
-                                location.getLatitude(),
-                                location.getLongitude());
+               if (location != null)
+                       AltosDebug.debug("Resume, location is %f,%f\n",
+                                        location.getLatitude(),
+                                        location.getLongitude());
 
                update_ui(telemetry_state, saved_state);
        }
@@ -740,6 +761,12 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                        if (resultCode == Activity.RESULT_OK)
                                set_map_type(data);
                        break;
+               case REQUEST_IDLE_MODE:
+                       if (resultCode == Activity.RESULT_OK)
+                               idle_mode(data);
+                       break;
+               case REQUEST_IGNITERS:
+                       break;
                }
        }
 
@@ -798,6 +825,40 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
                }
        }
 
+       private void idle_mode(Intent data) {
+               int type = data.getIntExtra(IdleModeActivity.EXTRA_IDLE_RESULT, -1);
+               Message msg;
+
+               AltosDebug.debug("intent idle_mode %d", type);
+               switch (type) {
+               case IdleModeActivity.IDLE_MODE_CONNECT:
+                       msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_START);
+                       try {
+                               mService.send(msg);
+                       } catch (RemoteException re) {
+                       }
+                       break;
+               case IdleModeActivity.IDLE_MODE_DISCONNECT:
+                       msg = Message.obtain(null, TelemetryService.MSG_MONITOR_IDLE_STOP);
+                       try {
+                               mService.send(msg);
+                       } catch (RemoteException re) {
+                       }
+                       break;
+               case IdleModeActivity.IDLE_MODE_REBOOT:
+                       msg = Message.obtain(null, TelemetryService.MSG_REBOOT);
+                       try {
+                               mService.send(msg);
+                       } catch (RemoteException re) {
+                       }
+                       break;
+               case IdleModeActivity.IDLE_MODE_IGNITERS:
+                       Intent serverIntent = new Intent(this, IgniterActivity.class);
+                       startActivityForResult(serverIntent, REQUEST_IGNITERS);
+                       break;
+               }
+       }
+
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                MenuInflater inflater = getMenuInflater();
@@ -1025,6 +1086,11 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener,
 
                        }
                        return true;
+               case R.id.idle_mode:
+                       serverIntent = new Intent(this, IdleModeActivity.class);
+                       serverIntent.putExtra(EXTRA_IDLE_MODE, idle_mode);
+                       startActivityForResult(serverIntent, REQUEST_IDLE_MODE);
+                       return true;
                }
                return false;
        }
index 6bc52d7e0bbbab9f863dec68c25696138e993891..2b10b11fdcf7937df1a1f3ed82afba7d7b16548e 100644 (file)
@@ -207,10 +207,9 @@ public abstract class AltosDroidLink extends AltosLink {
 
        public void print(String data) {
                byte[] bytes = data.getBytes();
-               AltosDebug.debug("print(): begin");
+               AltosDebug.debug(data.replace('\n', '\\'));
                for (byte b : bytes)
                        putchar(b);
-               AltosDebug.debug("print(): Wrote bytes: '" + data.replace('\n', '\\') + "'");
        }
 
        public AltosDroidLink(Handler handler) {
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/IdleModeActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/IdleModeActivity.java
new file mode 100644 (file)
index 0000000..ec12c19
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright © 2016 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_10.*;
+
+public class IdleModeActivity extends Activity {
+       private EditText callsign;
+       private Button connect;
+       private Button disconnect;
+       private Button reboot;
+       private Button igniters;
+
+       public static final String EXTRA_IDLE_MODE = "idle_mode";
+       public static final String EXTRA_IDLE_RESULT = "idle_result";
+
+       public static final int IDLE_MODE_CONNECT = 1;
+       public static final int IDLE_MODE_REBOOT = 2;
+       public static final int IDLE_MODE_IGNITERS = 3;
+       public static final int IDLE_MODE_DISCONNECT = 4;
+
+       private void done(int type) {
+               AltosPreferences.set_callsign(callsign());
+               Intent intent = new Intent();
+               intent.putExtra(EXTRA_IDLE_RESULT, type);
+               setResult(Activity.RESULT_OK, intent);
+               finish();
+       }
+
+       private String callsign() {
+               return callsign.getEditableText().toString();
+       }
+
+       public void connect_idle() {
+               done(IDLE_MODE_CONNECT);
+       }
+
+       public void disconnect_idle() {
+               AltosDebug.debug("Disconnect idle button pressed");
+               done(IDLE_MODE_DISCONNECT);
+       }
+
+       public void reboot_idle() {
+               done(IDLE_MODE_REBOOT);
+       }
+
+       public void igniters_idle() {
+               done(IDLE_MODE_IGNITERS);
+       }
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               // Setup the window
+               requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+               setContentView(R.layout.idle_mode);
+
+               callsign = (EditText) findViewById(R.id.set_callsign);
+               callsign.setText(new StringBuffer(AltosPreferences.callsign()));
+
+               connect = (Button) findViewById(R.id.connect_idle);
+               connect.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       connect_idle();
+                               }
+                       });
+               disconnect = (Button) findViewById(R.id.disconnect_idle);
+               disconnect.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       disconnect_idle();
+                               }
+                       });
+
+               boolean idle_mode = getIntent().getBooleanExtra(AltosDroid.EXTRA_IDLE_MODE, false);
+
+               if (idle_mode)
+                       connect.setVisibility(View.GONE);
+               else
+                       disconnect.setVisibility(View.GONE);
+
+               reboot = (Button) findViewById(R.id.reboot_idle);
+               reboot.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       reboot_idle();
+                               }
+                       });
+               igniters = (Button) findViewById(R.id.igniters_idle);
+               igniters.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       igniters_idle();
+                               }
+                       });
+
+               // Set result CANCELED incase the user backs out
+               setResult(Activity.RESULT_CANCELED);
+       }
+}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/IgniterActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/IgniterActivity.java
new file mode 100644 (file)
index 0000000..a608bba
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * Copyright © 2016 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.lang.ref.WeakReference;
+import java.util.*;
+import org.altusmetrum.AltosDroid.R;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.*;
+import android.graphics.*;
+import android.os.*;
+import android.view.*;
+import android.view.View.*;
+import android.widget.*;
+import android.widget.AdapterView.*;
+
+import org.altusmetrum.altoslib_10.*;
+
+class IgniterItem {
+       public String name;
+       public String status;
+       public LinearLayout igniter_view = null;
+       public TextView name_view = null;
+       public TextView status_view = null;
+
+       private void update() {
+               AltosDebug.debug("update item %s %s", name, status);
+               if (name_view != null)
+                       name_view.setText(name);
+               if (status_view != null)
+                       status_view.setText(status);
+       }
+
+       public void set(String name, String status) {
+               if (!name.equals(this.name) || !status.equals(this.status)) {
+                       this.name = name;
+                       this.status = status;
+                       update();
+               }
+       }
+
+       public void realize(LinearLayout igniter_view,
+                           TextView name_view,
+                           TextView status_view) {
+               if (igniter_view != this.igniter_view ||
+                   name_view != this.name_view ||
+                   status_view != this.status_view)
+               {
+                       this.igniter_view = igniter_view;
+                       this.name_view = name_view;
+                       this.status_view = status_view;
+                       update();
+               }
+       }
+
+       public IgniterItem() {
+               AltosDebug.debug("New igniter item");
+       }
+}
+
+class IgniterAdapter extends ArrayAdapter<IgniterItem> {
+       int resource;
+       int selected_item = -1;
+
+       public IgniterAdapter(Context context, int in_resource) {
+               super(context, in_resource);
+               resource = in_resource;
+       }
+
+       @Override
+       public View getView(int position, View convertView, ViewGroup parent) {
+               IgniterItem item = getItem(position);
+               if (item.igniter_view == null) {
+                       LinearLayout igniter_view = new LinearLayout(getContext());
+                       String inflater = Context.LAYOUT_INFLATER_SERVICE;
+                       LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflater);
+                       li.inflate(resource, igniter_view, true);
+
+                       item.realize(igniter_view,
+                                    (TextView) igniter_view.findViewById(R.id.igniter_name),
+                                    (TextView) igniter_view.findViewById(R.id.igniter_status));
+                       AltosDebug.debug("Realize new igniter view");
+               } else
+                       AltosDebug.debug("Reuse existing igniter view");
+               if (position == selected_item)
+                       item.igniter_view.setBackgroundColor(Color.RED);
+               else
+                       item.igniter_view.setBackgroundColor(Color.BLACK);
+               return item.igniter_view;
+       }
+}
+
+public class IgniterActivity extends Activity {
+       private ListView igniters_view;
+       private ToggleButton arm;
+       private Button fire;
+
+       private HashMap<String,IgniterItem> igniters = new HashMap<String,IgniterItem>();;
+
+       private IgniterAdapter igniters_adapter;
+
+       private boolean is_bound;
+       private boolean timer_running;
+       private Messenger service = null;
+       private final Messenger messenger = new Messenger(new IncomingHandler(this));
+
+       private Timer timer;
+
+       public static final int IGNITER_QUERY = 1;
+       public static final int IGNITER_FIRE = 2;
+
+       // The Handler that gets information back from the Telemetry Service
+       static class IncomingHandler extends Handler {
+               private final WeakReference<IgniterActivity> igniter_activity;
+               IncomingHandler(IgniterActivity ia) { igniter_activity = new WeakReference<IgniterActivity>(ia); }
+
+               @Override
+               public void handleMessage(Message msg) {
+                       IgniterActivity ia = igniter_activity.get();
+
+                       switch (msg.what) {
+                       case AltosDroid.MSG_IGNITER_STATUS:
+                               ia.igniter_status((HashMap <String,Integer>) msg.obj);
+                               break;
+                       }
+               }
+       };
+
+
+       private ServiceConnection connection = new ServiceConnection() {
+               public void onServiceConnected(ComponentName className, IBinder binder) {
+                       service = new Messenger(binder);
+               }
+
+               public void onServiceDisconnected(ComponentName className) {
+                       // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
+                       service = null;
+               }
+       };
+
+       void doBindService() {
+               bindService(new Intent(this, TelemetryService.class), connection, Context.BIND_AUTO_CREATE);
+               is_bound = true;
+       }
+
+       void doUnbindService() {
+               if (is_bound) {
+                       // If we have received the service, and hence registered with it, then now is the time to unregister.
+                       unbindService(connection);
+                       is_bound = false;
+               }
+       }
+
+       private void done() {
+               Intent intent = new Intent();
+               setResult(Activity.RESULT_OK, intent);
+               finish();
+       }
+
+       private void fire_igniter() {
+       }
+
+       private void arm_igniter(boolean is_checked) {
+       }
+
+       private synchronized void timer_tick() {
+               if (timer_running)
+                       return;
+               timer_running = true;
+               try {
+                       Message msg = Message.obtain(null, TelemetryService.MSG_IGNITER_QUERY);
+                       msg.replyTo = messenger;
+                       service.send(msg);
+               } catch (RemoteException re) {
+               }
+       }
+
+       private boolean set_igniter(HashMap <String,Integer> status, String name, String pretty) {
+               if (!status.containsKey(name))
+                       return false;
+
+               IgniterItem item;
+               if (!igniters.containsKey(name)) {
+                       item = new IgniterItem();
+                       igniters.put(name, item);
+                       igniters_adapter.add(item);
+               } else
+                       item = igniters.get(name);
+
+               item.set(pretty, AltosIgnite.status_string(status.get(name)));
+               return true;
+       }
+
+       private synchronized void igniter_status(HashMap <String,Integer> status) {
+               timer_running = false;
+               if (status == null) {
+                       AltosDebug.debug("no igniter status");
+                       return;
+               }
+               set_igniter(status, "drogue", "Apogee");
+               set_igniter(status, "main", "Main");
+               for (int extra = 0;; extra++) {
+                       String  name = String.format("%d", extra);
+                       String  pretty = String.format("%c", 'A' + extra);
+                       if (!set_igniter(status, name, pretty))
+                               break;
+               }
+//             if (igniters_adapter.selected_item >= 0)
+//                     igniters_view.setSelection(selected_item);
+       }
+
+       private class IgniterItemClickListener implements ListView.OnItemClickListener {
+               @Override
+               public void onItemClick(AdapterView<?> av, View v, int position, long id) {
+                       if (igniters_adapter.selected_item >= 0)
+                               igniters_view.setItemChecked(igniters_adapter.selected_item, false);
+                       igniters_view.setItemChecked(position, true);
+                       igniters_adapter.selected_item = position;
+               }
+       }
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               // Setup the window
+               requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+               setContentView(R.layout.igniters);
+
+               igniters_view = (ListView) findViewById(R.id.igniters);
+               igniters_view.setClickable(true);
+
+               igniters_adapter = new IgniterAdapter(this, R.layout.igniter_status);
+
+               igniters_view.setAdapter(igniters_adapter);
+               igniters_view.setOnItemClickListener(new IgniterItemClickListener());
+
+               fire = (Button) findViewById(R.id.igniter_fire);
+               fire.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       fire_igniter();
+                               }
+                       });
+
+               arm = (ToggleButton) findViewById(R.id.igniter_arm);
+               arm.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() {
+                               public void onCheckedChanged(CompoundButton v, boolean is_checked) {
+                                       arm_igniter(is_checked);
+                               }
+                       });
+
+               // Set result CANCELED incase the user backs out
+               setResult(Activity.RESULT_CANCELED);
+       }
+
+       @Override
+       protected void onStart() {
+               super.onStart();
+               doBindService();
+       }
+
+       @Override
+       protected void onResume() {
+               super.onResume();
+               timer = new Timer(true);
+               timer.scheduleAtFixedRate(new TimerTask() {
+                               public void run() {
+                                       timer_tick();
+                               }},
+                       1000L, 1000L);
+       }
+
+       @Override
+       protected void onPause() {
+               super.onPause();
+               timer.cancel();
+               timer = null;
+       }
+
+       @Override
+       protected void onStop() {
+               super.onStop();
+               doUnbindService();
+       }
+}
index 926109e2b27642d0ec538daa0113354d350a49f4..4b26d263509b18ff1cb3bc6dff49bf258ab74a07 100644 (file)
@@ -42,7 +42,7 @@ import android.location.Criteria;
 
 import org.altusmetrum.altoslib_10.*;
 
-public class TelemetryService extends Service {
+public class TelemetryService extends Service implements AltosIdleMonitorListener {
 
        static final int MSG_REGISTER_CLIENT   = 1;
        static final int MSG_UNREGISTER_CLIENT = 2;
@@ -58,6 +58,11 @@ public class TelemetryService extends Service {
        static final int MSG_DISCONNECT        = 12;
        static final int MSG_DELETE_SERIAL     = 13;
        static final int MSG_BLUETOOTH_ENABLED = 14;
+       static final int MSG_MONITOR_IDLE_START= 15;
+       static final int MSG_MONITOR_IDLE_STOP = 16;
+       static final int MSG_REBOOT            = 17;
+       static final int MSG_IGNITER_QUERY     = 18;
+       static final int MSG_IGNITER_FIRE      = 19;
 
        // Unique Identification Number for the Notification.
        // We use it on Notification start, and to cancel it.
@@ -80,6 +85,13 @@ public class TelemetryService extends Service {
        // Last data seen; send to UI when it starts
        private TelemetryState  telemetry_state;
 
+       // Idle monitor if active
+       AltosIdleMonitor idle_monitor = null;
+
+       // Igniter bits
+       AltosIgnite ignite = null;
+       boolean ignite_running;
+
        // Handler of incoming messages from clients.
        static class IncomingHandler extends Handler {
                private final WeakReference<TelemetryService> service;
@@ -93,6 +105,7 @@ public class TelemetryService extends Service {
                        AltosDroidLink bt = null;
                        if (s == null)
                                return;
+
                        switch (msg.what) {
 
                                /* Messages from application */
@@ -212,6 +225,26 @@ public class TelemetryService extends Service {
                                if (address != null && !address.address.startsWith("USB"))
                                        s.start_altos_bluetooth(address, false);
                                break;
+                       case MSG_MONITOR_IDLE_START:
+                               AltosDebug.debug("start monitor idle");
+                               s.start_idle_monitor();
+                               break;
+                       case MSG_MONITOR_IDLE_STOP:
+                               AltosDebug.debug("stop monitor idle");
+                               s.stop_idle_monitor();
+                               break;
+                       case MSG_REBOOT:
+                               AltosDebug.debug("reboot");
+                               s.reboot_remote();
+                               break;
+                       case MSG_IGNITER_QUERY:
+                               AltosDebug.debug("igniter query");
+                               s.igniter_query(msg.replyTo);
+                               break;
+                       case MSG_IGNITER_FIRE:
+                               AltosDebug.debug("igniter fire");
+                               s.igniter_fire((String) msg.obj);
+                               break;
                        default:
                                super.handleMessage(msg);
                        }
@@ -255,6 +288,7 @@ public class TelemetryService extends Service {
                /* On connect, send the current state to the new client
                 */
                send_to_client(client);
+               send_idle_mode_to_client(client);
 
                /* If we've got an address from a previous session, then
                 * go ahead and try to reconnect to the device
@@ -296,17 +330,29 @@ public class TelemetryService extends Service {
                        send_to_client(client);
        }
 
-       private void disconnect(boolean notify) {
-               AltosDebug.debug("disconnect(): begin");
-
-               telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
-               telemetry_state.address = null;
+       private void send_idle_mode_to_client(Messenger client) {
+               Message m = Message.obtain(null, AltosDroid.MSG_IDLE_MODE, idle_monitor != null);
+               try {
+                       client.send(m);
+               } catch (RemoteException e) {
+                       AltosDebug.error("Client %s disappeared", client.toString());
+                       remove_client(client);
+               }
+       }
 
-               if (altos_link != null)
-                       altos_link.closing();
+       private void send_idle_mode_to_clients() {
+               for (Messenger client : clients)
+                       send_idle_mode_to_client(client);
+       }
 
-               stop_receiver_voltage_timer();
+       private void telemetry_start() {
+               if (telemetry_reader == null && idle_monitor == null && !ignite_running) {
+                       telemetry_reader = new TelemetryReader(altos_link, handler);
+                       telemetry_reader.start();
+               }
+       }
 
+       private void telemetry_stop() {
                if (telemetry_reader != null) {
                        AltosDebug.debug("disconnect(): stopping TelemetryReader");
                        telemetry_reader.interrupt();
@@ -316,6 +362,23 @@ public class TelemetryService extends Service {
                        }
                        telemetry_reader = null;
                }
+       }
+
+       private void disconnect(boolean notify) {
+               AltosDebug.debug("disconnect(): begin");
+
+               telemetry_state.connect = TelemetryState.CONNECT_DISCONNECTED;
+               telemetry_state.address = null;
+
+               if (idle_monitor != null)
+                       stop_idle_monitor();
+
+               if (altos_link != null)
+                       altos_link.closing();
+
+               stop_receiver_voltage_timer();
+
+               telemetry_stop();
                if (telemetry_logger != null) {
                        AltosDebug.debug("disconnect(): stopping TelemetryLogger");
                        telemetry_logger.stop();
@@ -325,6 +388,7 @@ public class TelemetryService extends Service {
                        AltosDebug.debug("disconnect(): stopping AltosDroidLink");
                        altos_link.close();
                        altos_link = null;
+                       ignite = null;
                }
                telemetry_state.config = null;
                if (notify) {
@@ -373,14 +437,95 @@ public class TelemetryService extends Service {
                send_to_clients();
        }
 
+       private void start_idle_monitor() {
+               if (altos_link != null && idle_monitor == null) {
+                       telemetry_stop();
+                       idle_monitor = new AltosIdleMonitor(this, altos_link, true, false);
+                       idle_monitor.set_callsign(AltosPreferences.callsign());
+                       idle_monitor.start();
+                       send_idle_mode_to_clients();
+               }
+       }
+
+       private void stop_idle_monitor() {
+               if (idle_monitor != null) {
+                       try {
+                               idle_monitor.abort();
+                       } catch (InterruptedException ie) {
+                       }
+                       idle_monitor = null;
+                       telemetry_start();
+                       send_idle_mode_to_clients();
+               }
+       }
+
+       private void reboot_remote() {
+               if (altos_link != null) {
+                       stop_idle_monitor();
+                       try {
+                               altos_link.start_remote();
+                               altos_link.printf("r eboot\n");
+                               altos_link.flush_output();
+                       } catch (TimeoutException te) {
+                       } catch (InterruptedException ie) {
+                       } finally {
+                               try {
+                                       altos_link.stop_remote();
+                               } catch (InterruptedException ie) {
+                               }
+                       }
+               }
+       }
+
+       private void ensure_ignite() {
+               if (ignite == null)
+                       ignite = new AltosIgnite(altos_link, true, false);
+       }
+
+       private synchronized void igniter_query(Messenger client) {
+               ensure_ignite();
+               HashMap<String,Integer> status_map = null;
+               ignite_running = true;
+               try {
+                       stop_idle_monitor();
+                       try {
+                               status_map = ignite.status();
+                       } catch (InterruptedException ie) {
+                               AltosDebug.debug("ignite.status interrupted");
+                       } catch (TimeoutException te) {
+                               AltosDebug.debug("ignite.status timeout");
+                       }
+               } finally {
+                       ignite_running = false;
+               }
+               Message m = Message.obtain(null, AltosDroid.MSG_IGNITER_STATUS, status_map);
+               try {
+                       client.send(m);
+               } catch (RemoteException e) {
+               }
+       }
+
+       private synchronized void igniter_fire(String igniter) {
+               ensure_ignite();
+               ignite_running = true;
+               stop_idle_monitor();
+               try {
+                       ignite.fire(igniter);
+               } catch (InterruptedException ie) {
+               } finally {
+                       ignite_running = false;
+               }
+       }
+
        // Timer for receiver battery voltage monitoring
        Timer receiver_voltage_timer;
 
        private void update_receiver_voltage() {
-               if (altos_link != null) {
+               if (altos_link != null && idle_monitor == null && !ignite_running) {
                        try {
                                double  voltage = altos_link.monitor_battery();
                                telemetry_state.receiver_battery = voltage;
+                               send_to_clients();
                        } catch (InterruptedException ie) {
                        }
                }
@@ -428,8 +573,7 @@ public class TelemetryService extends Service {
                telemetry_state.connect = TelemetryState.CONNECT_CONNECTED;
                telemetry_state.address = address;
 
-               telemetry_reader = new TelemetryReader(altos_link, handler);
-               telemetry_reader.start();
+               telemetry_start();
 
                AltosDebug.debug("connected TelemetryReader started");
 
@@ -549,4 +693,14 @@ public class TelemetryService extends Service {
        public IBinder onBind(Intent intent) {
                return messenger.getBinder();
        }
+
+       /* AltosIdleMonitorListener */
+       public void update(AltosState state, AltosListenerState listener_state) {
+               telemetry_state.states.put(state.serial, state);
+               telemetry_state.receiver_battery = listener_state.battery;
+               send_to_clients();
+       }
+
+       public void failed() {
+       }
 }
index d377fbfdc1a6f266921807e59aacf4372022c60a..bcf20ef36fe767c940767a9ee9c20e4499785791 100644 (file)
@@ -28,6 +28,7 @@ public class AltosIdleMonitor extends Thread {
        AltosIdleFetch          fetch;
 
        boolean                 remote;
+       boolean                 close_on_exit;
        double                  frequency;
        String                  callsign;
 
@@ -107,18 +108,25 @@ public class AltosIdleMonitor extends Thread {
                        }
                } catch (InterruptedException ie) {
                }
-               try {
-                       link.close();
-               } catch (InterruptedException ie) {
+               if (close_on_exit) {
+                       try {
+                               link.close();
+                       } catch (InterruptedException ie) {
+                       }
                }
        }
 
-       public AltosIdleMonitor(AltosIdleMonitorListener in_listener, AltosLink in_link, boolean in_remote)
-               throws FileNotFoundException, InterruptedException, TimeoutException {
+       public AltosIdleMonitor(AltosIdleMonitorListener in_listener, AltosLink in_link, boolean in_remote, boolean in_close_on_exit) {
                listener = in_listener;
                link = in_link;
                remote = in_remote;
+               close_on_exit = in_close_on_exit;
                listener_state = new AltosListenerState();
                fetch = new AltosIdleFetch(link);
        }
+
+       public AltosIdleMonitor(AltosIdleMonitorListener in_listener, AltosLink in_link, boolean in_remote) {
+               this(in_listener, in_link, in_remote, true);
+       }
 }
+
index 61873b22c78d8e5db4be485e3b2ac5db210a8bc1..ab9c2da667b72991d976b505324869382bd3865c 100644 (file)
@@ -24,6 +24,7 @@ import java.util.concurrent.*;
 public class AltosIgnite {
        AltosLink       link;
        boolean         remote;
+       boolean         close_on_exit;
        boolean         link_started;
        boolean         have_npyro = false;
        int             npyro;
@@ -180,14 +181,18 @@ public class AltosIgnite {
 
        public void close() throws InterruptedException {
                stop_link();
-               link.close();
+               if (close_on_exit)
+                       link.close();
                link = null;
        }
 
-       public AltosIgnite(AltosLink in_link, boolean in_remote)
-               throws FileNotFoundException, TimeoutException, InterruptedException {
-
+       public AltosIgnite(AltosLink in_link, boolean in_remote, boolean in_close_on_exit) {
                link = in_link;
                remote = in_remote;
+               close_on_exit = in_close_on_exit;
+       }
+
+       public AltosIgnite(AltosLink in_link, boolean in_remote) {
+               this(in_link, in_remote, true);
        }
-}
\ No newline at end of file
+}
index d3c178b7a6e6a095634ba35bd0fafe70dd64647c..73bab3efe99f8567b17a732acc3945c4f5d9e58c 100644 (file)
@@ -539,15 +539,15 @@ public abstract class AltosLink implements Runnable {
 
                if (config_data.has_monitor_battery()) {
                        try {
-                       String[] items = adc();
-                       for (int i = 0; i < items.length;) {
-                               if (items[i].equals("batt")) {
-                                       monitor_batt = Integer.parseInt(items[i+1]);
-                                       i += 2;
-                                       continue;
+                               String[] items = adc();
+                               for (int i = 0; i < items.length;) {
+                                       if (items[i].equals("batt")) {
+                                               monitor_batt = Integer.parseInt(items[i+1]);
+                                               i += 2;
+                                               continue;
+                                       }
+                                       i++;
                                }
-                               i++;
-                       }
                        } catch (TimeoutException te) {
                        }
                }