7117443632dcfa9553a706be58dba74fefaef2a2
[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         AltosDisplayThread      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         AltosSiteMap            sitemap;
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 AltosSiteMapPreload(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                 frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial));
327                 frequencies.set_product("Monitor");
328                 frequencies.set_serial(serial);
329                 frequencies.addActionListener(new ActionListener() {
330                                 public void actionPerformed(ActionEvent e) {
331                                         double frequency = frequencies.frequency();
332                                         try {
333                                                 reader.set_frequency(frequency);
334                                         } catch (TimeoutException te) {
335                                         } catch (InterruptedException ie) {
336                                         }
337                                         reader.save_frequency();
338                                 }
339                         });
340                 menu_bar.add(frequencies);
341         }
342
343         void remove_frequency_menu() {
344                 if (frequencies != null) {
345                         menu_bar.remove(frequencies);
346                         frequencies = null;
347                 }
348         }
349
350         public void set_reader(AltosFlightReader reader) {
351                 setTitle(String.format("TeleGPS %s", reader.name));
352                 thread = new AltosDisplayThread(this, voice(), this, reader);
353                 thread.start();
354         }
355
356         static int      number_of_windows;
357
358         static public void add_window() {
359                 ++number_of_windows;
360         }
361
362         static public void subtract_window() {
363                 --number_of_windows;
364                 if (number_of_windows == 0)
365                         System.exit(0);
366         }
367
368         private void close() {
369                 AltosUIPreferences.unregister_font_listener(this);
370                 AltosPreferences.unregister_units_listener(this);
371                 setVisible(false);
372                 dispose();
373                 subtract_window();
374         }
375
376         private void add_menu(JMenu menu, String label, String action) {
377                 JMenuItem       item = new JMenuItem(label);
378                 menu.add(item);
379                 item.addActionListener(this);
380                 item.setActionCommand(action);
381         }
382
383
384         private JMenu make_menu(String label, String[][] items) {
385                 JMenu   menu = new JMenu(label);
386                 for (int i = 0; i < items.length; i++)
387                         add_menu(menu, items[i][0], items[i][1]);
388                 menu_bar.add(menu);
389                 return menu;
390         }
391
392         public TeleGPS() {
393
394                 AltosUIPreferences.set_component(this);
395
396                 reader = null;
397
398                 bag = getContentPane();
399                 bag.setLayout(new GridBagLayout());
400
401                 GridBagConstraints c = new GridBagConstraints();
402
403                 setTitle("TeleGPS");
404
405                 menu_bar = new JMenuBar();
406                 setJMenuBar(menu_bar);
407
408                 file_menu = make_menu("File", file_menu_entries);
409                 monitor_menu = make_menu("Monitor", monitor_menu_entries);
410                 device_menu = make_menu("Device", device_menu_entries);
411                 displays = new LinkedList<AltosFlightDisplay>();
412
413                 int serial = -1;
414
415                 /* TeleGPS status is always visible */
416                 telegps_status = new TeleGPSStatus();
417                 c.gridx = 0;
418                 c.gridy = 1;
419                 c.fill = GridBagConstraints.HORIZONTAL;
420                 c.weightx = 1;
421                 c.gridwidth = 2;
422                 bag.add(telegps_status, c);
423                 c.gridwidth = 1;
424                 displays.add(telegps_status);
425
426
427                 /* The rest of the window uses a tabbed pane to
428                  * show one of the alternate data views
429                  */
430                 pane = new JTabbedPane();
431
432                 /* Make the tabbed pane use the rest of the window space */
433                 c.gridx = 0;
434                 c.gridy = 2;
435                 c.fill = GridBagConstraints.BOTH;
436                 c.weightx = 1;
437                 c.weighty = 1;
438                 c.gridwidth = 2;
439                 bag.add(pane, c);
440
441                 sitemap = new AltosSiteMap();
442                 pane.add("Site Map", sitemap);
443                 displays.add(sitemap);
444
445                 gps_info = new TeleGPSInfo();
446                 pane.add("Info", gps_info);
447                 displays.add(gps_info);
448
449                 info_table = new AltosInfoTable();
450                 pane.add("Table", info_table);
451                 displays.add(info_table);
452
453                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
454
455                 AltosUIPreferences.register_font_listener(this);
456                 AltosPreferences.register_units_listener(this);
457
458                 addWindowListener(new WindowAdapter() {
459                                 @Override
460                                 public void windowClosing(WindowEvent e) {
461                                         close();
462                                 }
463                         });
464
465                 pack();
466                 setVisible(true);
467
468                 add_window();
469
470                 status_update = new TeleGPSStatusUpdate(telegps_status);
471
472                 new javax.swing.Timer(100, status_update).start();
473         }
474
475         public TeleGPS(AltosFlightReader reader) {
476                 this();
477                 set_reader(reader);
478         }
479
480         public TeleGPS(AltosDevice device) {
481                 this();
482                 connect(device);
483         }
484
485         static AltosStateIterable record_iterable(File file) {
486                 FileInputStream in;
487                 try {
488                         in = new FileInputStream(file);
489                 } catch (Exception e) {
490                         System.out.printf("Failed to open file '%s'\n", file);
491                         return null;
492                 }
493                 if (file.getName().endsWith("telem"))
494                         return new AltosTelemetryFile(in);
495                 else
496                         return new AltosEepromFile(in);
497         }
498
499         static AltosReplayReader replay_file(File file) {
500                 AltosStateIterable states = record_iterable(file);
501                 if (states == null)
502                         return null;
503                 return new AltosReplayReader(states.iterator(), file);
504         }
505
506         static boolean process_graph(File file) {
507                 AltosStateIterable states = record_iterable(file);
508                 if (states == null)
509                         return false;
510                 try {
511                         new TeleGPSGraphUI(states, file);
512                 } catch (Exception e) {
513                         return false;
514                 }
515                 return true;
516         }
517
518         static boolean process_replay(File file) {
519                 AltosReplayReader new_reader = replay_file(file);
520                 if (new_reader == null)
521                         return false;
522
523                 new TeleGPS(new_reader);
524                 return true;
525         }
526
527         static final int process_none = 0;
528         static final int process_csv = 1;
529         static final int process_kml = 2;
530         static final int process_graph = 3;
531         static final int process_replay = 4;
532         static final int process_summary = 5;
533         static final int process_cat = 6;
534
535         public static boolean load_library(Frame frame) {
536                 if (!AltosUILib.load_library()) {
537                         JOptionPane.showMessageDialog(frame,
538                                                       String.format("No AltOS library in \"%s\"",
539                                                                     System.getProperty("java.library.path","<undefined>")),
540                                                       "Cannot load device access library",
541                                                       JOptionPane.ERROR_MESSAGE);
542                         return false;
543                 }
544                 return true;
545         }
546
547         public static void help(int code) {
548                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
549                 System.out.printf("  Options:\n");
550                 System.out.printf("    --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n");
551                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
552                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
553                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
554                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
555                 System.exit(code);
556         }
557
558         public static void main(String[] args) {
559                 int     errors = 0;
560
561                 load_library(null);
562                 try {
563                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
564                 } catch (Exception e) {
565                 }
566
567                 boolean any_created = false;
568
569
570                 /* Handle batch-mode */
571                 int process = process_none;
572                 for (int i = 0; i < args.length; i++) {
573                         if (args[i].equals("--help"))
574                                 help(0);
575                         else if (args[i].equals("--fetchmaps")) {
576                                 if (args.length < i + 3) {
577                                         help(1);
578                                 } else {
579                                         double lat = Double.parseDouble(args[i+1]);
580                                         double lon = Double.parseDouble(args[i+2]);
581                                         AltosSiteMap.prefetchMaps(lat, lon);
582                                         i += 2;
583                                 }
584                         } else if (args[i].equals("--replay"))
585                                 process = process_replay;
586                         else if (args[i].equals("--kml"))
587                                 process = process_kml;
588                         else if (args[i].equals("--csv"))
589                                 process = process_csv;
590                         else if (args[i].equals("--graph"))
591                                 process = process_graph;
592                         else if (args[i].equals("--summary"))
593                                 process = process_summary;
594                         else if (args[i].equals("--cat"))
595                                 process = process_cat;
596                         else if (args[i].startsWith("--"))
597                                 help(1);
598                         else {
599                                 File file = new File(args[i]);
600                                 switch (process) {
601                                 case process_none:
602                                 case process_graph:
603                                         if (!process_graph(file))
604                                                 ++errors;
605                                         break;
606                                 case process_replay:
607                                         if (!process_replay(file))
608                                                 ++errors;
609                                         any_created = true;
610                                         break;
611                                 case process_kml:
612                                         ++errors;
613                                         break;
614                                 case process_csv:
615                                         ++errors;
616                                         break;
617                                 case process_summary:
618                                         ++errors;
619                                         break;
620                                 case process_cat:
621                                         ++errors;
622                                 }
623                         }
624                 }
625                 if (errors != 0)
626                         System.exit(errors);
627                 if (number_of_windows == 0) {
628                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
629                         if (devices != null)
630                                 for (AltosDevice device : devices) {
631                                         new TeleGPS(device);
632                                         any_created = true;
633                                 }
634                         if (number_of_windows == 0)
635                                 new TeleGPS();
636                 }
637         }
638 }