0af09651e304509dce7358d5e92f860f63effb63
[fw/altos] / 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 java.io.*;
24 import java.util.concurrent.*;
25 import org.altusmetrum.altoslib_6.*;
26 import org.altusmetrum.altosuilib_6.*;
27
28 public class AltosUI extends AltosUIFrame {
29         public AltosVoice voice = new AltosVoice();
30
31         public static boolean load_library(Frame frame) {
32                 if (!Altos.load_library()) {
33                         JOptionPane.showMessageDialog(frame,
34                                                       String.format("No AltOS library in \"%s\"",
35                                                                     System.getProperty("java.library.path","<undefined>")),
36                                                       "Cannot load device access library",
37                                                       JOptionPane.ERROR_MESSAGE);
38                         return false;
39                 }
40                 return true;
41         }
42
43         void telemetry_window(AltosDevice device) {
44                 try {
45                         AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device));
46                         if (reader != null)
47                                 new AltosFlightUI(voice, reader, device.getSerial());
48                 } catch (FileNotFoundException ee) {
49                         JOptionPane.showMessageDialog(AltosUI.this,
50                                                       ee.getMessage(),
51                                                       String.format ("Cannot open %s", device.toShortString()),
52                                                       JOptionPane.ERROR_MESSAGE);
53                 } catch (AltosSerialInUseException si) {
54                         JOptionPane.showMessageDialog(AltosUI.this,
55                                                       String.format("Device \"%s\" already in use",
56                                                                     device.toShortString()),
57                                                       "Device in use",
58                                                       JOptionPane.ERROR_MESSAGE);
59                 } catch (IOException ee) {
60                         JOptionPane.showMessageDialog(AltosUI.this,
61                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
62                                                       "Unknown I/O error",
63                                                       JOptionPane.ERROR_MESSAGE);
64                 } catch (TimeoutException te) {
65                         JOptionPane.showMessageDialog(this,
66                                                       String.format ("Timeout on %s", device.toShortString()),
67                                                       "Timeout error",
68                                                       JOptionPane.ERROR_MESSAGE);
69                 } catch (InterruptedException ie) {
70                         JOptionPane.showMessageDialog(this,
71                                                       String.format("Interrupted %s", device.toShortString()),
72                                                       "Interrupted exception",
73                                                       JOptionPane.ERROR_MESSAGE);
74                 }
75         }
76
77         public void scan_device_selected(AltosDevice device) {
78                 telemetry_window(device);
79         }
80
81         Container       pane;
82         GridBagLayout   gridbag;
83
84         JButton addButton(int x, int y, String label) {
85                 GridBagConstraints      c;
86                 JButton                 b;
87
88                 c = new GridBagConstraints();
89                 c.gridx = x; c.gridy = y;
90                 c.fill = GridBagConstraints.BOTH;
91                 c.weightx = 1;
92                 c.weighty = 1;
93                 b = new JButton(label);
94
95                 //Dimension ps = b.getPreferredSize();
96
97                 gridbag.setConstraints(b, c);
98                 add(b, c);
99                 return b;
100         }
101
102         /* OSXAdapter interfaces */
103         public void macosx_file_handler(String path) {
104                 process_graph(new File(path));
105         }
106
107         public void macosx_quit_handler() {
108                 System.exit(0);
109         }
110
111         public void macosx_preferences_handler() {
112                 ConfigureAltosUI();
113         }
114
115         public AltosUI() {
116
117                 load_library(null);
118
119                 register_for_macosx_events();
120
121                 AltosUIPreferences.set_component(this);
122
123                 pane = getContentPane();
124                 gridbag = new GridBagLayout();
125                 pane.setLayout(gridbag);
126
127                 JButton b;
128
129                 b = addButton(0, 0, "Monitor Flight");
130                 b.addActionListener(new ActionListener() {
131                                         public void actionPerformed(ActionEvent e) {
132                                                 ConnectToDevice();
133                                         }
134                                 });
135                 b.setToolTipText("Connect to TeleDongle and monitor telemetry");
136                 b = addButton(1, 0, "Save Flight Data");
137                 b.addActionListener(new ActionListener() {
138                                         public void actionPerformed(ActionEvent e) {
139                                                 SaveFlightData();
140                                         }
141                                 });
142                 b.setToolTipText("Download and/or delete flight data from an altimeter");
143                 b = addButton(2, 0, "Replay Flight");
144                 b.addActionListener(new ActionListener() {
145                                         public void actionPerformed(ActionEvent e) {
146                                                 Replay();
147                                         }
148                                 });
149                 b.setToolTipText("Watch an old flight in real-time");
150                 b = addButton(3, 0, "Graph Data");
151                 b.addActionListener(new ActionListener() {
152                                         public void actionPerformed(ActionEvent e) {
153                                                 GraphData();
154                                         }
155                                 });
156                 b.setToolTipText("Present flight data in a graph and table of statistics");
157                 b = addButton(4, 0, "Export Data");
158                 b.addActionListener(new ActionListener() {
159                                         public void actionPerformed(ActionEvent e) {
160                                                 ExportData();
161                                         }
162                                 });
163                 b.setToolTipText("Convert flight data for a spreadsheet or GoogleEarth");
164                 b = addButton(0, 1, "Configure Altimeter");
165                 b.addActionListener(new ActionListener() {
166                                         public void actionPerformed(ActionEvent e) {
167                                                 ConfigureTeleMetrum();
168                                         }
169                                 });
170                 b.setToolTipText("Set flight, storage and communication parameters");
171                 b = addButton(1, 1, "Configure AltosUI");
172                 b.addActionListener(new ActionListener() {
173                                 public void actionPerformed(ActionEvent e) {
174                                         ConfigureAltosUI();
175                                 }
176                         });
177                 b.setToolTipText("Global AltosUI settings");
178
179                 b = addButton(2, 1, "Configure Ground Station");
180                 b.addActionListener(new ActionListener() {
181                                 public void actionPerformed(ActionEvent e) {
182                                         ConfigureTeleDongle();
183                                 }
184                         });
185
186                 b = addButton(3, 1, "Flash Image");
187                 b.addActionListener(new ActionListener() {
188                                 public void actionPerformed(ActionEvent e) {
189                                         FlashImage();
190                                 }
191                         });
192                 b.setToolTipText("Replace the firmware in any AltusMetrum product");
193
194                 b = addButton(4, 1, "Fire Igniter");
195                 b.addActionListener(new ActionListener() {
196                                 public void actionPerformed(ActionEvent e) {
197                                         FireIgniter();
198                                 }
199                         });
200                 b.setToolTipText("Remote control of igniters for deployment testing");
201                 b = addButton(0, 2, "Scan Channels");
202                 b.addActionListener(new ActionListener() {
203                                 public void actionPerformed(ActionEvent e) {
204                                         ScanChannels();
205                                 }
206                         });
207                 b.setToolTipText("Find what channel an altimeter is sending telemetry on");
208                 b = addButton(1, 2, "Load Maps");
209                 b.addActionListener(new ActionListener() {
210                                 public void actionPerformed(ActionEvent e) {
211                                         LoadMaps();
212                                 }
213                         });
214                 b.setToolTipText("Download satellite images for off-line flight monitoring");
215                 b = addButton(2, 2, "Monitor Idle");
216                 b.addActionListener(new ActionListener() {
217                                 public void actionPerformed(ActionEvent e) {
218                                         IdleMonitor();
219                                 }
220                         });
221                 b.setToolTipText("Check flight readiness of altimeter in idle mode");
222
223 //              b = addButton(3, 2, "Launch Controller");
224 //              b.addActionListener(new ActionListener() {
225 //                              public void actionPerformed(ActionEvent e) {
226 //                                      LaunchController();
227 //                              }
228 //                      });
229
230                 b = addButton(4, 2, "Quit");
231                 b.addActionListener(new ActionListener() {
232                                 public void actionPerformed(ActionEvent e) {
233                                         System.exit(0);
234                                 }
235                         });
236                 b.setToolTipText("Close all active windows and terminate AltosUI");
237
238                 setTitle("AltOS");
239
240                 pane.doLayout();
241                 pane.validate();
242
243                 doLayout();
244                 validate();
245
246                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
247                 addWindowListener(new WindowAdapter() {
248                         @Override
249                         public void windowClosing(WindowEvent e) {
250                                 System.exit(0);
251                         }
252                 });
253
254                 setLocationByPlatform(false);
255
256                 /* Insets aren't set before the window is visible */
257                 setVisible(true);
258         }
259
260         private void ConnectToDevice() {
261                 AltosDevice     device = AltosDeviceUIDialog.show(AltosUI.this,
262                                                                 Altos.product_basestation);
263
264                 if (device != null)
265                         telemetry_window(device);
266         }
267
268         void ConfigureCallsign() {
269                 String  result;
270                 result = JOptionPane.showInputDialog(AltosUI.this,
271                                                      "Configure Callsign",
272                                                      AltosUIPreferences.callsign());
273                 if (result != null)
274                         AltosUIPreferences.set_callsign(result);
275         }
276
277         void ConfigureTeleMetrum() {
278                 new AltosConfig(AltosUI.this);
279         }
280
281         void ConfigureTeleDongle() {
282                 new AltosConfigTD(AltosUI.this);
283         }
284
285         void FlashImage() {
286                 AltosFlashUI.show(AltosUI.this);
287         }
288
289         void FireIgniter() {
290                 new AltosIgniteUI(AltosUI.this);
291         }
292
293         void ScanChannels() {
294                 new AltosScanUI(AltosUI.this, true);
295         }
296
297         void LoadMaps() {
298                 new AltosUIMapPreload(AltosUI.this);
299         }
300
301         void LaunchController() {
302                 new AltosLaunchUI(AltosUI.this);
303         }
304
305         /*
306          * Replay a flight from telemetry data
307          */
308         private void Replay() {
309                 AltosDataChooser chooser = new AltosDataChooser(
310                         AltosUI.this);
311
312                 Iterable<AltosState> states = chooser.runDialog();
313                 if (states != null) {
314                         AltosFlightReader reader = new AltosReplayReader(states.iterator(),
315                                                                          chooser.file());
316                         new AltosFlightUI(voice, reader);
317                 }
318         }
319
320         /* Connect to TeleMetrum, either directly or through
321          * a TeleDongle over the packet link
322          */
323         private void SaveFlightData() {
324                 new AltosEepromManage(AltosUI.this, AltosLib.product_any);
325         }
326
327         /* Load a flight log file and write out a CSV file containing
328          * all of the data in standard units
329          */
330
331         private void ExportData() {
332                 AltosDataChooser chooser;
333                 chooser = new AltosDataChooser(this);
334                 AltosStateIterable states = chooser.runDialog();
335                 if (states == null)
336                         return;
337                 new AltosCSVUI(AltosUI.this, states, chooser.file());
338         }
339
340         /* Load a flight log CSV file and display a pretty graph.
341          */
342
343         private void GraphData() {
344                 AltosDataChooser chooser;
345                 chooser = new AltosDataChooser(this);
346                 AltosStateIterable states = chooser.runDialog();
347                 if (states == null)
348                         return;
349                 try {
350                         new AltosGraphUI(states, chooser.file());
351                 } catch (InterruptedException ie) {
352                 } catch (IOException ie) {
353                 }
354         }
355
356         private void ConfigureAltosUI() {
357                 new AltosConfigureUI(AltosUI.this, voice);
358         }
359
360         private void IdleMonitor() {
361                 try {
362                         new AltosIdleMonitorUI(this);
363                 } catch (Exception e) {
364                 }
365         }
366
367         static AltosStateIterable open_logfile(File file) {
368                 try {
369                         FileInputStream in;
370
371                         in = new FileInputStream(file);
372                         if (file.getName().endsWith("telem"))
373                                 return new AltosTelemetryFile(in);
374                         else
375                                 return new AltosEepromFile(in);
376                 } catch (FileNotFoundException fe) {
377                         System.out.printf("%s\n", fe.getMessage());
378                         return null;
379                 }
380         }
381
382         static AltosWriter open_csv(File file) {
383                 try {
384                         return new AltosCSV(file);
385                 } catch (FileNotFoundException fe) {
386                         System.out.printf("%s\n", fe.getMessage());
387                         return null;
388                 }
389         }
390
391         static AltosWriter open_kml(File file) {
392                 try {
393                         return new AltosKML(file);
394                 } catch (FileNotFoundException fe) {
395                         System.out.printf("%s\n", fe.getMessage());
396                         return null;
397                 }
398         }
399
400         static final int process_none = 0;
401         static final int process_csv = 1;
402         static final int process_kml = 2;
403         static final int process_graph = 3;
404         static final int process_replay = 4;
405         static final int process_summary = 5;
406         static final int process_cat = 6;
407
408         static boolean process_csv(File input) {
409                 AltosStateIterable states = open_logfile(input);
410                 if (states == null)
411                         return false;
412
413                 File output = Altos.replace_extension(input,".csv");
414                 System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
415                 if (input.equals(output)) {
416                         System.out.printf("Not processing '%s'\n", input);
417                         return false;
418                 } else {
419                         AltosWriter writer = open_csv(output);
420                         if (writer == null)
421                                 return false;
422                         writer.write(states);
423                         writer.close();
424                 }
425                 return true;
426         }
427
428         static boolean process_kml(File input) {
429                 AltosStateIterable states = open_logfile(input);
430                 if (states == null)
431                         return false;
432
433                 File output = Altos.replace_extension(input,".kml");
434                 System.out.printf("Processing \"%s\" to \"%s\"\n", input, output);
435                 if (input.equals(output)) {
436                         System.out.printf("Not processing '%s'\n", input);
437                         return false;
438                 } else {
439                         AltosWriter writer = open_kml(output);
440                         if (writer == null)
441                                 return false;
442                         writer.write(states);
443                         writer.close();
444                         return true;
445                 }
446         }
447
448         static AltosStateIterable record_iterable(File file) {
449                 FileInputStream in;
450                 try {
451                         in = new FileInputStream(file);
452                 } catch (Exception e) {
453                         System.out.printf("Failed to open file '%s'\n", file);
454                         return null;
455                 }
456                 if (file.getName().endsWith("telem"))
457                         return new AltosTelemetryFile(in);
458                 else
459                         return new AltosEepromFile(in);
460         }
461
462         static AltosReplayReader replay_file(File file) {
463                 AltosStateIterable states = record_iterable(file);
464                 if (states == null)
465                         return null;
466                 return new AltosReplayReader(states.iterator(), file);
467         }
468
469         static boolean process_replay(File file) {
470                 AltosReplayReader reader = replay_file(file);
471                 if (reader == null)
472                         return false;
473                 AltosFlightUI flight_ui = new AltosFlightUI(new AltosVoice(), reader);
474                 return true;
475         }
476
477         static boolean process_graph(File file) {
478                 AltosStateIterable states = record_iterable(file);
479                 if (states == null)
480                         return false;
481                 try {
482                         new AltosGraphUI(states, file);
483                         return true;
484                 } catch (InterruptedException ie) {
485                 } catch (IOException ie) {
486                 }
487                 return false;
488         }
489
490         static boolean process_summary(File file) {
491                 AltosStateIterable states = record_iterable(file);
492                 if (states == null)
493                         return false;
494                 try {
495                         AltosFlightStats stats = new AltosFlightStats(states);
496                         if (stats.serial > 0)
497                                 System.out.printf("Serial:       %5d\n", stats.serial);
498                         if (stats.flight > 0)
499                                 System.out.printf("Flight:       %5d\n", stats.flight);
500                         if (stats.year > 0)
501                                 System.out.printf("Date:    %04d-%02d-%02d\n",
502                                                   stats.year, stats.month, stats.day);
503                         if (stats.hour > 0)
504                                 System.out.printf("Time:      %02d:%02d:%02d UTC\n",
505                                                   stats.hour, stats.minute, stats.second);
506                         System.out.printf("Max height:  %6.0f m    %6.0f ft\n",
507                                           stats.max_height,
508                                           AltosConvert.meters_to_feet(stats.max_height));
509                         System.out.printf("Max speed:   %6.0f m/s  %6.0f ft/s  %6.4f Mach\n",
510                                           stats.max_speed,
511                                           AltosConvert.meters_to_feet(stats.max_speed),
512                                           AltosConvert.meters_to_mach(stats.max_speed));
513                         if (stats.max_acceleration != AltosLib.MISSING) {
514                                 System.out.printf("Max accel:   %6.0f m/s² %6.0f ft/s² %6.2f g\n",
515                                                   stats.max_acceleration,
516                                                   AltosConvert.meters_to_feet(stats.max_acceleration),
517                                                   AltosConvert.meters_to_g(stats.max_acceleration));
518                         }
519                         System.out.printf("Drogue rate: %6.0f m/s  %6.0f ft/s\n",
520                                           stats.state_speed[Altos.ao_flight_drogue],
521                                           AltosConvert.meters_to_feet(stats.state_speed[Altos.ao_flight_drogue]));
522                         System.out.printf("Main rate:   %6.0f m/s  %6.0f ft/s\n",
523                                           stats.state_speed[Altos.ao_flight_main],
524                                           AltosConvert.meters_to_feet(stats.state_speed[Altos.ao_flight_main]));
525                         System.out.printf("Flight time: %6.0f s\n",
526                                           stats.state_end[Altos.ao_flight_main] -
527                                           stats.state_start[Altos.ao_flight_boost]);
528                         return true;
529                 } catch (InterruptedException ie) {
530                 } catch (IOException ie) {
531                 }
532                 return false;
533         }
534
535         static boolean process_cat(File file) {
536                 try {
537                         AltosStateIterable eef = record_iterable(file);
538
539                         System.out.printf ("process cat\n");
540                         for (AltosState state : eef) {
541                                 System.out.printf ("tick %d state %d height %g\n",
542                                                    state.tick, state.state, state.height());
543                                 if ((state.set & AltosState.set_gps) != 0)
544                                         System.out.printf ("time %g lat %g lon %g alt %g\n",
545                                                            state.time_since_boost(),
546                                                            state.gps.lat,
547                                                            state.gps.lon,
548                                                            state.gps.alt);
549                         }
550
551                 } catch (Exception e) {
552                         System.out.printf("Failed to open file '%s'\n", file);
553                         return false;
554                 }
555                 return true;
556         }
557
558         public static void help(int code) {
559                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
560                 System.out.printf("  Options:\n");
561                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
562                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
563                 System.out.printf("    --summary <filename>\t\tText summary of a flight\n");
564                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
565                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
566                 System.exit(code);
567         }
568
569         public static void main(final String[] args) {
570                 int     errors = 0;
571                 load_library(null);
572                 try {
573                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
574                 } catch (Exception e) {
575                 }
576                 AltosUI altosui = null;
577
578                 /* Handle batch-mode */
579                 if (args.length == 0) {
580                         altosui = new AltosUI();
581                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(Altos.product_basestation);
582                         if (devices != null)
583                                 for (AltosDevice device : devices)
584                                         altosui.telemetry_window(device);
585                 } else {
586                         int process = process_none;
587                         for (int i = 0; i < args.length; i++) {
588                                 if (args[i].equals("--help"))
589                                         help(0);
590                                 else if (args[i].equals("--fetchmaps")) {
591                                         if (args.length < i + 3) {
592                                                 help(1);
593                                         } else {
594                                                 double lat = Double.parseDouble(args[i+1]);
595                                                 double lon = Double.parseDouble(args[i+2]);
596 //                                              AltosSiteMap.prefetchMaps(lat, lon);
597                                                 i += 2;
598                                         }
599                                 } else if (args[i].equals("--replay"))
600                                         process = process_replay;
601                                 else if (args[i].equals("--kml"))
602                                         process = process_kml;
603                                 else if (args[i].equals("--csv"))
604                                         process = process_csv;
605                                 else if (args[i].equals("--graph"))
606                                         process = process_graph;
607                                 else if (args[i].equals("--summary"))
608                                         process = process_summary;
609                                 else if (args[i].equals("--cat"))
610                                         process = process_cat;
611                                 else if (args[i].startsWith("--"))
612                                         help(1);
613                                 else {
614                                         File file = new File(args[i]);
615                                         switch (process) {
616                                         case process_none:
617                                         case process_graph:
618                                                 if (altosui == null)
619                                                         altosui = new AltosUI();
620                                                 if (!process_graph(file))
621                                                         ++errors;
622                                                 break;
623                                         case process_replay:
624                                                 if (altosui == null)
625                                                         altosui = new AltosUI();
626                                                 if (!process_replay(file))
627                                                         ++errors;
628                                                 break;
629                                         case process_kml:
630                                                 if (!process_kml(file))
631                                                         ++errors;
632                                                 break;
633                                         case process_csv:
634                                                 if (!process_csv(file))
635                                                         ++errors;
636                                                 break;
637                                         case process_summary:
638                                                 if (!process_summary(file))
639                                                         ++errors;
640                                                 break;
641                                         case process_cat:
642                                                 if (!process_cat(file))
643                                                         ++errors;
644                                         }
645                                 }
646                         }
647                 }
648                 if (errors != 0)
649                         System.exit(errors);
650         }
651 }