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