telegps: Add flash device functionality
[fw/altos] / telegps / TeleGPS.java
1 /*
2  * Copyright © 2014 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 org.altusmetrum.telegps;
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 java.util.*;
26 import org.altusmetrum.altoslib_4.*;
27 import org.altusmetrum.altosuilib_2.*;
28
29 public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFontListener, ActionListener {
30
31         static String[] telegps_icon_names = {
32                 "/telegps-16.png",
33                 "/telegps-32.png",
34                 "/telegps-48.png",
35                 "/telegps-64.png",
36                 "/telegps-128.png",
37                 "/telegps-256.png"
38         };
39
40         static { set_icon_names(telegps_icon_names); }
41
42         static AltosVoice       voice;
43
44         static AltosVoice voice() {
45                 if (voice == null)
46                         voice = new AltosVoice();
47                 return voice;
48         }
49
50         AltosFlightReader       reader;
51         AltosDisplayThread      thread;
52
53         JTabbedPane     pane;
54
55         AltosSiteMap    sitemap;
56         TeleGPSInfo     gps_info;
57         boolean         has_map;
58
59         JMenuBar        menu_bar;
60         JMenu           file_menu;
61         JMenu           monitor_menu;
62         JMenu           device_menu;
63
64         /* File menu */
65         final static String     new_command = "new";
66         final static String     preferences_command = "preferences";
67         final static String     load_maps_command = "loadmaps";
68         final static String     close_command = "close";
69         final static String     exit_command = "exit";
70
71         static final String[][] file_menu_entries = new String[][] {
72                 { "New Window",         new_command },
73                 { "Preferences",        preferences_command },
74                 { "Load Maps",          load_maps_command },
75                 { "Close",              close_command },
76                 { "Exit",               exit_command },
77         };
78
79         /* Monitor menu */
80         final static String     monitor_command = "monitor";
81         final static String     disconnect_command = "disconnect";
82         final static String     scan_command = "scan";
83
84         static final String[][] monitor_menu_entries = new String[][] {
85                 { "Monitor Device",     monitor_command },
86                 { "Disconnect",         disconnect_command },
87                 { "Scan Channels",      scan_command },
88         };
89
90         /* Device menu */
91         final static String     download_command = "download";
92         final static String     export_command = "export";
93         final static String     graph_command = "graph";
94         final static String     configure_command = "configure";
95         final static String     flash_command = "flash";
96
97         static final String[][] device_menu_entries = new String[][] {
98                 { "Download Data",      download_command },
99                 { "Configure Device",   configure_command },
100                 { "Export Data",        export_command },
101                 { "Graph Data",         graph_command },
102                 { "Flash Device",       flash_command },
103         };
104
105         void stop_display() {
106                 if (thread != null && thread.isAlive()) {
107                         thread.interrupt();
108                         try {
109                                 thread.join();
110                         } catch (InterruptedException ie) {}
111                 }
112                 thread = null;
113         }
114
115         public void reset() {
116                 sitemap.reset();
117                 gps_info.reset();
118         }
119
120         public void set_font() {
121                 sitemap.set_font();
122                 gps_info.set_font();
123         }
124
125         public void font_size_changed(int font_size) {
126                 set_font();
127         }
128
129
130 //      AltosFlightStatusUpdate status_update;
131
132         public void show(AltosState state, AltosListenerState listener_state) {
133 //              status_update.saved_state = state;
134
135                 if (state == null)
136                         state = new AltosState();
137
138                 sitemap.show(state, listener_state);
139                 gps_info.show(state, listener_state);
140                 telegps_status.show(state, listener_state);
141         }
142
143         Container               bag;
144         AltosFreqList           frequencies;
145         JLabel                  telemetry;
146         TeleGPSStatus           telegps_status;
147         TeleGPSStatusUpdate     status_update;
148
149         ActionListener          show_timer;
150
151         void new_window() {
152                 new TeleGPS();
153         }
154
155         void preferences() {
156                 new TeleGPSPreferences(this, voice());
157         }
158
159         void load_maps() {
160                 new AltosSiteMapPreload(this);
161         }
162
163         void disconnect() {
164                 setTitle("TeleGPS");
165                 stop_display();
166                 remove_frequency_menu();
167         }
168
169         void connect(AltosDevice device) {
170                 if (reader != null)
171                         disconnect();
172                 try {
173                         AltosFlightReader       reader = new AltosTelemetryReader(new AltosSerial(device));
174                         set_reader(reader);
175                         add_frequency_menu(device.getSerial(), reader);
176                 } catch (FileNotFoundException ee) {
177                         JOptionPane.showMessageDialog(this,
178                                                       ee.getMessage(),
179                                                       String.format ("Cannot open %s", device.toShortString()),
180                                                       JOptionPane.ERROR_MESSAGE);
181                 } catch (AltosSerialInUseException si) {
182                         JOptionPane.showMessageDialog(this,
183                                                       String.format("Device \"%s\" already in use",
184                                                                     device.toShortString()),
185                                                       "Device in use",
186                                                       JOptionPane.ERROR_MESSAGE);
187                 } catch (IOException ee) {
188                         JOptionPane.showMessageDialog(this,
189                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
190                                                       "Unknown I/O error",
191                                                       JOptionPane.ERROR_MESSAGE);
192                 } catch (TimeoutException te) {
193                         JOptionPane.showMessageDialog(this,
194                                                       String.format ("Timeout on %s", device.toShortString()),
195                                                       "Timeout error",
196                                                       JOptionPane.ERROR_MESSAGE);
197                 } catch (InterruptedException ie) {
198                         JOptionPane.showMessageDialog(this,
199                                                       String.format("Interrupted %s", device.toShortString()),
200                                                       "Interrupted exception",
201                                                       JOptionPane.ERROR_MESSAGE);
202                 }
203         }
204
205         void monitor() {
206                 AltosDevice     device = AltosDeviceUIDialog.show(this,
207                                                                   AltosLib.product_basestation);
208                 if (device == null)
209                         return;
210                 connect(device);
211         }
212
213         public void scan_device_selected(AltosDevice device) {
214                 connect(device);
215         }
216
217         void scan() {
218                 new AltosScanUI(this, false);
219         }
220
221         void download(){
222                 new AltosEepromManage(this, AltosLib.product_telegps);
223         }
224
225         void configure() {
226                 new TeleGPSConfig(this);
227         }
228
229         void export() {
230                 AltosDataChooser chooser;
231                 chooser = new AltosDataChooser(this);
232                 AltosStateIterable states = chooser.runDialog();
233                 if (states == null)
234                         return;
235                 new AltosCSVUI(this, states, chooser.file());
236         }
237
238         void graph() {
239         }
240
241         void flash() {
242                 AltosFlashUI.show(this);
243         }
244
245         public void actionPerformed(ActionEvent ev) {
246
247                 /* File menu */
248                 if (new_command.equals(ev.getActionCommand())) {
249                         new_window();
250                         return;
251                 }
252                 if (preferences_command.equals(ev.getActionCommand())) {
253                         preferences();
254                         return;
255                 }
256                 if (load_maps_command.equals(ev.getActionCommand())) {
257                         load_maps();
258                         return;
259                 }
260                 if (close_command.equals(ev.getActionCommand())) {
261                         close();
262                         return;
263                 }
264                 if (exit_command.equals(ev.getActionCommand()))
265                         System.exit(0);
266
267                 /* Monitor menu */
268                 if (monitor_command.equals(ev.getActionCommand())) {
269                         monitor();
270                         return;
271                 }
272                 if (disconnect_command.equals(ev.getActionCommand())) {
273                         disconnect();
274                         return;
275                 }
276                 if (scan_command.equals(ev.getActionCommand())) {
277                         scan();
278                         return;
279                 }
280
281                 /* Device menu */
282                 if (download_command.equals(ev.getActionCommand())) {
283                         download();
284                         return;
285                 }
286                 if (configure_command.equals(ev.getActionCommand())) {
287                         configure();
288                         return;
289                 }
290                 if (export_command.equals(ev.getActionCommand())) {
291                         export();
292                         return;
293                 }
294                 if (graph_command.equals(ev.getActionCommand())) {
295                         graph();
296                         return;
297                 }
298                 if (flash_command.equals(ev.getActionCommand())) {
299                         flash();
300                         return;
301                 }
302         }
303
304         void add_frequency_menu(int serial, final AltosFlightReader reader) {
305                 // Channel menu
306                 frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial));
307                 frequencies.set_product("Monitor");
308                 frequencies.set_serial(serial);
309                 frequencies.addActionListener(new ActionListener() {
310                                 public void actionPerformed(ActionEvent e) {
311                                         double frequency = frequencies.frequency();
312                                         try {
313                                                 reader.set_frequency(frequency);
314                                         } catch (TimeoutException te) {
315                                         } catch (InterruptedException ie) {
316                                         }
317                                         reader.save_frequency();
318                                 }
319                         });
320                 menu_bar.add(frequencies);
321         }
322
323         void remove_frequency_menu() {
324                 if (frequencies != null) {
325                         menu_bar.remove(frequencies);
326                         frequencies = null;
327                 }
328         }
329
330         public void set_reader(AltosFlightReader reader) {
331                 setTitle(String.format("TeleGPS %s", reader.name));
332                 thread = new AltosDisplayThread(this, voice(), this, reader);
333                 thread.start();
334         }
335
336         static int      number_of_windows;
337
338         private void close() {
339                 AltosUIPreferences.unregister_font_listener(this);
340                 setVisible(false);
341                 dispose();
342                 --number_of_windows;
343                 if (number_of_windows == 0)
344                         System.exit(0);
345         }
346
347         private void add_menu(JMenu menu, String label, String action) {
348                 JMenuItem       item = new JMenuItem(label);
349                 menu.add(item);
350                 item.addActionListener(this);
351                 item.setActionCommand(action);
352         }
353
354
355         private JMenu make_menu(String label, String[][] items) {
356                 JMenu   menu = new JMenu(label);
357                 for (int i = 0; i < items.length; i++)
358                         add_menu(menu, items[i][0], items[i][1]);
359                 menu_bar.add(menu);
360                 return menu;
361         }
362
363         public TeleGPS() {
364
365                 AltosUIPreferences.set_component(this);
366
367                 reader = null;
368
369                 bag = getContentPane();
370                 bag.setLayout(new GridBagLayout());
371
372                 GridBagConstraints c = new GridBagConstraints();
373
374                 setTitle("TeleGPS");
375
376                 menu_bar = new JMenuBar();
377                 setJMenuBar(menu_bar);
378
379                 file_menu = make_menu("File", file_menu_entries);
380                 monitor_menu = make_menu("Monitor", monitor_menu_entries);
381                 device_menu = make_menu("Device", device_menu_entries);
382
383                 int serial = -1;
384
385                 /* TeleGPS status is always visible */
386                 telegps_status = new TeleGPSStatus();
387                 c.gridx = 0;
388                 c.gridy = 1;
389                 c.fill = GridBagConstraints.HORIZONTAL;
390                 c.weightx = 1;
391                 c.gridwidth = 2;
392                 bag.add(telegps_status, c);
393                 c.gridwidth = 1;
394
395
396                 /* The rest of the window uses a tabbed pane to
397                  * show one of the alternate data views
398                  */
399                 pane = new JTabbedPane();
400
401                 /* Make the tabbed pane use the rest of the window space */
402                 c.gridx = 0;
403                 c.gridy = 2;
404                 c.fill = GridBagConstraints.BOTH;
405                 c.weightx = 1;
406                 c.weighty = 1;
407                 c.gridwidth = 2;
408                 bag.add(pane, c);
409
410                 sitemap = new AltosSiteMap();
411                 pane.add("Site Map", sitemap);
412
413                 gps_info = new TeleGPSInfo();
414                 pane.add("Info", gps_info);
415
416                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
417
418                 AltosUIPreferences.register_font_listener(this);
419
420                 addWindowListener(new WindowAdapter() {
421                                 @Override
422                                 public void windowClosing(WindowEvent e) {
423                                         close();
424                                 }
425                         });
426
427                 pack();
428                 setVisible(true);
429
430                 ++number_of_windows;
431
432                 status_update = new TeleGPSStatusUpdate(telegps_status);
433
434                 new javax.swing.Timer(100, status_update).start();
435         }
436
437         public TeleGPS(AltosFlightReader reader) {
438                 this();
439                 set_reader(reader);
440         }
441
442         public TeleGPS(AltosDevice device) {
443                 this();
444                 connect(device);
445         }
446
447         static AltosStateIterable record_iterable(File file) {
448                 FileInputStream in;
449                 try {
450                         in = new FileInputStream(file);
451                 } catch (Exception e) {
452                         System.out.printf("Failed to open file '%s'\n", file);
453                         return null;
454                 }
455                 if (file.getName().endsWith("telem"))
456                         return new AltosTelemetryFile(in);
457                 else
458                         return new AltosEepromFile(in);
459         }
460
461         static AltosReplayReader replay_file(File file) {
462                 AltosStateIterable states = record_iterable(file);
463                 if (states == null)
464                         return null;
465                 return new AltosReplayReader(states.iterator(), file);
466         }
467
468         static boolean process_replay(File file) {
469                 AltosReplayReader new_reader = replay_file(file);
470                 if (new_reader == null)
471                         return false;
472
473                 new TeleGPS(new_reader);
474                 return true;
475         }
476
477         static final int process_none = 0;
478         static final int process_csv = 1;
479         static final int process_kml = 2;
480         static final int process_graph = 3;
481         static final int process_replay = 4;
482         static final int process_summary = 5;
483         static final int process_cat = 6;
484
485         public static boolean load_library(Frame frame) {
486                 if (!AltosUILib.load_library()) {
487                         JOptionPane.showMessageDialog(frame,
488                                                       String.format("No AltOS library in \"%s\"",
489                                                                     System.getProperty("java.library.path","<undefined>")),
490                                                       "Cannot load device access library",
491                                                       JOptionPane.ERROR_MESSAGE);
492                         return false;
493                 }
494                 return true;
495         }
496
497         public static void help(int code) {
498                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
499                 System.out.printf("  Options:\n");
500                 System.out.printf("    --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n");
501                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
502                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
503                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
504                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
505                 System.exit(code);
506         }
507
508         public static void main(String[] args) {
509                 int     errors = 0;
510
511                 load_library(null);
512                 try {
513                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
514                 } catch (Exception e) {
515                 }
516
517                 boolean any_created = false;
518
519
520                 /* Handle batch-mode */
521                 int process = process_none;
522                 for (int i = 0; i < args.length; i++) {
523                         if (args[i].equals("--help"))
524                                 help(0);
525                         else if (args[i].equals("--fetchmaps")) {
526                                 if (args.length < i + 3) {
527                                         help(1);
528                                 } else {
529                                         double lat = Double.parseDouble(args[i+1]);
530                                         double lon = Double.parseDouble(args[i+2]);
531                                         AltosSiteMap.prefetchMaps(lat, lon);
532                                         i += 2;
533                                 }
534                         } else if (args[i].equals("--replay"))
535                                 process = process_replay;
536                         else if (args[i].equals("--kml"))
537                                 process = process_kml;
538                         else if (args[i].equals("--csv"))
539                                 process = process_csv;
540                         else if (args[i].equals("--graph"))
541                                 process = process_graph;
542                         else if (args[i].equals("--summary"))
543                                 process = process_summary;
544                         else if (args[i].equals("--cat"))
545                                 process = process_cat;
546                         else if (args[i].startsWith("--"))
547                                 help(1);
548                         else {
549                                 File file = new File(args[i]);
550                                 switch (process) {
551                                 case process_graph:
552                                         ++errors;
553                                         break;
554                                 case process_none:
555                                 case process_replay:
556                                         if (!process_replay(file))
557                                                 ++errors;
558                                         any_created = true;
559                                         break;
560                                 case process_kml:
561                                         ++errors;
562                                         break;
563                                 case process_csv:
564                                         ++errors;
565                                         break;
566                                 case process_summary:
567                                         ++errors;
568                                         break;
569                                 case process_cat:
570                                         ++errors;
571                                 }
572                         }
573                 }
574                 if (errors != 0)
575                         System.exit(errors);
576                 if (!any_created) {
577                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
578                         if (devices != null)
579                                 for (AltosDevice device : devices) {
580                                         new TeleGPS(device);
581                                         any_created = true;
582                                 }
583                         if (!any_created)
584                                 new TeleGPS();
585                 }
586         }
587 }