2 * Copyright © 2017 Bdale Garbee <bdale@gag.com>
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.
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.
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.
19 package org.altusmetrum.teststand;
22 import java.awt.event.*;
25 import java.util.concurrent.*;
28 import org.altusmetrum.altoslib_11.*;
29 import org.altusmetrum.altosuilib_11.*;
31 public class TestStand
33 implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener
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"
45 static { set_icon_names(teststand_icon_names); }
47 static AltosVoice voice;
49 static AltosVoice voice() {
51 voice = new AltosVoice();
55 AltosFlightReader reader;
56 TestStandDisplayThread thread;
64 AltosUIFreqList frequencies;
65 ActionListener frequency_listener;
66 AltosUIRateList rates;
67 ActionListener rate_listener;
71 TestStandStatus teststand_status;
72 TestStandStatusUpdate status_update;
76 TestStandState gps_state;
77 AltosInfoTable info_table;
79 LinkedList<AltosFlightDisplay> displays;
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";
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 },
98 final static String connect_command = "connect";
99 final static String disconnect_command = "disconnect";
100 final static String scan_command = "scan";
102 static final String[][] monitor_menu_entries = new String[][] {
103 { "Connect Device", connect_command },
104 { "Disconnect", disconnect_command },
105 { "Scan Channels", scan_command },
109 final static String download_command = "download";
110 final static String configure_command = "configure";
111 final static String flash_command = "flash";
113 static final String[][] device_menu_entries = new String[][] {
114 { "Download Data", download_command },
115 { "Configure Device", configure_command },
116 { "Flash Device", flash_command },
119 void stop_display() {
120 if (thread != null && thread.isAlive()) {
124 } catch (InterruptedException ie) {}
129 public void reset() {
130 for (AltosFlightDisplay display : displays)
134 public void font_size_changed(int font_size) {
135 for (AltosFlightDisplay display : displays)
136 display.font_size_changed(font_size);
139 public void units_changed(boolean imperial_units) {
140 for (AltosFlightDisplay display : displays)
141 display.units_changed(imperial_units);
144 public void show(AltosState state, AltosListenerState listener_state) {
146 status_update.saved_state = state;
147 status_update.saved_listener_state = listener_state;
150 state = new AltosState();
153 for (AltosFlightDisplay display : displays) {
154 display.show(state, listener_state);
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());
165 new TestStandPreferences(this, voice());
169 setTitle("TestStand");
171 teststand_status.stop();
173 teststand_status.disable_receive();
174 disable_frequency_menu();
178 void connect_flight(AltosDevice device) {
180 AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device));
181 set_reader(reader, device, false);
182 } catch (FileNotFoundException ee) {
183 JOptionPane.showMessageDialog(this,
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()),
192 JOptionPane.ERROR_MESSAGE);
193 } catch (IOException ee) {
194 JOptionPane.showMessageDialog(this,
195 String.format ("Unknown I/O error on %s", device.toShortString()),
197 JOptionPane.ERROR_MESSAGE);
198 } catch (TimeoutException te) {
199 JOptionPane.showMessageDialog(this,
200 String.format ("Timeout on %s", device.toShortString()),
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);
211 void connect_idle(AltosDevice device) {
213 AltosFlightReader reader = new AltosIdleReader(new AltosSerial(device), false);
214 set_reader(reader, device, true);
215 } catch (FileNotFoundException ee) {
216 JOptionPane.showMessageDialog(this,
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()),
225 JOptionPane.ERROR_MESSAGE);
226 } catch (IOException ee) {
227 JOptionPane.showMessageDialog(this,
228 String.format ("Unknown I/O error on %s", device.toShortString()),
230 JOptionPane.ERROR_MESSAGE);
231 } catch (TimeoutException te) {
232 JOptionPane.showMessageDialog(this,
233 String.format ("Timeout on %s", device.toShortString()),
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);
244 void connect(AltosDevice device) {
247 if (device.matchProduct(AltosLib.product_basestation))
248 connect_flight(device);
250 connect_idle(device);
254 AltosDevice device = AltosDeviceUIDialog.show(this,
255 AltosLib.product_any);
261 public void scan_device_selected(AltosDevice device) {
266 new AltosScanUI(this, false);
270 new AltosEepromManage(this, AltosLib.product_any);
274 new TestStandConfig(this);
278 AltosDataChooser chooser;
279 chooser = new AltosDataChooser(this);
280 AltosStateIterable states = chooser.runDialog();
283 new AltosCSVUI(this, states, chooser.file());
287 AltosDataChooser chooser;
288 chooser = new AltosDataChooser(this);
289 AltosStateIterable states = chooser.runDialog();
293 new TestStandGraphUI(states, chooser.file());
294 } catch (InterruptedException ie) {
295 } catch (IOException ie) {
300 AltosFlashUI.show(this);
303 public void actionPerformed(ActionEvent ev) {
306 if (preferences_command.equals(ev.getActionCommand())) {
310 if (close_command.equals(ev.getActionCommand())) {
314 if (exit_command.equals(ev.getActionCommand()))
318 if (connect_command.equals(ev.getActionCommand())) {
322 if (disconnect_command.equals(ev.getActionCommand())) {
326 if (scan_command.equals(ev.getActionCommand())) {
332 if (download_command.equals(ev.getActionCommand())) {
336 if (configure_command.equals(ev.getActionCommand())) {
340 if (export_command.equals(ev.getActionCommand())) {
344 if (graph_command.equals(ev.getActionCommand())) {
348 if (flash_command.equals(ev.getActionCommand())) {
354 void enable_frequency_menu(int serial, final AltosFlightReader reader) {
356 if (frequency_listener != null)
357 disable_frequency_menu();
359 frequency_listener = new ActionListener() {
360 public void actionPerformed(ActionEvent e) {
361 double frequency = frequencies.frequency();
363 reader.set_frequency(frequency);
364 } catch (TimeoutException te) {
365 } catch (InterruptedException ie) {
367 reader.save_frequency();
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);
379 void disable_frequency_menu() {
380 if (frequency_listener != null) {
381 frequencies.removeActionListener(frequency_listener);
382 frequencies.setEnabled(false);
383 frequency_listener = null;
388 void enable_rate_menu(int serial, final AltosFlightReader reader) {
390 if (rate_listener != null)
393 rate_listener = new ActionListener() {
394 public void actionPerformed(ActionEvent e) {
395 int rate = rates.rate();
397 reader.set_telemetry_rate(rate);
398 } catch (TimeoutException te) {
399 } catch (InterruptedException ie) {
401 reader.save_telemetry_rate();
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));
412 void disable_rate_menu() {
413 if (rate_listener != null) {
414 rates.removeActionListener(rate_listener);
415 rates.setEnabled(false);
416 rate_listener = null;
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);
425 teststand_status.start(status_update);
427 setTitle(String.format("TestStand %s", reader.name));
428 thread = new TestStandDisplayThread(this, voice(), this, reader);
431 if (device != null) {
433 disable_frequency_menu();
436 enable_frequency_menu(device.getSerial(), reader);
437 enable_rate_menu(device.getSerial(), reader);
442 static int number_of_windows;
444 static public void add_window() {
448 static public void subtract_window() {
450 if (number_of_windows == 0)
454 private void close() {
456 AltosUIPreferences.unregister_font_listener(this);
457 AltosPreferences.unregister_units_listener(this);
463 private void add_menu(JMenu menu, String label, String action) {
464 JMenuItem item = new JMenuItem(label);
466 item.addActionListener(this);
467 item.setActionCommand(action);
471 private JMenu make_menu(String label, String[][] items) {
472 JMenu menu = new JMenu(label);
473 for (int i = 0; i < items.length; i++) {
475 if (items[i][1].equals("exit"))
477 if (items[i][1].equals("preferences"))
480 add_menu(menu, items[i][0], items[i][1]);
486 /* OSXAdapter interfaces */
487 public void macosx_file_handler(String path) {
488 process_graph(new File(path));
491 public void macosx_quit_handler() {
495 public void macosx_preferences_handler() {
501 AltosUIPreferences.set_component(this);
503 register_for_macosx_events();
507 bag = getContentPane();
508 bag.setLayout(new GridBagLayout());
510 setTitle("TestStand");
512 menu_bar = new JMenuBar();
513 setJMenuBar(menu_bar);
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);
520 frequencies = new AltosUIFreqList();
521 frequencies.setEnabled(false);
522 bag.add(frequencies, constraints (0, 1));
524 rates = new AltosUIRateList();
525 rates.setEnabled(false);
526 bag.add(rates, constraints(1, 1));
530 displays = new LinkedList<AltosFlightDisplay>();
534 /* TestStand status is always visible */
535 teststand_status = new TestStandStatus();
536 bag.add(teststand_status, constraints(0, 3, GridBagConstraints.HORIZONTAL));
539 displays.add(teststand_status);
542 /* The rest of the window uses a tabbed pane to
543 * show one of the alternate data views
545 pane = new JTabbedPane();
547 /* Make the tabbed pane use the rest of the window space */
548 bag.add(pane, constraints(0, 3, GridBagConstraints.BOTH));
550 gps_state = new TestStandState();
551 pane.add(gps_state.getName(), gps_state);
552 displays.add(gps_state);
554 info_table = new AltosInfoTable();
555 pane.add("Table", info_table);
556 displays.add(info_table);
558 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
560 AltosUIPreferences.register_font_listener(this);
561 AltosPreferences.register_units_listener(this);
563 addWindowListener(new WindowAdapter() {
565 public void windowClosing(WindowEvent e) {
576 public TestStand(AltosFlightReader reader, boolean idle_mode) {
578 set_reader(reader, null, idle_mode);
581 public TestStand(AltosDevice device) {
586 static AltosStateIterable record_iterable(File file) {
589 AltosEepromFile f = new AltosEepromFile(new FileReader(file));
591 } catch (Exception e) {
592 System.out.printf("Failed to open file '%s'\n", file);
597 static AltosReplayReader replay_file(File file) {
598 AltosStateIterable states = record_iterable(file);
601 return new AltosReplayReader(states.iterator(), file);
604 static boolean process_graph(File file) {
605 AltosStateIterable states = record_iterable(file);
609 new TestStandGraphUI(states, file);
610 } catch (Exception e) {
616 static boolean process_replay(File file) {
617 AltosReplayReader new_reader = replay_file(file);
618 if (new_reader == null)
621 new TestStand(new_reader, true);
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;
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);
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");
655 public static void main(String[] args) {
660 UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
661 } catch (Exception e) {
664 boolean any_created = false;
667 /* Handle batch-mode */
668 int process = process_none;
669 for (int i = 0; i < args.length; i++) {
670 if (args[i].equals("--help"))
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("--"))
687 File file = new File(args[i]);
691 if (!process_graph(file))
695 if (!process_replay(file))
705 case process_summary:
715 if (number_of_windows == 0) {
716 java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
718 for (AltosDevice device : devices) {
719 new TestStand(device);
722 if (number_of_windows == 0)