telegps: Add status tab
[fw/altos] / telegps / TeleGPS.java
1 /*
2  * Copyright © 2014 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 org.altusmetrum.telegps;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import javax.swing.*;
23 import java.io.*;
24 import java.util.concurrent.*;
25 import java.util.*;
26 import org.altusmetrum.altoslib_4.*;
27 import org.altusmetrum.altosuilib_2.*;
28
29 public class TeleGPS
30         extends AltosUIFrame
31         implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener
32 {
33
34         static String[] telegps_icon_names = {
35                 "/telegps-16.png",
36                 "/telegps-32.png",
37                 "/telegps-48.png",
38                 "/telegps-64.png",
39                 "/telegps-128.png",
40                 "/telegps-256.png"
41         };
42
43         static { set_icon_names(telegps_icon_names); }
44
45         static AltosVoice       voice;
46
47         static AltosVoice voice() {
48                 if (voice == null)
49                         voice = new AltosVoice();
50                 return voice;
51         }
52
53         AltosFlightReader       reader;
54         TeleGPSDisplayThread    thread;
55
56         JMenuBar                menu_bar;
57
58         JMenu                   file_menu;
59         JMenu                   monitor_menu;
60         JMenu                   device_menu;
61         AltosFreqList           frequencies;
62
63         Container               bag;
64
65         TeleGPSStatus           telegps_status;
66         TeleGPSStatusUpdate     status_update;
67
68         JTabbedPane             pane;
69
70         AltosUIMap              map;
71         TeleGPSInfo             gps_info;
72         TeleGPSState            gps_state;
73         AltosInfoTable          info_table;
74
75         LinkedList<AltosFlightDisplay>  displays;
76
77         /* File menu */
78         final static String     new_command = "new";
79         final static String     preferences_command = "preferences";
80         final static String     load_maps_command = "loadmaps";
81         final static String     close_command = "close";
82         final static String     exit_command = "exit";
83
84         static final String[][] file_menu_entries = new String[][] {
85                 { "New Window",         new_command },
86                 { "Preferences",        preferences_command },
87                 { "Load Maps",          load_maps_command },
88                 { "Close",              close_command },
89                 { "Exit",               exit_command },
90         };
91
92         /* Monitor menu */
93         final static String     monitor_command = "monitor";
94         final static String     disconnect_command = "disconnect";
95         final static String     scan_command = "scan";
96
97         static final String[][] monitor_menu_entries = new String[][] {
98                 { "Monitor Device",     monitor_command },
99                 { "Disconnect",         disconnect_command },
100                 { "Scan Channels",      scan_command },
101         };
102
103         /* Device menu */
104         final static String     download_command = "download";
105         final static String     export_command = "export";
106         final static String     graph_command = "graph";
107         final static String     configure_command = "configure";
108         final static String     flash_command = "flash";
109
110         static final String[][] device_menu_entries = new String[][] {
111                 { "Download Data",      download_command },
112                 { "Configure Device",   configure_command },
113                 { "Export Data",        export_command },
114                 { "Graph Data",         graph_command },
115                 { "Flash Device",       flash_command },
116         };
117
118         void stop_display() {
119                 if (thread != null && thread.isAlive()) {
120                         thread.interrupt();
121                         try {
122                                 thread.join();
123                         } catch (InterruptedException ie) {}
124                 }
125                 thread = null;
126         }
127
128         public void reset() {
129                 for (AltosFlightDisplay display : displays)
130                         display.reset();
131         }
132
133         public void font_size_changed(int font_size) {
134                 for (AltosFlightDisplay display : displays)
135                         display.font_size_changed(font_size);
136         }
137
138         public void units_changed(boolean imperial_units) {
139                 for (AltosFlightDisplay display : displays)
140                         display.units_changed(imperial_units);
141         }
142
143         public void show(AltosState state, AltosListenerState listener_state) {
144                 try {
145                         status_update.saved_state = state;
146
147                         if (state == null)
148                                 state = new AltosState();
149
150                         int i = 0;
151                         for (AltosFlightDisplay display : displays) {
152                                 display.show(state, listener_state);
153                                 i++;
154                         }
155                 } catch (Exception ex) {
156                         System.out.printf("Exception %s\n", ex.toString());
157                         for (StackTraceElement e : ex.getStackTrace())
158                                 System.out.printf("%s\n", e.toString());
159                 }
160         }
161
162         void new_window() {
163                 new TeleGPS();
164         }
165
166         void preferences() {
167                 new TeleGPSPreferences(this, voice());
168         }
169
170         void load_maps() {
171                 new AltosUIMapPreload(this);
172         }
173
174         void disconnect() {
175                 setTitle("TeleGPS");
176                 stop_display();
177                 remove_frequency_menu();
178         }
179
180         void connect(AltosDevice device) {
181                 if (reader != null)
182                         disconnect();
183                 try {
184                         AltosFlightReader       reader = new AltosTelemetryReader(new AltosSerial(device));
185                         set_reader(reader);
186                         add_frequency_menu(device.getSerial(), reader);
187                 } catch (FileNotFoundException ee) {
188                         JOptionPane.showMessageDialog(this,
189                                                       ee.getMessage(),
190                                                       String.format ("Cannot open %s", device.toShortString()),
191                                                       JOptionPane.ERROR_MESSAGE);
192                 } catch (AltosSerialInUseException si) {
193                         JOptionPane.showMessageDialog(this,
194                                                       String.format("Device \"%s\" already in use",
195                                                                     device.toShortString()),
196                                                       "Device in use",
197                                                       JOptionPane.ERROR_MESSAGE);
198                 } catch (IOException ee) {
199                         JOptionPane.showMessageDialog(this,
200                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
201                                                       "Unknown I/O error",
202                                                       JOptionPane.ERROR_MESSAGE);
203                 } catch (TimeoutException te) {
204                         JOptionPane.showMessageDialog(this,
205                                                       String.format ("Timeout on %s", device.toShortString()),
206                                                       "Timeout error",
207                                                       JOptionPane.ERROR_MESSAGE);
208                 } catch (InterruptedException ie) {
209                         JOptionPane.showMessageDialog(this,
210                                                       String.format("Interrupted %s", device.toShortString()),
211                                                       "Interrupted exception",
212                                                       JOptionPane.ERROR_MESSAGE);
213                 }
214         }
215
216         void monitor() {
217                 AltosDevice     device = AltosDeviceUIDialog.show(this,
218                                                                   AltosLib.product_basestation);
219                 if (device == null)
220                         return;
221                 connect(device);
222         }
223
224         public void scan_device_selected(AltosDevice device) {
225                 connect(device);
226         }
227
228         void scan() {
229                 new AltosScanUI(this, false);
230         }
231
232         void download(){
233                 new AltosEepromManage(this, AltosLib.product_telegps);
234         }
235
236         void configure() {
237                 new TeleGPSConfig(this);
238         }
239
240         void export() {
241                 AltosDataChooser chooser;
242                 chooser = new AltosDataChooser(this);
243                 AltosStateIterable states = chooser.runDialog();
244                 if (states == null)
245                         return;
246                 new AltosCSVUI(this, states, chooser.file());
247         }
248
249         void graph() {
250                 AltosDataChooser chooser;
251                 chooser = new AltosDataChooser(this);
252                 AltosStateIterable states = chooser.runDialog();
253                 if (states == null)
254                         return;
255                 try {
256                         new TeleGPSGraphUI(states, chooser.file());
257                 } catch (InterruptedException ie) {
258                 } catch (IOException ie) {
259                 }
260         }
261
262         void flash() {
263                 AltosFlashUI.show(this);
264         }
265
266         public void actionPerformed(ActionEvent ev) {
267
268                 /* File menu */
269                 if (new_command.equals(ev.getActionCommand())) {
270                         new_window();
271                         return;
272                 }
273                 if (preferences_command.equals(ev.getActionCommand())) {
274                         preferences();
275                         return;
276                 }
277                 if (load_maps_command.equals(ev.getActionCommand())) {
278                         load_maps();
279                         return;
280                 }
281                 if (close_command.equals(ev.getActionCommand())) {
282                         close();
283                         return;
284                 }
285                 if (exit_command.equals(ev.getActionCommand()))
286                         System.exit(0);
287
288                 /* Monitor menu */
289                 if (monitor_command.equals(ev.getActionCommand())) {
290                         monitor();
291                         return;
292                 }
293                 if (disconnect_command.equals(ev.getActionCommand())) {
294                         disconnect();
295                         return;
296                 }
297                 if (scan_command.equals(ev.getActionCommand())) {
298                         scan();
299                         return;
300                 }
301
302                 /* Device menu */
303                 if (download_command.equals(ev.getActionCommand())) {
304                         download();
305                         return;
306                 }
307                 if (configure_command.equals(ev.getActionCommand())) {
308                         configure();
309                         return;
310                 }
311                 if (export_command.equals(ev.getActionCommand())) {
312                         export();
313                         return;
314                 }
315                 if (graph_command.equals(ev.getActionCommand())) {
316                         graph();
317                         return;
318                 }
319                 if (flash_command.equals(ev.getActionCommand())) {
320                         flash();
321                         return;
322                 }
323         }
324
325         void add_frequency_menu(int serial, final AltosFlightReader reader) {
326                 // Channel menu
327                 if (frequencies != null)
328                         return;
329
330                 frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial));
331                 frequencies.set_product("Monitor");
332                 frequencies.set_serial(serial);
333                 frequencies.addActionListener(new ActionListener() {
334                                 public void actionPerformed(ActionEvent e) {
335                                         double frequency = frequencies.frequency();
336                                         try {
337                                                 reader.set_frequency(frequency);
338                                         } catch (TimeoutException te) {
339                                         } catch (InterruptedException ie) {
340                                         }
341                                         reader.save_frequency();
342                                 }
343                         });
344                 menu_bar.add(frequencies);
345         }
346
347         void remove_frequency_menu() {
348                 if (frequencies != null) {
349                         menu_bar.remove(frequencies);
350                         menu_bar.repaint();
351                         frequencies = null;
352                 }
353         }
354
355         public void set_reader(AltosFlightReader reader) {
356                 setTitle(String.format("TeleGPS %s", reader.name));
357                 thread = new TeleGPSDisplayThread(this, voice(), this, reader);
358                 thread.start();
359         }
360
361         static int      number_of_windows;
362
363         static public void add_window() {
364                 ++number_of_windows;
365         }
366
367         static public void subtract_window() {
368                 --number_of_windows;
369                 if (number_of_windows == 0)
370                         System.exit(0);
371         }
372
373         private void close() {
374                 disconnect();
375                 AltosUIPreferences.unregister_font_listener(this);
376                 AltosPreferences.unregister_units_listener(this);
377                 setVisible(false);
378                 dispose();
379                 subtract_window();
380         }
381
382         private void add_menu(JMenu menu, String label, String action) {
383                 JMenuItem       item = new JMenuItem(label);
384                 menu.add(item);
385                 item.addActionListener(this);
386                 item.setActionCommand(action);
387         }
388
389
390         private JMenu make_menu(String label, String[][] items) {
391                 JMenu   menu = new JMenu(label);
392                 for (int i = 0; i < items.length; i++)
393                         add_menu(menu, items[i][0], items[i][1]);
394                 menu_bar.add(menu);
395                 return menu;
396         }
397
398         public TeleGPS() {
399
400                 AltosUIPreferences.set_component(this);
401
402                 reader = null;
403
404                 bag = getContentPane();
405                 bag.setLayout(new GridBagLayout());
406
407                 GridBagConstraints c = new GridBagConstraints();
408
409                 setTitle("TeleGPS");
410
411                 menu_bar = new JMenuBar();
412                 setJMenuBar(menu_bar);
413
414                 file_menu = make_menu("File", file_menu_entries);
415                 monitor_menu = make_menu("Monitor", monitor_menu_entries);
416                 device_menu = make_menu("Device", device_menu_entries);
417                 displays = new LinkedList<AltosFlightDisplay>();
418
419                 int serial = -1;
420
421                 /* TeleGPS status is always visible */
422                 telegps_status = new TeleGPSStatus();
423                 c.gridx = 0;
424                 c.gridy = 1;
425                 c.fill = GridBagConstraints.HORIZONTAL;
426                 c.weightx = 1;
427                 c.gridwidth = 2;
428                 bag.add(telegps_status, c);
429                 c.gridwidth = 1;
430                 displays.add(telegps_status);
431
432
433                 /* The rest of the window uses a tabbed pane to
434                  * show one of the alternate data views
435                  */
436                 pane = new JTabbedPane();
437
438                 /* Make the tabbed pane use the rest of the window space */
439                 c.gridx = 0;
440                 c.gridy = 2;
441                 c.fill = GridBagConstraints.BOTH;
442                 c.weightx = 1;
443                 c.weighty = 1;
444                 c.gridwidth = 2;
445                 bag.add(pane, c);
446
447                 map = new AltosUIMap();
448                 pane.add(map.getName(), map);
449                 displays.add(map);
450
451                 gps_info = new TeleGPSInfo();
452                 pane.add(gps_info.getName(), gps_info);
453                 displays.add(gps_info);
454
455                 gps_state = new TeleGPSState();
456                 pane.add(gps_state.getName(), gps_state);
457                 displays.add(gps_state);
458
459                 info_table = new AltosInfoTable();
460                 pane.add("Table", info_table);
461                 displays.add(info_table);
462
463                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
464
465                 AltosUIPreferences.register_font_listener(this);
466                 AltosPreferences.register_units_listener(this);
467
468                 addWindowListener(new WindowAdapter() {
469                                 @Override
470                                 public void windowClosing(WindowEvent e) {
471                                         close();
472                                 }
473                         });
474
475                 pack();
476                 setVisible(true);
477
478                 add_window();
479
480                 status_update = new TeleGPSStatusUpdate(telegps_status);
481
482                 new javax.swing.Timer(100, status_update).start();
483         }
484
485         public TeleGPS(AltosFlightReader reader) {
486                 this();
487                 set_reader(reader);
488         }
489
490         public TeleGPS(AltosDevice device) {
491                 this();
492                 connect(device);
493         }
494
495         static AltosStateIterable record_iterable(File file) {
496                 FileInputStream in;
497                 try {
498                         in = new FileInputStream(file);
499                 } catch (Exception e) {
500                         System.out.printf("Failed to open file '%s'\n", file);
501                         return null;
502                 }
503                 if (file.getName().endsWith("telem"))
504                         return new AltosTelemetryFile(in);
505                 else
506                         return new AltosEepromFile(in);
507         }
508
509         static AltosReplayReader replay_file(File file) {
510                 AltosStateIterable states = record_iterable(file);
511                 if (states == null)
512                         return null;
513                 return new AltosReplayReader(states.iterator(), file);
514         }
515
516         static boolean process_graph(File file) {
517                 AltosStateIterable states = record_iterable(file);
518                 if (states == null)
519                         return false;
520                 try {
521                         new TeleGPSGraphUI(states, file);
522                 } catch (Exception e) {
523                         return false;
524                 }
525                 return true;
526         }
527
528         static boolean process_replay(File file) {
529                 AltosReplayReader new_reader = replay_file(file);
530                 if (new_reader == null)
531                         return false;
532
533                 new TeleGPS(new_reader);
534                 return true;
535         }
536
537         static final int process_none = 0;
538         static final int process_csv = 1;
539         static final int process_kml = 2;
540         static final int process_graph = 3;
541         static final int process_replay = 4;
542         static final int process_summary = 5;
543         static final int process_cat = 6;
544
545         public static boolean load_library(Frame frame) {
546                 if (!AltosUILib.load_library()) {
547                         JOptionPane.showMessageDialog(frame,
548                                                       String.format("No AltOS library in \"%s\"",
549                                                                     System.getProperty("java.library.path","<undefined>")),
550                                                       "Cannot load device access library",
551                                                       JOptionPane.ERROR_MESSAGE);
552                         return false;
553                 }
554                 return true;
555         }
556
557         public static void help(int code) {
558                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
559                 System.out.printf("  Options:\n");
560                 System.out.printf("    --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n");
561                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
562                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
563                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
564                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
565                 System.exit(code);
566         }
567
568         public static void main(String[] args) {
569                 int     errors = 0;
570
571                 load_library(null);
572                 try {
573                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
574                 } catch (Exception e) {
575                 }
576
577                 boolean any_created = false;
578
579
580                 /* Handle batch-mode */
581                 int process = process_none;
582                 for (int i = 0; i < args.length; i++) {
583                         if (args[i].equals("--help"))
584                                 help(0);
585                         else if (args[i].equals("--fetchmaps")) {
586                                 if (args.length < i + 3) {
587                                         help(1);
588                                 } else {
589                                         double lat = Double.parseDouble(args[i+1]);
590                                         double lon = Double.parseDouble(args[i+2]);
591                                         AltosUIMap.prefetch_maps(lat, lon);
592                                         i += 2;
593                                 }
594                         } else if (args[i].equals("--replay"))
595                                 process = process_replay;
596                         else if (args[i].equals("--kml"))
597                                 process = process_kml;
598                         else if (args[i].equals("--csv"))
599                                 process = process_csv;
600                         else if (args[i].equals("--graph"))
601                                 process = process_graph;
602                         else if (args[i].equals("--summary"))
603                                 process = process_summary;
604                         else if (args[i].equals("--cat"))
605                                 process = process_cat;
606                         else if (args[i].startsWith("--"))
607                                 help(1);
608                         else {
609                                 File file = new File(args[i]);
610                                 switch (process) {
611                                 case process_none:
612                                 case process_graph:
613                                         if (!process_graph(file))
614                                                 ++errors;
615                                         break;
616                                 case process_replay:
617                                         if (!process_replay(file))
618                                                 ++errors;
619                                         any_created = true;
620                                         break;
621                                 case process_kml:
622                                         ++errors;
623                                         break;
624                                 case process_csv:
625                                         ++errors;
626                                         break;
627                                 case process_summary:
628                                         ++errors;
629                                         break;
630                                 case process_cat:
631                                         ++errors;
632                                 }
633                         }
634                 }
635                 if (errors != 0)
636                         System.exit(errors);
637                 if (number_of_windows == 0) {
638                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
639                         if (devices != null)
640                                 for (AltosDevice device : devices) {
641                                         new TeleGPS(device);
642                                         any_created = true;
643                                 }
644                         if (number_of_windows == 0)
645                                 new TeleGPS();
646                 }
647         }
648 }