altos/test: Adjust CRC error rate after FEC fix
[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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.telegps;
20
21 import java.awt.*;
22 import java.awt.event.*;
23 import javax.swing.*;
24 import java.io.*;
25 import java.util.concurrent.*;
26 import java.util.*;
27 import java.text.*;
28 import org.altusmetrum.altoslib_14.*;
29 import org.altusmetrum.altosuilib_14.*;
30
31 public class TeleGPS
32         extends AltosUIFrame
33         implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener, AltosEepromGrapher
34 {
35
36         static String[] telegps_icon_names = {
37                 "/altusmetrum-telegps-16.png",
38                 "/altusmetrum-telegps-32.png",
39                 "/altusmetrum-telegps-48.png",
40                 "/altusmetrum-telegps-64.png",
41                 "/altusmetrum-telegps-128.png",
42                 "/altusmetrum-telegps-256.png"
43         };
44
45         static { set_icon_names(telegps_icon_names); }
46
47         static AltosVoice       voice;
48
49         static AltosVoice voice() {
50                 if (voice == null)
51                         voice = new AltosVoice();
52                 return voice;
53         }
54
55         AltosFlightReader       reader;
56         TeleGPSDisplayThread    thread;
57         boolean                 idle_mode;
58
59         JMenuBar                menu_bar;
60
61         JMenu                   file_menu;
62         JMenu                   monitor_menu;
63         JMenu                   device_menu;
64         AltosUIFreqList         frequencies;
65         ActionListener          frequency_listener;
66         AltosUIRateList         rates;
67         ActionListener          rate_listener;
68
69         Container               bag;
70
71         TeleGPSStatus           telegps_status;
72         TeleGPSStatusUpdate     status_update;
73
74         JTabbedPane             pane;
75
76         AltosUIMap              map;
77         TeleGPSInfo             gps_info;
78         TeleGPSState            gps_state;
79         AltosInfoTable          info_table;
80
81         LinkedList<AltosFlightDisplay>  displays;
82
83         /* File menu */
84         final static String     new_command = "new";
85         final static String     graph_command = "graph";
86         final static String     export_command = "export";
87         final static String     load_maps_command = "loadmaps";
88         final static String     preferences_command = "preferences";
89         final static String     close_command = "close";
90         final static String     exit_command = "exit";
91
92         static final String[][] file_menu_entries = new String[][] {
93                 { "New Window",         new_command },
94                 { "Graph Data",         graph_command },
95                 { "Export Data",        export_command },
96                 { "Load Maps",          load_maps_command },
97                 { "Preferences",        preferences_command },
98                 { "Close",              close_command },
99                 { "Exit",               exit_command },
100         };
101
102         /* Monitor menu */
103         final static String     connect_command = "connect";
104         final static String     disconnect_command = "disconnect";
105         final static String     scan_command = "scan";
106
107         static final String[][] monitor_menu_entries = new String[][] {
108                 { "Connect Device",     connect_command },
109                 { "Disconnect",         disconnect_command },
110                 { "Scan Channels",      scan_command },
111         };
112
113         /* Device menu */
114         final static String     download_command = "download";
115         final static String     configure_command = "configure";
116         final static String     flash_command = "flash";
117
118         static final String[][] device_menu_entries = new String[][] {
119                 { "Download Data",      download_command },
120                 { "Configure Device",   configure_command },
121                 { "Flash Device",       flash_command },
122         };
123
124         void stop_display() {
125                 if (thread != null && thread.isAlive()) {
126                         thread.interrupt();
127                         try {
128                                 thread.join();
129                         } catch (InterruptedException ie) {}
130                 }
131                 thread = null;
132         }
133
134         public void reset() {
135                 for (AltosFlightDisplay display : displays)
136                         display.reset();
137         }
138
139         public void font_size_changed(int font_size) {
140                 for (AltosFlightDisplay display : displays)
141                         display.font_size_changed(font_size);
142         }
143
144         public void units_changed(boolean imperial_units) {
145                 for (AltosFlightDisplay display : displays)
146                         display.units_changed(imperial_units);
147         }
148
149         public void show(AltosState state, AltosListenerState listener_state) {
150                 try {
151                         status_update.saved_state = state;
152                         status_update.saved_listener_state = listener_state;
153
154                         if (state == null)
155                                 state = new AltosState(new AltosCalData());
156
157                         int i = 0;
158                         for (AltosFlightDisplay display : displays) {
159                                 display.show(state, listener_state);
160                                 i++;
161                         }
162                 } catch (Exception ex) {
163                         System.out.printf("Exception %s\n", ex.toString());
164                         for (StackTraceElement e : ex.getStackTrace())
165                                 System.out.printf("%s\n", e.toString());
166                 }
167         }
168
169         void new_window() {
170                 new TeleGPS();
171         }
172
173         void preferences() {
174                 new TeleGPSPreferences(this, voice());
175         }
176
177         void load_maps() {
178                 new AltosUIMapPreload(this);
179         }
180
181         void disconnect() {
182                 setTitle("TeleGPS");
183                 stop_display();
184                 telegps_status.stop();
185
186                 telegps_status.disable_receive();
187                 disable_frequency_menu();
188                 disable_rate_menu();
189         }
190
191         void connect_flight(AltosDevice device) {
192                 try {
193                         AltosFlightReader       reader = new AltosTelemetryReader(new AltosSerial(device));
194                         set_reader(reader, device, false);
195                 } catch (FileNotFoundException ee) {
196                         JOptionPane.showMessageDialog(this,
197                                                       ee.getMessage(),
198                                                       String.format ("Cannot open %s", device.toShortString()),
199                                                       JOptionPane.ERROR_MESSAGE);
200                 } catch (AltosSerialInUseException si) {
201                         JOptionPane.showMessageDialog(this,
202                                                       String.format("Device \"%s\" already in use",
203                                                                     device.toShortString()),
204                                                       "Device in use",
205                                                       JOptionPane.ERROR_MESSAGE);
206                 } catch (IOException ee) {
207                         JOptionPane.showMessageDialog(this,
208                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
209                                                       "Unknown I/O error",
210                                                       JOptionPane.ERROR_MESSAGE);
211                 } catch (TimeoutException te) {
212                         JOptionPane.showMessageDialog(this,
213                                                       String.format ("Timeout on %s", device.toShortString()),
214                                                       "Timeout error",
215                                                       JOptionPane.ERROR_MESSAGE);
216                 } catch (InterruptedException ie) {
217                         JOptionPane.showMessageDialog(this,
218                                                       String.format("Interrupted %s", device.toShortString()),
219                                                       "Interrupted exception",
220                                                       JOptionPane.ERROR_MESSAGE);
221                 }
222         }
223
224         void connect_idle(AltosDevice device) {
225                 try {
226                         AltosFlightReader       reader = new AltosIdleReader(new AltosSerial(device), false);
227                         set_reader(reader, device, true);
228                 } catch (FileNotFoundException ee) {
229                         JOptionPane.showMessageDialog(this,
230                                                       ee.getMessage(),
231                                                       String.format ("Cannot open %s", device.toShortString()),
232                                                       JOptionPane.ERROR_MESSAGE);
233                 } catch (AltosSerialInUseException si) {
234                         JOptionPane.showMessageDialog(this,
235                                                       String.format("Device \"%s\" already in use",
236                                                                     device.toShortString()),
237                                                       "Device in use",
238                                                       JOptionPane.ERROR_MESSAGE);
239                 } catch (IOException ee) {
240                         JOptionPane.showMessageDialog(this,
241                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
242                                                       "Unknown I/O error",
243                                                       JOptionPane.ERROR_MESSAGE);
244                 } catch (TimeoutException te) {
245                         JOptionPane.showMessageDialog(this,
246                                                       String.format ("Timeout on %s", device.toShortString()),
247                                                       "Timeout error",
248                                                       JOptionPane.ERROR_MESSAGE);
249                 } catch (InterruptedException ie) {
250                         JOptionPane.showMessageDialog(this,
251                                                       String.format("Interrupted %s", device.toShortString()),
252                                                       "Interrupted exception",
253                                                       JOptionPane.ERROR_MESSAGE);
254                 }
255         }
256
257         void connect(AltosDevice device) {
258                 if (reader != null)
259                         disconnect();
260                 if (device.matchProduct(AltosLib.product_basestation))
261                         connect_flight(device);
262                 else
263                         connect_idle(device);
264         }
265
266         void connect() {
267                 AltosDevice     device = AltosDeviceUIDialog.show(this,
268                                                                   AltosLib.product_any);
269                 if (device == null)
270                         return;
271                 connect(device);
272         }
273
274         public void scan_device_selected(AltosDevice device) {
275                 connect(device);
276         }
277
278         void scan() {
279                 new AltosScanUI(this, false);
280         }
281
282         void download(){
283                 new AltosEepromManage(this, this, AltosLib.product_telegps);
284         }
285
286         void configure() {
287                 new TeleGPSConfig(this);
288         }
289
290         private static AltosFlightSeries make_series(AltosRecordSet set) {
291                 AltosFlightSeries series = new AltosFlightSeries(set.cal_data());
292                 set.capture_series(series);
293                 series.finish();
294                 return series;
295         }
296
297         void export() {
298                 AltosDataChooser chooser = new AltosDataChooser(this);
299
300                 AltosRecordSet set = chooser.runDialog();
301                 if (set == null)
302                         return;
303                 AltosFlightSeries series = make_series(set);
304                 new AltosCSVUI(this, series, chooser.file());
305         }
306
307         void graph() {
308                 AltosDataChooser chooser = new AltosDataChooser(this);
309                 AltosRecordSet set = chooser.runDialog();
310                 graph_file(this, set, chooser.file());
311         }
312
313         public void graph_flights(AltosEepromList list) {
314                 for (AltosEepromLog log : list) {
315                         if (log.file != null) {
316                                 AltosRecordSet set = record_set(log.file);
317                                 graph_file(this, set, log.file);
318                         }
319                 }
320         }
321
322         void flash() {
323                 AltosFlashUI.show(this);
324         }
325
326         public void actionPerformed(ActionEvent ev) {
327
328                 /* File menu */
329                 if (new_command.equals(ev.getActionCommand())) {
330                         new_window();
331                         return;
332                 }
333                 if (preferences_command.equals(ev.getActionCommand())) {
334                         preferences();
335                         return;
336                 }
337                 if (load_maps_command.equals(ev.getActionCommand())) {
338                         load_maps();
339                         return;
340                 }
341                 if (close_command.equals(ev.getActionCommand())) {
342                         close();
343                         return;
344                 }
345                 if (exit_command.equals(ev.getActionCommand()))
346                         System.exit(0);
347
348                 /* Monitor menu */
349                 if (connect_command.equals(ev.getActionCommand())) {
350                         connect();
351                         return;
352                 }
353                 if (disconnect_command.equals(ev.getActionCommand())) {
354                         disconnect();
355                         return;
356                 }
357                 if (scan_command.equals(ev.getActionCommand())) {
358                         scan();
359                         return;
360                 }
361
362                 /* Device menu */
363                 if (download_command.equals(ev.getActionCommand())) {
364                         download();
365                         return;
366                 }
367                 if (configure_command.equals(ev.getActionCommand())) {
368                         configure();
369                         return;
370                 }
371                 if (export_command.equals(ev.getActionCommand())) {
372                         export();
373                         return;
374                 }
375                 if (graph_command.equals(ev.getActionCommand())) {
376                         graph();
377                         return;
378                 }
379                 if (flash_command.equals(ev.getActionCommand())) {
380                         flash();
381                         return;
382                 }
383         }
384
385         void enable_frequency_menu(int serial, final AltosFlightReader reader) {
386
387                 if (frequency_listener != null)
388                         disable_frequency_menu();
389
390                 frequency_listener = new ActionListener() {
391                                 public void actionPerformed(ActionEvent e) {
392                                         double frequency = frequencies.frequency();
393                                         try {
394                                                 reader.set_frequency(frequency);
395                                         } catch (TimeoutException te) {
396                                         } catch (InterruptedException ie) {
397                                         }
398                                         reader.save_frequency();
399                                 }
400                         };
401
402                 frequencies.addActionListener(frequency_listener);
403                 frequencies.set_product("Monitor");
404                 frequencies.set_serial(serial);
405                 frequencies.set_frequency(AltosUIPreferences.frequency(serial));
406                 frequencies.setEnabled(true);
407
408         }
409
410         void disable_frequency_menu() {
411                 if (frequency_listener != null) {
412                         frequencies.removeActionListener(frequency_listener);
413                         frequencies.setEnabled(false);
414                         frequency_listener = null;
415                 }
416
417         }
418
419         void enable_rate_menu(int serial, final AltosFlightReader reader) {
420
421                 if (rate_listener != null)
422                         disable_rate_menu();
423
424                 rate_listener = new ActionListener() {
425                                 public void actionPerformed(ActionEvent e) {
426                                         int rate = rates.rate();
427                                         try {
428                                                 reader.set_telemetry_rate(rate);
429                                         } catch (TimeoutException te) {
430                                         } catch (InterruptedException ie) {
431                                         }
432                                         reader.save_telemetry_rate();
433                                 }
434                         };
435
436                 rates.addActionListener(rate_listener);
437                 rates.set_product("Monitor");
438                 rates.set_serial(serial);
439                 rates.set_rate(AltosUIPreferences.telemetry_rate(serial));
440                 rates.setEnabled(reader.supports_telemetry_rate(AltosLib.ao_telemetry_rate_2400));
441         }
442
443         void disable_rate_menu() {
444                 if (rate_listener != null) {
445                         rates.removeActionListener(rate_listener);
446                         rates.setEnabled(false);
447                         rate_listener = null;
448                 }
449
450         }
451
452         public void set_reader(AltosFlightReader reader, AltosDevice device, boolean idle_mode) {
453                 this.idle_mode = idle_mode;
454                 status_update = new TeleGPSStatusUpdate(telegps_status);
455
456                 telegps_status.start(status_update);
457
458                 setTitle(String.format("TeleGPS %s", reader.name));
459                 thread = new TeleGPSDisplayThread(this, voice(), this, reader);
460                 thread.start();
461
462                 if (device != null) {
463                         if (idle_mode) {
464                                 disable_frequency_menu();
465                                 disable_rate_menu();
466                         } else {
467                                 enable_frequency_menu(device.getSerial(), reader);
468                                 enable_rate_menu(device.getSerial(), reader);
469                         }
470                 }
471         }
472
473         static int      number_of_windows;
474
475         static public void add_window() {
476                 ++number_of_windows;
477         }
478
479         static public void subtract_window() {
480                 --number_of_windows;
481                 if (number_of_windows == 0)
482                         System.exit(0);
483         }
484
485         private void close() {
486                 disconnect();
487                 AltosUIPreferences.unregister_font_listener(this);
488                 AltosPreferences.unregister_units_listener(this);
489                 setVisible(false);
490                 dispose();
491                 subtract_window();
492         }
493
494         private void add_menu(JMenu menu, String label, String action) {
495                 JMenuItem       item = new JMenuItem(label);
496                 menu.add(item);
497                 item.addActionListener(this);
498                 item.setActionCommand(action);
499         }
500
501
502         private JMenu make_menu(String label, String[][] items) {
503                 JMenu   menu = new JMenu(label);
504                 for (int i = 0; i < items.length; i++) {
505                         if (MAC_OS_X) {
506                                 if (items[i][1].equals("exit"))
507                                         continue;
508                                 if (items[i][1].equals("preferences"))
509                                         continue;
510                         }
511                         add_menu(menu, items[i][0], items[i][1]);
512                 }
513                 menu_bar.add(menu);
514                 return menu;
515         }
516
517         /* OSXAdapter interfaces */
518         public void macosx_file_handler(String path) {
519                 process_graph(new File(path));
520         }
521
522         public void macosx_quit_handler() {
523                 System.exit(0);
524         }
525
526         public void macosx_preferences_handler() {
527                 preferences();
528         }
529
530         public TeleGPS() {
531
532                 AltosUIPreferences.set_component(this);
533
534                 register_for_macosx_events();
535
536                 reader = null;
537
538                 bag = getContentPane();
539                 bag.setLayout(new GridBagLayout());
540
541                 setTitle("TeleGPS");
542
543                 menu_bar = new JMenuBar();
544                 setJMenuBar(menu_bar);
545
546                 file_menu = make_menu("File", file_menu_entries);
547                 monitor_menu = make_menu("Monitor", monitor_menu_entries);
548                 device_menu = make_menu("Device", device_menu_entries);
549
550                 set_inset(3);
551                 frequencies = new AltosUIFreqList();
552                 frequencies.setEnabled(false);
553                 bag.add(frequencies, constraints (0, 1));
554
555                 rates = new AltosUIRateList();
556                 rates.setEnabled(false);
557                 bag.add(rates, constraints(1, 1));
558                 next_row();
559                 set_inset(0);
560
561                 displays = new LinkedList<AltosFlightDisplay>();
562
563                 int serial = -1;
564
565                 /* TeleGPS status is always visible */
566                 telegps_status = new TeleGPSStatus();
567                 bag.add(telegps_status, constraints(0, 3, GridBagConstraints.HORIZONTAL));
568                 next_row();
569
570                 displays.add(telegps_status);
571
572
573                 /* The rest of the window uses a tabbed pane to
574                  * show one of the alternate data views
575                  */
576                 pane = new JTabbedPane();
577
578                 /* Make the tabbed pane use the rest of the window space */
579                 bag.add(pane, constraints(0, 3, GridBagConstraints.BOTH));
580
581                 map = new AltosUIMap();
582                 pane.add(map.getName(), map);
583                 displays.add(map);
584
585                 gps_info = new TeleGPSInfo();
586                 pane.add(gps_info.getName(), gps_info);
587                 displays.add(gps_info);
588
589                 gps_state = new TeleGPSState();
590                 pane.add(gps_state.getName(), gps_state);
591                 displays.add(gps_state);
592
593                 info_table = new AltosInfoTable();
594                 pane.add("Table", info_table);
595                 displays.add(info_table);
596
597                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
598
599                 AltosUIPreferences.register_font_listener(this);
600                 AltosPreferences.register_units_listener(this);
601
602                 addWindowListener(new WindowAdapter() {
603                                 @Override
604                                 public void windowClosing(WindowEvent e) {
605                                         close();
606                                 }
607                         });
608
609                 pack();
610                 setVisible(true);
611
612                 add_window();
613         }
614
615         public TeleGPS(AltosFlightReader reader, boolean idle_mode) {
616                 this();
617                 set_reader(reader, null, idle_mode);
618         }
619
620         public TeleGPS(AltosDevice device) {
621                 this();
622                 connect(device);
623         }
624
625         static AltosRecordSet record_set(File file) {
626                 try {
627                         return AltosLib.record_set(file);
628                 } catch (IOException ie) {
629                         System.out.printf("%s\n", ie.getMessage());
630                 }
631                 return null;
632         }
633
634         static AltosReplayReader replay_file(File file) {
635                 AltosRecordSet set = record_set(file);
636                 if (set == null)
637                         return null;
638                 return new AltosReplayReader(set, file);
639         }
640
641         private static boolean graph_file(TeleGPS telegps, AltosRecordSet set, File file) {
642                 if (set == null)
643                         return false;
644                 if (!set.valid()) {
645                         JOptionPane.showMessageDialog(telegps,
646                                                       String.format("Failed to parse file %s", file),
647                                                       "Graph Failed",
648                                                       JOptionPane.ERROR_MESSAGE);
649                         return false;
650                 }
651                 try {
652                         new TeleGPSGraphUI(set, file);
653                 } catch (IOException e) {
654                         System.out.printf("Exception %s\n", e.toString());
655                 } catch (InterruptedException e) {
656                         System.out.printf("Exception %s\n", e.toString());
657                         return false;
658                 }
659                 return true;
660         }
661
662         static boolean process_graph(File file) {
663                 AltosRecordSet set = record_set(file);
664                 return graph_file(null, set, file);
665         }
666
667         static boolean process_replay(File file) {
668                 AltosReplayReader new_reader = replay_file(file);
669                 if (new_reader == null)
670                         return false;
671
672                 new TeleGPS(new_reader, true);
673                 return true;
674         }
675
676         static final int process_none = 0;
677         static final int process_csv = 1;
678         static final int process_kml = 2;
679         static final int process_graph = 3;
680         static final int process_replay = 4;
681         static final int process_summary = 5;
682         static final int process_cat = 6;
683
684         public static boolean load_library(Frame frame) {
685                 if (!AltosUILib.load_library()) {
686                         JOptionPane.showMessageDialog(frame,
687                                                       String.format("No AltOS library in \"%s\"",
688                                                                     System.getProperty("java.library.path","<undefined>")),
689                                                       "Cannot load device access library",
690                                                       JOptionPane.ERROR_MESSAGE);
691                         return false;
692                 }
693                 return true;
694         }
695
696         public static void help(int code) {
697                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
698                 System.out.printf("  Options:\n");
699                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
700                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
701                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
702                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
703                 System.exit(code);
704         }
705
706         public static void main(String[] args) {
707                 int     errors = 0;
708
709                 load_library(null);
710                 try {
711                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
712                 } catch (Exception e) {
713                 }
714
715                 boolean any_created = false;
716
717
718                 /* Handle batch-mode */
719                 int process = process_none;
720                 for (int i = 0; i < args.length; i++) {
721                         if (args[i].equals("--help"))
722                                 help(0);
723                         else if (args[i].equals("--replay"))
724                                 process = process_replay;
725                         else if (args[i].equals("--kml"))
726                                 process = process_kml;
727                         else if (args[i].equals("--csv"))
728                                 process = process_csv;
729                         else if (args[i].equals("--graph"))
730                                 process = process_graph;
731                         else if (args[i].equals("--summary"))
732                                 process = process_summary;
733                         else if (args[i].equals("--cat"))
734                                 process = process_cat;
735                         else if (args[i].startsWith("--"))
736                                 help(1);
737                         else {
738                                 File file = new File(args[i]);
739                                 switch (process) {
740                                 case process_none:
741                                 case process_graph:
742                                         if (!process_graph(file))
743                                                 ++errors;
744                                         break;
745                                 case process_replay:
746                                         if (!process_replay(file))
747                                                 ++errors;
748                                         any_created = true;
749                                         break;
750                                 case process_kml:
751                                         ++errors;
752                                         break;
753                                 case process_csv:
754                                         ++errors;
755                                         break;
756                                 case process_summary:
757                                         ++errors;
758                                         break;
759                                 case process_cat:
760                                         ++errors;
761                                 }
762                         }
763                 }
764                 if (errors != 0)
765                         System.exit(errors);
766                 if (number_of_windows == 0) {
767                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
768                         if (devices != null)
769                                 for (AltosDevice device : devices) {
770                                         new TeleGPS(device);
771                                         any_created = true;
772                                 }
773                         if (number_of_windows == 0)
774                                 new TeleGPS();
775                 }
776         }
777 }