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