From: Keith Packard Date: Wed, 24 Jun 2015 04:39:09 +0000 (-0700) Subject: altosdroid: Change voice output around X-Git-Tag: 1.6.0.3~6 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=bfa6cd8934b993bd4a67cfc7a4eeecf9b11915ef altosdroid: Change voice output around 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 --- diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index c2a6cb29..db7815f3 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -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; } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java index e79972e3..9e953ffe 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java @@ -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; + } + } }