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"
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"
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();
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();
- }
}
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;
}
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);
}
}
import java.util.*;
import java.text.*;
import java.util.prefs.*;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.*;
import libaltosJNI.*;
}
}
- 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);
}
}
- 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() {
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();
}
}
try {
init_ui();
config_ui.make_visible();
-// } catch (InterruptedException ie) {
- } finally {
+ } catch (InterruptedException ie) {
+ abort();
+ } catch (TimeoutException te) {
+ abort();
}
}
} 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);
}
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();
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) {
});
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) {
});
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();
--- /dev/null
+/*
+ * 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());
+ }
+}
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
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;
import java.util.*;
import java.text.*;
import java.util.prefs.*;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.*;
import libaltosJNI.*;
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;
/* 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());
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) {
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("~");
} 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);
}
} 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) {
AltosFlightReader reader;
AltosDisplayThread thread;
- private Box vbox;
-
JTabbedPane pane;
AltosPad pad;
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();
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();
+++ /dev/null
-/*
- * 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());
- }
-}
}
}
- 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)
{
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
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 {
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);
+++ /dev/null
-/*
- * 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
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;
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);
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");
}
}
- public AltosSerial(altos_device in_device) throws FileNotFoundException, AltosSerialInUseException {
+ public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
device = in_device;
line = "";
monitor_mode = false;
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);
} 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);
}
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);
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(),
*/
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() {
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 \
AltosGraph.java \
AltosGraphTime.java \
AltosGraphUI.java \
- AltosGraphDataChooser.java \
+ AltosDataChooser.java \
AltosVoice.java
JFREECHART_CLASS= \
--- /dev/null
+*.html
+*.pdf
+*.fo
# 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
--- /dev/null
+<?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