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