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