2 * Copyright © 2014 Keith Packard <keithp@keithp.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.telegps;
22 import java.awt.event.*;
25 import java.util.concurrent.*;
28 import org.altusmetrum.altoslib_11.*;
29 import org.altusmetrum.altosuilib_11.*;
33 implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener
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"
45 static { set_icon_names(telegps_icon_names); }
47 static AltosVoice voice;
49 static AltosVoice voice() {
51 voice = new AltosVoice();
55 AltosFlightReader reader;
56 TeleGPSDisplayThread thread;
64 AltosUIFreqList frequencies;
65 ActionListener frequency_listener;
66 AltosUIRateList rates;
67 ActionListener rate_listener;
71 TeleGPSStatus telegps_status;
72 TeleGPSStatusUpdate status_update;
78 TeleGPSState gps_state;
79 AltosInfoTable info_table;
81 LinkedList<AltosFlightDisplay> displays;
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";
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 },
103 final static String connect_command = "connect";
104 final static String disconnect_command = "disconnect";
105 final static String scan_command = "scan";
107 static final String[][] monitor_menu_entries = new String[][] {
108 { "Connect Device", connect_command },
109 { "Disconnect", disconnect_command },
110 { "Scan Channels", scan_command },
114 final static String download_command = "download";
115 final static String configure_command = "configure";
116 final static String flash_command = "flash";
118 static final String[][] device_menu_entries = new String[][] {
119 { "Download Data", download_command },
120 { "Configure Device", configure_command },
121 { "Flash Device", flash_command },
124 void stop_display() {
125 if (thread != null && thread.isAlive()) {
129 } catch (InterruptedException ie) {}
134 public void reset() {
135 for (AltosFlightDisplay display : displays)
139 public void font_size_changed(int font_size) {
140 for (AltosFlightDisplay display : displays)
141 display.font_size_changed(font_size);
144 public void units_changed(boolean imperial_units) {
145 for (AltosFlightDisplay display : displays)
146 display.units_changed(imperial_units);
149 public void show(AltosState state, AltosListenerState listener_state) {
151 status_update.saved_state = state;
152 status_update.saved_listener_state = listener_state;
155 state = new AltosState();
158 for (AltosFlightDisplay display : displays) {
159 display.show(state, listener_state);
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());
174 new TeleGPSPreferences(this, voice());
178 new AltosUIMapPreload(this);
184 telegps_status.stop();
186 telegps_status.disable_receive();
187 disable_frequency_menu();
191 void connect_flight(AltosDevice device) {
193 AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device));
194 set_reader(reader, device, false);
195 } catch (FileNotFoundException ee) {
196 JOptionPane.showMessageDialog(this,
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()),
205 JOptionPane.ERROR_MESSAGE);
206 } catch (IOException ee) {
207 JOptionPane.showMessageDialog(this,
208 String.format ("Unknown I/O error on %s", device.toShortString()),
210 JOptionPane.ERROR_MESSAGE);
211 } catch (TimeoutException te) {
212 JOptionPane.showMessageDialog(this,
213 String.format ("Timeout on %s", device.toShortString()),
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);
224 void connect_idle(AltosDevice device) {
226 AltosFlightReader reader = new AltosIdleReader(new AltosSerial(device), false);
227 set_reader(reader, device, true);
228 } catch (FileNotFoundException ee) {
229 JOptionPane.showMessageDialog(this,
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()),
238 JOptionPane.ERROR_MESSAGE);
239 } catch (IOException ee) {
240 JOptionPane.showMessageDialog(this,
241 String.format ("Unknown I/O error on %s", device.toShortString()),
243 JOptionPane.ERROR_MESSAGE);
244 } catch (TimeoutException te) {
245 JOptionPane.showMessageDialog(this,
246 String.format ("Timeout on %s", device.toShortString()),
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);
257 void connect(AltosDevice device) {
260 if (device.matchProduct(AltosLib.product_basestation))
261 connect_flight(device);
263 connect_idle(device);
267 AltosDevice device = AltosDeviceUIDialog.show(this,
268 AltosLib.product_any);
274 public void scan_device_selected(AltosDevice device) {
279 new AltosScanUI(this, false);
283 new AltosEepromManage(this, AltosLib.product_telegps);
287 new TeleGPSConfig(this);
291 AltosDataChooser chooser;
292 chooser = new AltosDataChooser(this);
293 AltosStateIterable states = chooser.runDialog();
296 new AltosCSVUI(this, states, chooser.file());
300 AltosDataChooser chooser;
301 chooser = new AltosDataChooser(this);
302 AltosStateIterable states = chooser.runDialog();
306 new TeleGPSGraphUI(states, chooser.file());
307 } catch (InterruptedException ie) {
308 } catch (IOException ie) {
313 AltosFlashUI.show(this);
316 public void actionPerformed(ActionEvent ev) {
319 if (new_command.equals(ev.getActionCommand())) {
323 if (preferences_command.equals(ev.getActionCommand())) {
327 if (load_maps_command.equals(ev.getActionCommand())) {
331 if (close_command.equals(ev.getActionCommand())) {
335 if (exit_command.equals(ev.getActionCommand()))
339 if (connect_command.equals(ev.getActionCommand())) {
343 if (disconnect_command.equals(ev.getActionCommand())) {
347 if (scan_command.equals(ev.getActionCommand())) {
353 if (download_command.equals(ev.getActionCommand())) {
357 if (configure_command.equals(ev.getActionCommand())) {
361 if (export_command.equals(ev.getActionCommand())) {
365 if (graph_command.equals(ev.getActionCommand())) {
369 if (flash_command.equals(ev.getActionCommand())) {
375 void enable_frequency_menu(int serial, final AltosFlightReader reader) {
377 if (frequency_listener != null)
378 disable_frequency_menu();
380 frequency_listener = new ActionListener() {
381 public void actionPerformed(ActionEvent e) {
382 double frequency = frequencies.frequency();
384 reader.set_frequency(frequency);
385 } catch (TimeoutException te) {
386 } catch (InterruptedException ie) {
388 reader.save_frequency();
392 frequencies.addActionListener(frequency_listener);
393 frequencies.set_product("Monitor");
394 frequencies.set_serial(serial);
395 frequencies.set_frequency(AltosUIPreferences.frequency(serial));
396 frequencies.setEnabled(true);
400 void disable_frequency_menu() {
401 if (frequency_listener != null) {
402 frequencies.removeActionListener(frequency_listener);
403 frequencies.setEnabled(false);
404 frequency_listener = null;
409 void enable_rate_menu(int serial, final AltosFlightReader reader) {
411 if (rate_listener != null)
414 rate_listener = new ActionListener() {
415 public void actionPerformed(ActionEvent e) {
416 int rate = rates.rate();
418 reader.set_telemetry_rate(rate);
419 } catch (TimeoutException te) {
420 } catch (InterruptedException ie) {
422 reader.save_telemetry_rate();
426 rates.addActionListener(rate_listener);
427 rates.set_product("Monitor");
428 rates.set_serial(serial);
429 rates.set_rate(AltosUIPreferences.telemetry_rate(serial));
430 rates.setEnabled(reader.supports_telemetry_rate(AltosLib.ao_telemetry_rate_2400));
433 void disable_rate_menu() {
434 if (rate_listener != null) {
435 rates.removeActionListener(rate_listener);
436 rates.setEnabled(false);
437 rate_listener = null;
442 public void set_reader(AltosFlightReader reader, AltosDevice device, boolean idle_mode) {
443 this.idle_mode = idle_mode;
444 status_update = new TeleGPSStatusUpdate(telegps_status);
446 telegps_status.start(status_update);
448 setTitle(String.format("TeleGPS %s", reader.name));
449 thread = new TeleGPSDisplayThread(this, voice(), this, reader);
452 if (device != null) {
454 disable_frequency_menu();
457 enable_frequency_menu(device.getSerial(), reader);
458 enable_rate_menu(device.getSerial(), reader);
463 static int number_of_windows;
465 static public void add_window() {
469 static public void subtract_window() {
471 if (number_of_windows == 0)
475 private void close() {
477 AltosUIPreferences.unregister_font_listener(this);
478 AltosPreferences.unregister_units_listener(this);
484 private void add_menu(JMenu menu, String label, String action) {
485 JMenuItem item = new JMenuItem(label);
487 item.addActionListener(this);
488 item.setActionCommand(action);
492 private JMenu make_menu(String label, String[][] items) {
493 JMenu menu = new JMenu(label);
494 for (int i = 0; i < items.length; i++) {
496 if (items[i][1].equals("exit"))
498 if (items[i][1].equals("preferences"))
501 add_menu(menu, items[i][0], items[i][1]);
507 /* OSXAdapter interfaces */
508 public void macosx_file_handler(String path) {
509 process_graph(new File(path));
512 public void macosx_quit_handler() {
516 public void macosx_preferences_handler() {
522 AltosUIPreferences.set_component(this);
524 register_for_macosx_events();
528 bag = getContentPane();
529 bag.setLayout(new GridBagLayout());
533 menu_bar = new JMenuBar();
534 setJMenuBar(menu_bar);
536 file_menu = make_menu("File", file_menu_entries);
537 monitor_menu = make_menu("Monitor", monitor_menu_entries);
538 device_menu = make_menu("Device", device_menu_entries);
541 frequencies = new AltosUIFreqList();
542 frequencies.setEnabled(false);
543 bag.add(frequencies, constraints (0, 1));
545 rates = new AltosUIRateList();
546 rates.setEnabled(false);
547 bag.add(rates, constraints(1, 1));
551 displays = new LinkedList<AltosFlightDisplay>();
555 /* TeleGPS status is always visible */
556 telegps_status = new TeleGPSStatus();
557 bag.add(telegps_status, constraints(0, 3, GridBagConstraints.HORIZONTAL));
560 displays.add(telegps_status);
563 /* The rest of the window uses a tabbed pane to
564 * show one of the alternate data views
566 pane = new JTabbedPane();
568 /* Make the tabbed pane use the rest of the window space */
569 bag.add(pane, constraints(0, 3, GridBagConstraints.BOTH));
571 map = new AltosUIMap();
572 pane.add(map.getName(), map);
575 gps_info = new TeleGPSInfo();
576 pane.add(gps_info.getName(), gps_info);
577 displays.add(gps_info);
579 gps_state = new TeleGPSState();
580 pane.add(gps_state.getName(), gps_state);
581 displays.add(gps_state);
583 info_table = new AltosInfoTable();
584 pane.add("Table", info_table);
585 displays.add(info_table);
587 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
589 AltosUIPreferences.register_font_listener(this);
590 AltosPreferences.register_units_listener(this);
592 addWindowListener(new WindowAdapter() {
594 public void windowClosing(WindowEvent e) {
605 public TeleGPS(AltosFlightReader reader, boolean idle_mode) {
607 set_reader(reader, null, idle_mode);
610 public TeleGPS(AltosDevice device) {
615 static AltosStateIterable record_iterable(File file) {
618 in = new FileInputStream(file);
619 } catch (Exception e) {
620 System.out.printf("Failed to open file '%s'\n", file);
623 if (file.getName().endsWith("telem"))
624 return new AltosTelemetryFile(in);
626 return new AltosEepromFile(in);
629 static AltosReplayReader replay_file(File file) {
630 AltosStateIterable states = record_iterable(file);
633 return new AltosReplayReader(states.iterator(), file);
636 static boolean process_graph(File file) {
637 AltosStateIterable states = record_iterable(file);
641 new TeleGPSGraphUI(states, file);
642 } catch (Exception e) {
648 static boolean process_replay(File file) {
649 AltosReplayReader new_reader = replay_file(file);
650 if (new_reader == null)
653 new TeleGPS(new_reader, true);
657 static final int process_none = 0;
658 static final int process_csv = 1;
659 static final int process_kml = 2;
660 static final int process_graph = 3;
661 static final int process_replay = 4;
662 static final int process_summary = 5;
663 static final int process_cat = 6;
665 public static boolean load_library(Frame frame) {
666 if (!AltosUILib.load_library()) {
667 JOptionPane.showMessageDialog(frame,
668 String.format("No AltOS library in \"%s\"",
669 System.getProperty("java.library.path","<undefined>")),
670 "Cannot load device access library",
671 JOptionPane.ERROR_MESSAGE);
677 public static void help(int code) {
678 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
679 System.out.printf(" Options:\n");
680 System.out.printf(" --replay <filename>\t\trelive the glory of past flights \n");
681 System.out.printf(" --graph <filename>\t\tgraph a flight\n");
682 System.out.printf(" --csv\tgenerate comma separated output for spreadsheets, etc\n");
683 System.out.printf(" --kml\tgenerate KML output for use with Google Earth\n");
687 public static void main(String[] args) {
692 UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
693 } catch (Exception e) {
696 boolean any_created = false;
699 /* Handle batch-mode */
700 int process = process_none;
701 for (int i = 0; i < args.length; i++) {
702 if (args[i].equals("--help"))
704 else if (args[i].equals("--replay"))
705 process = process_replay;
706 else if (args[i].equals("--kml"))
707 process = process_kml;
708 else if (args[i].equals("--csv"))
709 process = process_csv;
710 else if (args[i].equals("--graph"))
711 process = process_graph;
712 else if (args[i].equals("--summary"))
713 process = process_summary;
714 else if (args[i].equals("--cat"))
715 process = process_cat;
716 else if (args[i].startsWith("--"))
719 File file = new File(args[i]);
723 if (!process_graph(file))
727 if (!process_replay(file))
737 case process_summary:
747 if (number_of_windows == 0) {
748 java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
750 for (AltosDevice device : devices) {
754 if (number_of_windows == 0)