2 * Copyright © 2010 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; version 2 of the License.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
21 import java.awt.event.*;
23 import javax.swing.filechooser.FileNameExtensionFilter;
24 import javax.swing.table.*;
28 import java.util.prefs.*;
29 import java.util.concurrent.LinkedBlockingQueue;
32 import altosui.AltosSerial;
33 import altosui.AltosSerialMonitor;
34 import altosui.AltosRecord;
35 import altosui.AltosTelemetry;
36 import altosui.AltosState;
37 import altosui.AltosDeviceDialog;
38 import altosui.AltosPreferences;
39 import altosui.AltosLog;
40 import altosui.AltosVoice;
41 import altosui.AltosFlightInfoTableModel;
42 import altosui.AltosChannelMenu;
43 import altosui.AltosFlashUI;
44 import altosui.AltosLogfileChooser;
45 import altosui.AltosCSVUI;
46 import altosui.AltosLine;
47 import altosui.AltosStatusTable;
48 import altosui.AltosInfoTable;
49 import altosui.AltosDisplayThread;
53 public class AltosUI extends JFrame {
54 private int channel = -1;
56 private AltosStatusTable flightStatus;
57 private AltosInfoTable flightInfo;
58 private AltosSerial serial_line;
59 private AltosLog altos_log;
62 private Font statusFont = new Font("SansSerif", Font.BOLD, 24);
63 private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 14);
64 private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 14);
66 public AltosVoice voice = new AltosVoice();
68 public static boolean load_library(Frame frame) {
69 if (!AltosDevice.load_library()) {
70 JOptionPane.showMessageDialog(frame,
71 String.format("No AltOS library in \"%s\"",
72 System.getProperty("java.library.path","<undefined>")),
73 "Cannot load device access library",
74 JOptionPane.ERROR_MESSAGE);
84 String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
85 Object[][] statusData = { { "0", "pad", "-50", "0" } };
87 java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
89 setIconImage(new ImageIcon(imgURL).getImage());
91 AltosPreferences.init(this);
93 vbox = Box.createVerticalBox();
96 flightStatus = new AltosStatusTable(this);
98 vbox.add(flightStatus);
100 flightInfo = new AltosInfoTable();
101 vbox.add(flightInfo.box());
107 serial_line = new AltosSerial();
108 altos_log = new AltosLog(serial_line);
109 int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
110 this.setSize(new Dimension (flightInfo.width(),
111 flightStatus.height() + flightInfo.height()));
113 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
114 addWindowListener(new WindowAdapter() {
116 public void windowClosing(WindowEvent e) {
120 voice.speak("Rocket flight monitor ready.");
123 class DeviceThread extends AltosDisplayThread {
125 LinkedBlockingQueue<AltosLine> telem;
127 AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException {
128 AltosLine l = telem.take();
130 throw new IOException("IO error");
131 return new AltosTelemetry(l.line);
134 void close(boolean interrupted) {
136 serial.remove_monitor(telem);
139 public DeviceThread(AltosSerial s, String in_name, AltosVoice voice, AltosStatusTable status, AltosInfoTable info) {
140 super(AltosUI.this, voice, status, info);
142 telem = new LinkedBlockingQueue<AltosLine>();
143 serial.add_monitor(telem);
148 private void ConnectToDevice() {
149 AltosDevice device = AltosDeviceDialog.show(AltosUI.this,
150 AltosDevice.product_basestation);
152 if (device != null) {
155 serial_line.open(device);
156 DeviceThread thread = new DeviceThread(serial_line, device.getPath(), voice, flightStatus, flightInfo);
157 serial_line.set_channel(AltosPreferences.channel());
158 serial_line.set_callsign(AltosPreferences.callsign());
160 } catch (FileNotFoundException ee) {
161 JOptionPane.showMessageDialog(AltosUI.this,
162 String.format("Cannot open device \"%s\"",
164 "Cannot open target device",
165 JOptionPane.ERROR_MESSAGE);
166 } catch (IOException ee) {
167 JOptionPane.showMessageDialog(AltosUI.this,
170 JOptionPane.ERROR_MESSAGE);
175 void DisconnectFromDevice () {
179 void ConfigureCallsign() {
181 result = JOptionPane.showInputDialog(AltosUI.this,
182 "Configure Callsign",
183 AltosPreferences.callsign());
184 if (result != null) {
185 AltosPreferences.set_callsign(result);
186 if (serial_line != null)
187 serial_line.set_callsign(result);
191 void ConfigureTeleMetrum() {
192 new AltosConfig(AltosUI.this);
196 new AltosFlashUI(AltosUI.this);
200 Thread display_thread;
202 private void stop_display() {
203 if (display_thread != null && display_thread.isAlive()) {
204 display_thread.interrupt();
206 display_thread.join();
207 } catch (InterruptedException ie) {}
209 display_thread = null;
212 private void run_display(Thread thread) {
214 display_thread = thread;
215 display_thread.start();
219 * Replay a flight from telemetry data
221 private void Replay() {
222 AltosLogfileChooser chooser = new AltosLogfileChooser(
224 AltosRecordIterable iterable = chooser.runDialog();
225 if (iterable != null)
226 run_display(new AltosReplayThread(this, iterable.iterator(),
233 /* Connect to TeleMetrum, either directly or through
234 * a TeleDongle over the packet link
236 private void SaveFlightData() {
237 new AltosEepromDownload(AltosUI.this);
240 /* Load a flight log file and write out a CSV file containing
241 * all of the data in standard units
244 private void ExportData() {
245 new AltosCSVUI(AltosUI.this);
248 /* Load a flight log CSV file and display a pretty graph.
251 private void GraphData() {
252 new AltosGraphUI(AltosUI.this);
255 /* Create the AltosUI menus
257 private void createMenu() {
258 JMenuBar menubar = new JMenuBar();
261 JRadioButtonMenuItem radioitem;
265 menu = new JMenu("File");
266 menu.setMnemonic(KeyEvent.VK_F);
269 item = new JMenuItem("Replay File",KeyEvent.VK_R);
270 item.addActionListener(new ActionListener() {
271 public void actionPerformed(ActionEvent e) {
277 item = new JMenuItem("Save Flight Data",KeyEvent.VK_S);
278 item.addActionListener(new ActionListener() {
279 public void actionPerformed(ActionEvent e) {
285 item = new JMenuItem("Flash Image",KeyEvent.VK_F);
286 item.addActionListener(new ActionListener() {
287 public void actionPerformed(ActionEvent e) {
293 item = new JMenuItem("Export Data",KeyEvent.VK_F);
294 item.addActionListener(new ActionListener() {
295 public void actionPerformed(ActionEvent e) {
301 item = new JMenuItem("Graph Data",KeyEvent.VK_F);
302 item.addActionListener(new ActionListener() {
303 public void actionPerformed(ActionEvent e) {
309 item = new JMenuItem("Quit",KeyEvent.VK_Q);
310 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
311 ActionEvent.CTRL_MASK));
312 item.addActionListener(new ActionListener() {
313 public void actionPerformed(ActionEvent e) {
322 menu = new JMenu("Device");
323 menu.setMnemonic(KeyEvent.VK_D);
326 item = new JMenuItem("Connect to Device",KeyEvent.VK_C);
327 item.addActionListener(new ActionListener() {
328 public void actionPerformed(ActionEvent e) {
334 item = new JMenuItem("Disconnect from Device",KeyEvent.VK_D);
335 item.addActionListener(new ActionListener() {
336 public void actionPerformed(ActionEvent e) {
337 DisconnectFromDevice();
344 item = new JMenuItem("Set Callsign",KeyEvent.VK_S);
345 item.addActionListener(new ActionListener() {
346 public void actionPerformed(ActionEvent e) {
353 item = new JMenuItem("Configure TeleMetrum device",KeyEvent.VK_T);
354 item.addActionListener(new ActionListener() {
355 public void actionPerformed(ActionEvent e) {
356 ConfigureTeleMetrum();
364 menu = new JMenu("Log");
365 menu.setMnemonic(KeyEvent.VK_L);
368 item = new JMenuItem("New Log",KeyEvent.VK_N);
369 item.addActionListener(new ActionListener() {
370 public void actionPerformed(ActionEvent e) {
375 item = new JMenuItem("Configure Log",KeyEvent.VK_C);
376 item.addActionListener(new ActionListener() {
377 public void actionPerformed(ActionEvent e) {
378 AltosPreferences.ConfigureLog();
385 menu = new JMenu("Voice", true);
386 menu.setMnemonic(KeyEvent.VK_V);
389 radioitem = new JRadioButtonMenuItem("Enable Voice", AltosPreferences.voice());
390 radioitem.addActionListener(new ActionListener() {
391 public void actionPerformed(ActionEvent e) {
392 JRadioButtonMenuItem item = (JRadioButtonMenuItem) e.getSource();
393 boolean enabled = item.isSelected();
394 AltosPreferences.set_voice(enabled);
396 voice.speak_always("Enable voice.");
398 voice.speak_always("Disable voice.");
402 item = new JMenuItem("Test Voice",KeyEvent.VK_T);
403 item.addActionListener(new ActionListener() {
404 public void actionPerformed(ActionEvent e) {
405 voice.speak("That's one small step for man; one giant leap for mankind.");
413 menu = new AltosChannelMenu(AltosPreferences.channel());
414 menu.addActionListener(new ActionListener() {
415 public void actionPerformed(ActionEvent e) {
416 int new_channel = Integer.parseInt(e.getActionCommand());
417 AltosPreferences.set_channel(new_channel);
418 serial_line.set_channel(new_channel);
421 menu.setMnemonic(KeyEvent.VK_C);
425 this.setJMenuBar(menubar);
429 static AltosRecordIterable open_logfile(String filename) {
430 File file = new File (filename);
434 in = new FileInputStream(file);
435 if (filename.endsWith("eeprom"))
436 return new AltosEepromIterable(in);
438 return new AltosTelemetryIterable(in);
439 } catch (FileNotFoundException fe) {
440 System.out.printf("Cannot open '%s'\n", filename);
445 static AltosWriter open_csv(String filename) {
446 File file = new File (filename);
448 return new AltosCSV(file);
449 } catch (FileNotFoundException fe) {
450 System.out.printf("Cannot open '%s'\n", filename);
455 static AltosWriter open_kml(String filename) {
456 File file = new File (filename);
458 return new AltosKML(file);
459 } catch (FileNotFoundException fe) {
460 System.out.printf("Cannot open '%s'\n", filename);
465 static final int process_csv = 1;
466 static final int process_kml = 2;
468 static void process_file(String input, int process) {
469 AltosRecordIterable iterable = open_logfile(input);
470 if (iterable == null)
473 process = process_csv;
474 if ((process & process_csv) != 0) {
475 String output = Altos.replace_extension(input,".csv");
476 System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
477 if (input.equals(output)) {
478 System.out.printf("Not processing '%s'\n", input);
480 AltosWriter writer = open_csv(output);
481 if (writer != null) {
482 writer.write(iterable);
487 if ((process & process_kml) != 0) {
488 String output = Altos.replace_extension(input,".kml");
489 System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
490 if (input.equals(output)) {
491 System.out.printf("Not processing '%s'\n", input);
493 AltosWriter writer = open_kml(output);
496 writer.write(iterable);
502 public static void main(final String[] args) {
504 /* Handle batch-mode */
505 if (args.length > 0) {
506 for (int i = 0; i < args.length; i++) {
507 if (args[i].equals("--kml"))
508 process |= process_kml;
509 else if (args[i].equals("--csv"))
510 process |= process_csv;
512 process_file(args[i], process);
515 AltosUI altosui = new AltosUI();
516 altosui.setVisible(true);