Merge branch 'buttonbox' of git://git.gag.com/fw/altos into buttonbox
authorAnthony Towns <aj@erisian.com.au>
Sat, 20 Nov 2010 07:40:49 +0000 (17:40 +1000)
committerAnthony Towns <aj@erisian.com.au>
Sat, 20 Nov 2010 07:40:49 +0000 (17:40 +1000)
Conflicts:
ao-tools/altosui/AltosFlightUI.java

25 files changed:
ao-bringup/turnon_teledongle
ao-bringup/turnon_telemetrum
ao-tools/altosui/AltosCSVUI.java
ao-tools/altosui/AltosChannelMenu.java
ao-tools/altosui/AltosConfig.java
ao-tools/altosui/AltosConfigureUI.java
ao-tools/altosui/AltosDataChooser.java [new file with mode: 0644]
ao-tools/altosui/AltosDebug.java
ao-tools/altosui/AltosDevice.java
ao-tools/altosui/AltosEepromDownload.java
ao-tools/altosui/AltosFlashUI.java
ao-tools/altosui/AltosFlightUI.java
ao-tools/altosui/AltosGraphDataChooser.java [deleted file]
ao-tools/altosui/AltosGraphUI.java
ao-tools/altosui/AltosIgnite.java [new file with mode: 0644]
ao-tools/altosui/AltosIgniteUI.java [new file with mode: 0644]
ao-tools/altosui/AltosLog.java
ao-tools/altosui/AltosLogfileChooser.java [deleted file]
ao-tools/altosui/AltosSerial.java
ao-tools/altosui/AltosTelemetryReader.java
ao-tools/altosui/AltosUI.java
ao-tools/altosui/Makefile.am
doc/.gitignore [new file with mode: 0644]
doc/Makefile
doc/altosui-doc.xsl [new file with mode: 0644]

index 216afa2a421e8f8a7f2c99827c956ce9dfef2459..5145e9b0bafc0831a7c98d45a9f1834853ad64bb 100755 (executable)
@@ -42,7 +42,7 @@ read FREQ
 CAL_VALUE=`nickle -e "floor(434.55 / $FREQ * 1186611 + 0.5)"`
 
 echo "Programming flash with cal value " $CAL_VALUE
-$AOLOAD --cal $CAL_VALUE /usr/share/altos/teledongle-v0.2*.ihx $SERIAL
+$AOLOAD --cal $CAL_VALUE /usr/share/altos/stable/teledongle-v0.2*.ihx $SERIAL
 
 echo "Serial number "$SERIAL" programmed with RF cal value "$CAL_VALUE
 echo "Unplug and replug USB, cu to the board, confirm freq and record power"
index 440eda1bb4ad62b0222f478b85310ba12b97a54e..405247fafd027a3c2cda765965b8faf201f05545 100755 (executable)
@@ -42,7 +42,7 @@ read FREQ
 CAL_VALUE=`nickle -e "floor(434.55 / $FREQ * 1186611 + 0.5)"`
 
 echo "Programming flash with cal value " $CAL_VALUE
-$AOLOAD --cal $CAL_VALUE /usr/share/altos/telemetrum-v1.0*.ihx $SERIAL
+$AOLOAD --cal $CAL_VALUE /usr/share/altos/stable/telemetrum-v1.0*.ihx $SERIAL
 
 echo "Serial number "$SERIAL" programmed with RF cal value "$CAL_VALUE
 echo "Unplug and replug USB, cu to the board, confirm freq and record power"
index 16f253380419029dfb788823741e18ec550276c5..e1b6002dfa23235d08a1fb5ae4a92a537be5e15e 100644 (file)
@@ -30,16 +30,15 @@ import java.util.concurrent.LinkedBlockingQueue;
 
 public class AltosCSVUI
        extends JDialog
-       implements Runnable, ActionListener
+       implements ActionListener
 {
-       JFrame                  frame;
-       Thread                  thread;
-       AltosRecordIterable     iterable;
-       AltosWriter             writer;
        JFileChooser            csv_chooser;
+       JPanel                  accessory;
        JComboBox               combo_box;
+       AltosRecordIterable     iterable;
+       AltosWriter             writer;
 
-       static String[]         combo_box_items = { "CSV", "KML" };
+       static String[]         combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" };
 
        void set_default_file() {
                File    current = csv_chooser.getSelectedFile();
@@ -47,57 +46,63 @@ public class AltosCSVUI
                String  new_name = null;
                String  selected = (String) combo_box.getSelectedItem();
 
-               if (selected.equals("CSV"))
+               if (selected.contains("CSV"))
                        new_name = Altos.replace_extension(current_name, ".csv");
-               else if (selected.equals("KML"))
+               else if (selected.contains("KML"))
                        new_name = Altos.replace_extension(current_name, ".kml");
                if (new_name != null)
                        csv_chooser.setSelectedFile(new File(new_name));
        }
 
-       public void run() {
-               AltosLogfileChooser     chooser;
+       public void actionPerformed(ActionEvent e) {
+               if (e.getActionCommand().equals("comboBoxChanged"))
+                       set_default_file();
+       }
+
+       public AltosCSVUI(JFrame frame, AltosRecordIterable in_iterable, File source_file) {
+               iterable = in_iterable;
+               csv_chooser = new JFileChooser(source_file);
+
+               accessory = new JPanel();
+               accessory.setLayout(new GridBagLayout());
 
-               chooser = new AltosLogfileChooser(frame);
-               iterable = chooser.runDialog();
-               if (iterable == null)
-                       return;
+               GridBagConstraints      c = new GridBagConstraints();
+               c.fill = GridBagConstraints.NONE;
+               c.weightx = 1;
+               c.weighty = 0;
+               c.insets = new Insets (4, 4, 4, 4);
+
+               JLabel accessory_label = new JLabel("Export File Type");
+               c.gridx = 0;
+               c.gridy = 0;
+               accessory.add(accessory_label, c);
 
-               csv_chooser = new JFileChooser(chooser.file());
                combo_box = new JComboBox(combo_box_items);
                combo_box.addActionListener(this);
-               csv_chooser.setAccessory(combo_box);
-               csv_chooser.setSelectedFile(chooser.file());
+               c.gridx = 0;
+               c.gridy = 1;
+               accessory.add(combo_box, c);
+
+               csv_chooser.setAccessory(accessory);
+               csv_chooser.setSelectedFile(source_file);
                set_default_file();
                int ret = csv_chooser.showSaveDialog(frame);
                if (ret == JFileChooser.APPROVE_OPTION) {
-                       File    file = csv_chooser.getSelectedFile();
-                       String  type = (String) combo_box.getSelectedItem();
+                       File file = csv_chooser.getSelectedFile();
+                       String type = (String) combo_box.getSelectedItem();
                        try {
-                               if (type.equals("CSV"))
+                               if (type.contains("CSV"))
                                        writer = new AltosCSV(file);
                                else
                                        writer = new AltosKML(file);
+                               writer.write(iterable);
+                               writer.close();
                        } catch (FileNotFoundException ee) {
                                JOptionPane.showMessageDialog(frame,
                                                              file.getName(),
                                                              "Cannot open file",
                                                              JOptionPane.ERROR_MESSAGE);
                        }
-                       writer.write(iterable);
-                       writer.close();
                }
        }
-
-       public void actionPerformed(ActionEvent e) {
-               System.out.printf("command %s param %s\n", e.getActionCommand(), e.paramString());
-               if (e.getActionCommand().equals("comboBoxChanged"))
-                       set_default_file();
-       }
-
-       public AltosCSVUI(JFrame in_frame) {
-               frame = in_frame;
-               thread = new Thread(this);
-               thread.start();
-       }
 }
index 504c13c6273c5ee366b56313463c4ea5a7e4ba2e..8069c8531b1c71f9a5802be9c1e3a94d23a4c352 100644 (file)
@@ -28,8 +28,7 @@ import java.text.*;
 import java.util.prefs.*;
 import java.util.concurrent.LinkedBlockingQueue;
 
-public class AltosChannelMenu extends JMenu implements ActionListener {
-       ButtonGroup                     group;
+public class AltosChannelMenu extends JComboBox implements ActionListener {
        int                             channel;
        LinkedList<ActionListener>      listeners;
 
@@ -38,33 +37,28 @@ public class AltosChannelMenu extends JMenu implements ActionListener {
        }
 
        public void actionPerformed(ActionEvent e) {
-               channel = Integer.parseInt(e.getActionCommand());
+               channel = getSelectedIndex();
+
+               ActionEvent newe = new ActionEvent(this, channel, e.getActionCommand());
 
                ListIterator<ActionListener>    i = listeners.listIterator();
 
-               ActionEvent newe = new ActionEvent(this, channel, e.getActionCommand());
                while (i.hasNext()) {
                        ActionListener  listener = i.next();
                        listener.actionPerformed(newe);
                }
+               setMaximumSize(getPreferredSize());
        }
 
        public AltosChannelMenu(int current_channel) {
-               super("Channel", true);
-               group = new ButtonGroup();
 
                channel = current_channel;
 
                listeners = new LinkedList<ActionListener>();
-               for (int c = 0; c <= 9; c++) {
-                       JRadioButtonMenuItem radioitem = new JRadioButtonMenuItem(String.format("Channel %1d (%7.3fMHz)", c,
-                                                                                               434.550 + c * 0.1),
-                                                            c == channel);
-                       radioitem.setActionCommand(String.format("%d", c));
-                       radioitem.addActionListener(this);
-                       add(radioitem);
-                       group.add(radioitem);
-               }
+               for (int c = 0; c <= 9; c++)
+                       addItem(String.format("Channel %1d (%7.3fMHz)", c, 434.550 + c * 0.1));
+               setSelectedIndex(channel);
+               setMaximumRowCount(10);
        }
 
 }
index a0fdb6236cfc3491cfb45abf7eb2d8dba4ab2360..6bda20d804135880b7511f8b4e89bba806fa3d42 100644 (file)
@@ -26,7 +26,7 @@ import java.io.*;
 import java.util.*;
 import java.text.*;
 import java.util.prefs.*;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.*;
 
 import libaltosJNI.*;
 
@@ -123,12 +123,14 @@ public class AltosConfig implements Runnable, ActionListener {
                }
        }
 
-       void get_data() throws InterruptedException {
+       void get_data() throws InterruptedException, TimeoutException {
                try {
                        start_serial();
                        serial_line.printf("c s\nv\n");
                        for (;;) {
-                               String line = serial_line.get_reply();
+                               String line = serial_line.get_reply(1000);
+                               if (line == null)
+                                       throw new TimeoutException();
                                get_int(line, "serial-number", serial);
                                get_int(line, "Main deploy:", main_deploy);
                                get_int(line, "Apogee delay:", apogee_delay);
@@ -147,27 +149,34 @@ public class AltosConfig implements Runnable, ActionListener {
                }
        }
 
-       void init_ui () {
+       void init_ui () throws InterruptedException, TimeoutException {
                config_ui = new AltosConfigUI(owner);
                config_ui.addActionListener(this);
                set_ui();
        }
 
-       void set_ui() {
-               try {
-                       if (serial_line != null)
-                               get_data();
-                       config_ui.set_serial(serial.get());
-                       config_ui.set_product(product.get());
-                       config_ui.set_version(version.get());
-                       config_ui.set_main_deploy(main_deploy.get());
-                       config_ui.set_apogee_delay(apogee_delay.get());
-                       config_ui.set_radio_channel(radio_channel.get());
-                       config_ui.set_radio_calibration(radio_calibration.get());
-                       config_ui.set_callsign(callsign.get());
-                       config_ui.set_clean();
-               } catch (InterruptedException ie) {
-               }
+       void abort() {
+               JOptionPane.showMessageDialog(owner,
+                                             String.format("Connection to \"%s\" failed",
+                                                           device.toShortString()),
+                                             "Connection Failed",
+                                             JOptionPane.ERROR_MESSAGE);
+               serial_line.close();
+               serial_line = null;
+       }
+
+       void set_ui() throws InterruptedException, TimeoutException {
+               if (serial_line != null)
+                       get_data();
+               config_ui.set_serial(serial.get());
+               config_ui.set_product(product.get());
+               config_ui.set_version(version.get());
+               config_ui.set_main_deploy(main_deploy.get());
+               config_ui.set_apogee_delay(apogee_delay.get());
+               config_ui.set_radio_channel(radio_channel.get());
+               config_ui.set_radio_calibration(radio_calibration.get());
+               config_ui.set_callsign(callsign.get());
+               config_ui.set_clean();
        }
 
        void run_dialog() {
@@ -198,28 +207,28 @@ public class AltosConfig implements Runnable, ActionListener {
 
        public void actionPerformed(ActionEvent e) {
                String  cmd = e.getActionCommand();
-               if (cmd.equals("Save")) {
-                       save_data();
-                       set_ui();
-               } else if (cmd.equals("Reset")) {
-                       set_ui();
-               } else if (cmd.equals("Reboot")) {
-                       if (serial_line != null) {
-                               try {
+               try {
+                       if (cmd.equals("Save")) {
+                               save_data();
+                               set_ui();
+                       } else if (cmd.equals("Reset")) {
+                               set_ui();
+                       } else if (cmd.equals("Reboot")) {
+                               if (serial_line != null) {
                                        start_serial();
                                        serial_line.printf("r eboot\n");
-                               } catch (InterruptedException ie) {
-                               } finally {
-                                       try {
-                                               stop_serial();
-                                       } catch (InterruptedException ie) {
-                                       }
+                                       serial_line.flush_output();
+                                       stop_serial();
+                                       serial_line.close();
                                }
-                               serial_line.close();
+                       } else if (cmd.equals("Close")) {
+                               if (serial_line != null)
+                                       serial_line.close();
                        }
-               } else if (cmd.equals("Close")) {
-                       if (serial_line != null)
-                               serial_line.close();
+               } catch (InterruptedException ie) {
+                       abort();
+               } catch (TimeoutException te) {
+                       abort();
                }
        }
 
@@ -227,8 +236,10 @@ public class AltosConfig implements Runnable, ActionListener {
                try {
                        init_ui();
                        config_ui.make_visible();
-//             } catch (InterruptedException ie) {
-               } finally {
+               } catch (InterruptedException ie) {
+                       abort();
+               } catch (TimeoutException te) {
+                       abort();
                }
        }
 
@@ -255,18 +266,18 @@ public class AltosConfig implements Runnable, ActionListener {
                        } catch (FileNotFoundException ee) {
                                JOptionPane.showMessageDialog(owner,
                                                              String.format("Cannot open device \"%s\"",
-                                                                           device.getPath()),
+                                                                           device.toShortString()),
                                                              "Cannot open target device",
                                                              JOptionPane.ERROR_MESSAGE);
                        } catch (AltosSerialInUseException si) {
                                JOptionPane.showMessageDialog(owner,
                                                              String.format("Device \"%s\" already in use",
-                                                                           device.getPath()),
+                                                                           device.toShortString()),
                                                              "Device in use",
                                                              JOptionPane.ERROR_MESSAGE);
                        } catch (IOException ee) {
                                JOptionPane.showMessageDialog(owner,
-                                                             device.getPath(),
+                                                             device.toShortString(),
                                                              ee.getLocalizedMessage(),
                                                              JOptionPane.ERROR_MESSAGE);
                        }
index 64c17eaffd38937bfeda2b98a69283a5773f7333..153c59fdedc719818f7fa605b8a2f825c30ac410 100644 (file)
@@ -75,12 +75,25 @@ public class AltosConfigureUI
                c = new GridBagConstraints();
                c.insets = insets;
                c.fill = GridBagConstraints.NONE;
-               c.anchor = GridBagConstraints.CENTER;
+               c.anchor = GridBagConstraints.WEST;
 
-               /* Enable Voice */
+               /* Nice label at the top */
                c.gridx = 0;
                c.gridy = 0;
-               enable_voice = new JRadioButton("Enable Voice", AltosPreferences.voice());
+               c.gridwidth = 3;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.CENTER;
+               pane.add(new JLabel ("Configure AltOS UI"), c);
+
+               /* Voice settings */
+               c.gridx = 0;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Voice"), c);
+
+               enable_voice = new JRadioButton("Enable", AltosPreferences.voice());
                enable_voice.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
                                        JRadioButton item = (JRadioButton) e.getSource();
@@ -92,9 +105,20 @@ public class AltosConfigureUI
                                                voice.speak_always("Disable voice.");
                                }
                        });
-               pane.add(enable_voice, c);
                c.gridx = 1;
-               c.gridy = 0;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.weightx = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(enable_voice, c);
+
+               c.gridx = 2;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.weightx = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.EAST;
                test_voice = new JButton("Test Voice");
                test_voice.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
@@ -103,36 +127,46 @@ public class AltosConfigureUI
                        });
                pane.add(test_voice, c);
 
-               configure_log = new JButton("Configure Log");
+               /* Log directory settings */
+               c.gridx = 0;
+               c.gridy = 2;
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Log Directory"), c);
+
+               configure_log = new JButton(AltosPreferences.logdir().getPath());
                configure_log.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
                                        AltosPreferences.ConfigureLog();
-                                       log_directory.setText(AltosPreferences.logdir().getPath());
+                                       configure_log.setText(AltosPreferences.logdir().getPath());
                                }
                        });
-               c.gridwidth = 1;
-
-               c.gridx = 0;
-               c.gridy = 2;
-               pane.add(configure_log, c);
-
-               log_directory = new JTextField(AltosPreferences.logdir().getPath());
                c.gridx = 1;
                c.gridy = 2;
+               c.gridwidth = 2;
                c.fill = GridBagConstraints.BOTH;
-               pane.add(log_directory, c);
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(configure_log, c);
 
-               callsign_label = new JLabel("Callsign");
+               /* Callsign setting */
                c.gridx = 0;
                c.gridy = 3;
-               pane.add(callsign_label, c);
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Callsign"), c);
 
                callsign_value = new JTextField(AltosPreferences.callsign());
                callsign_value.getDocument().addDocumentListener(this);
                c.gridx = 1;
                c.gridy = 3;
+               c.gridwidth = 2;
+               c.fill = GridBagConstraints.BOTH;
+               c.anchor = GridBagConstraints.WEST;
                pane.add(callsign_value, c);
 
+               /* And a close button at the bottom */
                close = new JButton("Close");
                close.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
@@ -141,8 +175,9 @@ public class AltosConfigureUI
                        });
                c.gridx = 0;
                c.gridy = 4;
-               c.gridwidth = 2;
+               c.gridwidth = 3;
                c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.CENTER;
                pane.add(close, c);
 
                pack();
diff --git a/ao-tools/altosui/AltosDataChooser.java b/ao-tools/altosui/AltosDataChooser.java
new file mode 100644 (file)
index 0000000..15de05c
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+
+public class AltosDataChooser extends JFileChooser {
+       JFrame  frame;
+       String  filename;
+       File    file;
+
+       public String filename() {
+               return filename;
+       }
+
+       public File file() {
+               return file;
+       }
+
+       public AltosRecordIterable runDialog() {
+               int     ret;
+
+               ret = showOpenDialog(frame);
+               if (ret == APPROVE_OPTION) {
+                       file = getSelectedFile();
+                       if (file == null)
+                               return null;
+                       filename = file.getName();
+                       try {
+                               if (filename.endsWith("eeprom")) {
+                                       FileInputStream in = new FileInputStream(file);
+                                       return new AltosEepromIterable(in);
+                               } else if (filename.endsWith("telem")) {
+                                       FileInputStream in = new FileInputStream(file);
+                                       return new AltosTelemetryIterable(in);
+                               } else {
+                                       throw new FileNotFoundException();
+                               }
+                       } catch (FileNotFoundException fe) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             filename,
+                                                             "Cannot open file",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+               return null;
+       }
+
+       public AltosDataChooser(JFrame in_frame) {
+               frame = in_frame;
+               setDialogTitle("Select Flight Record File");
+               setFileFilter(new FileNameExtensionFilter("Flight data file",
+                                                         "telem", "eeprom"));
+               setCurrentDirectory(AltosPreferences.logdir());
+       }
+}
index 9aa35d3f2f834813d7cfdd51a5dbf2a631b750ee..8d435b667d5ecb584b118bed27a5d9945b4abb92 100644 (file)
@@ -261,7 +261,7 @@ public class AltosDebug extends AltosSerial {
                printf ("R\n");
        }
 
-       public AltosDebug (altos_device in_device) throws FileNotFoundException, AltosSerialInUseException {
+       public AltosDebug (AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
                super(in_device);
        }
 }
\ No newline at end of file
index f646305b3dc178da98433012ef24adabe1cb8136..f0fda57bc0b825c71d4af0dfa08180e4b3d4899e 100644 (file)
@@ -101,6 +101,15 @@ public class AltosDevice extends altos_device {
                                     getName(), getSerial(), getPath());
        }
 
+       public String toShortString() {
+               String  name = getName();
+               if (name == null)
+                       name = "Altus Metrum";
+               return String.format("%s %d %s",
+                                    name, getSerial(), getPath());
+
+       }
+
        public boolean isAltusMetrum() {
                if (getVendor() != vendor_altusmetrum)
                        return false;
index 8996b9243a5284cc8a5b2606a6d15abc648e653b..fb5dcfc0db06574611cc00b4f277f6be6a5c9ca5 100644 (file)
@@ -26,7 +26,7 @@ import java.io.*;
 import java.util.*;
 import java.text.*;
 import java.util.prefs.*;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.*;
 
 import libaltosJNI.*;
 
@@ -78,7 +78,7 @@ public class AltosEepromDownload implements Runnable {
        Thread                  eeprom_thread;
        AltosEepromMonitor      monitor;
 
-       void CaptureLog() throws IOException, InterruptedException {
+       void CaptureLog() throws IOException, InterruptedException, TimeoutException {
                int                     serial = 0;
                int                     block, state_block = 0;
                int                     addr;
@@ -97,8 +97,10 @@ public class AltosEepromDownload implements Runnable {
                /* Pull the serial number out of the version information */
 
                for (;;) {
-                       String  line = serial_line.get_reply();
+                       String  line = serial_line.get_reply(1000);
 
+                       if (line == null)
+                               throw new TimeoutException();
                        if (line.startsWith("serial-number")) {
                                try {
                                        serial = Integer.parseInt(line.substring(13).trim());
@@ -125,7 +127,9 @@ public class AltosEepromDownload implements Runnable {
                        any_valid = false;
                        monitor.set_value(state_names[state], state, block - state_block);
                        for (addr = 0; addr < 0x100;) {
-                               String  line = serial_line.get_reply();
+                               String  line = serial_line.get_reply(1000);
+                               if (line == null)
+                                       throw new TimeoutException();
                                int[] values = ParseHex(line);
 
                                if (values == null) {
@@ -228,10 +232,16 @@ public class AltosEepromDownload implements Runnable {
                        CaptureLog();
                } catch (IOException ee) {
                        JOptionPane.showMessageDialog(frame,
-                                                     device.getPath(),
+                                                     device.toShortString(),
                                                      ee.getLocalizedMessage(),
                                                      JOptionPane.ERROR_MESSAGE);
                } catch (InterruptedException ie) {
+               } catch (TimeoutException te) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     String.format("Connection to \"%s\" failed",
+                                                                   device.toShortString()),
+                                                     "Connection Failed",
+                                                     JOptionPane.ERROR_MESSAGE);
                }
                if (remote)
                        serial_line.printf("~");
@@ -256,18 +266,18 @@ public class AltosEepromDownload implements Runnable {
                        } catch (FileNotFoundException ee) {
                                JOptionPane.showMessageDialog(frame,
                                                              String.format("Cannot open device \"%s\"",
-                                                                           device.getPath()),
+                                                                           device.toShortString()),
                                                              "Cannot open target device",
                                                              JOptionPane.ERROR_MESSAGE);
                        } catch (AltosSerialInUseException si) {
                                JOptionPane.showMessageDialog(frame,
                                                              String.format("Device \"%s\" already in use",
-                                                                           device.getPath()),
+                                                                           device.toShortString()),
                                                              "Device in use",
                                                              JOptionPane.ERROR_MESSAGE);
                        } catch (IOException ee) {
                                JOptionPane.showMessageDialog(frame,
-                                                             device.getPath(),
+                                                             device.toShortString(),
                                                              ee.getLocalizedMessage(),
                                                              JOptionPane.ERROR_MESSAGE);
                        }
index b09cb59472e0d6c2608161bce26d2f838ae0fdbd..f63097ac408ad1f71356311365b487203ce8788f 100644 (file)
@@ -90,7 +90,7 @@ public class AltosFlashUI
                } catch (AltosSerialInUseException si) {
                        JOptionPane.showMessageDialog(frame,
                                                      String.format("Device \"%s\" already in use",
-                                                                   debug_dongle.getPath()),
+                                                                   debug_dongle.toShortString()),
                                                      "Device in use",
                                                      JOptionPane.ERROR_MESSAGE);
                } catch (IOException e) {
index 658d6f6f611f8e36a5b324b29bdc0c16e23dd559..21b41528b32a056aee22989167cbead46b506425 100644 (file)
@@ -36,8 +36,6 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {
        AltosFlightReader       reader;
        AltosDisplayThread      thread;
 
-       private Box vbox;
-
        JTabbedPane     pane;
 
        AltosPad        pad;
@@ -132,22 +130,47 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {
                exit_on_close = true;
        }
 
+       Container       bag;
+
        public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) {
                AltosPreferences.init(this);
 
                voice = in_voice;
                reader = in_reader;
 
+               bag = getContentPane();
+               bag.setLayout(new GridBagLayout());
+
+               GridBagConstraints c = new GridBagConstraints();
+
                java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
                if (imgURL != null)
                        setIconImage(new ImageIcon(imgURL).getImage());
 
                setTitle(String.format("AltOS %s", reader.name));
 
-               flightStatus = new AltosFlightStatus();
+               if (serial >= 0) {
+                       // Channel menu
+                       JComboBox channels = new AltosChannelMenu(AltosPreferences.channel(serial));
+                       channels.addActionListener(new ActionListener() {
+                                       public void actionPerformed(ActionEvent e) {
+                                               int channel = Integer.parseInt(e.getActionCommand());
+                                               reader.set_channel(channel);
+                                               AltosPreferences.set_channel(serial, channel);
+                                       }
+                               });
+                       c.gridx = 0;
+                       c.gridy = 0;
+                       c.anchor = GridBagConstraints.WEST;
+                       bag.add (channels, c);
+               }
 
-               vbox = new Box (BoxLayout.Y_AXIS);
-               vbox.add(flightStatus);
+               flightStatus = new AltosFlightStatus();
+               c.gridx = 0;
+               c.gridy = 1;
+               c.fill = GridBagConstraints.HORIZONTAL;
+               c.weightx = 1;
+               bag.add(flightStatus, c);
 
                pane = new JTabbedPane();
 
@@ -171,29 +194,12 @@ public class AltosFlightUI extends JFrame implements AltosFlightDisplay {
                sitemapPane = new JScrollPane(sitemap);
         pane.add("Site Map", sitemapPane);
 
-               vbox.add(pane);
-
-               this.add(vbox);
-
-               if (serial >= 0) {
-                       JMenuBar menubar = new JMenuBar();
-
-                       // Channel menu
-                       {
-                               JMenu menu = new AltosChannelMenu(AltosPreferences.channel(serial));
-                               menu.addActionListener(new ActionListener() {
-                                               public void actionPerformed(ActionEvent e) {
-                                                       int channel = Integer.parseInt(e.getActionCommand());
-                                                       reader.set_channel(channel);
-                                                       AltosPreferences.set_channel(serial, channel);
-                                               }
-                                       });
-                               menu.setMnemonic(KeyEvent.VK_C);
-                               menubar.add(menu);
-                       }
-
-                       this.setJMenuBar(menubar);
-               }
+               c.gridx = 0;
+               c.gridy = 2;
+               c.fill = GridBagConstraints.BOTH;
+               c.weightx = 1;
+               c.weighty = 1;
+               bag.add(pane, c);
 
                this.setSize(this.getPreferredSize());
                this.validate();
diff --git a/ao-tools/altosui/AltosGraphDataChooser.java b/ao-tools/altosui/AltosGraphDataChooser.java
deleted file mode 100644 (file)
index d128f4d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import javax.swing.table.*;
-import java.io.*;
-import java.util.*;
-import java.text.*;
-import java.util.prefs.*;
-
-public class AltosGraphDataChooser extends JFileChooser {
-       JFrame  frame;
-       String  filename;
-       File    file;
-
-       public String filename() {
-               return filename;
-       }
-
-       public File file() {
-               return file;
-       }
-
-       public Iterable<AltosDataPoint> runDialog() {
-               int     ret;
-
-               ret = showOpenDialog(frame);
-               if (ret == APPROVE_OPTION) {
-                       file = getSelectedFile();
-                       if (file == null)
-                               return null;
-                       filename = file.getName();
-                       try {
-                if (filename.endsWith("eeprom")) {
-                    FileInputStream in = new FileInputStream(file);
-                    return new AltosDataPointReader(new AltosEepromIterable(in));
-                } else if (filename.endsWith("telem")) {
-                    FileInputStream in = new FileInputStream(file);
-                    return new AltosDataPointReader(new AltosTelemetryIterable(in));
-                } else {
-                    throw new FileNotFoundException();
-                }
-                       } catch (FileNotFoundException fe) {
-                               JOptionPane.showMessageDialog(frame,
-                                                             filename,
-                                                             "Cannot open file",
-                                                             JOptionPane.ERROR_MESSAGE);
-                       }
-               }
-               return null;
-       }
-
-       public AltosGraphDataChooser(JFrame in_frame) {
-               frame = in_frame;
-               setDialogTitle("Select Flight Record File");
-               setFileFilter(new FileNameExtensionFilter("Flight data file",
-                                                         "telem", "eeprom"));
-               setCurrentDirectory(AltosPreferences.logdir());
-       }
-}
index 908aa3b45baeab54a2900a4a7304f239bbc53e26..cd158651402d2634e4763015c1d36214b7899fb4 100644 (file)
@@ -151,18 +151,15 @@ public class AltosGraphUI extends JFrame
         }
     }
 
-    public AltosGraphUI(JFrame frame)
-    {
-        super("Altos Graph");
+       public AltosGraphUI(AltosRecordIterable records) {
+               super("Altos Graph");
 
-        AltosGraphDataChooser chooser;
-        chooser = new AltosGraphDataChooser(frame);
-        Iterable<AltosDataPoint> reader = chooser.runDialog();
-        if (reader == null)
-            return;
+               Iterable<AltosDataPoint> reader = new AltosDataPointReader (records);
+               if (reader == null)
+                       return;
         
-        init(reader, 0);
-    }
+               init(reader, 0);
+       }
 
     public AltosGraphUI(Iterable<AltosDataPoint> data, int which) 
     {
diff --git a/ao-tools/altosui/AltosIgnite.java b/ao-tools/altosui/AltosIgnite.java
new file mode 100644 (file)
index 0000000..8e92ec1
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.io.*;
+import java.util.concurrent.*;
+
+public class AltosIgnite {
+       AltosDevice     device;
+       AltosSerial     serial;
+       boolean         remote;
+       final static int        None = 0;
+       final static int        Apogee = 1;
+       final static int        Main = 2;
+
+       final static int        Unknown = 0;
+       final static int        Ready = 1;
+       final static int        Active = 2;
+       final static int        Open = 3;
+
+       private void start_serial() throws InterruptedException {
+               if (remote) {
+                       serial.set_channel(AltosPreferences.channel(device.getSerial()));
+                       serial.set_callsign(AltosPreferences.callsign());
+                       serial.printf("~\np\n");
+                       serial.flush_input();
+               }
+       }
+
+       private void stop_serial() throws InterruptedException {
+               if (serial == null)
+                       return;
+               if (remote) {
+                       serial.printf("~");
+                       serial.flush_output();
+               }
+       }
+
+       class string_ref {
+               String  value;
+
+               public String get() {
+                       return value;
+               }
+               public void set(String i) {
+                       value = i;
+               }
+               public string_ref() {
+                       value = null;
+               }
+       }
+
+       private boolean get_string(String line, String label, string_ref s) {
+               if (line.startsWith(label)) {
+                       String  quoted = line.substring(label.length()).trim();
+
+                       if (quoted.startsWith("\""))
+                               quoted = quoted.substring(1);
+                       if (quoted.endsWith("\""))
+                               quoted = quoted.substring(0,quoted.length()-1);
+                       s.set(quoted);
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       private int status(String status_name) {
+               if (status_name.equals("unknown"))
+                       return Unknown;
+               if (status_name.equals("ready"))
+                       return Ready;
+               if (status_name.equals("active"))
+                       return Active;
+               if (status_name.equals("open"))
+                       return Open;
+               return Unknown;
+       }
+
+       public int status(int igniter) throws InterruptedException, TimeoutException {
+               int status = Unknown;
+               if (serial == null)
+                       return status;
+               string_ref status_name = new string_ref();
+               start_serial();
+               serial.printf("t\n");
+               for (;;) {
+                       String line = serial.get_reply(1000);
+                       if (line == null)
+                               throw new TimeoutException();
+                       if (get_string(line, "Igniter: drogue Status: ", status_name))
+                               if (igniter == Apogee)
+                                       status = status(status_name.get());
+                       if (get_string(line, "Igniter:   main Status: ", status_name)) {
+                               if (igniter == Main)
+                                       status = status(status_name.get());
+                               break;
+                       }
+               }
+               stop_serial();
+               return status;
+       }
+
+       public String status_string(int status) {
+               switch (status) {
+               case Unknown: return "Unknown";
+               case Ready: return "Ready";
+               case Active: return "Active";
+               case Open: return "Open";
+               default: return "Unknown";
+               }
+       }
+
+       public void fire(int igniter) {
+               if (serial == null)
+                       return;
+               try {
+                       start_serial();
+                       switch (igniter) {
+                       case Main:
+                               serial.printf("i DoIt main\n");
+                               break;
+                       case Apogee:
+                               serial.printf("i DoIt drogue\n");
+                               break;
+                       }
+               } catch (InterruptedException ie) {
+               } finally {
+                       try {
+                               stop_serial();
+                       } catch (InterruptedException ie) {
+                       }
+               }
+       }
+
+       public void close() {
+               serial.close();
+               serial = null;
+       }
+
+       public AltosIgnite(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
+
+               device = in_device;
+               serial = new AltosSerial(device);
+               remote = false;
+
+               if (!device.matchProduct(AltosDevice.product_telemetrum))
+                       remote = true;
+       }
+}
\ No newline at end of file
diff --git a/ao-tools/altosui/AltosIgniteUI.java b/ao-tools/altosui/AltosIgniteUI.java
new file mode 100644 (file)
index 0000000..0207e39
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+
+public class AltosIgniteUI
+       extends JDialog
+       implements ActionListener
+{
+       AltosDevice     device;
+       AltosIgnite     ignite;
+       JFrame          owner;
+       JLabel          label;
+       JRadioButton    apogee;
+       JLabel          apogee_status_label;
+       JRadioButton    main;
+       JLabel          main_status_label;
+       JToggleButton   arm;
+       JButton         fire;
+       javax.swing.Timer       timer;
+
+       int             apogee_status;
+       int             main_status;
+
+       final static int        timeout = 1 * 1000;
+
+       int             time_remaining;
+       boolean         timer_running;
+
+       void set_arm_text() {
+               if (arm.isSelected())
+                       arm.setText(String.format("%d", time_remaining));
+               else
+                       arm.setText("Arm");
+       }
+
+       void start_timer() {
+               time_remaining = 10;
+               set_arm_text();
+               timer_running = true;
+       }
+
+       void stop_timer() {
+               time_remaining = 0;
+               arm.setSelected(false);
+               arm.setEnabled(false);
+               fire.setEnabled(false);
+               timer_running = false;
+               set_arm_text();
+       }
+
+       void cancel () {
+               apogee.setSelected(false);
+               main.setSelected(false);
+               fire.setEnabled(false);
+               stop_timer();
+       }
+
+       void get_ignite_status() throws InterruptedException, TimeoutException {
+               apogee_status = ignite.status(AltosIgnite.Apogee);
+               main_status = ignite.status(AltosIgnite.Main);
+       }
+
+       void set_ignite_status() throws InterruptedException, TimeoutException {
+               get_ignite_status();
+               apogee_status_label.setText(String.format("\"%s\"", ignite.status_string(apogee_status)));
+               main_status_label.setText(String.format("\"%s\"", ignite.status_string(main_status)));
+       }
+
+       void close() {
+               timer.stop();
+               setVisible(false);
+               ignite.close();
+       }
+
+       void abort() {
+               close();
+               JOptionPane.showMessageDialog(owner,
+                                             String.format("Connection to \"%s\" failed",
+                                                           device.toShortString()),
+                                             "Connection Failed",
+                                             JOptionPane.ERROR_MESSAGE);
+       }
+
+       void tick_timer() {
+               if (timer_running) {
+                       --time_remaining;
+                       if (time_remaining <= 0)
+                               cancel();
+                       else
+                               set_arm_text();
+               }
+               try {
+                       set_ignite_status();
+               } catch (InterruptedException ie) {
+                       abort();
+               } catch (TimeoutException te) {
+                       abort();
+               }
+       }
+
+       void fire() {
+               if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) {
+                       int     igniter = AltosIgnite.None;
+                       if (apogee.isSelected() && !main.isSelected())
+                               igniter = AltosIgnite.Apogee;
+                       else if (main.isSelected() && !apogee.isSelected())
+                               igniter = AltosIgnite.Main;
+                       ignite.fire(igniter);
+                       cancel();
+               }
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               String cmd = e.getActionCommand();
+               if (cmd.equals("apogee") || cmd.equals("main")) {
+                       stop_timer();
+               }
+
+               if (cmd.equals("apogee") && apogee.isSelected()) {
+                       main.setSelected(false);
+                       if (apogee_status == AltosIgnite.Ready)
+                               arm.setEnabled(true);
+               }
+               if (cmd.equals("main") && main.isSelected()) {
+                       apogee.setSelected(false);
+                       if (main_status == AltosIgnite.Ready)
+                               arm.setEnabled(true);
+               }
+
+               if (cmd.equals("arm")) {
+                       if (arm.isSelected()) {
+                               fire.setEnabled(true);
+                               start_timer();
+                       } else
+                               cancel();
+               }
+               if (cmd.equals("fire"))
+                       fire();
+               if (cmd.equals("tick"))
+                       tick_timer();
+               if (cmd.equals("close")) {
+                       close();
+               }
+       }
+
+       /* A window listener to catch closing events and tell the config code */
+       class ConfigListener extends WindowAdapter {
+               AltosIgniteUI   ui;
+
+               public ConfigListener(AltosIgniteUI this_ui) {
+                       ui = this_ui;
+               }
+
+               public void windowClosing(WindowEvent e) {
+                       ui.actionPerformed(new ActionEvent(e.getSource(),
+                                                          ActionEvent.ACTION_PERFORMED,
+                                                          "close"));
+               }
+       }
+
+       private boolean open() {
+               device = AltosDeviceDialog.show(owner, AltosDevice.product_any);
+               if (device != null) {
+                       try {
+                               ignite = new AltosIgnite(device);
+                               return true;
+                       } catch (FileNotFoundException ee) {
+                               JOptionPane.showMessageDialog(owner,
+                                                             String.format("Cannot open device \"%s\"",
+                                                                           device.toShortString()),
+                                                             "Cannot open target device",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       } catch (AltosSerialInUseException si) {
+                               JOptionPane.showMessageDialog(owner,
+                                                             String.format("Device \"%s\" already in use",
+                                                                           device.toShortString()),
+                                                             "Device in use",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       } catch (IOException ee) {
+                               JOptionPane.showMessageDialog(owner,
+                                                             device.toShortString(),
+                                                             ee.getLocalizedMessage(),
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+               return false;
+       }
+
+       public AltosIgniteUI(JFrame in_owner) {
+
+               owner = in_owner;
+               apogee_status = AltosIgnite.Unknown;
+               main_status = AltosIgnite.Unknown;
+
+               if (!open())
+                       return;
+
+               Container               pane = getContentPane();
+               GridBagConstraints      c = new GridBagConstraints();
+               Insets                  i = new Insets(4,4,4,4);
+
+               timer = new javax.swing.Timer(timeout, this);
+               timer.setActionCommand("tick");
+               timer_running = false;
+               timer.restart();
+
+               owner = in_owner;
+
+               pane.setLayout(new GridBagLayout());
+
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.CENTER;
+               c.insets = i;
+               c.weightx = 1;
+               c.weighty = 1;
+
+               c.gridx = 0;
+               c.gridy = 0;
+               c.gridwidth = 2;
+               c.anchor = GridBagConstraints.CENTER;
+               label = new JLabel ("Fire Igniter");
+               pane.add(label, c);
+
+               c.gridx = 0;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.WEST;
+               apogee = new JRadioButton ("Apogee");
+               pane.add(apogee, c);
+               apogee.addActionListener(this);
+               apogee.setActionCommand("apogee");
+
+               c.gridx = 1;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.WEST;
+               apogee_status_label = new JLabel();
+               pane.add(apogee_status_label, c);
+
+               c.gridx = 0;
+               c.gridy = 2;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.WEST;
+               main = new JRadioButton ("Main");
+               pane.add(main, c);
+               main.addActionListener(this);
+               main.setActionCommand("main");
+
+               c.gridx = 1;
+               c.gridy = 2;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.WEST;
+               main_status_label = new JLabel();
+               pane.add(main_status_label, c);
+
+               try {
+                       set_ignite_status();
+               } catch (InterruptedException ie) {
+                       abort();
+                       return;
+               } catch (TimeoutException te) {
+                       abort();
+                       return;
+               }
+
+               c.gridx = 0;
+               c.gridy = 3;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.CENTER;
+               arm = new JToggleButton ("Arm");
+               pane.add(arm, c);
+               arm.addActionListener(this);
+               arm.setActionCommand("arm");
+               arm.setEnabled(false);
+
+               c.gridx = 1;
+               c.gridy = 3;
+               c.gridwidth = 1;
+               c.anchor = GridBagConstraints.CENTER;
+               fire = new JButton ("Fire");
+               fire.setEnabled(false);
+               pane.add(fire, c);
+               fire.addActionListener(this);
+               fire.setActionCommand("fire");
+
+               pack();
+               setLocationRelativeTo(owner);
+               setVisible(true);
+
+               addWindowListener(new ConfigListener(this));
+       }
+}
\ No newline at end of file
index 137147d5cc1aa2079c66ef1cf7d865a0cf2e3965..dd147d21adb960818ff3d26423f7c5e48816ba36 100644 (file)
@@ -36,15 +36,22 @@ class AltosLog implements Runnable {
        FileWriter                      log_file;
        Thread                          log_thread;
 
-       void close() {
+       private void close_log_file() {
                if (log_file != null) {
                        try {
                                log_file.close();
                        } catch (IOException io) {
                        }
+                       log_file = null;
                }
-               if (log_thread != null)
+       }
+
+       void close() {
+               close_log_file();
+               if (log_thread != null) {
                        log_thread.interrupt();
+                       log_thread = null;
+               }
        }
 
        boolean open (AltosTelemetry telem) throws IOException {
@@ -74,7 +81,7 @@ class AltosLog implements Runnable {
                                try {
                                        AltosTelemetry  telem = new AltosTelemetry(line.line);
                                        if (telem.serial != serial || telem.flight != flight || log_file == null) {
-                                               close();
+                                               close_log_file();
                                                serial = telem.serial;
                                                flight = telem.flight;
                                                open(telem);
diff --git a/ao-tools/altosui/AltosLogfileChooser.java b/ao-tools/altosui/AltosLogfileChooser.java
deleted file mode 100644 (file)
index 8b9d77d..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import javax.swing.table.*;
-import java.io.*;
-import java.util.*;
-import java.text.*;
-import java.util.prefs.*;
-
-public class AltosLogfileChooser extends JFileChooser {
-       JFrame  frame;
-       String  filename;
-       File    file;
-
-       public String filename() {
-               return filename;
-       }
-
-       public File file() {
-               return file;
-       }
-
-       public AltosRecordIterable runDialog() {
-               int     ret;
-
-               ret = showOpenDialog(frame);
-               if (ret == APPROVE_OPTION) {
-                       file = getSelectedFile();
-                       if (file == null)
-                               return null;
-                       filename = file.getName();
-                       try {
-                               FileInputStream in;
-
-                               in = new FileInputStream(file);
-                               if (filename.endsWith("eeprom"))
-                                       return new AltosEepromIterable(in);
-                               else
-                                       return new AltosTelemetryIterable(in);
-                       } catch (FileNotFoundException fe) {
-                               JOptionPane.showMessageDialog(frame,
-                                                             filename,
-                                                             "Cannot open file",
-                                                             JOptionPane.ERROR_MESSAGE);
-                       }
-               }
-               return null;
-       }
-
-       public AltosLogfileChooser(JFrame in_frame) {
-               frame = in_frame;
-               setDialogTitle("Select Flight Record File");
-               setFileFilter(new FileNameExtensionFilter("Flight data file",
-                                                         "eeprom",
-                                                         "telem"));
-               setCurrentDirectory(AltosPreferences.logdir());
-       }
-}
\ No newline at end of file
index 99a92fdba5153c69aa8569de3a21012220172a43..ab74486b49904e018beae5ca66df2dd2a02cce8a 100644 (file)
@@ -38,7 +38,7 @@ public class AltosSerial implements Runnable {
 
        static List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>());
 
-       altos_device device;
+       AltosDevice device;
        SWIGTYPE_p_altos_file altos;
        LinkedList<LinkedBlockingQueue<AltosLine>> monitors;
        LinkedBlockingQueue<AltosLine> reply_queue;
@@ -132,6 +132,14 @@ public class AltosSerial implements Runnable {
                return line.line;
        }
 
+       public String get_reply(int timeout) throws InterruptedException {
+               flush_output();
+               AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS);
+               if (line == null)
+                       return null;
+               return line.line;
+       }
+
        public void add_monitor(LinkedBlockingQueue<AltosLine> q) {
                set_monitor(true);
                monitors.add(q);
@@ -185,10 +193,9 @@ public class AltosSerial implements Runnable {
                                throw new AltosSerialInUseException(device);
                        devices_opened.add(device.getPath());
                }
-               close();
                altos = libaltos.altos_open(device);
                if (altos == null)
-                       throw new FileNotFoundException(device.getPath());
+                       throw new FileNotFoundException(device.toShortString());
                input_thread = new Thread(this);
                input_thread.start();
                print("~\nE 0\n");
@@ -226,7 +233,7 @@ public class AltosSerial implements Runnable {
                }
        }
 
-       public AltosSerial(altos_device in_device) throws FileNotFoundException, AltosSerialInUseException {
+       public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
                device = in_device;
                line = "";
                monitor_mode = false;
index ff02c7225c00762b1516e420edb778d7317fe89b..de5f50e93052895ac7d7e9aefd06ec48ccdae8c9 100644 (file)
@@ -55,7 +55,7 @@ class AltosTelemetryReader extends AltosFlightReader {
                device = in_device;
                serial = new AltosSerial(device);
                log = new AltosLog(serial);
-               name = device.getPath();
+               name = device.toShortString();
 
                telem = new LinkedBlockingQueue<AltosLine>();
                serial.add_monitor(telem);
index bedf24598c2bd29406dcf304dc093176b3073e49..6bfde014556b6e260c26306917b25dc5e5573d96 100644 (file)
@@ -53,18 +53,18 @@ public class AltosUI extends JFrame {
                } catch (FileNotFoundException ee) {
                        JOptionPane.showMessageDialog(AltosUI.this,
                                                      String.format("Cannot open device \"%s\"",
-                                                                   device.getPath()),
+                                                                   device.toShortString()),
                                                      "Cannot open target device",
                                                      JOptionPane.ERROR_MESSAGE);
                } catch (AltosSerialInUseException si) {
                        JOptionPane.showMessageDialog(AltosUI.this,
                                                      String.format("Device \"%s\" already in use",
-                                                                   device.getPath()),
+                                                                   device.toShortString()),
                                                      "Device in use",
                                                      JOptionPane.ERROR_MESSAGE);
                } catch (IOException ee) {
                        JOptionPane.showMessageDialog(AltosUI.this,
-                                                     device.getPath(),
+                                                     device.toShortString(),
                                                      "Unkonwn I/O error",
                                                      JOptionPane.ERROR_MESSAGE);
                }
@@ -125,40 +125,47 @@ public class AltosUI extends JFrame {
                                                Replay();
                                        }
                                });
-               b = addButton(0, 1, "Graph Data");
+               b = addButton(3, 0, "Graph Data");
                b.addActionListener(new ActionListener() {
                                        public void actionPerformed(ActionEvent e) {
                                                GraphData();
                                        }
                                });
-               b = addButton(1, 1, "Export Data");
+               b = addButton(4, 0, "Export Data");
                b.addActionListener(new ActionListener() {
                                        public void actionPerformed(ActionEvent e) {
                                                ExportData();
                                        }
                                });
-               b = addButton(2, 1, "Configure TeleMetrum");
+               b = addButton(0, 1, "Configure TeleMetrum");
                b.addActionListener(new ActionListener() {
                                        public void actionPerformed(ActionEvent e) {
                                                ConfigureTeleMetrum();
                                        }
                                });
 
-               b = addButton(0, 2, "Configure AltosUI");
+               b = addButton(1, 1, "Configure AltosUI");
                b.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
                                        ConfigureAltosUI();
                                }
                        });
 
-               b = addButton(1, 2, "Flash Image");
+               b = addButton(2, 1, "Flash Image");
                b.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
                                        FlashImage();
                                }
                        });
 
-               b = addButton(2, 2, "Quit");
+               b = addButton(3, 1, "Fire Igniter");
+               b.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       FireIgniter();
+                               }
+                       });
+
+               b = addButton(4, 1, "Quit");
                b.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
                                        System.exit(0);
@@ -215,12 +222,17 @@ public class AltosUI extends JFrame {
                new AltosFlashUI(AltosUI.this);
        }
 
+       void FireIgniter() {
+               new AltosIgniteUI(AltosUI.this);
+       }
+
        /*
         * Replay a flight from telemetry data
         */
        private void Replay() {
-               AltosLogfileChooser chooser = new AltosLogfileChooser(
+               AltosDataChooser chooser = new AltosDataChooser(
                        AltosUI.this);
+
                AltosRecordIterable iterable = chooser.runDialog();
                if (iterable != null) {
                        AltosFlightReader reader = new AltosReplayReader(iterable.iterator(),
@@ -241,14 +253,24 @@ public class AltosUI extends JFrame {
         */
 
        private void ExportData() {
-               new AltosCSVUI(AltosUI.this);
+               AltosDataChooser chooser;
+               chooser = new AltosDataChooser(this);
+               AltosRecordIterable record_reader = chooser.runDialog();
+               if (record_reader == null)
+                       return;
+               new AltosCSVUI(AltosUI.this, record_reader, chooser.file());
        }
 
        /* Load a flight log CSV file and display a pretty graph.
         */
 
        private void GraphData() {
-               new AltosGraphUI(AltosUI.this);
+               AltosDataChooser chooser;
+               chooser = new AltosDataChooser(this);
+               AltosRecordIterable record_reader = chooser.runDialog();
+               if (record_reader == null)
+                       return;
+               new AltosGraphUI(record_reader);
        }
 
        private void ConfigureAltosUI() {
index b6b2e572c205f0655d9d4fc307cb8863262442b5..41afdf2733e441531abdf7dee99944b4b0505a8f 100644 (file)
@@ -40,13 +40,14 @@ altosui_JAVA = \
        AltosGreatCircle.java \
        AltosHexfile.java \
        Altos.java \
+       AltosIgnite.java \
+       AltosIgniteUI.java \
        AltosInfoTable.java \
        AltosKML.java \
        AltosLanded.java \
        AltosLed.java \
        AltosLights.java \
        AltosLine.java \
-       AltosLogfileChooser.java \
        AltosLog.java \
        AltosPad.java \
        AltosParse.java \
@@ -73,7 +74,7 @@ altosui_JAVA = \
        AltosGraph.java \
        AltosGraphTime.java \
        AltosGraphUI.java \
-       AltosGraphDataChooser.java \
+       AltosDataChooser.java \
        AltosVoice.java
 
 JFREECHART_CLASS= \
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..54ca39b
--- /dev/null
@@ -0,0 +1,3 @@
+*.html
+*.pdf
+*.fo
index 238cefb08cf33ccee7d573ef7bf8df28824aa595..57300c10f8a9bf931b57ee9ce3c0f3e7d140a180 100644 (file)
@@ -2,32 +2,35 @@
 #      http://docbook.sourceforge.net/release/xsl/current/README
 #
 
-all:   telemetrum-doc.html telemetrum-doc.pdf
+HTML=telemetrum-doc.html altosui-doc.html
+PDF=telemetrum-doc.pdf altosui-doc.pdf
+DOC=$(HTML) $(PDF)
+HTMLSTYLE=/usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl
+FOSTYLE=/usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl
+PDFSTYLE=
 
-publish:       all
-       cp telemetrum-doc.html \
-               telemetrum-doc.pdf /home/bdale/web/altusmetrum/TeleMetrum/doc/
-       (cd /home/bdale/web/altusmetrum ; echo "update docs" | git commit -F - /home/bdale/web/altusmetrum/TeleMetrum/doc/* ; git push)
+.SUFFIXES: .xsl .html .fo .pdf
+
+.xsl.html:
+       xsltproc -o $@ $(HTMLSTYLE) $*.xsl
 
+.xsl.fo:
+       xsltproc -o $@ $(FOSTYLE) $*.xsl
 
-telemetrum-doc.html:   telemetrum-doc.xsl
-       xsltproc -o telemetrum-doc.html \
-               /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl \
-               telemetrum-doc.xsl
+.fo.pdf:
+       fop -fo $*.fo -pdf $@
 
-telemetrum-doc.fo:     telemetrum-doc.xsl
-       xsltproc -o telemetrum-doc.fo \
-               /usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl \
-               telemetrum-doc.xsl
+all:   $(HTML) $(PDF)
 
-telemetrum-doc.pdf:    telemetrum-doc.fo
-       fop -fo telemetrum-doc.fo -pdf telemetrum-doc.pdf
+publish:       $(DOC)
+       cp $(DOC)telemetrum-doc.html home/bdale/web/altusmetrum/TeleMetrum/doc/
+       (cd /home/bdale/web/altusmetrum ; echo "update docs" | git commit -F - /home/bdale/web/altusmetrum/TeleMetrum/doc/* ; git push)
 
 clean:
-       rm -f telemetrum-doc.html telemetrum-doc.pdf telemetrum-doc.fo
+       rm -f *.html *.pdf *.fo
 
 distclean:
-       rm -f telemetrum-doc.html telemetrum-doc.pdf telemetrum-doc.fo
+       rm -f *.html *.pdf *.fo
 
 indent:                telemetrum-doc.xsl
        xmlindent -i 2 < telemetrum-doc.xsl > telemetrum-doc.new
diff --git a/doc/altosui-doc.xsl b/doc/altosui-doc.xsl
new file mode 100644 (file)
index 0000000..4a1f43b
--- /dev/null
@@ -0,0 +1,596 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "/usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd">
+
+<book>
+  <title>AltosUI</title>
+  <subtitle>Altos Metrum Graphical User Interface Manual</subtitle>
+  <bookinfo>
+    <author>
+      <firstname>Bdale</firstname>
+      <surname>Garbee</surname>
+    </author>
+    <author>
+      <firstname>Keith</firstname>
+      <surname>Packard</surname>
+    </author>
+    <copyright>
+      <year>2010</year>
+      <holder>Bdale Garbee and Keith Packard</holder>
+    </copyright>
+    <legalnotice>
+      <para>
+        This document is released under the terms of the
+        <ulink url="http://creativecommons.org/licenses/by-sa/3.0/">
+          Creative Commons ShareAlike 3.0
+        </ulink>
+        license.
+      </para>
+    </legalnotice>
+    <revhistory>
+      <revision>
+        <revnumber>0.1</revnumber>
+        <date>19 November 2010</date>
+        <revremark>Initial content</revremark>
+      </revision>
+    </revhistory>
+  </bookinfo>
+  <chapter>
+    <title>Introduction</title>
+    <para>
+      The AltosUI program provides a graphical user interface for
+      interacting with the Altus Metrum product family, including
+      TeleMetrum and TeleDongle. AltosUI can monitor telemetry data,
+      configure TeleMetrum and TeleDongle devices and many other
+      tasks. The primary interface window provides a selection of
+      buttons, one for each major activity in the system.  This manual
+      is split into chapters, each of which documents one of the tasks
+      provided from the top-level toolbar.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Packet Command Mode</title>
+    <subtitle>Controlling TeleMetrum Over The Radio Link</subtitle>
+    <para>
+      One of the unique features of the Altos Metrum environment is
+      the ability to create a two way command link between TeleDongle
+      and TeleMetrum using the digital radio transceivers built into
+      each device. This allows you to interact with TeleMetrum from
+      afar, as if it were directly connected to the computer.
+    </para>
+    <para>
+      Any operation which can be performed with TeleMetrum
+      can either be done with TeleMetrum directly connected to
+      the computer via the USB cable, or through the packet
+      link. Simply select the appropriate TeleDongle device when
+      the list of devices is presented and AltosUI will use packet
+      command mode.
+    </para>
+    <itemizedlist>
+      <listitem>
+       <para>
+         Save Flight Data—Recover flight data from the rocket without
+         opening it up.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         Configure TeleMetrum—Reset apogee delays or main deploy
+         heights to respond to changing launch conditions. You can
+         also 'reboot' the TeleMetrum device. Use this to remotely
+         enable the flight computer by turning TeleMetrum on while
+         horizontal, then once the airframe is oriented for launch,
+         you can reboot TeleMetrum and have it restart in pad mode
+         without having to climb the scary ladder.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         Fire Igniters—Test your deployment charges without snaking
+         wires out through holes in the airframe. Simply assembly the
+         rocket as if for flight with the apogee and main charges
+         loaded, then remotely command TeleMetrum to fire the
+         igniters.
+       </para>
+      </listitem>
+    </itemizedlist>
+    <para>
+      Packet command mode uses the same RF channels as telemetry
+      mode. Configure the desired TeleDongle channel using the
+      flight monitor window channel selector and then close that
+      window before performing the desired operation.
+    </para>
+    <para>
+      TeleMetrum only enables packet command mode in 'idle' mode, so
+      make sure you have TeleMetrum lying horizontally when you turn
+      it on. Otherwise, TeleMetrum will start in 'pad' mode ready for
+      flight and will not be listening for command packets from TeleDongle.
+    </para>
+    <para>
+      When packet command mode is enabled, you can monitor the link
+      by watching the lights on the TeleDongle and TeleMetrum
+      devices. The red LED will flash each time TeleDongle or
+      TeleMetrum transmit a packet while the green LED will light up
+      on TeleDongle while it is waiting to receive a packet from
+      TeleMetrum.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Monitor Flight</title>
+    <subtitle>Receive, Record and Display Telemetry Data</subtitle>
+    <para>
+      Selecting this item brings up a dialog box listing all of the
+      connected TeleDongle devices. When you choose one of these,
+      AltosUI will create a window to display telemetry data as
+      received by the selected TeleDongle device.
+    </para>
+    <para>
+      All telemetry data received are automatically recorded in
+      suitable log files. The name of the files includes the current
+      date and rocket serial and flight numbers.
+    </para>
+    <para>
+      The radio channel being monitored by the TeleDongle device is
+      displayed at the top of the window. You can configure the
+      channel by clicking on the channel box and selecting the desired
+      channel. AltosUI remembers the last channel selected for each
+      TeleDongle and selects that automatically the next time you use
+      that device.
+    </para>
+    <para>
+      Below the TeleDongle channel selector, the window contains a few
+      significant pieces of information about the TeleMetrum providing
+      the telemetry data stream:
+    </para>
+    <itemizedlist>
+      <listitem>
+       <para>The TeleMetrum callsign</para>
+      </listitem>
+      <listitem>
+       <para>The TeleMetrum serial number</para>
+      </listitem>
+      <listitem>
+       <para>The flight number. Each TeleMetrum remembers how many
+       times it has flown.</para>
+      </listitem>
+      <listitem>
+       <para>
+         The rocket flight state. Each flight passes through several
+         states including Pad, Boost, Fast, Coast, Drogue, Main and
+         Landed.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         The Received Signal Strength Indicator value. This lets
+         you know how strong a signal TeleDongle is receiving. The
+         radio inside TeleDongle operates down to about -99dBm;
+         weaker signals may not be receiveable. The packet link uses
+         error correction and detection techniques which prevent
+         incorrect data from being reported.
+       </para>
+      </listitem>
+    </itemizedlist>
+    <para>
+      Finally, the largest portion of the window contains a set of
+      tabs, each of which contain some information about the rocket.
+      They're arranged in 'flight order' so that as the flight
+      progresses, the selected tab automatically switches to display
+      data relevant to the current state of the flight. You can select
+      other tabs at any time. The final 'table' tab contains all of
+      the telemetry data in one place.
+    </para>
+    <section>
+      <title>Launch Pad</title>
+      <para>
+       The 'Launch Pad' tab shows information used to decide when the
+       rocket is ready for flight. The first elements include red/green
+       indicators, if any of these is red, you'll want to evaluate
+       whether the rocket is ready to launch:
+       <itemizedlist>
+         <listitem>
+           <para>
+             Battery Voltage. This indicates whether the LiPo battery
+             powering the TeleMetrum has sufficient charge to last for
+             the duration of the flight. A value of more than
+             3.7V is required for a 'GO' status.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             Apogee Igniter Voltage. This indicates whether the apogee
+             igniter has continuity. If the igniter has a low
+             resistance, then the voltage measured here will be close
+             to the LiPo battery voltage. A value greater than 3.2V is
+             required for a 'GO' status.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             Main Igniter Voltage. This indicates whether the main
+             igniter has continuity. If the igniter has a low
+             resistance, then the voltage measured here will be close
+             to the LiPo battery voltage. A value greater than 3.2V is
+             required for a 'GO' status.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             GPS Locked. This indicates whether the GPS receiver is
+             currently able to compute position information. GPS requires
+             at least 4 satellites to compute an accurate position.
+           </para>
+         </listitem>
+         <listitem>
+           <para>
+             GPS Ready. This indicates whether GPS has reported at least
+             10 consecutive positions without losing lock. This ensures
+             that the GPS receiver has reliable reception from the
+             satellites.
+           </para>
+         </listitem>
+       </itemizedlist>
+       <para>
+         The LaunchPad tab also shows the computed launch pad position
+         and altitude, averaging many reported positions to improve the
+         accuracy of the fix.
+       </para>
+      </para>
+    </section>
+    <section>
+      <title>Ascent</title>
+      <para>
+       This tab is shown during Boost, Fast and Coast
+       phases. The information displayed here helps monitor the
+       rocket as it heads towards apogee.
+      </para>
+      <para>
+       The height, speed and acceleration are shown along with the
+       maxium values for each of them. This allows you to quickly
+       answer the most commonly asked questions you'll hear during
+       flight.
+      </para>
+      <para>
+       The current latitude and longitude reported by the GPS are
+       also shown. Note that under high acceleration, these values
+       may not get updated as the GPS receiver loses position
+       fix. Once the rocket starts coasting, the receiver should
+       start reporting position again.
+      </para>
+      <para>
+       Finally, the current igniter voltages are reported as in the
+       Launch Pad tab. This can help diagnose deployment failures
+       caused by wiring which comes loose under high acceleration.
+      </para>
+    </section>
+    <section>
+      <title>Descent</title>
+      <para>
+       Once the rocket has reached apogee and (we hope) activated the
+       apogee charge, attention switches to tracking the rocket on
+       the way back to the ground, and for dual-deploy flights,
+       waiting for the main charge to fire.
+      </para>
+      <para>
+       To monitor whether the apogee charge operated correctly, the
+       current descent rate is reported along with the current
+       height. Good descent rates generally range from 15-30m/s.
+      </para>
+      <para>
+       To help locate the rocket in the sky, use the elevation and
+       bearing information to figure out where to look. Elevation is
+       in degrees above the horizon. Bearing is reported in degrees
+       relative to true north. Range can help figure out how big the
+       rocket will appear. Note that all of these values are relative
+       to the pad location. If the elevation is near 90°, the rocket
+       is over the pad, not over you.
+      </para>
+      <para>
+       Finally, the igniter voltages are reported in this tab as
+       well, both to monitor the main charge as well as to see what
+       the status of the apogee charge is.
+      </para>
+    </section>
+    <section>
+      <title>Landed</title>
+      <para>
+       Once the rocket is on the ground, attention switches to
+       recovery. While the radio signal is generally lost once the
+       rocket is on the ground, the last reported GPS position is
+       generally within a short distance of the actual landing location.
+      </para>
+      <para>
+       The last reported GPS position is reported both by
+       latitude and longitude as well as a bearing and distance from
+       the launch pad. The distance should give you a good idea of
+       whether you'll want to walk or hitch a ride. Take the reported
+       latitude and longitude and enter them into your handheld GPS
+       unit and have that compute a track to the landing location.
+      </para>
+      <para>
+       Finally, the maximum height, speed and acceleration reported
+       during the flight are displayed for your admiring observers.
+      </para>
+    </section>
+  </chapter>
+  <chapter>
+    <title>Save Flight Data</title>
+    <para>
+      TeleMetrum records flight data to its internal flash memory.
+      This data is recorded at a much higher rate than the telemetry
+      system can handle, and is not subject to radio drop-outs. As
+      such, it provides a more complete and precise record of the
+      flight. The 'Save Flight Data' button allows you to read the
+      flash memory and write it to disk.
+    </para>
+    <para>
+      Clicking on the 'Save Flight Data' button brings up a list of
+      connected TeleMetrum and TeleDongle devices. If you select a
+      TeleMetrum device, the flight data will be downloaded from that
+      device directly. If you select a TeleDongle device, flight data
+      will be downloaded from a TeleMetrum device connected via the
+      packet command link to the specified TeleDongle. See the chapter
+      on Packet Command Mode for more information about this.
+    </para>
+    <para>
+      The filename for the data is computed automatically from the recorded
+      flight date, TeleMetrum serial number and flight number
+      information.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Replay Flight</title>
+    <para>
+      Select this button and you are prompted to select a flight
+      record file, either a .telem file recording telemetry data or a
+      .eeprom file containing flight data saved from the TeleMetrum
+      flash memory.
+    </para>
+    <para>
+      Once a flight record is selected, the flight monitor interface
+      is displayed and the flight is re-enacted in real time. Check
+      the Monitor Flight chapter above to learn how this window operates.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Graph Data</title>
+    <para>
+      This section should be written by AJ.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Export Data</title>
+    <para>
+     This tool takes the raw data files and makes them available for
+     external analysis. When you select this button, you are prompted to select a flight
+      data file (either .eeprom or .telem will do, remember that
+      .eeprom files contain higher resolution and more continuous
+      data). Next, a second dialog appears which is used to select
+      where to write the resulting file. It has a selector to choose
+      between CSV and KML file formats.
+    </para>
+    <section>
+      <title>Comma Separated Value Format</title>
+      <para>
+       This is a text file containing the data in a form suitable for
+       import into a spreadsheet or other external data analysis
+       tool. The first few lines of the file contain the version and
+       configuration information from the TeleMetrum device, then
+       there is a single header line which labels all of the
+       fields. All of these lines start with a '#' character which
+       most tools can be configured to skip over.
+      </para>
+      <para>
+       The remaining lines of the file contain the data, with each
+       field separated by a comma and at least one space. All of
+       the sensor values are converted to standard units, with the
+       barometric data reported in both pressure, altitude and
+       height above pad units.
+      </para>
+    </section>
+    <section>
+      <title>Keyhole Markup Language (for Google Earth)</title>
+      <para>
+       This is the format used by
+       Googleearth to provide an overlay within that
+       application. With this, you can use Googleearth to see the
+       whole flight path in 3D.
+      </para>
+    </section>
+  </chapter>
+  <chapter>
+    <title>Configure TeleMetrum</title>
+    <para>
+      Select this button and then select either a TeleMetrum or
+      TeleDongle Device from the list provided. Selecting a TeleDongle
+      device will use Packet Comamnd Mode to configure remote
+      TeleMetrum device. Learn how to use this in the Packet Command
+      Mode chapter.
+    </para>
+    <para>
+      The first few lines of the dialog provide information about the
+      connected TeleMetrum device, including the product name,
+      software version and hardware serial number. Below that are the
+      individual configuration entries.
+    </para>
+    <para>
+      At the bottom of the dialog, there are four buttons:
+    </para>
+    <itemizedlist>
+      <listitem>
+       <para>
+         Save. This writes any changes to the TeleMetrum
+         configuration parameter block in flash memory. If you don't
+         press this button, any changes you make will be lost.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         Reset. This resets the dialog to the most recently saved values,
+         erasing any changes you have made.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         Reboot. This reboots the TeleMetrum device. Use this to
+         switch from idle to pad mode by rebooting once the rocket is
+         oriented for flight.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+         Close. This closes the dialog. Any unsaved changes will be
+         lost.
+       </para>
+      </listitem>
+    </itemizedlist>
+    <para>
+      The rest of the dialog contains the parameters to be configured.
+    </para>
+    <section>
+      <title>Main Deploy Altitude</title>
+      <para>
+       This sets the altitude (above the recorded pad altitude) at
+       which the 'main' igniter will fire. The drop-down menu shows
+       some common values, but you can edit the text directly and
+       choose whatever you like. If the apogee charge fires below
+       this altitude, then the main charge will fire two seconds
+       after the apogee charge fires.
+      </para>
+    </section>
+    <section>
+      <title>Apogee Delay</title>
+      <para>
+       When flying redundant electronics, it's often important to
+       ensure that multiple apogee charges don't fire at precisely
+       the same time as that can overpressurize the apogee deployment
+       bay and cause a structural failure of the airframe. The Apogee
+       Delay parameter tells the flight computer to fire the apogee
+       charge a certain number of seconds after apogee has been
+       detected.
+      </para>
+    </section>
+    <section>
+      <title>Radio Channel</title>
+      <para>
+       This configures which of the 10 radio channels to use for both
+       telemetry and packet command mode. Note that if you set this
+       value via packet command mode, you will have to reconfigure
+       the TeleDongle channel before you will be able to use packet
+       command mode again.
+      </para>
+    </section>
+    <section>
+      <title>Radio Calibration</title>
+      <para>
+       The radios in every Altus Metrum device are calibrated at the
+       factory to ensure that they transmit and receive on the
+       specified frequency for each channel. You can adjust that
+       calibration by changing this value. To change the TeleDongle's
+       calibration, you must reprogram the unit completely.
+      </para>
+    </section>
+    <section>
+      <title>Callsign</title>
+      <para>
+       This sets the callsign included in each telemetry packet. Set this
+       as needed to conform to your local radio regulations.
+      </para>
+    </section>
+  </chapter>
+  <chapter>
+    <title>Configure AltosUI</title>
+    <para>
+      This button presents a dialog so that you can configure the AltosUI global settings.
+    </para>
+    <section>
+      <title>Voice Settings</title>
+      <para>
+       AltosUI provides voice annoucements during flight so that you
+       can keep your eyes on the sky and still get information about
+       the current flight status. However, sometimes you don't want
+       to hear them.
+      </para>
+      <itemizedlist>
+       <listitem>
+         <para>Enable—turns all voice announcements on and off</para>
+       </listitem>
+       <listitem>
+         <para>
+           Test Voice—Plays a short message allowing you to verify
+           that the audio systme is working and the volume settings
+           are reasonable
+         </para>
+       </listitem>
+      </itemizedlist>
+    </section>
+    <section>
+      <title>Log Directory</title>
+      <para>
+       AltosUI logs all telemetry data and saves all TeleMetrum flash
+       data to this directory. This directory is also used as the
+       staring point when selecting data files for display or export.
+      </para>
+      <para>
+       Click on the directory name to bring up a directory choosing
+       dialog, select a new directory and click 'Select Directory' to
+       change where AltosUI reads and writes data files.
+      </para>
+    </section>
+    <section>
+      <title>Callsign</title>
+      <para>
+       This value is used in command packet mode and is transmitted
+       in each packet sent from TeleDongle and received from
+       TeleMetrum. It is not used in telemetry mode as that transmits
+       packets only from TeleMetrum to TeleDongle. Configure this
+       with the AltosUI operators callsign as needed to comply with
+       your local radio regulations.
+      </para>
+    </section>
+  </chapter>
+  <chapter>
+    <title>Flash Image</title>
+    <para>
+      This reprograms any Altus Metrum device by using a TeleMetrum or
+      TeleDongle as a programming dongle. Please read the directions
+      for connecting the programming cable in the main TeleMetrum
+      manual before reading these instructions.
+    </para>
+    <para>
+      Once you have the programmer and target devices connected,
+      push the 'Flash Image' button. That will present a dialog box
+      listing all of the connected devices. Carefully select the
+      programmer device, not the device to be programmed.
+    </para>
+    <para>
+      Next, select the image to flash to the device. These are named
+      with the product name and firmware version. The file selector
+      will start in the directory containing the firmware included
+      with the AltosUI package. Navigate to the directory containing
+      the desired firmware if it isn't there.
+    </para>
+    <para>
+      Next, a small dialog containing the device serial number and
+      RF calibration values should appear. If these values are
+      incorrect (possibly due to a corrupted image in the device),
+      enter the correct values here.
+    </para>
+    <para>
+      Finally, a dialog containing a progress bar will follow the
+      programming process.
+    </para>
+    <para>
+      When programming is complete, the target device will
+      reboot. Note that if the target device is connected via USB, you
+      will have to unplug it and then plug it back in for the USB
+      connection to reset so that you can communicate with the device
+      again.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Fire Igniter</title>
+    <para>
+    </para>
+  </chapter>
+</book>
\ No newline at end of file