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