2 * Copyright © 2011 Keith Packard <keithp@keithp.com>
3 * Copyright © 2012 Mike Beattie <mike@ethernal.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 package org.altusmetrum.AltosDroid;
22 import android.speech.tts.TextToSpeech;
23 import android.speech.tts.TextToSpeech.OnInitListener;
24 import android.location.Location;
26 import org.altusmetrum.altoslib_12.*;
28 public class AltosVoice {
30 private TextToSpeech tts = null;
31 private boolean tts_enabled = false;
33 static final int TELL_MODE_NONE = 0;
34 static final int TELL_MODE_PAD = 1;
35 static final int TELL_MODE_FLIGHT = 2;
36 static final int TELL_MODE_RECOVER = 3;
38 static final int TELL_FLIGHT_NONE = 0;
39 static final int TELL_FLIGHT_STATE = 1;
40 static final int TELL_FLIGHT_SPEED = 2;
41 static final int TELL_FLIGHT_HEIGHT = 3;
42 static final int TELL_FLIGHT_TRACK = 4;
44 private int last_tell_mode;
45 private int last_tell_serial = AltosLib.MISSING;
46 private int last_state;
47 private AltosGPS last_gps;
48 private double last_height = AltosLib.MISSING;
49 private Location last_receiver;
50 private long last_speak_time;
51 private int last_flight_tell = TELL_FLIGHT_NONE;
52 private boolean quiet = false;
55 return System.currentTimeMillis();
58 private void reset_last() {
59 last_tell_mode = TELL_MODE_NONE;
60 last_speak_time = now() - 100 * 1000;
62 last_height = AltosLib.MISSING;
64 last_state = AltosLib.ao_flight_invalid;
65 last_flight_tell = TELL_FLIGHT_NONE;
68 public AltosVoice(AltosDroid a) {
69 tts = new TextToSpeech(a, new OnInitListener() {
70 public void onInit(int status) {
71 if (status == TextToSpeech.SUCCESS) tts_enabled = true;
77 public synchronized void set_enable(boolean enable) {
81 public synchronized void speak(String s) {
82 if (!tts_enabled) return;
83 last_speak_time = now();
85 tts.speak(s, TextToSpeech.QUEUE_ADD, null);
88 public synchronized long time_since_speak() {
89 return now() - last_speak_time;
92 public synchronized void speak(String format, Object ... arguments) {
93 speak(String.format(format, arguments));
96 public synchronized boolean is_speaking() {
97 return tts.isSpeaking();
107 private boolean last_apogee_good;
108 private boolean last_main_good;
109 private boolean last_gps_good;
111 private boolean tell_gonogo(String name,
115 if (current != previous || new_mode)
116 speak("%s %s.", name, current ? "ready" : "not ready");
120 private boolean tell_pad(TelemetryState telem_state, AltosState state,
121 AltosGreatCircle from_receiver, Location receiver) {
126 AltosDebug.debug("tell_pad lag %b ltm %d\n", last_apogee_good, last_tell_mode);
128 if (state.apogee_voltage != AltosLib.MISSING)
129 last_apogee_good = tell_gonogo("apogee",
130 state.apogee_voltage >= AltosLib.ao_igniter_good,
132 last_tell_mode != TELL_MODE_PAD);
134 if (state.main_voltage != AltosLib.MISSING)
135 last_main_good = tell_gonogo("main",
136 state.main_voltage >= AltosLib.ao_igniter_good,
138 last_tell_mode != TELL_MODE_PAD);
140 if (state.gps != null)
141 last_gps_good = tell_gonogo("G P S",
144 last_tell_mode != TELL_MODE_PAD);
149 private boolean descending(int state) {
150 return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
153 private boolean target_moved(AltosState state) {
154 if (last_gps != null && state != null && state.gps != null) {
155 AltosGreatCircle moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
156 state.gps.lat, state.gps.lon, state.gps.alt);
157 double height_change = 0;
158 double height = state.height();
160 if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
161 height_change = Math.abs(last_height - height);
163 if (moved.range < 10 && height_change < 10)
169 private boolean receiver_moved(Location receiver) {
170 if (last_receiver != null && receiver != null) {
171 AltosGreatCircle moved = new AltosGreatCircle(last_receiver.getLatitude(),
172 last_receiver.getLongitude(),
173 last_receiver.getAltitude(),
174 receiver.getLatitude(),
175 receiver.getLongitude(),
176 receiver.getAltitude());
177 if (moved.range < 10)
183 private boolean tell_flight(TelemetryState telem_state, AltosState state,
184 AltosGreatCircle from_receiver, Location receiver) {
186 boolean spoken = false;
191 if (last_tell_mode != TELL_MODE_FLIGHT)
192 last_flight_tell = TELL_FLIGHT_NONE;
194 if (state.state() != last_state && AltosLib.ao_flight_boost <= state.state() && state.state() <= AltosLib.ao_flight_landed) {
195 speak(state.state_name());
196 if (descending(state.state()) && !descending(last_state)) {
197 if (state.max_height() != AltosLib.MISSING) {
198 speak("max height: %s.",
199 AltosConvert.height.say_units(state.max_height()));
202 last_flight_tell = TELL_FLIGHT_STATE;
206 if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
207 if (time_since_speak() < 10 * 1000)
209 if (!target_moved(state) && !receiver_moved(receiver))
216 if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
217 last_flight_tell = TELL_FLIGHT_SPEED;
219 if (state.state() <= AltosLib.ao_flight_coast) {
220 speed = state.speed();
222 speed = state.gps_speed();
223 if (speed == AltosLib.MISSING)
224 speed = state.speed();
227 if (speed != AltosLib.MISSING) {
228 speak("speed: %s.", AltosConvert.speed.say_units(speed));
233 if (last_flight_tell == TELL_FLIGHT_SPEED) {
234 last_flight_tell = TELL_FLIGHT_HEIGHT;
235 height = state.height();
237 if (height != AltosLib.MISSING) {
238 speak("height: %s.", AltosConvert.height.say_units(height));
243 if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
244 last_flight_tell = TELL_FLIGHT_TRACK;
245 if (from_receiver != null) {
246 speak("bearing %s %d, elevation %d, distance %s.",
247 from_receiver.bearing_words(
248 AltosGreatCircle.BEARING_VOICE),
249 (int) (from_receiver.bearing + 0.5),
250 (int) (from_receiver.elevation + 0.5),
251 AltosConvert.distance.say(from_receiver.distance));
259 private boolean tell_recover(TelemetryState telem_state, AltosState state,
260 AltosGreatCircle from_receiver, Location receiver) {
262 if (from_receiver == null)
265 if (last_tell_mode == TELL_MODE_RECOVER) {
266 if (!target_moved(state) && !receiver_moved(receiver))
268 if (time_since_speak() <= 10 * 1000)
272 String direction = AltosDroid.direction(from_receiver, receiver);
273 if (direction == null)
274 direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
276 speak("%s, distance %s.", direction,
277 AltosConvert.distance.say_units(from_receiver.distance));
282 public void tell(TelemetryState telem_state, AltosState state,
283 AltosGreatCircle from_receiver, Location receiver,
284 AltosDroidTab tab, boolean quiet) {
288 boolean spoken = false;
290 if (!tts_enabled) return;
292 if (is_speaking()) return;
294 int tell_serial = last_tell_serial;
297 tell_serial = state.cal_data().serial;
299 if (tell_serial != last_tell_serial)
302 int tell_mode = TELL_MODE_NONE;
304 if (tab.tab_name().equals(AltosDroid.tab_pad_name))
305 tell_mode = TELL_MODE_PAD;
306 else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
307 tell_mode = TELL_MODE_FLIGHT;
309 tell_mode = TELL_MODE_RECOVER;
311 if (tell_mode == TELL_MODE_PAD)
312 spoken = tell_pad(telem_state, state, from_receiver, receiver);
313 else if (tell_mode == TELL_MODE_FLIGHT)
314 spoken = tell_flight(telem_state, state, from_receiver, receiver);
316 spoken = tell_recover(telem_state, state, from_receiver, receiver);
319 last_tell_mode = tell_mode;
320 last_tell_serial = tell_serial;
322 last_state = state.state();
323 last_height = state.height();
324 if (state.gps != null)
325 last_gps = state.gps;
327 if (receiver != null)
328 last_receiver = receiver;