From: Anthony Towns Date: Sat, 20 Nov 2010 07:40:49 +0000 (+1000) Subject: Merge branch 'buttonbox' of git://git.gag.com/fw/altos into buttonbox X-Git-Tag: debian/0.7.1+117+ge7954c8~8^2~11 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=081fbd5715f9d3d81d98e149fb95d40447c07a79;hp=90b9bc4475011bead7117ed72fa5efa0f77b2813 Merge branch 'buttonbox' of git://git.gag.com/fw/altos into buttonbox Conflicts: ao-tools/altosui/AltosFlightUI.java --- diff --git a/ao-bringup/turnon_teledongle b/ao-bringup/turnon_teledongle index 216afa2a..5145e9b0 100755 --- a/ao-bringup/turnon_teledongle +++ b/ao-bringup/turnon_teledongle @@ -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" diff --git a/ao-bringup/turnon_telemetrum b/ao-bringup/turnon_telemetrum index 440eda1b..405247fa 100755 --- a/ao-bringup/turnon_telemetrum +++ b/ao-bringup/turnon_telemetrum @@ -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" diff --git a/ao-tools/altosui/AltosCSVUI.java b/ao-tools/altosui/AltosCSVUI.java index 16f25338..e1b6002d 100644 --- a/ao-tools/altosui/AltosCSVUI.java +++ b/ao-tools/altosui/AltosCSVUI.java @@ -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(); - } } diff --git a/ao-tools/altosui/AltosChannelMenu.java b/ao-tools/altosui/AltosChannelMenu.java index 504c13c6..8069c853 100644 --- a/ao-tools/altosui/AltosChannelMenu.java +++ b/ao-tools/altosui/AltosChannelMenu.java @@ -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 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 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(); - 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); } } diff --git a/ao-tools/altosui/AltosConfig.java b/ao-tools/altosui/AltosConfig.java index a0fdb623..6bda20d8 100644 --- a/ao-tools/altosui/AltosConfig.java +++ b/ao-tools/altosui/AltosConfig.java @@ -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); } diff --git a/ao-tools/altosui/AltosConfigureUI.java b/ao-tools/altosui/AltosConfigureUI.java index 64c17eaf..153c59fd 100644 --- a/ao-tools/altosui/AltosConfigureUI.java +++ b/ao-tools/altosui/AltosConfigureUI.java @@ -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 index 00000000..15de05c2 --- /dev/null +++ b/ao-tools/altosui/AltosDataChooser.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2010 Keith Packard + * + * 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()); + } +} diff --git a/ao-tools/altosui/AltosDebug.java b/ao-tools/altosui/AltosDebug.java index 9aa35d3f..8d435b66 100644 --- a/ao-tools/altosui/AltosDebug.java +++ b/ao-tools/altosui/AltosDebug.java @@ -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 diff --git a/ao-tools/altosui/AltosDevice.java b/ao-tools/altosui/AltosDevice.java index f646305b..f0fda57b 100644 --- a/ao-tools/altosui/AltosDevice.java +++ b/ao-tools/altosui/AltosDevice.java @@ -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; diff --git a/ao-tools/altosui/AltosEepromDownload.java b/ao-tools/altosui/AltosEepromDownload.java index 8996b924..fb5dcfc0 100644 --- a/ao-tools/altosui/AltosEepromDownload.java +++ b/ao-tools/altosui/AltosEepromDownload.java @@ -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); } diff --git a/ao-tools/altosui/AltosFlashUI.java b/ao-tools/altosui/AltosFlashUI.java index b09cb594..f63097ac 100644 --- a/ao-tools/altosui/AltosFlashUI.java +++ b/ao-tools/altosui/AltosFlashUI.java @@ -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) { diff --git a/ao-tools/altosui/AltosFlightUI.java b/ao-tools/altosui/AltosFlightUI.java index 658d6f6f..21b41528 100644 --- a/ao-tools/altosui/AltosFlightUI.java +++ b/ao-tools/altosui/AltosFlightUI.java @@ -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 index d128f4d5..00000000 --- a/ao-tools/altosui/AltosGraphDataChooser.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * 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 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()); - } -} diff --git a/ao-tools/altosui/AltosGraphUI.java b/ao-tools/altosui/AltosGraphUI.java index 908aa3b4..cd158651 100644 --- a/ao-tools/altosui/AltosGraphUI.java +++ b/ao-tools/altosui/AltosGraphUI.java @@ -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 reader = chooser.runDialog(); - if (reader == null) - return; + Iterable reader = new AltosDataPointReader (records); + if (reader == null) + return; - init(reader, 0); - } + init(reader, 0); + } public AltosGraphUI(Iterable data, int which) { diff --git a/ao-tools/altosui/AltosIgnite.java b/ao-tools/altosui/AltosIgnite.java new file mode 100644 index 00000000..8e92ec1b --- /dev/null +++ b/ao-tools/altosui/AltosIgnite.java @@ -0,0 +1,165 @@ +/* + * Copyright © 2010 Keith Packard + * + * 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 index 00000000..0207e39f --- /dev/null +++ b/ao-tools/altosui/AltosIgniteUI.java @@ -0,0 +1,319 @@ +/* + * Copyright © 2010 Keith Packard + * + * 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 diff --git a/ao-tools/altosui/AltosLog.java b/ao-tools/altosui/AltosLog.java index 137147d5..dd147d21 100644 --- a/ao-tools/altosui/AltosLog.java +++ b/ao-tools/altosui/AltosLog.java @@ -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 index 8b9d77d6..00000000 --- a/ao-tools/altosui/AltosLogfileChooser.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * 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 diff --git a/ao-tools/altosui/AltosSerial.java b/ao-tools/altosui/AltosSerial.java index 99a92fdb..ab74486b 100644 --- a/ao-tools/altosui/AltosSerial.java +++ b/ao-tools/altosui/AltosSerial.java @@ -38,7 +38,7 @@ public class AltosSerial implements Runnable { static List devices_opened = Collections.synchronizedList(new LinkedList()); - altos_device device; + AltosDevice device; SWIGTYPE_p_altos_file altos; LinkedList> monitors; LinkedBlockingQueue 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 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; diff --git a/ao-tools/altosui/AltosTelemetryReader.java b/ao-tools/altosui/AltosTelemetryReader.java index ff02c722..de5f50e9 100644 --- a/ao-tools/altosui/AltosTelemetryReader.java +++ b/ao-tools/altosui/AltosTelemetryReader.java @@ -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(); serial.add_monitor(telem); diff --git a/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java index bedf2459..6bfde014 100644 --- a/ao-tools/altosui/AltosUI.java +++ b/ao-tools/altosui/AltosUI.java @@ -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() { diff --git a/ao-tools/altosui/Makefile.am b/ao-tools/altosui/Makefile.am index b6b2e572..41afdf27 100644 --- a/ao-tools/altosui/Makefile.am +++ b/ao-tools/altosui/Makefile.am @@ -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 index 00000000..54ca39bc --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,3 @@ +*.html +*.pdf +*.fo diff --git a/doc/Makefile b/doc/Makefile index 238cefb0..57300c10 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -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 index 00000000..4a1f43b5 --- /dev/null +++ b/doc/altosui-doc.xsl @@ -0,0 +1,596 @@ + + + + + AltosUI + Altos Metrum Graphical User Interface Manual + + + Bdale + Garbee + + + Keith + Packard + + + 2010 + Bdale Garbee and Keith Packard + + + + This document is released under the terms of the + + Creative Commons ShareAlike 3.0 + + license. + + + + + 0.1 + 19 November 2010 + Initial content + + + + + Introduction + + 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. + + + + Packet Command Mode + Controlling TeleMetrum Over The Radio Link + + 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. + + + 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. + + + + + Save Flight Data—Recover flight data from the rocket without + opening it up. + + + + + 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. + + + + + 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. + + + + + 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. + + + 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. + + + 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. + + + + Monitor Flight + Receive, Record and Display Telemetry Data + + 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. + + + 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. + + + 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. + + + Below the TeleDongle channel selector, the window contains a few + significant pieces of information about the TeleMetrum providing + the telemetry data stream: + + + + The TeleMetrum callsign + + + The TeleMetrum serial number + + + The flight number. Each TeleMetrum remembers how many + times it has flown. + + + + The rocket flight state. Each flight passes through several + states including Pad, Boost, Fast, Coast, Drogue, Main and + Landed. + + + + + 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. + + + + + 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. + +
+ Launch Pad + + 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: + + + + 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. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + The LaunchPad tab also shows the computed launch pad position + and altitude, averaging many reported positions to improve the + accuracy of the fix. + + +
+
+ Ascent + + This tab is shown during Boost, Fast and Coast + phases. The information displayed here helps monitor the + rocket as it heads towards apogee. + + + 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. + + + 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. + + + 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. + +
+
+ Descent + + 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. + + + 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. + + + 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. + + + 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. + +
+
+ Landed + + 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. + + + 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. + + + Finally, the maximum height, speed and acceleration reported + during the flight are displayed for your admiring observers. + +
+
+ + Save Flight Data + + 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. + + + 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. + + + The filename for the data is computed automatically from the recorded + flight date, TeleMetrum serial number and flight number + information. + + + + Replay Flight + + 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. + + + 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. + + + + Graph Data + + This section should be written by AJ. + + + + Export Data + + 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. + +
+ Comma Separated Value Format + + 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. + + + 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. + +
+
+ Keyhole Markup Language (for Google Earth) + + 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. + +
+
+ + Configure TeleMetrum + + 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. + + + 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. + + + At the bottom of the dialog, there are four buttons: + + + + + 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. + + + + + Reset. This resets the dialog to the most recently saved values, + erasing any changes you have made. + + + + + Reboot. This reboots the TeleMetrum device. Use this to + switch from idle to pad mode by rebooting once the rocket is + oriented for flight. + + + + + Close. This closes the dialog. Any unsaved changes will be + lost. + + + + + The rest of the dialog contains the parameters to be configured. + +
+ Main Deploy Altitude + + 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. + +
+
+ Apogee Delay + + 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. + +
+
+ Radio Channel + + 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. + +
+
+ Radio Calibration + + 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. + +
+
+ Callsign + + This sets the callsign included in each telemetry packet. Set this + as needed to conform to your local radio regulations. + +
+
+ + Configure AltosUI + + This button presents a dialog so that you can configure the AltosUI global settings. + +
+ Voice Settings + + 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. + + + + Enable—turns all voice announcements on and off + + + + Test Voice—Plays a short message allowing you to verify + that the audio systme is working and the volume settings + are reasonable + + + +
+
+ Log Directory + + 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. + + + 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. + +
+
+ Callsign + + 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. + +
+
+ + Flash Image + + 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. + + + 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. + + + 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. + + + 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. + + + Finally, a dialog containing a progress bar will follow the + programming process. + + + 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. + + + + Fire Igniter + + + +
\ No newline at end of file