altosdroid: Change voice output around
authorKeith Packard <keithp@keithp.com>
Wed, 24 Jun 2015 04:39:09 +0000 (21:39 -0700)
committerKeith Packard <keithp@keithp.com>
Wed, 24 Jun 2015 04:42:35 +0000 (21:42 -0700)
This makes the voice output depend on the current displayed tab; where
the 'recovery' and 'map' tabs get the same value.

Pad

Reports igniter and GPS status changes

Flight

Report flight state changes and max height after apogee
Report current speed, height and bearing/elevation/range once
every 10 seconds while the rocket is in motion.

Recovery

Report distance and bearing when the location of either the
tracker or the receiver changes by more than 10m, but not more
than once every 10 seconds.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java

index c2a6cb2..db7815f 100644 (file)
@@ -466,8 +466,8 @@ public class AltosDroid extends FragmentActivity implements AltosUnitsListener {
                for (AltosDroidTab mTab : mTabs)
                        mTab.update_ui(telem_state, state, from_receiver, location, mTab == mTabsAdapter.currentItem());
 
-               if (state != null && mAltosVoice != null)
-                       mAltosVoice.tell(state, from_receiver);
+               if (mAltosVoice != null)
+                       mAltosVoice.tell(telem_state, state, from_receiver, location, (AltosDroidTab) mTabsAdapter.currentItem());
 
                saved_state = state;
        }
index e79972e..9e953ff 100644 (file)
@@ -20,6 +20,7 @@ package org.altusmetrum.AltosDroid;
 
 import android.speech.tts.TextToSpeech;
 import android.speech.tts.TextToSpeech.OnInitListener;
+import android.location.Location;
 
 import org.altusmetrum.altoslib_7.*;
 
@@ -28,193 +29,297 @@ 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;
-                       }
-               }
-               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;
-                       }
+       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;
                }
-               old_state = state;
-               if (idle_thread != null)
-                       idle_thread.notice(state, from_receiver, spoke);
+               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;
+               }
+               return true;
+       }
 
-       class IdleThread extends Thread {
-               boolean            started;
-               private AltosState state;
-               private AltosGreatCircle from_receiver;
-               int                reported_landing;
-               int                report_interval;
-               long               report_time;
+       private boolean tell_flight(TelemetryState telem_state, AltosState state,
+                                   AltosGreatCircle from_receiver, Location receiver) {
 
-               public synchronized void report(boolean last) {
-                       if (state == null)
-                               return;
+               boolean spoken = false;
 
-                       /* reset the landing count once we hear about a new flight */
-                       if (state.state < AltosLib.ao_flight_drogue)
-                               reported_landing = 0;
+               if (state == null)
+                       return false;
 
-                       /* 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) {
+                       if (time_since_speak() < 10 * 1000 && last_flight_tell == TELL_FLIGHT_TRACK)
+                               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;
+
+               AltosDebug.debug("last_serial %d serial %d last_mode %d mode %d\n",
+                                last_tell_serial, tell_serial,
+                                last_tell_mode, tell_mode);
+
+               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;
+               }
+       }
 }