28ed42fbe4f89848db63fcaa2d11c46cb82ef59e
[fw/altos] / ao-tools / altosui / AltosUI.java
1 /*
2  * Copyright © 2010 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; version 2 of the License.
7  *
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.
12  *
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.
16  */
17
18 package altosui;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import javax.swing.*;
23 import javax.swing.filechooser.FileNameExtensionFilter;
24 import javax.swing.table.*;
25 import java.io.*;
26 import java.util.*;
27 import java.text.*;
28 import java.util.prefs.*;
29 import java.util.concurrent.LinkedBlockingQueue;
30
31 import altosui.Altos;
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;
50
51 import libaltosJNI.*;
52
53 public class AltosUI extends JFrame {
54         private int channel = -1;
55
56         private AltosStatusTable flightStatus;
57         private AltosInfoTable flightInfo;
58         private AltosSerial serial_line;
59         private AltosLog altos_log;
60         private Box vbox;
61
62         public AltosVoice voice = new AltosVoice();
63
64         public static boolean load_library(Frame frame) {
65                 if (!AltosDevice.load_library()) {
66                         JOptionPane.showMessageDialog(frame,
67                                                       String.format("No AltOS library in \"%s\"",
68                                                                     System.getProperty("java.library.path","<undefined>")),
69                                                       "Cannot load device access library",
70                                                       JOptionPane.ERROR_MESSAGE);
71                         return false;
72                 }
73                 return true;
74         }
75
76         public AltosUI() {
77
78                 load_library(null);
79
80                 String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
81                 Object[][] statusData = { { "0", "pad", "-50", "0" } };
82
83                 java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
84                 if (imgURL != null)
85                         setIconImage(new ImageIcon(imgURL).getImage());
86
87                 AltosPreferences.init(this);
88
89                 vbox = Box.createVerticalBox();
90                 this.add(vbox);
91
92                 flightStatus = new AltosStatusTable(this);
93
94                 vbox.add(flightStatus);
95
96                 flightInfo = new AltosInfoTable();
97                 vbox.add(flightInfo.box());
98
99                 setTitle("AltOS");
100
101                 createMenu();
102
103                 serial_line = new AltosSerial();
104                 altos_log = new AltosLog(serial_line);
105                 int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
106                 this.setSize(new Dimension (flightInfo.width(),
107                                             flightStatus.height() + flightInfo.height()));
108                 this.validate();
109                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
110                 addWindowListener(new WindowAdapter() {
111                         @Override
112                         public void windowClosing(WindowEvent e) {
113                                 System.exit(0);
114                         }
115                 });
116                 voice.speak("Rocket flight monitor ready.");
117         }
118
119         class DeviceThread extends AltosDisplayThread {
120                 AltosSerial     serial;
121                 LinkedBlockingQueue<AltosLine> telem;
122
123                 AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException {
124                         AltosLine l = telem.take();
125                         if (l.line == null)
126                                 throw new IOException("IO error");
127                         return new AltosTelemetry(l.line);
128                 }
129
130                 void close(boolean interrupted) {
131                         serial.close();
132                         serial.remove_monitor(telem);
133                 }
134
135                 public DeviceThread(AltosSerial s, String in_name, AltosVoice voice, AltosStatusTable status, AltosInfoTable info) {
136                         super(AltosUI.this, voice, status, info);
137                         serial = s;
138                         telem = new LinkedBlockingQueue<AltosLine>();
139                         serial.add_monitor(telem);
140                         name = in_name;
141                 }
142         }
143
144         private void ConnectToDevice() {
145                 AltosDevice     device = AltosDeviceDialog.show(AltosUI.this,
146                                                                 AltosDevice.product_basestation);
147
148                 if (device != null) {
149                         try {
150                                 stop_display();
151                                 serial_line.open(device);
152                                 DeviceThread thread = new DeviceThread(serial_line, device.getPath(), voice, flightStatus, flightInfo);
153                                 serial_line.set_channel(AltosPreferences.channel());
154                                 serial_line.set_callsign(AltosPreferences.callsign());
155                                 run_display(thread);
156                         } catch (FileNotFoundException ee) {
157                                 JOptionPane.showMessageDialog(AltosUI.this,
158                                                               String.format("Cannot open device \"%s\"",
159                                                                             device.getPath()),
160                                                               "Cannot open target device",
161                                                               JOptionPane.ERROR_MESSAGE);
162                         } catch (IOException ee) {
163                                 JOptionPane.showMessageDialog(AltosUI.this,
164                                                               device.getPath(),
165                                                               "Unkonwn I/O error",
166                                                               JOptionPane.ERROR_MESSAGE);
167                         }
168                 }
169         }
170
171         void DisconnectFromDevice () {
172                 stop_display();
173         }
174
175         void ConfigureCallsign() {
176                 String  result;
177                 result = JOptionPane.showInputDialog(AltosUI.this,
178                                                      "Configure Callsign",
179                                                      AltosPreferences.callsign());
180                 if (result != null) {
181                         AltosPreferences.set_callsign(result);
182                         if (serial_line != null)
183                                 serial_line.set_callsign(result);
184                 }
185         }
186
187         void ConfigureTeleMetrum() {
188                 new AltosConfig(AltosUI.this);
189         }
190
191         void FlashImage() {
192                 new AltosFlashUI(AltosUI.this);
193         }
194
195
196         Thread          display_thread;
197
198         private void stop_display() {
199                 if (display_thread != null && display_thread.isAlive()) {
200                         display_thread.interrupt();
201                         try {
202                                 display_thread.join();
203                         } catch (InterruptedException ie) {}
204                 }
205                 display_thread = null;
206         }
207
208         private void run_display(Thread thread) {
209                 stop_display();
210                 display_thread = thread;
211                 display_thread.start();
212         }
213
214         /*
215          * Replay a flight from telemetry data
216          */
217         private void Replay() {
218                 AltosLogfileChooser chooser = new AltosLogfileChooser(
219                         AltosUI.this);
220                 AltosRecordIterable iterable = chooser.runDialog();
221                 if (iterable != null)
222                         run_display(new AltosReplayThread(this, iterable.iterator(),
223                                                           chooser.filename(),
224                                                           voice,
225                                                           flightStatus,
226                                                           flightInfo));
227         }
228
229         /* Connect to TeleMetrum, either directly or through
230          * a TeleDongle over the packet link
231          */
232         private void SaveFlightData() {
233                 new AltosEepromDownload(AltosUI.this);
234         }
235
236         /* Load a flight log file and write out a CSV file containing
237          * all of the data in standard units
238          */
239
240         private void ExportData() {
241                 new AltosCSVUI(AltosUI.this);
242         }
243
244         /* Load a flight log CSV file and display a pretty graph.
245          */
246
247         private void GraphData() {
248                 new AltosGraphUI(AltosUI.this);
249         }
250
251         /* Create the AltosUI menus
252          */
253         private void createMenu() {
254                 JMenuBar menubar = new JMenuBar();
255                 JMenu menu;
256                 JMenuItem item;
257                 JRadioButtonMenuItem radioitem;
258
259                 // File menu
260                 {
261                         menu = new JMenu("File");
262                         menu.setMnemonic(KeyEvent.VK_F);
263                         menubar.add(menu);
264
265                         item = new JMenuItem("Replay File",KeyEvent.VK_R);
266                         item.addActionListener(new ActionListener() {
267                                         public void actionPerformed(ActionEvent e) {
268                                                 Replay();
269                                         }
270                                 });
271                         menu.add(item);
272
273                         item = new JMenuItem("Save Flight Data",KeyEvent.VK_S);
274                         item.addActionListener(new ActionListener() {
275                                         public void actionPerformed(ActionEvent e) {
276                                                 SaveFlightData();
277                                         }
278                                 });
279                         menu.add(item);
280
281                         item = new JMenuItem("Flash Image",KeyEvent.VK_F);
282                         item.addActionListener(new ActionListener() {
283                                         public void actionPerformed(ActionEvent e) {
284                                                 FlashImage();
285                                         }
286                                 });
287                         menu.add(item);
288
289                         item = new JMenuItem("Export Data",KeyEvent.VK_F);
290                         item.addActionListener(new ActionListener() {
291                                         public void actionPerformed(ActionEvent e) {
292                                                 ExportData();
293                                         }
294                                 });
295                         menu.add(item);
296
297                         item = new JMenuItem("Graph Data",KeyEvent.VK_F);
298                         item.addActionListener(new ActionListener() {
299                                         public void actionPerformed(ActionEvent e) {
300                                                 GraphData();
301                                         }
302                                 });
303                         menu.add(item);
304
305                         item = new JMenuItem("Quit",KeyEvent.VK_Q);
306                         item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
307                                                                    ActionEvent.CTRL_MASK));
308                         item.addActionListener(new ActionListener() {
309                                         public void actionPerformed(ActionEvent e) {
310                                                 System.exit(0);
311                                         }
312                                 });
313                         menu.add(item);
314                 }
315
316                 // Device menu
317                 {
318                         menu = new JMenu("Device");
319                         menu.setMnemonic(KeyEvent.VK_D);
320                         menubar.add(menu);
321
322                         item = new JMenuItem("Connect to Device",KeyEvent.VK_C);
323                         item.addActionListener(new ActionListener() {
324                                         public void actionPerformed(ActionEvent e) {
325                                                 ConnectToDevice();
326                                         }
327                                 });
328                         menu.add(item);
329
330                         item = new JMenuItem("Disconnect from Device",KeyEvent.VK_D);
331                         item.addActionListener(new ActionListener() {
332                                         public void actionPerformed(ActionEvent e) {
333                                                 DisconnectFromDevice();
334                                         }
335                                 });
336                         menu.add(item);
337
338                         menu.addSeparator();
339
340                         item = new JMenuItem("Set Callsign",KeyEvent.VK_S);
341                         item.addActionListener(new ActionListener() {
342                                         public void actionPerformed(ActionEvent e) {
343                                                 ConfigureCallsign();
344                                         }
345                                 });
346
347                         menu.add(item);
348
349                         item = new JMenuItem("Configure TeleMetrum device",KeyEvent.VK_T);
350                         item.addActionListener(new ActionListener() {
351                                         public void actionPerformed(ActionEvent e) {
352                                                 ConfigureTeleMetrum();
353                                         }
354                                 });
355
356                         menu.add(item);
357                 }
358                 // Log menu
359                 {
360                         menu = new JMenu("Log");
361                         menu.setMnemonic(KeyEvent.VK_L);
362                         menubar.add(menu);
363
364                         item = new JMenuItem("New Log",KeyEvent.VK_N);
365                         item.addActionListener(new ActionListener() {
366                                         public void actionPerformed(ActionEvent e) {
367                                         }
368                                 });
369                         menu.add(item);
370
371                         item = new JMenuItem("Configure Log",KeyEvent.VK_C);
372                         item.addActionListener(new ActionListener() {
373                                         public void actionPerformed(ActionEvent e) {
374                                                 AltosPreferences.ConfigureLog();
375                                         }
376                                 });
377                         menu.add(item);
378                 }
379                 // Voice menu
380                 {
381                         menu = new JMenu("Voice", true);
382                         menu.setMnemonic(KeyEvent.VK_V);
383                         menubar.add(menu);
384
385                         radioitem = new JRadioButtonMenuItem("Enable Voice", AltosPreferences.voice());
386                         radioitem.addActionListener(new ActionListener() {
387                                         public void actionPerformed(ActionEvent e) {
388                                                 JRadioButtonMenuItem item = (JRadioButtonMenuItem) e.getSource();
389                                                 boolean enabled = item.isSelected();
390                                                 AltosPreferences.set_voice(enabled);
391                                                 if (enabled)
392                                                         voice.speak_always("Enable voice.");
393                                                 else
394                                                         voice.speak_always("Disable voice.");
395                                         }
396                                 });
397                         menu.add(radioitem);
398                         item = new JMenuItem("Test Voice",KeyEvent.VK_T);
399                         item.addActionListener(new ActionListener() {
400                                         public void actionPerformed(ActionEvent e) {
401                                                 voice.speak("That's one small step for man; one giant leap for mankind.");
402                                         }
403                                 });
404                         menu.add(item);
405                 }
406
407                 // Channel menu
408                 {
409                         menu = new AltosChannelMenu(AltosPreferences.channel());
410                         menu.addActionListener(new ActionListener() {
411                                                 public void actionPerformed(ActionEvent e) {
412                                                         int new_channel = Integer.parseInt(e.getActionCommand());
413                                                         AltosPreferences.set_channel(new_channel);
414                                                         serial_line.set_channel(new_channel);
415                                                 }
416                                 });
417                         menu.setMnemonic(KeyEvent.VK_C);
418                         menubar.add(menu);
419                 }
420
421                 this.setJMenuBar(menubar);
422
423         }
424
425         static AltosRecordIterable open_logfile(String filename) {
426                 File file = new File (filename);
427                 try {
428                         FileInputStream in;
429
430                         in = new FileInputStream(file);
431                         if (filename.endsWith("eeprom"))
432                                 return new AltosEepromIterable(in);
433                         else
434                                 return new AltosTelemetryIterable(in);
435                 } catch (FileNotFoundException fe) {
436                         System.out.printf("Cannot open '%s'\n", filename);
437                         return null;
438                 }
439         }
440
441         static AltosWriter open_csv(String filename) {
442                 File file = new File (filename);
443                 try {
444                         return new AltosCSV(file);
445                 } catch (FileNotFoundException fe) {
446                         System.out.printf("Cannot open '%s'\n", filename);
447                         return null;
448                 }
449         }
450
451         static AltosWriter open_kml(String filename) {
452                 File file = new File (filename);
453                 try {
454                         return new AltosKML(file);
455                 } catch (FileNotFoundException fe) {
456                         System.out.printf("Cannot open '%s'\n", filename);
457                         return null;
458                 }
459         }
460
461         static final int process_csv = 1;
462         static final int process_kml = 2;
463
464         static void process_file(String input, int process) {
465                 AltosRecordIterable iterable = open_logfile(input);
466                 if (iterable == null)
467                         return;
468                 if (process == 0)
469                         process = process_csv;
470                 if ((process & process_csv) != 0) {
471                         String output = Altos.replace_extension(input,".csv");
472                         System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
473                         if (input.equals(output)) {
474                                 System.out.printf("Not processing '%s'\n", input);
475                         } else {
476                                 AltosWriter writer = open_csv(output);
477                                 if (writer != null) {
478                                         writer.write(iterable);
479                                         writer.close();
480                                 }
481                         }
482                 }
483                 if ((process & process_kml) != 0) {
484                         String output = Altos.replace_extension(input,".kml");
485                         System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
486                         if (input.equals(output)) {
487                                 System.out.printf("Not processing '%s'\n", input);
488                         } else {
489                                 AltosWriter writer = open_kml(output);
490                                 if (writer == null)
491                                         return;
492                                 writer.write(iterable);
493                                 writer.close();
494                         }
495                 }
496         }
497
498         public static void main(final String[] args) {
499                 int     process = 0;
500                 /* Handle batch-mode */
501                 if (args.length > 0) {
502                         for (int i = 0; i < args.length; i++) {
503                                 if (args[i].equals("--kml"))
504                                         process |= process_kml;
505                                 else if (args[i].equals("--csv"))
506                                         process |= process_csv;
507                                 else
508                                         process_file(args[i], process);
509                         }
510                 } else {
511                         AltosUI altosui = new AltosUI();
512                         altosui.setVisible(true);
513                 }
514         }
515 }