b15472edf7393a5764b6066cc7ae8a124609249d
[fw/altos] / ao-tools / altosui / AltosDisplayThread.java
1 /*
2  * Copyright © 2010 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 package altosui;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import javax.swing.*;
23 import javax.swing.filechooser.FileNameExtensionFilter;
24 import javax.swing.table.*;
25 import java.io.*;
26 import java.util.*;
27 import java.text.*;
28 import java.util.prefs.*;
29 import java.util.concurrent.LinkedBlockingQueue;
30
31 public class AltosDisplayThread extends Thread {
32
33         Frame                   parent;
34         IdleThread              idle_thread;
35         AltosVoice              voice;
36         String                  name;
37         AltosFlightReader       reader;
38         int                     crc_errors;
39         AltosStatusTable        flightStatus;
40         AltosInfoTable          flightInfo;
41
42         class IdleThread extends Thread {
43
44                 boolean started;
45                 private AltosState state;
46                 int     reported_landing;
47                 int     report_interval;
48                 long    report_time;
49
50                 public synchronized void report(boolean last) {
51                         if (state == null)
52                                 return;
53
54                         /* reset the landing count once we hear about a new flight */
55                         if (state.state < Altos.ao_flight_drogue)
56                                 reported_landing = 0;
57
58                         /* Shut up once the rocket is on the ground */
59                         if (reported_landing > 2) {
60                                 return;
61                         }
62
63                         /* If the rocket isn't on the pad, then report height */
64                         if (Altos.ao_flight_drogue <= state.state &&
65                             state.state < Altos.ao_flight_landed &&
66                             state.range >= 0)
67                         {
68                                 voice.speak("Height %d, bearing %d, elevation %d, range %d.\n",
69                                             (int) (state.height + 0.5),
70                                             (int) (state.from_pad.bearing + 0.5),
71                                             (int) (state.elevation + 0.5),
72                                             (int) (state.range + 0.5));
73                         } else if (state.state > Altos.ao_flight_pad) {
74                                 voice.speak("%d meters", (int) (state.height + 0.5));
75                         } else {
76                                 reported_landing = 0;
77                         }
78
79                         /* If the rocket is coming down, check to see if it has landed;
80                          * either we've got a landed report or we haven't heard from it in
81                          * a long time
82                          */
83                         if (state.state >= Altos.ao_flight_drogue &&
84                             (last ||
85                              System.currentTimeMillis() - state.report_time >= 15000 ||
86                              state.state == Altos.ao_flight_landed))
87                         {
88                                 if (Math.abs(state.baro_speed) < 20 && state.height < 100)
89                                         voice.speak("rocket landed safely");
90                                 else
91                                         voice.speak("rocket may have crashed");
92                                 if (state.from_pad != null)
93                                         voice.speak("Bearing %d degrees, range %d meters.",
94                                                     (int) (state.from_pad.bearing + 0.5),
95                                                     (int) (state.from_pad.distance + 0.5));
96                                 ++reported_landing;
97                         }
98                 }
99
100                 long now () {
101                         return System.currentTimeMillis();
102                 }
103
104                 void set_report_time() {
105                         report_time = now() + report_interval;
106                 }
107
108                 public void run () {
109
110                         reported_landing = 0;
111                         state = null;
112                         report_interval = 10000;
113                         try {
114                                 for (;;) {
115                                         set_report_time();
116                                         for (;;) {
117                                                 voice.drain();
118                                                 synchronized (this) {
119                                                         long    sleep_time = report_time - now();
120                                                         if (sleep_time <= 0)
121                                                                 break;
122                                                         wait(sleep_time);
123                                                 }
124                                         }
125                                         report(false);
126                                 }
127                         } catch (InterruptedException ie) {
128                                 try {
129                                         voice.drain();
130                                 } catch (InterruptedException iie) { }
131                         }
132                 }
133
134                 public synchronized void notice(AltosState new_state, boolean spoken) {
135                         AltosState old_state = state;
136                         state = new_state;
137                         if (!started && state.state > Altos.ao_flight_pad) {
138                                 started = true;
139                                 start();
140                         }
141
142                         if (state.state < Altos.ao_flight_drogue)
143                                 report_interval = 10000;
144                         else
145                                 report_interval = 20000;
146                         if (old_state != null && old_state.state != state.state) {
147                                 report_time = now();
148                                 this.notify();
149                         } else if (spoken)
150                                 set_report_time();
151                 }
152         }
153
154         boolean tell(AltosState state, AltosState old_state) {
155                 boolean ret = false;
156                 if (old_state == null || old_state.state != state.state) {
157                         voice.speak(state.data.state());
158                         if ((old_state == null || old_state.state <= Altos.ao_flight_boost) &&
159                             state.state > Altos.ao_flight_boost) {
160                                 voice.speak("max speed: %d meters per second.",
161                                             (int) (state.max_speed + 0.5));
162                                 ret = true;
163                         } else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) &&
164                                    state.state >= Altos.ao_flight_drogue) {
165                                 voice.speak("max height: %d meters.",
166                                             (int) (state.max_height + 0.5));
167                                 ret = true;
168                         }
169                 }
170                 if (old_state == null || old_state.gps_ready != state.gps_ready) {
171                         if (state.gps_ready) {
172                                 voice.speak("GPS ready");
173                                 ret = true;
174                         }
175                         else if (old_state != null) {
176                                 voice.speak("GPS lost");
177                                 ret = true;
178                         }
179                 }
180                 old_state = state;
181                 return ret;
182         }
183
184         void show(AltosState state, int crc_errors) {
185                 if (state != null) {
186                         flightStatus.set(state);
187                         flightInfo.show(state, crc_errors);
188                 }
189         }
190
191         public void run() {
192                 boolean         interrupted = false;
193                 String          line;
194                 AltosState      state = null;
195                 AltosState      old_state = null;
196                 boolean         told;
197
198                 idle_thread = new IdleThread();
199
200                 flightInfo.clear();
201                 try {
202                         for (;;) {
203                                 try {
204                                         AltosRecord record = reader.read();
205                                         if (record == null)
206                                                 break;
207                                         old_state = state;
208                                         state = new AltosState(record, state);
209                                         reader.update(state);
210                                         show(state, crc_errors);
211                                         told = tell(state, old_state);
212                                         idle_thread.notice(state, told);
213                                 } catch (ParseException pp) {
214                                         System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage());
215                                 } catch (AltosCRCException ce) {
216                                         ++crc_errors;
217                                         show(state, crc_errors);
218                                 }
219                         }
220                 } catch (InterruptedException ee) {
221                         interrupted = true;
222                 } catch (IOException ie) {
223                         JOptionPane.showMessageDialog(parent,
224                                                       String.format("Error reading from \"%s\"", name),
225                                                       "Telemetry Read Error",
226                                                       JOptionPane.ERROR_MESSAGE);
227                 } finally {
228                         if (!interrupted)
229                                 idle_thread.report(true);
230                         reader.close(interrupted);
231                         idle_thread.interrupt();
232                         try {
233                                 idle_thread.join();
234                         } catch (InterruptedException ie) {}
235                 }
236         }
237
238         public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosStatusTable in_status, AltosInfoTable in_info, AltosFlightReader in_reader) {
239                 parent = in_parent;
240                 voice = in_voice;
241                 flightStatus = in_status;
242                 flightInfo = in_info;
243                 reader = in_reader;
244         }
245 }