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