altosui: Report error message back from libaltos
[fw/altos] / altosui / AltosLaunchUI.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 javax.swing.event.*;
26 import java.io.*;
27 import java.util.*;
28 import java.text.*;
29 import java.util.prefs.*;
30 import java.util.concurrent.*;
31
32 class FireButton extends JButton {
33         protected void processMouseEvent(MouseEvent e) {
34                 super.processMouseEvent(e);
35                 switch (e.getID()) {
36                 case MouseEvent.MOUSE_PRESSED:
37                         if (actionListener != null)
38                                 actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_down"));
39                         break;
40                 case MouseEvent.MOUSE_RELEASED:
41                         if (actionListener != null)
42                                 actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_up"));
43                         break;
44                 }
45         }
46
47         public FireButton(String s) {
48                 super(s);
49         }
50 }
51
52 public class AltosLaunchUI
53         extends JDialog
54         implements ActionListener
55 {
56         AltosDevice     device;
57         JFrame          owner;
58         JLabel          label;
59
60         int             radio_channel;
61         JLabel          radio_channel_label;
62         JTextField      radio_channel_text;
63
64         int             launcher_serial;
65         JLabel          launcher_serial_label;
66         JTextField      launcher_serial_text;
67
68         int             launcher_channel;
69         JLabel          launcher_channel_label;
70         JTextField      launcher_channel_text;
71
72         JLabel          armed_label;
73         JLabel          armed_status_label;
74         JLabel          igniter;
75         JLabel          igniter_status_label;
76         JToggleButton   arm;
77         FireButton      fire;
78         javax.swing.Timer       arm_timer;
79         javax.swing.Timer       fire_timer;
80
81         boolean         firing;
82         boolean         armed;
83         int             armed_status;
84         int             igniter_status;
85         int             rssi;
86
87         final static int        arm_timeout = 1 * 1000;
88         final static int        fire_timeout = 250;
89
90         int             armed_count;
91
92         LinkedBlockingQueue<String>     command_queue;
93
94         class LaunchHandler implements Runnable {
95                 AltosLaunch     launch;
96                 JFrame          owner;
97
98                 void send_exception(Exception e) {
99                         final Exception f_e = e;
100                         Runnable r = new Runnable() {
101                                         public void run() {
102                                                 launch_exception(f_e);
103                                         }
104                                 };
105                         SwingUtilities.invokeLater(r);
106                 }
107
108                 public void run () {
109                         try {
110                                 launch = new AltosLaunch(device);
111                         } catch (Exception e) {
112                                 send_exception(e);
113                                 return;
114                         }
115                         launch.set_frame(owner);
116                         launch.set_remote(launcher_serial, launcher_channel);
117
118                         for (;;) {
119                                 Runnable        r;
120
121                                 try {
122                                         String          command = command_queue.take();
123                                         String          reply = null;
124
125                                         if (command.equals("get_status")) {
126                                                 launch.status();
127                                                 reply = "status";
128                                                 armed_status = launch.armed;
129                                                 igniter_status = launch.igniter;
130                                                 rssi = launch.rssi;
131                                         } else if (command.equals("set_remote")) {
132                                                 launch.set_remote(launcher_serial, launcher_channel);
133                                                 reply = "remote set";
134                                         } else if (command.equals("arm")) {
135                                                 launch.arm();
136                                                 reply = "armed";
137                                         } else if (command.equals("fire")) {
138                                                 launch.fire();
139                                                 reply = "fired";
140                                         } else if (command.equals("quit")) {
141                                                 launch.close();
142                                                 break;
143                                         } else {
144                                                 throw new ParseException(String.format("invalid command %s", command), 0);
145                                         }
146                                         final String f_reply = reply;
147                                         r = new Runnable() {
148                                                         public void run() {
149                                                                 launch_reply(f_reply);
150                                                         }
151                                                 };
152                                         SwingUtilities.invokeLater(r);
153                                 } catch (Exception e) {
154                                         send_exception(e);
155                                 }
156                         }
157                 }
158
159                 public LaunchHandler(JFrame in_owner) {
160                         owner = in_owner;
161                 }
162         }
163
164         void launch_exception(Exception e) {
165                 if (e instanceof FileNotFoundException) {
166                         JOptionPane.showMessageDialog(owner,
167                                                       ((FileNotFoundException) e).getMessage(),
168                                                       "Cannot open target device",
169                                                       JOptionPane.ERROR_MESSAGE);
170                 } else if (e instanceof AltosSerialInUseException) {
171                         JOptionPane.showMessageDialog(owner,
172                                                       String.format("Device \"%s\" already in use",
173                                                                     device.toShortString()),
174                                                       "Device in use",
175                                                       JOptionPane.ERROR_MESSAGE);
176                 } else if (e instanceof IOException) {
177                         IOException ee = (IOException) e;
178                         JOptionPane.showMessageDialog(owner,
179                                                       device.toShortString(),
180                                                       ee.getLocalizedMessage(),
181                                                       JOptionPane.ERROR_MESSAGE);
182                 } else {
183                         JOptionPane.showMessageDialog(owner,
184                                                       String.format("Connection to \"%s\" failed",
185                                                                     device.toShortString()),
186                                                       "Connection Failed",
187                                                       JOptionPane.ERROR_MESSAGE);
188                 }
189                 close();
190         }
191
192         void launch_reply(String reply) {
193                 if (reply == null)
194                         return;
195                 if (reply.equals("remote set"))
196                         poll_launch_status();
197                 if (reply.equals("status")) {
198                         set_launch_status();
199                 }
200         }
201
202         void set_arm_text() {
203                 if (arm.isSelected())
204                         arm.setText(String.format("%d", armed_count));
205                 else
206                         arm.setText("Arm");
207         }
208
209         void start_arm_timer() {
210                 armed_count = 30;
211                 set_arm_text();
212         }
213
214         void stop_arm_timer() {
215                 armed_count = 0;
216                 armed = false;
217                 arm.setSelected(false);
218                 fire.setEnabled(false);
219                 set_arm_text();
220         }
221
222         void cancel () {
223                 fire.setEnabled(false);
224                 firing = false;
225                 stop_arm_timer();
226         }
227
228         void send_command(String command) {
229                 try {
230                         command_queue.put(command);
231                 } catch (Exception ex) {
232                         launch_exception(ex);
233                 }
234         }
235
236         boolean getting_status = false;
237
238         void set_launch_status() {
239                 getting_status = false;
240                 armed_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(armed_status)));
241                 igniter_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(igniter_status)));
242         }
243
244         void poll_launch_status() {
245                 if (!getting_status && !firing && !armed) {
246                         getting_status = true;
247                         send_command("get_status");
248                 }
249         }
250
251         void fired() {
252                 firing = false;
253                 cancel();
254         }
255
256         void close() {
257                 send_command("quit");
258                 arm_timer.stop();
259                 setVisible(false);
260                 dispose();
261         }
262
263         void tick_arm_timer() {
264                 if (armed_count > 0) {
265                         --armed_count;
266                         if (armed_count <= 0) {
267                                 armed_count = 0;
268                                 cancel();
269                         } else {
270                                 if (!firing) {
271                                         send_command("arm");
272                                         set_arm_text();
273                                 }
274                         }
275                 }
276                 poll_launch_status();
277         }
278
279         void arm() {
280                 if (arm.isSelected()) {
281                         fire.setEnabled(true);
282                         start_arm_timer();
283                         if (!firing)
284                                 send_command("arm");
285                         armed = true;
286                 } else
287                         cancel();
288         }
289
290         void fire_more() {
291                 if (firing)
292                         send_command("fire");
293         }
294
295         void fire_down() {
296                 if (arm.isEnabled() && arm.isSelected() && armed_count > 0) {
297                         firing = true;
298                         fire_more();
299                         fire_timer.restart();
300                 }
301         }
302
303         void fire_up() {
304                 firing = false;
305                 fire_timer.stop();
306         }
307
308         void set_radio() {
309                 try {
310                         radio_channel = Integer.parseInt(radio_channel_text.getText());
311                 } catch (NumberFormatException ne) {
312                         radio_channel_text.setText(String.format("%d", radio_channel));
313                 }
314         }
315
316         void set_serial() {
317                 try {
318                         launcher_serial = Integer.parseInt(launcher_serial_text.getText());
319                         AltosPreferences.set_launcher_serial(launcher_serial);
320                         send_command("set_remote");
321                 } catch (NumberFormatException ne) {
322                         launcher_serial_text.setText(String.format("%d", launcher_serial));
323                 }
324         }
325
326         void set_channel() {
327                 try {
328                         launcher_channel = Integer.parseInt(launcher_channel_text.getText());
329                         AltosPreferences.set_launcher_serial(launcher_channel);
330                         send_command("set_remote");
331                 } catch (NumberFormatException ne) {
332                         launcher_channel_text.setText(String.format("%d", launcher_channel));
333                 }
334         }
335
336         public void actionPerformed(ActionEvent e) {
337                 String cmd = e.getActionCommand();
338                 System.out.printf("cmd %s\n", cmd);
339                 if (cmd.equals("armed") || cmd.equals("igniter")) {
340                         stop_arm_timer();
341                 }
342
343                 if (cmd.equals("arm"))
344                         arm();
345                 if (cmd.equals("tick_arm"))
346                         tick_arm_timer();
347                 if (cmd.equals("close"))
348                         close();
349                 if (cmd.equals("fire_down"))
350                         fire_down();
351                 if (cmd.equals("fire_up"))
352                         fire_up();
353                 if (cmd.equals("tick_fire"))
354                         fire_more();
355                 if (cmd.equals("new_serial"))
356                         set_serial();
357                 if (cmd.equals("new_channel"))
358                         set_channel();
359         }
360
361         /* A window listener to catch closing events and tell the config code */
362         class ConfigListener extends WindowAdapter {
363                 AltosLaunchUI   ui;
364
365                 public ConfigListener(AltosLaunchUI this_ui) {
366                         ui = this_ui;
367                 }
368
369                 public void windowClosing(WindowEvent e) {
370                         ui.actionPerformed(new ActionEvent(e.getSource(),
371                                                            ActionEvent.ACTION_PERFORMED,
372                                                            "close"));
373                 }
374         }
375
376         private boolean open() {
377                 command_queue = new LinkedBlockingQueue<String>();
378
379                 device = AltosDeviceDialog.show(owner, Altos.product_any);
380                 if (device != null) {
381                                 LaunchHandler   handler = new LaunchHandler(owner);
382                                 Thread          t = new Thread(handler);
383                                 t.start();
384                                 return true;
385                 }
386                 return false;
387         }
388
389         public AltosLaunchUI(JFrame in_owner) {
390
391                 launcher_channel = AltosPreferences.launcher_channel();
392                 launcher_serial = AltosPreferences.launcher_serial();
393                 owner = in_owner;
394                 armed_status = AltosLaunch.Unknown;
395                 igniter_status = AltosLaunch.Unknown;
396
397                 if (!open())
398                         return;
399
400                 Container               pane = getContentPane();
401                 GridBagConstraints      c = new GridBagConstraints();
402                 Insets                  i = new Insets(4,4,4,4);
403
404                 arm_timer = new javax.swing.Timer(arm_timeout, this);
405                 arm_timer.setActionCommand("tick_arm");
406                 arm_timer.restart();
407
408                 fire_timer = new javax.swing.Timer(fire_timeout, this);
409                 fire_timer.setActionCommand("tick_fire");
410
411                 owner = in_owner;
412
413                 pane.setLayout(new GridBagLayout());
414
415                 c.fill = GridBagConstraints.NONE;
416                 c.anchor = GridBagConstraints.CENTER;
417                 c.insets = i;
418                 c.weightx = 1;
419                 c.weighty = 1;
420
421                 c.gridx = 0;
422                 c.gridy = 0;
423                 c.gridwidth = 2;
424                 c.anchor = GridBagConstraints.CENTER;
425                 label = new JLabel ("Launch Controller");
426                 pane.add(label, c);
427
428                 c.gridx = 0;
429                 c.gridy = 1;
430                 c.gridwidth = 1;
431                 c.anchor = GridBagConstraints.WEST;
432                 launcher_serial_label = new JLabel("Launcher Serial");
433                 pane.add(launcher_serial_label, c);
434
435                 c.gridx = 1;
436                 c.gridy = 1;
437                 c.gridwidth = 1;
438                 c.anchor = GridBagConstraints.WEST;
439                 launcher_serial_text = new JTextField(7);
440                 launcher_serial_text.setText(String.format("%d", launcher_serial));
441                 launcher_serial_text.setActionCommand("new_serial");
442                 launcher_serial_text.addActionListener(this);
443                 pane.add(launcher_serial_text, c);
444
445                 c.gridx = 0;
446                 c.gridy = 2;
447                 c.gridwidth = 1;
448                 c.anchor = GridBagConstraints.WEST;
449                 launcher_channel_label = new JLabel("Launcher Channel");
450                 pane.add(launcher_channel_label, c);
451
452                 c.gridx = 1;
453                 c.gridy = 2;
454                 c.gridwidth = 1;
455                 c.anchor = GridBagConstraints.WEST;
456                 launcher_channel_text = new JTextField(7);
457                 launcher_channel_text.setText(String.format("%d", launcher_channel));
458                 launcher_channel_text.setActionCommand("new_channel");
459                 launcher_channel_text.addActionListener(this);
460                 pane.add(launcher_channel_text, c);
461
462                 c.gridx = 0;
463                 c.gridy = 3;
464                 c.gridwidth = 1;
465                 c.anchor = GridBagConstraints.WEST;
466                 armed_label = new JLabel ("Armed");
467                 pane.add(armed_label, c);
468
469                 c.gridx = 1;
470                 c.gridy = 3;
471                 c.gridwidth = 1;
472                 c.anchor = GridBagConstraints.WEST;
473                 armed_status_label = new JLabel();
474                 pane.add(armed_status_label, c);
475
476                 c.gridx = 0;
477                 c.gridy = 4;
478                 c.gridwidth = 1;
479                 c.anchor = GridBagConstraints.WEST;
480                 igniter = new JLabel ("Igniter");
481                 pane.add(igniter, c);
482
483                 c.gridx = 1;
484                 c.gridy = 4;
485                 c.gridwidth = 1;
486                 c.anchor = GridBagConstraints.WEST;
487                 igniter_status_label = new JLabel();
488                 pane.add(igniter_status_label, c);
489
490                 c.gridx = 0;
491                 c.gridy = 5;
492                 c.gridwidth = 1;
493                 c.anchor = GridBagConstraints.CENTER;
494                 arm = new JToggleButton ("Arm");
495                 pane.add(arm, c);
496                 arm.addActionListener(this);
497                 arm.setActionCommand("arm");
498                 arm.setEnabled(true);
499
500                 c.gridx = 1;
501                 c.gridy = 5;
502                 c.gridwidth = 1;
503                 c.anchor = GridBagConstraints.CENTER;
504                 fire = new FireButton ("Fire");
505                 fire.setEnabled(false);
506                 pane.add(fire, c);
507                 fire.addActionListener(this);
508                 fire.setActionCommand("fire");
509
510                 pack();
511                 setLocationRelativeTo(owner);
512
513                 addWindowListener(new ConfigListener(this));
514
515                 setVisible(true);
516         }
517 }