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