diff options
| author | Anthony Towns <aj@erisian.com.au> | 2010-11-20 17:40:49 +1000 | 
|---|---|---|
| committer | Anthony Towns <aj@erisian.com.au> | 2010-11-20 17:40:49 +1000 | 
| commit | 081fbd5715f9d3d81d98e149fb95d40447c07a79 (patch) | |
| tree | 073885e98179ef663f08aaaf52cb7e74c2c66427 | |
| parent | 90b9bc4475011bead7117ed72fa5efa0f77b2813 (diff) | |
| parent | 7920ed5c34b088f45ce4213b061ddd1ffe22cee8 (diff) | |
Merge branch 'buttonbox' of git://git.gag.com/fw/altos into buttonbox
Conflicts:
	ao-tools/altosui/AltosFlightUI.java
24 files changed, 1399 insertions, 287 deletions
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<ActionListener>	listeners; @@ -38,33 +37,28 @@ public class AltosChannelMenu extends JMenu implements ActionListener {  	}  	public void actionPerformed(ActionEvent e) { -		channel = Integer.parseInt(e.getActionCommand()); +		channel = getSelectedIndex(); + +		ActionEvent newe = new ActionEvent(this, channel, e.getActionCommand());  		ListIterator<ActionListener>	i = listeners.listIterator(); -		ActionEvent newe = new ActionEvent(this, channel, e.getActionCommand());  		while (i.hasNext()) {  			ActionListener	listener = i.next();  			listener.actionPerformed(newe);  		} +		setMaximumSize(getPreferredSize());  	}  	public AltosChannelMenu(int current_channel) { -		super("Channel", true); -		group = new ButtonGroup();  		channel = current_channel;  		listeners = new LinkedList<ActionListener>(); -		for (int c = 0; c <= 9; c++) { -			JRadioButtonMenuItem radioitem = new JRadioButtonMenuItem(String.format("Channel %1d (%7.3fMHz)", c, -												434.550 + c * 0.1), -							     c == channel); -			radioitem.setActionCommand(String.format("%d", c)); -			radioitem.addActionListener(this); -			add(radioitem); -			group.add(radioitem); -		} +		for (int c = 0; c <= 9; c++) +			addItem(String.format("Channel %1d (%7.3fMHz)", c, 434.550 + c * 0.1)); +		setSelectedIndex(channel); +		setMaximumRowCount(10);  	}  } 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/AltosLogfileChooser.java b/ao-tools/altosui/AltosDataChooser.java index 8b9d77d6..15de05c2 100644 --- a/ao-tools/altosui/AltosLogfileChooser.java +++ b/ao-tools/altosui/AltosDataChooser.java @@ -27,7 +27,7 @@ import java.util.*;  import java.text.*;  import java.util.prefs.*; -public class AltosLogfileChooser extends JFileChooser { +public class AltosDataChooser extends JFileChooser {  	JFrame	frame;  	String	filename;  	File	file; @@ -50,13 +50,15 @@ public class AltosLogfileChooser extends JFileChooser {  				return null;  			filename = file.getName();  			try { -				FileInputStream in; - -				in = new FileInputStream(file); -				if (filename.endsWith("eeprom")) +				if (filename.endsWith("eeprom")) { +					FileInputStream in = new FileInputStream(file);  					return new AltosEepromIterable(in); -				else +				} 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, @@ -67,12 +69,11 @@ public class AltosLogfileChooser extends JFileChooser {  		return null;  	} -	public AltosLogfileChooser(JFrame in_frame) { +	public AltosDataChooser(JFrame in_frame) {  		frame = in_frame;  		setDialogTitle("Select Flight Record File");  		setFileFilter(new FileNameExtensionFilter("Flight data file", -							  "eeprom", -							  "telem")); +							  "telem", "eeprom"));  		setCurrentDirectory(AltosPreferences.logdir());  	} -}
\ No newline at end of file +} 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 <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()); -	} -} 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<AltosDataPoint> reader = chooser.runDialog(); -        if (reader == null) -            return; +		Iterable<AltosDataPoint> reader = new AltosDataPointReader (records); +		if (reader == null) +			return; -        init(reader, 0); -    } +		init(reader, 0); +	}      public AltosGraphUI(Iterable<AltosDataPoint> data, int which)       { diff --git a/ao-tools/altosui/AltosIgnite.java b/ao-tools/altosui/AltosIgnite.java new file mode 100644 index 00000000..8e92ec1b --- /dev/null +++ b/ao-tools/altosui/AltosIgnite.java @@ -0,0 +1,165 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.io.*; +import java.util.concurrent.*; + +public class AltosIgnite { +	AltosDevice	device; +	AltosSerial	serial; +	boolean		remote; +	final static int	None = 0; +	final static int	Apogee = 1; +	final static int	Main = 2; + +	final static int	Unknown = 0; +	final static int	Ready = 1; +	final static int	Active = 2; +	final static int	Open = 3; + +	private void start_serial() throws InterruptedException { +		if (remote) { +			serial.set_channel(AltosPreferences.channel(device.getSerial())); +			serial.set_callsign(AltosPreferences.callsign()); +			serial.printf("~\np\n"); +			serial.flush_input(); +		} +	} + +	private void stop_serial() throws InterruptedException { +		if (serial == null) +			return; +		if (remote) { +			serial.printf("~"); +			serial.flush_output(); +		} +	} + +	class string_ref { +		String	value; + +		public String get() { +			return value; +		} +		public void set(String i) { +			value = i; +		} +		public string_ref() { +			value = null; +		} +	} + +	private boolean get_string(String line, String label, string_ref s) { +		if (line.startsWith(label)) { +			String	quoted = line.substring(label.length()).trim(); + +			if (quoted.startsWith("\"")) +				quoted = quoted.substring(1); +			if (quoted.endsWith("\"")) +				quoted = quoted.substring(0,quoted.length()-1); +			s.set(quoted); +			return true; +		} else { +			return false; +		} +	} + +	private int status(String status_name) { +		if (status_name.equals("unknown")) +			return Unknown; +		if (status_name.equals("ready")) +			return Ready; +		if (status_name.equals("active")) +			return Active; +		if (status_name.equals("open")) +			return Open; +		return Unknown; +	} + +	public int status(int igniter) throws InterruptedException, TimeoutException { +		int status = Unknown; +		if (serial == null) +			return status; +		string_ref status_name = new string_ref(); +		start_serial(); +		serial.printf("t\n"); +		for (;;) { +			String line = serial.get_reply(1000); +			if (line == null) +				throw new TimeoutException(); +			if (get_string(line, "Igniter: drogue Status: ", status_name)) +				if (igniter == Apogee) +					status = status(status_name.get()); +			if (get_string(line, "Igniter:   main Status: ", status_name)) { +				if (igniter == Main) +					status = status(status_name.get()); +				break; +			} +		} +		stop_serial(); +		return status; +	} + +	public String status_string(int status) { +		switch (status) { +		case Unknown: return "Unknown"; +		case Ready: return "Ready"; +		case Active: return "Active"; +		case Open: return "Open"; +		default: return "Unknown"; +		} +	} + +	public void fire(int igniter) { +		if (serial == null) +			return; +		try { +			start_serial(); +			switch (igniter) { +			case Main: +				serial.printf("i DoIt main\n"); +				break; +			case Apogee: +				serial.printf("i DoIt drogue\n"); +				break; +			} +		} catch (InterruptedException ie) { +		} finally { +			try { +				stop_serial(); +			} catch (InterruptedException ie) { +			} +		} +	} + +	public void close() { +		serial.close(); +		serial = null; +	} + +	public AltosIgnite(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + +		device = in_device; +		serial = new AltosSerial(device); +		remote = false; + +		if (!device.matchProduct(AltosDevice.product_telemetrum)) +			remote = true; +	} +}
\ No newline at end of file diff --git a/ao-tools/altosui/AltosIgniteUI.java b/ao-tools/altosui/AltosIgniteUI.java new file mode 100644 index 00000000..0207e39f --- /dev/null +++ b/ao-tools/altosui/AltosIgniteUI.java @@ -0,0 +1,319 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +public class AltosIgniteUI +	extends JDialog +	implements ActionListener +{ +	AltosDevice	device; +	AltosIgnite	ignite; +	JFrame		owner; +	JLabel		label; +	JRadioButton	apogee; +	JLabel		apogee_status_label; +	JRadioButton	main; +	JLabel		main_status_label; +	JToggleButton	arm; +	JButton		fire; +	javax.swing.Timer	timer; + +	int		apogee_status; +	int		main_status; + +	final static int	timeout = 1 * 1000; + +	int		time_remaining; +	boolean		timer_running; + +	void set_arm_text() { +		if (arm.isSelected()) +			arm.setText(String.format("%d", time_remaining)); +		else +			arm.setText("Arm"); +	} + +	void start_timer() { +		time_remaining = 10; +		set_arm_text(); +		timer_running = true; +	} + +	void stop_timer() { +		time_remaining = 0; +		arm.setSelected(false); +		arm.setEnabled(false); +		fire.setEnabled(false); +		timer_running = false; +		set_arm_text(); +	} + +	void cancel () { +		apogee.setSelected(false); +		main.setSelected(false); +		fire.setEnabled(false); +		stop_timer(); +	} + +	void get_ignite_status() throws InterruptedException, TimeoutException { +		apogee_status = ignite.status(AltosIgnite.Apogee); +		main_status = ignite.status(AltosIgnite.Main); +	} + +	void set_ignite_status() throws InterruptedException, TimeoutException { +		get_ignite_status(); +		apogee_status_label.setText(String.format("\"%s\"", ignite.status_string(apogee_status))); +		main_status_label.setText(String.format("\"%s\"", ignite.status_string(main_status))); +	} + +	void close() { +		timer.stop(); +		setVisible(false); +		ignite.close(); +	} + +	void abort() { +		close(); +		JOptionPane.showMessageDialog(owner, +					      String.format("Connection to \"%s\" failed", +							    device.toShortString()), +					      "Connection Failed", +					      JOptionPane.ERROR_MESSAGE); +	} + +	void tick_timer() { +		if (timer_running) { +			--time_remaining; +			if (time_remaining <= 0) +				cancel(); +			else +				set_arm_text(); +		} +		try { +			set_ignite_status(); +		} catch (InterruptedException ie) { +			abort(); +		} catch (TimeoutException te) { +			abort(); +		} +	} + +	void fire() { +		if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) { +			int	igniter = AltosIgnite.None; +			if (apogee.isSelected() && !main.isSelected()) +				igniter = AltosIgnite.Apogee; +			else if (main.isSelected() && !apogee.isSelected()) +				igniter = AltosIgnite.Main; +			ignite.fire(igniter); +			cancel(); +		} +	} + +	public void actionPerformed(ActionEvent e) { +		String cmd = e.getActionCommand(); +		if (cmd.equals("apogee") || cmd.equals("main")) { +			stop_timer(); +		} + +		if (cmd.equals("apogee") && apogee.isSelected()) { +			main.setSelected(false); +			if (apogee_status == AltosIgnite.Ready) +				arm.setEnabled(true); +		} +		if (cmd.equals("main") && main.isSelected()) { +			apogee.setSelected(false); +			if (main_status == AltosIgnite.Ready) +				arm.setEnabled(true); +		} + +		if (cmd.equals("arm")) { +			if (arm.isSelected()) { +				fire.setEnabled(true); +				start_timer(); +			} else +				cancel(); +		} +		if (cmd.equals("fire")) +			fire(); +		if (cmd.equals("tick")) +			tick_timer(); +		if (cmd.equals("close")) { +			close(); +		} +	} + +	/* A window listener to catch closing events and tell the config code */ +	class ConfigListener extends WindowAdapter { +		AltosIgniteUI	ui; + +		public ConfigListener(AltosIgniteUI this_ui) { +			ui = this_ui; +		} + +		public void windowClosing(WindowEvent e) { +			ui.actionPerformed(new ActionEvent(e.getSource(), +							   ActionEvent.ACTION_PERFORMED, +							   "close")); +		} +	} + +	private boolean open() { +		device = AltosDeviceDialog.show(owner, AltosDevice.product_any); +		if (device != null) { +			try { +				ignite = new AltosIgnite(device); +				return true; +			} catch (FileNotFoundException ee) { +				JOptionPane.showMessageDialog(owner, +							      String.format("Cannot open device \"%s\"", +									    device.toShortString()), +							      "Cannot open target device", +							      JOptionPane.ERROR_MESSAGE); +			} catch (AltosSerialInUseException si) { +				JOptionPane.showMessageDialog(owner, +							      String.format("Device \"%s\" already in use", +									    device.toShortString()), +							      "Device in use", +							      JOptionPane.ERROR_MESSAGE); +			} catch (IOException ee) { +				JOptionPane.showMessageDialog(owner, +							      device.toShortString(), +							      ee.getLocalizedMessage(), +							      JOptionPane.ERROR_MESSAGE); +			} +		} +		return false; +	} + +	public AltosIgniteUI(JFrame in_owner) { + +		owner = in_owner; +		apogee_status = AltosIgnite.Unknown; +		main_status = AltosIgnite.Unknown; + +		if (!open()) +			return; + +		Container		pane = getContentPane(); +		GridBagConstraints	c = new GridBagConstraints(); +		Insets			i = new Insets(4,4,4,4); + +		timer = new javax.swing.Timer(timeout, this); +		timer.setActionCommand("tick"); +		timer_running = false; +		timer.restart(); + +		owner = in_owner; + +		pane.setLayout(new GridBagLayout()); + +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = i; +		c.weightx = 1; +		c.weighty = 1; + +		c.gridx = 0; +		c.gridy = 0; +		c.gridwidth = 2; +		c.anchor = GridBagConstraints.CENTER; +		label = new JLabel ("Fire Igniter"); +		pane.add(label, c); + +		c.gridx = 0; +		c.gridy = 1; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.WEST; +		apogee = new JRadioButton ("Apogee"); +		pane.add(apogee, c); +		apogee.addActionListener(this); +		apogee.setActionCommand("apogee"); + +		c.gridx = 1; +		c.gridy = 1; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.WEST; +		apogee_status_label = new JLabel(); +		pane.add(apogee_status_label, c); + +		c.gridx = 0; +		c.gridy = 2; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.WEST; +		main = new JRadioButton ("Main"); +		pane.add(main, c); +		main.addActionListener(this); +		main.setActionCommand("main"); + +		c.gridx = 1; +		c.gridy = 2; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.WEST; +		main_status_label = new JLabel(); +		pane.add(main_status_label, c); + +		try { +			set_ignite_status(); +		} catch (InterruptedException ie) { +			abort(); +			return; +		} catch (TimeoutException te) { +			abort(); +			return; +		} + +		c.gridx = 0; +		c.gridy = 3; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; +		arm = new JToggleButton ("Arm"); +		pane.add(arm, c); +		arm.addActionListener(this); +		arm.setActionCommand("arm"); +		arm.setEnabled(false); + +		c.gridx = 1; +		c.gridy = 3; +		c.gridwidth = 1; +		c.anchor = GridBagConstraints.CENTER; +		fire = new JButton ("Fire"); +		fire.setEnabled(false); +		pane.add(fire, c); +		fire.addActionListener(this); +		fire.setActionCommand("fire"); + +		pack(); +		setLocationRelativeTo(owner); +		setVisible(true); + +		addWindowListener(new ConfigListener(this)); +	} +}
\ No newline at end of file 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/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<String> devices_opened = Collections.synchronizedList(new LinkedList<String>()); -	altos_device device; +	AltosDevice device;  	SWIGTYPE_p_altos_file altos;  	LinkedList<LinkedBlockingQueue<AltosLine>> monitors;  	LinkedBlockingQueue<AltosLine> reply_queue; @@ -132,6 +132,14 @@ public class AltosSerial implements Runnable {  		return line.line;  	} +	public String get_reply(int timeout) throws InterruptedException { +		flush_output(); +		AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS); +		if (line == null) +			return null; +		return line.line; +	} +  	public void add_monitor(LinkedBlockingQueue<AltosLine> q) {  		set_monitor(true);  		monitors.add(q); @@ -185,10 +193,9 @@ public class AltosSerial implements Runnable {  				throw new AltosSerialInUseException(device);  			devices_opened.add(device.getPath());  		} -		close();  		altos = libaltos.altos_open(device);  		if (altos == null) -			throw new FileNotFoundException(device.getPath()); +			throw new FileNotFoundException(device.toShortString());  		input_thread = new Thread(this);  		input_thread.start();  		print("~\nE 0\n"); @@ -226,7 +233,7 @@ public class AltosSerial implements Runnable {  		}  	} -	public AltosSerial(altos_device in_device) throws FileNotFoundException, AltosSerialInUseException { +	public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {  		device = in_device;  		line = "";  		monitor_mode = false; 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<AltosLine>();  		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 @@ +<?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  | 
