altosui: Split out UI-specific preferences
[fw/altos] / altosui / AltosIdleMonitorUI.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.*;
30
31 class AltosADC {
32         int     tick;
33         int     accel;
34         int     pres;
35         int     temp;
36         int     batt;
37         int     drogue;
38         int     main;
39
40         public AltosADC(AltosSerial serial) throws InterruptedException, TimeoutException {
41                 serial.printf("a\n");
42                 for (;;) {
43                         String line = serial.get_reply_no_dialog(5000);
44                         if (line == null) {
45                                 throw new TimeoutException();
46                         }
47                         if (!line.startsWith("tick:"))
48                                 continue;
49                         String[] items = line.split("\\s+");
50                         for (int i = 0; i < items.length;) {
51                                 if (items[i].equals("tick:")) {
52                                         tick = Integer.parseInt(items[i+1]);
53                                         i += 2;
54                                         continue;
55                                 }
56                                 if (items[i].equals("accel:")) {
57                                         accel = Integer.parseInt(items[i+1]);
58                                         i += 2;
59                                         continue;
60                                 }
61                                 if (items[i].equals("pres:")) {
62                                         pres = Integer.parseInt(items[i+1]);
63                                         i += 2;
64                                         continue;
65                                 }
66                                 if (items[i].equals("temp:")) {
67                                         temp = Integer.parseInt(items[i+1]);
68                                         i += 2;
69                                         continue;
70                                 }
71                                 if (items[i].equals("batt:")) {
72                                         batt = Integer.parseInt(items[i+1]);
73                                         i += 2;
74                                         continue;
75                                 }
76                                 if (items[i].equals("drogue:")) {
77                                         drogue = Integer.parseInt(items[i+1]);
78                                         i += 2;
79                                         continue;
80                                 }
81                                 if (items[i].equals("main:")) {
82                                         main = Integer.parseInt(items[i+1]);
83                                         i += 2;
84                                         continue;
85                                 }
86                         }
87                         break;
88                 }
89         }
90 }
91
92 class AltosGPSQuery extends AltosGPS {
93         public AltosGPSQuery (AltosSerial serial, AltosConfigData config_data)
94                 throws TimeoutException, InterruptedException {
95                 boolean says_done = config_data.compare_version("1.0") >= 0;
96                 serial.printf("g\n");
97                 for (;;) {
98                         String line = serial.get_reply_no_dialog(5000);
99                         if (line == null)
100                                 throw new TimeoutException();
101                         String[] bits = line.split("\\s+");
102                         if (bits.length == 0)
103                                 continue;
104                         if (line.startsWith("Date:")) {
105                                 if (bits.length < 2)
106                                         continue;
107                                 String[] d = bits[1].split(":");
108                                 if (d.length < 3)
109                                         continue;
110                                 year = Integer.parseInt(d[0]) + 2000;
111                                 month = Integer.parseInt(d[1]);
112                                 day = Integer.parseInt(d[2]);
113                                 continue;
114                         }
115                         if (line.startsWith("Time:")) {
116                                 if (bits.length < 2)
117                                         continue;
118                                 String[] d = bits[1].split("/");
119                                 if (d.length < 3)
120                                         continue;
121                                 hour = Integer.parseInt(d[0]);
122                                 minute = Integer.parseInt(d[1]);
123                                 second = Integer.parseInt(d[2]);
124                                 continue;
125                         }
126                         if (line.startsWith("Lat/Lon:")) {
127                                 if (bits.length < 3)
128                                         continue;
129                                 lat = Integer.parseInt(bits[1]) * 1.0e-7;
130                                 lon = Integer.parseInt(bits[2]) * 1.0e-7;
131                                 continue;
132                         }
133                         if (line.startsWith("Alt:")) {
134                                 if (bits.length < 2)
135                                         continue;
136                                 alt = Integer.parseInt(bits[1]);
137                                 continue;
138                         }
139                         if (line.startsWith("Flags:")) {
140                                 if (bits.length < 2)
141                                         continue;
142                                 int status = Integer.decode(bits[1]);
143                                 connected = (status & Altos.AO_GPS_RUNNING) != 0;
144                                 locked = (status & Altos.AO_GPS_VALID) != 0;
145                                 if (!says_done)
146                                         break;
147                                 continue;
148                         }
149                         if (line.startsWith("Sats:")) {
150                                 if (bits.length < 2)
151                                         continue;
152                                 nsat = Integer.parseInt(bits[1]);
153                                 cc_gps_sat = new AltosGPSSat[nsat];
154                                 for (int i = 0; i < nsat; i++) {
155                                         int     svid = Integer.parseInt(bits[2+i*2]);
156                                         int     cc_n0 = Integer.parseInt(bits[3+i*2]);
157                                         cc_gps_sat[i] = new AltosGPSSat(svid, cc_n0);
158                                 }
159                         }
160                         if (line.startsWith("done"))
161                                 break;
162                         if (line.startsWith("Syntax error"))
163                                 break;
164                 }
165         }
166 }
167
168 class AltosIdleMonitor extends Thread {
169         AltosDevice             device;
170         AltosSerial             serial;
171         AltosIdleMonitorUI      ui;
172         AltosState              state;
173         boolean                 remote;
174         double                  frequency;
175         AltosState              previous_state;
176         AltosConfigData         config_data;
177         AltosADC                adc;
178         AltosGPS                gps;
179
180         void update_state() throws InterruptedException, TimeoutException {
181                 AltosRecord     record = new AltosRecord();
182
183                 try {
184                         if (remote) {
185                                 serial.set_radio_frequency(frequency);
186                                 serial.start_remote();
187                         } else
188                                 serial.flush_input();
189                         config_data = new AltosConfigData(serial);
190                         adc = new AltosADC(serial);
191                         gps = new AltosGPSQuery(serial, config_data);
192                 } finally {
193                         if (remote)
194                                 serial.stop_remote();
195                 }
196
197                 record.version = 0;
198                 record.callsign = config_data.callsign;
199                 record.serial = config_data.serial;
200                 record.flight = config_data.log_available() > 0 ? 255 : 0;
201                 record.rssi = 0;
202                 record.status = 0;
203                 record.state = Altos.ao_flight_idle;
204
205                 record.tick = adc.tick;
206                 record.accel = adc.accel;
207                 record.pres = adc.pres;
208                 record.batt = adc.batt;
209                 record.temp = adc.temp;
210                 record.drogue = adc.drogue;
211                 record.main = adc.main;
212
213                 record.ground_accel = record.accel;
214                 record.ground_pres = record.pres;
215                 record.accel_plus_g = config_data.accel_cal_plus;
216                 record.accel_minus_g = config_data.accel_cal_minus;
217                 record.acceleration = 0;
218                 record.speed = 0;
219                 record.height = 0;
220                 record.gps = gps;
221                 state = new AltosState (record, state);
222         }
223
224         void set_frequency(double in_frequency) {
225                 frequency = in_frequency;
226         }
227
228         public void post_state() {
229                 Runnable r = new Runnable() {
230                                 public void run() {
231                                         ui.update(state);
232                                 }
233                         };
234                 SwingUtilities.invokeLater(r);
235         }
236
237         public void run() {
238                 try {
239                         for (;;) {
240                                 try {
241                                         update_state();
242                                         post_state();
243                                 } catch (TimeoutException te) {
244                                         if (AltosSerial.debug)
245                                                 System.out.printf ("monitor idle data timeout\n");
246                                 }
247                                 Thread.sleep(1000);
248                         }
249                 } catch (InterruptedException ie) {
250                         serial.close();
251                 }
252         }
253
254         public AltosIdleMonitor(AltosIdleMonitorUI in_ui, AltosDevice in_device, boolean in_remote)
255                 throws FileNotFoundException, AltosSerialInUseException, InterruptedException, TimeoutException {
256                 device = in_device;
257                 ui = in_ui;
258                 serial = new AltosSerial(device);
259                 remote = in_remote;
260                 state = null;
261         }
262 }
263
264 public class AltosIdleMonitorUI extends AltosFrame implements AltosFlightDisplay, AltosFontListener {
265         AltosDevice             device;
266         JTabbedPane             pane;
267         AltosPad                pad;
268         AltosInfoTable          flightInfo;
269         AltosFlightStatus       flightStatus;
270         AltosIdleMonitor        thread;
271         int                     serial;
272         boolean                 remote;
273
274         void stop_display() {
275                 if (thread != null && thread.isAlive()) {
276                         thread.interrupt();
277                         try {
278                                 thread.join();
279                         } catch (InterruptedException ie) {}
280                 }
281                 thread = null;
282         }
283
284         void disconnect() {
285                 stop_display();
286         }
287
288         public void reset() {
289                 pad.reset();
290                 flightInfo.clear();
291         }
292
293         public void set_font() {
294                 pad.set_font();
295                 flightInfo.set_font();
296         }
297
298         public void font_size_changed(int font_size) {
299                 set_font();
300         }
301
302         AltosFlightStatusUpdate status_update;
303
304         public void show(AltosState state, int crc_errors) {
305                 status_update.saved_state = state;
306                 try {
307                         pad.show(state, crc_errors);
308                         flightStatus.show(state, crc_errors);
309                         flightInfo.show(state, crc_errors);
310                 } catch (Exception e) {
311                         System.out.print("Show exception" + e);
312                 }
313         }
314
315         public void update(AltosState state) {
316                 show (state, 0);
317         }
318
319         Container       bag;
320         AltosFreqList   frequencies;
321
322         public AltosIdleMonitorUI(JFrame in_owner)
323                 throws FileNotFoundException, AltosSerialInUseException, TimeoutException, InterruptedException {
324
325                 device = AltosDeviceDialog.show(in_owner, Altos.product_any);
326                 remote = false;
327                 if (!device.matchProduct(Altos.product_telemetrum))
328                         remote = true;
329
330                 serial = device.getSerial();
331                 bag = getContentPane();
332                 bag.setLayout(new GridBagLayout());
333
334                 GridBagConstraints c = new GridBagConstraints();
335
336                 java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
337                 if (imgURL != null)
338                         setIconImage(new ImageIcon(imgURL).getImage());
339
340                 setTitle(String.format("AltOS %s", device.toShortString()));
341
342                 /* Stick frequency selector at top of table for telemetry monitoring */
343                 if (remote && serial >= 0) {
344                         // Frequency menu
345                         frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial));
346                         frequencies.addActionListener(new ActionListener() {
347                                         public void actionPerformed(ActionEvent e) {
348                                                 double frequency = frequencies.frequency();
349                                                 thread.set_frequency(frequency);
350                                                 AltosUIPreferences.set_frequency(device.getSerial(),
351                                                                                frequency);
352                                         }
353                         });
354                         c.gridx = 0;
355                         c.gridy = 0;
356                         c.insets = new Insets(3, 3, 3, 3);
357                         c.anchor = GridBagConstraints.WEST;
358                         bag.add (frequencies, c);
359                 }
360
361
362                 /* Flight status is always visible */
363                 flightStatus = new AltosFlightStatus();
364                 c.gridx = 0;
365                 c.gridy = 1;
366                 c.fill = GridBagConstraints.HORIZONTAL;
367                 c.weightx = 1;
368                 c.gridwidth = 2;
369                 bag.add(flightStatus, c);
370                 c.gridwidth = 1;
371
372                 /* The rest of the window uses a tabbed pane to
373                  * show one of the alternate data views
374                  */
375                 pane = new JTabbedPane();
376
377                 pad = new AltosPad();
378                 pane.add("Launch Pad", pad);
379
380                 flightInfo = new AltosInfoTable();
381                 pane.add("Table", new JScrollPane(flightInfo));
382
383                 /* Make the tabbed pane use the rest of the window space */
384                 c.gridx = 0;
385                 c.gridy = 2;
386                 c.fill = GridBagConstraints.BOTH;
387                 c.weightx = 1;
388                 c.weighty = 1;
389                 c.gridwidth = 2;
390                 bag.add(pane, c);
391
392                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
393
394                 AltosUIPreferences.register_font_listener(this);
395
396                 addWindowListener(new WindowAdapter() {
397                                 @Override
398                                 public void windowClosing(WindowEvent e) {
399                                         disconnect();
400                                         setVisible(false);
401                                         dispose();
402                                         AltosUIPreferences.unregister_font_listener(AltosIdleMonitorUI.this);
403                                 }
404                         });
405
406                 pack();
407                 setVisible(true);
408
409                 thread = new AltosIdleMonitor(this, device, remote);
410
411                 status_update = new AltosFlightStatusUpdate(flightStatus);
412
413                 new javax.swing.Timer(100, status_update).start();
414
415                 thread.start();
416         }
417 }