9e953ffe7fc967a779704a73b800ad9218969785
[fw/altos] / altosdroid / src / 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; version 2 of the License.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.AltosDroid;
20
21 import android.speech.tts.TextToSpeech;
22 import android.speech.tts.TextToSpeech.OnInitListener;
23 import android.location.Location;
24
25 import org.altusmetrum.altoslib_7.*;
26
27 public class AltosVoice {
28
29         private TextToSpeech tts         = null;
30         private boolean      tts_enabled = false;
31
32         static final int TELL_MODE_NONE = 0;
33         static final int TELL_MODE_PAD = 1;
34         static final int TELL_MODE_FLIGHT = 2;
35         static final int TELL_MODE_RECOVER = 3;
36
37         static final int TELL_FLIGHT_NONE = 0;
38         static final int TELL_FLIGHT_STATE = 1;
39         static final int TELL_FLIGHT_SPEED = 2;
40         static final int TELL_FLIGHT_HEIGHT = 3;
41         static final int TELL_FLIGHT_TRACK = 4;
42
43         private int             last_tell_mode;
44         private int             last_tell_serial = AltosLib.MISSING;
45         private int             last_state;
46         private AltosGPS        last_gps;
47         private double          last_height = AltosLib.MISSING;
48         private Location        last_receiver;
49         private long            last_speak_time;
50         private int             last_flight_tell = TELL_FLIGHT_NONE;
51
52         private long now() {
53                 return System.currentTimeMillis();
54         }
55
56         private void reset_last() {
57                 last_tell_mode = TELL_MODE_NONE;
58                 last_speak_time = now() - 100 * 1000;
59                 last_gps = null;
60                 last_height = AltosLib.MISSING;
61                 last_receiver = null;
62                 last_state = AltosLib.ao_flight_invalid;
63                 last_flight_tell = TELL_FLIGHT_NONE;
64         }
65
66         public AltosVoice(AltosDroid a) {
67                 tts = new TextToSpeech(a, new OnInitListener() {
68                         public void onInit(int status) {
69                                 if (status == TextToSpeech.SUCCESS) tts_enabled = true;
70                         }
71                 });
72                 reset_last();
73         }
74
75         public synchronized void set_enable(boolean enable) {
76                 tts_enabled = enable;
77         }
78
79         public synchronized void speak(String s) {
80                 if (!tts_enabled) return;
81                 last_speak_time = now();
82                 tts.speak(s, TextToSpeech.QUEUE_ADD, null);
83         }
84
85         public synchronized long time_since_speak() {
86                 return now() - last_speak_time;
87         }
88
89         public synchronized void speak(String format, Object ... arguments) {
90                 speak(String.format(format, arguments));
91         }
92
93         public synchronized boolean is_speaking() {
94                 return tts.isSpeaking();
95         }
96
97         public void stop() {
98                 if (tts != null) {
99                         tts.stop();
100                         tts.shutdown();
101                 }
102         }
103
104         private boolean         last_apogee_good;
105         private boolean         last_main_good;
106         private boolean         last_gps_good;
107
108         private boolean tell_gonogo(String name,
109                                   boolean current,
110                                   boolean previous,
111                                   boolean new_mode) {
112                 if (current != previous || new_mode)
113                         speak("%s %s.", name, current ? "ready" : "not ready");
114                 return current;
115         }
116
117         private boolean tell_pad(TelemetryState telem_state, AltosState state,
118                               AltosGreatCircle from_receiver, Location receiver) {
119
120                 if (state == null)
121                         return false;
122
123                 if (state.apogee_voltage != AltosLib.MISSING)
124                         last_apogee_good = tell_gonogo("apogee",
125                                                        state.apogee_voltage >= AltosLib.ao_igniter_good,
126                                                        last_apogee_good,
127                                                        last_tell_mode != TELL_MODE_PAD);
128
129                 if (state.main_voltage != AltosLib.MISSING)
130                         last_main_good = tell_gonogo("main",
131                                                      state.main_voltage >= AltosLib.ao_igniter_good,
132                                                      last_main_good,
133                                                      last_tell_mode != TELL_MODE_PAD);
134
135                 if (state.gps != null)
136                         last_gps_good = tell_gonogo("G P S",
137                                                     state.gps_ready,
138                                                     last_gps_good,
139                                                     last_tell_mode != TELL_MODE_PAD);
140                 return true;
141         }
142
143
144         private boolean descending(int state) {
145                 return AltosLib.ao_flight_drogue <= state && state <= AltosLib.ao_flight_landed;
146         }
147
148         private boolean target_moved(AltosState state) {
149                 if (last_gps != null && state != null && state.gps != null) {
150                         AltosGreatCircle        moved = new AltosGreatCircle(last_gps.lat, last_gps.lon, last_gps.alt,
151                                                                              state.gps.lat, state.gps.lon, state.gps.alt);
152                         double                  height_change = 0;
153                         double                  height = state.height();
154
155                         if (height != AltosLib.MISSING && last_height != AltosLib.MISSING)
156                                 height_change = Math.abs(last_height - height);
157
158                         if (moved.range < 10 && height_change < 10)
159                                 return false;
160                 }
161                 return true;
162         }
163
164         private boolean receiver_moved(Location receiver) {
165                 if (last_receiver != null && receiver != null) {
166                         AltosGreatCircle        moved = new AltosGreatCircle(last_receiver.getLatitude(),
167                                                                              last_receiver.getLongitude(),
168                                                                              last_receiver.getAltitude(),
169                                                                              receiver.getLatitude(),
170                                                                              receiver.getLongitude(),
171                                                                              receiver.getAltitude());
172                         if (moved.range < 10)
173                                 return false;
174                 }
175                 return true;
176         }
177
178         private boolean tell_flight(TelemetryState telem_state, AltosState state,
179                                     AltosGreatCircle from_receiver, Location receiver) {
180
181                 boolean spoken = false;
182
183                 if (state == null)
184                         return false;
185
186                 if (state.state != last_state && AltosLib.ao_flight_boost <= state.state && state.state <= AltosLib.ao_flight_landed) {
187                         speak(state.state_name());
188                         if (descending(state.state) && !descending(last_state)) {
189                                 if (state.max_height() != AltosLib.MISSING) {
190                                         speak("max height: %s.",
191                                               AltosConvert.height.say_units(state.max_height()));
192                                 }
193                         }
194                         last_flight_tell = TELL_FLIGHT_STATE;
195                         return true;
196                 }
197
198                 if (last_tell_mode == TELL_MODE_FLIGHT) {
199                         if (time_since_speak() < 10 * 1000 && last_flight_tell == TELL_FLIGHT_TRACK)
200                                 return false;
201                         if (!target_moved(state) && !receiver_moved(receiver))
202                                 return false;
203                 }
204
205                 double  speed;
206                 double  height;
207
208                 if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
209                         last_flight_tell = TELL_FLIGHT_SPEED;
210
211                         if (state.state <= AltosLib.ao_flight_coast) {
212                                 speed = state.speed();
213                         } else {
214                                 speed = state.gps_speed();
215                                 if (speed == AltosLib.MISSING)
216                                         speed = state.speed();
217                         }
218
219                         if (speed != AltosLib.MISSING) {
220                                 speak("speed: %s.", AltosConvert.speed.say_units(speed));
221                                 return true;
222                         }
223                 }
224
225                 if (last_flight_tell == TELL_FLIGHT_SPEED) {
226                         last_flight_tell = TELL_FLIGHT_HEIGHT;
227                         height = state.height();
228
229                         if (height != AltosLib.MISSING) {
230                                 speak("height: %s.", AltosConvert.height.say_units(height));
231                                 return true;
232                         }
233                 }
234
235                 if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
236                         last_flight_tell = TELL_FLIGHT_TRACK;
237                         if (from_receiver != null) {
238                                 speak("bearing %s %d, elevation %d, range %s.",
239                                       from_receiver.bearing_words(
240                                               AltosGreatCircle.BEARING_VOICE),
241                                       (int) (from_receiver.bearing + 0.5),
242                                       (int) (from_receiver.elevation + 0.5),
243                                       AltosConvert.distance.say(from_receiver.range));
244                                 return true;
245                         }
246                 }
247
248                 return spoken;
249         }
250
251         private boolean tell_recover(TelemetryState telem_state, AltosState state,
252                                      AltosGreatCircle from_receiver, Location receiver) {
253
254                 if (from_receiver == null)
255                         return false;
256
257                 if (last_tell_mode == TELL_MODE_RECOVER) {
258                         if (!target_moved(state) && !receiver_moved(receiver))
259                                 return false;
260                         if (time_since_speak() <= 10 * 1000)
261                                 return false;
262                 }
263
264                 String direction = AltosDroid.direction(from_receiver, receiver);
265                 if (direction == null)
266                         direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
267
268                 speak("%s, range %s.", direction,
269                       AltosConvert.distance.say_units(from_receiver.distance));
270
271                 return true;
272         }
273
274         public void tell(TelemetryState telem_state, AltosState state,
275                          AltosGreatCircle from_receiver, Location receiver,
276                          AltosDroidTab tab) {
277
278                 boolean spoken = false;
279
280                 if (!tts_enabled) return;
281
282                 if (is_speaking()) return;
283
284                 int     tell_serial = last_tell_serial;
285
286                 if (state != null)
287                         tell_serial = state.serial;
288
289                 if (tell_serial != last_tell_serial)
290                         reset_last();
291
292                 int     tell_mode = TELL_MODE_NONE;
293
294                 if (tab.tab_name().equals(AltosDroid.tab_pad_name))
295                         tell_mode = TELL_MODE_PAD;
296                 else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
297                         tell_mode = TELL_MODE_FLIGHT;
298                 else
299                         tell_mode = TELL_MODE_RECOVER;
300
301                 AltosDebug.debug("last_serial %d serial %d last_mode %d mode %d\n",
302                                  last_tell_serial, tell_serial,
303                                  last_tell_mode, tell_mode);
304
305                 if (tell_mode == TELL_MODE_PAD)
306                         spoken = tell_pad(telem_state, state, from_receiver, receiver);
307                 else if (tell_mode == TELL_MODE_FLIGHT)
308                         spoken = tell_flight(telem_state, state, from_receiver, receiver);
309                 else
310                         spoken = tell_recover(telem_state, state, from_receiver, receiver);
311
312                 if (spoken) {
313                         last_tell_mode = tell_mode;
314                         last_tell_serial = tell_serial;
315                         if (state != null) {
316                                 last_state = state.state;
317                                 last_height = state.height();
318                                 if (state.gps != null)
319                                         last_gps = state.gps;
320                         }
321                         if (receiver != null)
322                                 last_receiver = receiver;
323                 }
324         }
325 }