Merge branch 'master' of git://git.gag.com/fw/altos
[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         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);
65
66         public AltosVoice voice = new AltosVoice();
67
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);
75                         return false;
76                 }
77                 return true;
78         }
79
80         public AltosUI() {
81
82                 load_library(null);
83
84                 String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
85                 Object[][] statusData = { { "0", "pad", "-50", "0" } };
86
87                 java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
88                 if (imgURL != null)
89                         setIconImage(new ImageIcon(imgURL).getImage());
90
91                 AltosPreferences.init(this);
92
93                 vbox = Box.createVerticalBox();
94                 this.add(vbox);
95
96                 flightStatus = new AltosStatusTable(this);
97
98                 vbox.add(flightStatus);
99
100                 flightInfo = new AltosInfoTable();
101                 vbox.add(flightInfo.box());
102
103                 setTitle("AltOS");
104
105                 createMenu();
106
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()));
112                 this.validate();
113                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
114                 addWindowListener(new WindowAdapter() {
115                         @Override
116                         public void windowClosing(WindowEvent e) {
117                                 System.exit(0);
118                         }
119                 });
120                 voice.speak("Rocket flight monitor ready.");
121         }
122
123         class DeviceThread extends AltosDisplayThread {
124                 AltosSerial     serial;
125                 LinkedBlockingQueue<AltosLine> telem;
126
127                 AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException {
128                         AltosLine l = telem.take();
129                         if (l.line == null)
130                                 throw new IOException("IO error");
131                         return new AltosTelemetry(l.line);
132                 }
133
134                 void close(boolean interrupted) {
135                         serial.close();
136                         serial.remove_monitor(telem);
137                 }
138
139                 public DeviceThread(AltosSerial s, String in_name, AltosVoice voice, AltosStatusTable status, AltosInfoTable info) {
140                         super(AltosUI.this, voice, status, info);
141                         serial = s;
142                         telem = new LinkedBlockingQueue<AltosLine>();
143                         serial.add_monitor(telem);
144                         name = in_name;
145                 }
146         }
147
148         private void ConnectToDevice() {
149                 AltosDevice     device = AltosDeviceDialog.show(AltosUI.this,
150                                                                 AltosDevice.product_basestation);
151
152                 if (device != null) {
153                         try {
154                                 stop_display();
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());
159                                 run_display(thread);
160                         } catch (FileNotFoundException ee) {
161                                 JOptionPane.showMessageDialog(AltosUI.this,
162                                                               String.format("Cannot open device \"%s\"",
163                                                                             device.getPath()),
164                                                               "Cannot open target device",
165                                                               JOptionPane.ERROR_MESSAGE);
166                         } catch (IOException ee) {
167                                 JOptionPane.showMessageDialog(AltosUI.this,
168                                                               device.getPath(),
169                                                               "Unkonwn I/O error",
170                                                               JOptionPane.ERROR_MESSAGE);
171                         }
172                 }
173         }
174
175         void DisconnectFromDevice () {
176                 stop_display();
177         }
178
179         void ConfigureCallsign() {
180                 String  result;
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);
188                 }
189         }
190
191         void ConfigureTeleMetrum() {
192                 new AltosConfig(AltosUI.this);
193         }
194
195         void FlashImage() {
196                 new AltosFlashUI(AltosUI.this);
197         }
198
199
200         Thread          display_thread;
201
202         private void stop_display() {
203                 if (display_thread != null && display_thread.isAlive()) {
204                         display_thread.interrupt();
205                         try {
206                                 display_thread.join();
207                         } catch (InterruptedException ie) {}
208                 }
209                 display_thread = null;
210         }
211
212         private void run_display(Thread thread) {
213                 stop_display();
214                 display_thread = thread;
215                 display_thread.start();
216         }
217
218         /*
219          * Replay a flight from telemetry data
220          */
221         private void Replay() {
222                 AltosLogfileChooser chooser = new AltosLogfileChooser(
223                         AltosUI.this);
224                 AltosRecordIterable iterable = chooser.runDialog();
225                 if (iterable != null)
226                         run_display(new AltosReplayThread(this, iterable.iterator(),
227                                                           chooser.filename(),
228                                                           voice,
229                                                           flightStatus,
230                                                           flightInfo));
231         }
232
233         /* Connect to TeleMetrum, either directly or through
234          * a TeleDongle over the packet link
235          */
236         private void SaveFlightData() {
237                 new AltosEepromDownload(AltosUI.this);
238         }
239
240         /* Load a flight log file and write out a CSV file containing
241          * all of the data in standard units
242          */
243
244         private void ExportData() {
245                 new AltosCSVUI(AltosUI.this);
246         }
247
248         /* Load a flight log CSV file and display a pretty graph.
249          */
250
251         private void GraphData() {
252                 new AltosGraphUI(AltosUI.this);
253         }
254
255         /* Create the AltosUI menus
256          */
257         private void createMenu() {
258                 JMenuBar menubar = new JMenuBar();
259                 JMenu menu;
260                 JMenuItem item;
261                 JRadioButtonMenuItem radioitem;
262
263                 // File menu
264                 {
265                         menu = new JMenu("File");
266                         menu.setMnemonic(KeyEvent.VK_F);
267                         menubar.add(menu);
268
269                         item = new JMenuItem("Replay File",KeyEvent.VK_R);
270                         item.addActionListener(new ActionListener() {
271                                         public void actionPerformed(ActionEvent e) {
272                                                 Replay();
273                                         }
274                                 });
275                         menu.add(item);
276
277                         item = new JMenuItem("Save Flight Data",KeyEvent.VK_S);
278                         item.addActionListener(new ActionListener() {
279                                         public void actionPerformed(ActionEvent e) {
280                                                 SaveFlightData();
281                                         }
282                                 });
283                         menu.add(item);
284
285                         item = new JMenuItem("Flash Image",KeyEvent.VK_F);
286                         item.addActionListener(new ActionListener() {
287                                         public void actionPerformed(ActionEvent e) {
288                                                 FlashImage();
289                                         }
290                                 });
291                         menu.add(item);
292
293                         item = new JMenuItem("Export Data",KeyEvent.VK_F);
294                         item.addActionListener(new ActionListener() {
295                                         public void actionPerformed(ActionEvent e) {
296                                                 ExportData();
297                                         }
298                                 });
299                         menu.add(item);
300
301                         item = new JMenuItem("Graph Data",KeyEvent.VK_F);
302                         item.addActionListener(new ActionListener() {
303                                         public void actionPerformed(ActionEvent e) {
304                                                 GraphData();
305                                         }
306                                 });
307                         menu.add(item);
308
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) {
314                                                 System.exit(0);
315                                         }
316                                 });
317                         menu.add(item);
318                 }
319
320                 // Device menu
321                 {
322                         menu = new JMenu("Device");
323                         menu.setMnemonic(KeyEvent.VK_D);
324                         menubar.add(menu);
325
326                         item = new JMenuItem("Connect to Device",KeyEvent.VK_C);
327                         item.addActionListener(new ActionListener() {
328                                         public void actionPerformed(ActionEvent e) {
329                                                 ConnectToDevice();
330                                         }
331                                 });
332                         menu.add(item);
333
334                         item = new JMenuItem("Disconnect from Device",KeyEvent.VK_D);
335                         item.addActionListener(new ActionListener() {
336                                         public void actionPerformed(ActionEvent e) {
337                                                 DisconnectFromDevice();
338                                         }
339                                 });
340                         menu.add(item);
341
342                         menu.addSeparator();
343
344                         item = new JMenuItem("Set Callsign",KeyEvent.VK_S);
345                         item.addActionListener(new ActionListener() {
346                                         public void actionPerformed(ActionEvent e) {
347                                                 ConfigureCallsign();
348                                         }
349                                 });
350
351                         menu.add(item);
352
353                         item = new JMenuItem("Configure TeleMetrum device",KeyEvent.VK_T);
354                         item.addActionListener(new ActionListener() {
355                                         public void actionPerformed(ActionEvent e) {
356                                                 ConfigureTeleMetrum();
357                                         }
358                                 });
359
360                         menu.add(item);
361                 }
362                 // Log menu
363                 {
364                         menu = new JMenu("Log");
365                         menu.setMnemonic(KeyEvent.VK_L);
366                         menubar.add(menu);
367
368                         item = new JMenuItem("New Log",KeyEvent.VK_N);
369                         item.addActionListener(new ActionListener() {
370                                         public void actionPerformed(ActionEvent e) {
371                                         }
372                                 });
373                         menu.add(item);
374
375                         item = new JMenuItem("Configure Log",KeyEvent.VK_C);
376                         item.addActionListener(new ActionListener() {
377                                         public void actionPerformed(ActionEvent e) {
378                                                 AltosPreferences.ConfigureLog();
379                                         }
380                                 });
381                         menu.add(item);
382                 }
383                 // Voice menu
384                 {
385                         menu = new JMenu("Voice", true);
386                         menu.setMnemonic(KeyEvent.VK_V);
387                         menubar.add(menu);
388
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);
395                                                 if (enabled)
396                                                         voice.speak_always("Enable voice.");
397                                                 else
398                                                         voice.speak_always("Disable voice.");
399                                         }
400                                 });
401                         menu.add(radioitem);
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.");
406                                         }
407                                 });
408                         menu.add(item);
409                 }
410
411                 // Channel menu
412                 {
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);
419                                                 }
420                                 });
421                         menu.setMnemonic(KeyEvent.VK_C);
422                         menubar.add(menu);
423                 }
424
425                 this.setJMenuBar(menubar);
426
427         }
428
429         static String replace_extension(String input, String extension) {
430                 int dot = input.lastIndexOf(".");
431                 if (dot > 0)
432                         input = input.substring(0,dot);
433                 return input.concat(extension);
434         }
435
436         static AltosRecordIterable open_logfile(String filename) {
437                 File file = new File (filename);
438                 try {
439                         FileInputStream in;
440
441                         in = new FileInputStream(file);
442                         if (filename.endsWith("eeprom"))
443                                 return new AltosEepromIterable(in);
444                         else
445                                 return new AltosTelemetryIterable(in);
446                 } catch (FileNotFoundException fe) {
447                         System.out.printf("Cannot open '%s'\n", filename);
448                         return null;
449                 }
450         }
451
452         static AltosCSV open_csv(String filename) {
453                 File file = new File (filename);
454                 try {
455                         return new AltosCSV(file);
456                 } catch (FileNotFoundException fe) {
457                         System.out.printf("Cannot open '%s'\n", filename);
458                         return null;
459                 }
460         }
461
462         static void process_file(String input) {
463                 String output = replace_extension(input,".csv");
464                 if (input.equals(output)) {
465                         System.out.printf("Not processing '%s'\n", input);
466                         return;
467                 }
468                 System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
469                 AltosRecordIterable iterable = open_logfile(input);
470                 if (iterable == null)
471                         return;
472                 AltosCSV writer = open_csv(output);
473                 if (writer == null)
474                         return;
475                 writer.write(iterable);
476                 writer.close();
477         }
478
479         public static void main(final String[] args) {
480
481                 /* Handle batch-mode */
482                 if (args.length > 0) {
483                         for (int i = 0; i < args.length; i++)
484                                 process_file(args[i]);
485                 } else {
486                         AltosUI altosui = new AltosUI();
487                         altosui.setVisible(true);
488                 }
489         }
490 }