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