Bump Java library versions
[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_8.*;
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 (last_tell_mode != TELL_MODE_FLIGHT)
187                         last_flight_tell = TELL_FLIGHT_NONE;
188
189                 if (state.state != last_state && AltosLib.ao_flight_boost <= state.state && state.state <= AltosLib.ao_flight_landed) {
190                         speak(state.state_name());
191                         if (descending(state.state) && !descending(last_state)) {
192                                 if (state.max_height() != AltosLib.MISSING) {
193                                         speak("max height: %s.",
194                                               AltosConvert.height.say_units(state.max_height()));
195                                 }
196                         }
197                         last_flight_tell = TELL_FLIGHT_STATE;
198                         return true;
199                 }
200
201                 if (last_tell_mode == TELL_MODE_FLIGHT && last_flight_tell == TELL_FLIGHT_TRACK) {
202                         if (time_since_speak() < 10 * 1000)
203                                 return false;
204                         if (!target_moved(state) && !receiver_moved(receiver))
205                                 return false;
206                 }
207
208                 double  speed;
209                 double  height;
210
211                 if (last_flight_tell == TELL_FLIGHT_NONE || last_flight_tell == TELL_FLIGHT_STATE || last_flight_tell == TELL_FLIGHT_TRACK) {
212                         last_flight_tell = TELL_FLIGHT_SPEED;
213
214                         if (state.state <= AltosLib.ao_flight_coast) {
215                                 speed = state.speed();
216                         } else {
217                                 speed = state.gps_speed();
218                                 if (speed == AltosLib.MISSING)
219                                         speed = state.speed();
220                         }
221
222                         if (speed != AltosLib.MISSING) {
223                                 speak("speed: %s.", AltosConvert.speed.say_units(speed));
224                                 return true;
225                         }
226                 }
227
228                 if (last_flight_tell == TELL_FLIGHT_SPEED) {
229                         last_flight_tell = TELL_FLIGHT_HEIGHT;
230                         height = state.height();
231
232                         if (height != AltosLib.MISSING) {
233                                 speak("height: %s.", AltosConvert.height.say_units(height));
234                                 return true;
235                         }
236                 }
237
238                 if (last_flight_tell == TELL_FLIGHT_HEIGHT) {
239                         last_flight_tell = TELL_FLIGHT_TRACK;
240                         if (from_receiver != null) {
241                                 speak("bearing %s %d, elevation %d, range %s.",
242                                       from_receiver.bearing_words(
243                                               AltosGreatCircle.BEARING_VOICE),
244                                       (int) (from_receiver.bearing + 0.5),
245                                       (int) (from_receiver.elevation + 0.5),
246                                       AltosConvert.distance.say(from_receiver.range));
247                                 return true;
248                         }
249                 }
250
251                 return spoken;
252         }
253
254         private boolean tell_recover(TelemetryState telem_state, AltosState state,
255                                      AltosGreatCircle from_receiver, Location receiver) {
256
257                 if (from_receiver == null)
258                         return false;
259
260                 if (last_tell_mode == TELL_MODE_RECOVER) {
261                         if (!target_moved(state) && !receiver_moved(receiver))
262                                 return false;
263                         if (time_since_speak() <= 10 * 1000)
264                                 return false;
265                 }
266
267                 String direction = AltosDroid.direction(from_receiver, receiver);
268                 if (direction == null)
269                         direction = String.format("Bearing %d", (int) (from_receiver.bearing + 0.5));
270
271                 speak("%s, range %s.", direction,
272                       AltosConvert.distance.say_units(from_receiver.distance));
273
274                 return true;
275         }
276
277         public void tell(TelemetryState telem_state, AltosState state,
278                          AltosGreatCircle from_receiver, Location receiver,
279                          AltosDroidTab tab) {
280
281                 boolean spoken = false;
282
283                 if (!tts_enabled) return;
284
285                 if (is_speaking()) return;
286
287                 int     tell_serial = last_tell_serial;
288
289                 if (state != null)
290                         tell_serial = state.serial;
291
292                 if (tell_serial != last_tell_serial)
293                         reset_last();
294
295                 int     tell_mode = TELL_MODE_NONE;
296
297                 if (tab.tab_name().equals(AltosDroid.tab_pad_name))
298                         tell_mode = TELL_MODE_PAD;
299                 else if (tab.tab_name().equals(AltosDroid.tab_flight_name))
300                         tell_mode = TELL_MODE_FLIGHT;
301                 else
302                         tell_mode = TELL_MODE_RECOVER;
303
304                 if (tell_mode == TELL_MODE_PAD)
305                         spoken = tell_pad(telem_state, state, from_receiver, receiver);
306                 else if (tell_mode == TELL_MODE_FLIGHT)
307                         spoken = tell_flight(telem_state, state, from_receiver, receiver);
308                 else
309                         spoken = tell_recover(telem_state, state, from_receiver, receiver);
310
311                 if (spoken) {
312                         last_tell_mode = tell_mode;
313                         last_tell_serial = tell_serial;
314                         if (state != null) {
315                                 last_state = state.state;
316                                 last_height = state.height();
317                                 if (state.gps != null)
318                                         last_gps = state.gps;
319                         }
320                         if (receiver != null)
321                                 last_receiver = receiver;
322                 }
323         }
324 }