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