8631023c3a76f9bab278297a5697c443d8c95657
[fw/altos] / altosdroid / app / src / main / java / org / altusmetrum / AltosDroid / AltosVoice.java
1 /*
2  * Copyright © 2011 Keith Packard <keithp@keithp.com>
3  * Copyright © 2012 Mike Beattie <mike@ethernal.org>
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 package org.altusmetrum.AltosDroid;
21
22 import android.speech.tts.TextToSpeech;
23 import android.speech.tts.TextToSpeech.OnInitListener;
24 import android.location.Location;
25
26 import org.altusmetrum.altoslib_13.*;
27
28 public class AltosVoice {
29
30         private TextToSpeech tts         = null;
31         private boolean      tts_enabled = false;
32
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;
37
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;
43
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;
53
54         private long now() {
55                 return System.currentTimeMillis();
56         }
57
58         private void reset_last() {
59                 last_tell_mode = TELL_MODE_NONE;
60                 last_speak_time = now() - 100 * 1000;
61                 last_gps = null;
62                 last_height = AltosLib.MISSING;
63                 last_receiver = null;
64                 last_state = AltosLib.ao_flight_invalid;
65                 last_flight_tell = TELL_FLIGHT_NONE;
66         }
67
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;
72                         }
73                 });
74                 reset_last();
75         }
76
77         public synchronized void set_enable(boolean enable) {
78                 tts_enabled = enable;
79         }
80
81         public synchronized void speak(String s) {
82                 if (!tts_enabled) return;
83                 last_speak_time = now();
84                 if (!quiet)
85                         tts.speak(s, TextToSpeech.QUEUE_ADD, null);
86         }
87
88         public synchronized long time_since_speak() {
89                 return now() - last_speak_time;
90         }
91
92         public synchronized void speak(String format, Object ... arguments) {
93                 speak(String.format(format, arguments));
94         }
95
96         public synchronized boolean is_speaking() {
97                 return tts.isSpeaking();
98         }
99
100         public void stop() {
101                 if (tts != null) {
102                         tts.stop();
103                         tts.shutdown();
104                 }
105         }
106
107         private boolean         last_apogee_good;
108         private boolean         last_main_good;
109         private boolean         last_gps_good;
110
111         private boolean tell_gonogo(String name,
112                                   boolean current,
113                                   boolean previous,
114                                   boolean new_mode) {
115                 if (current != previous || new_mode)
116                         speak("%s %s.", name, current ? "ready" : "not ready");
117                 return current;
118         }
119
120         private boolean tell_pad(TelemetryState telem_state, AltosState state,
121                               AltosGreatCircle from_receiver, Location receiver) {
122
123                 if (state == null)
124                         return false;
125
126                 if (state.apogee_voltage != AltosLib.MISSING)
127                         last_apogee_good = tell_gonogo("apogee",
128                                                        state.apogee_voltage >= AltosLib.ao_igniter_good,
129                                                        last_apogee_good,
130                                                        last_tell_mode != TELL_MODE_PAD);
131
132                 if (state.main_voltage != AltosLib.MISSING)
133                         last_main_good = tell_gonogo("main",
134                                                      state.main_voltage >= AltosLib.ao_igniter_good,
135                                                      last_main_good,
136                                                      last_tell_mode != TELL_MODE_PAD);
137
138                 if (state.gps != null)
139                         last_gps_good = tell_gonogo("G P S",
140                                                     state.gps_ready,
141                                                     last_gps_good,
142                                                     last_tell_mode != TELL_MODE_PAD);
143                 return true;
144         }
145
146
147         private boolean descending(int state) {
148                 return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
149         }
150
151         private boolean target_moved(AltosState state) {
152                 if (last_gps != null && state != null && state.gps != null) {
153                         AltosGreatCircle        moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
154                                                                              state.gps.lat, state.gps.lon, state.gps.alt);
155                         double                  height_change = 0;
156                         double                  height = state.height();
157
158                         if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
159                                 height_change = Math.abs(last_height - height);
160
161                         if (moved.range < 10 && height_change < 10)
162                                 return false;
163                 }
164                 return true;
165         }
166
167         private boolean receiver_moved(Location receiver) {
168                 if (last_receiver != null && receiver != null) {
169                         AltosGreatCircle        moved = new AltosGreatCircle(last_receiver.getLatitude(),
170                                                                              last_receiver.getLongitude(),
171                                                                              last_receiver.getAltitude(),
172                                                                              receiver.getLatitude(),
173                                                                              receiver.getLongitude(),
174                                                                              receiver.getAltitude());
175                         if (moved.range < 10)
176                                 return false;
177                 }
178                 return true;
179         }
180
181         private boolean tell_flight(TelemetryState telem_state, AltosState state,
182                                     AltosGreatCircle from_receiver, Location receiver) {
183
184                 boolean spoken = false;
185
186                 if (state == null)
187                         return false;
188
189                 if (last_tell_mode != TELL_MODE_FLIGHT)
190                         last_flight_tell = TELL_FLIGHT_NONE;
191
192                 if (state.state() != last_state && AltosLib.ao_flight_boost <= state.state() && state.state() <= AltosLib.ao_flight_landed) {
193                         speak(state.state_name());
194                         if (descending(state.state()) && !descending(last_state)) {
195                                 if (state.max_height() != AltosLib.MISSING) {
196                                         speak("max height: %s.",
197                                               AltosConvert.height.say_units(state.max_height()));
198                                 }
199                         }
200                         last_flight_tell = TELL_FLIGHT_STATE;
201                         return true;
202                 }
203
204                 if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
205                         if (time_since_speak() < 10 * 1000)
206                                 return false;
207                         if (!target_moved(state) && !receiver_moved(receiver))
208                                 return false;
209                 }
210
211                 double  speed;
212                 double  height;
213
214                 if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
215                         last_flight_tell = TELL_FLIGHT_SPEED;
216
217                         if (state.state() <= AltosLib.ao_flight_coast) {
218                                 speed = state.speed();
219                         } else {
220                                 speed = state.gps_speed();
221                                 if (speed == AltosLib.MISSING)
222                                         speed = state.speed();
223                         }
224
225                         if (speed != AltosLib.MISSING) {
226                                 speak("speed: %s.", AltosConvert.speed.say_units(speed));
227                                 return true;
228                         }
229                 }
230
231                 if (last_flight_tell == TELL_FLIGHT_SPEED) {
232                         last_flight_tell = TELL_FLIGHT_HEIGHT;
233                         height = state.height();
234
235                         if (height != AltosLib.MISSING) {
236                                 speak("height: %s.", AltosConvert.height.say_units(height));
237                                 return true;
238                         }
239                 }
240
241                 if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
242                         last_flight_tell = TELL_FLIGHT_TRACK;
243                         if (from_receiver != null) {
244                                 speak("bearing %s %d, elevation %d, distance %s.",
245                                       from_receiver.bearing_words(
246                                               AltosGreatCircle.BEARING_VOICE),
247                                       (int) (from_receiver.bearing + 0.5),
248                                       (int) (from_receiver.elevation + 0.5),
249                                       AltosConvert.distance.say(from_receiver.distance));
250                                 return true;
251                         }
252                 }
253
254                 return spoken;
255         }
256
257         private boolean tell_recover(TelemetryState telem_state, AltosState state,
258                                      AltosGreatCircle from_receiver, Location receiver) {
259
260                 if (from_receiver == null)
261                         return false;
262
263                 if (last_tell_mode == TELL_MODE_RECOVER) {
264                         if (!target_moved(state) && !receiver_moved(receiver))
265                                 return false;
266                         if (time_since_speak() <= 10 * 1000)
267                                 return false;
268                 }
269
270                 String direction = AltosDroid.direction(from_receiver, receiver);
271                 if (direction == null)
272                         direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
273
274                 speak("%s, distance %s.", direction,
275                       AltosConvert.distance.say_units(from_receiver.distance));
276
277                 return true;
278         }
279
280         public void tell(TelemetryState telem_state, AltosState state,
281                          AltosGreatCircle from_receiver, Location receiver,
282                          AltosDroidTab tab, boolean quiet) {
283
284                 this.quiet = quiet;
285
286                 boolean spoken = false;
287
288                 if (!tts_enabled) return;
289
290                 if (is_speaking()) return;
291
292                 int     tell_serial = last_tell_serial;
293
294                 if (state != null)
295                         tell_serial = state.cal_data().serial;
296
297                 if (tell_serial != last_tell_serial)
298                         reset_last();
299
300                 int     tell_mode = TELL_MODE_NONE;
301
302                 if (tab.tab_name().equals(AltosDroid.tab_pad_name))
303                         tell_mode = TELL_MODE_PAD;
304                 else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
305                         tell_mode = TELL_MODE_FLIGHT;
306                 else
307                         tell_mode = TELL_MODE_RECOVER;
308
309                 if (tell_mode == TELL_MODE_PAD)
310                         spoken = tell_pad(telem_state, state, from_receiver, receiver);
311                 else if (tell_mode == TELL_MODE_FLIGHT)
312                         spoken = tell_flight(telem_state, state, from_receiver, receiver);
313                 else
314                         spoken = tell_recover(telem_state, state, from_receiver, receiver);
315
316                 if (spoken) {
317                         last_tell_mode = tell_mode;
318                         last_tell_serial = tell_serial;
319                         if (state != null) {
320                                 last_state = state.state();
321                                 last_height = state.height();
322                                 if (state.gps != null)
323                                         last_gps = state.gps;
324                         }
325                         if (receiver != null)
326                                 last_receiver = receiver;
327                 }
328         }
329 }