diff options
Diffstat (limited to 'ao-tools')
31 files changed, 2640 insertions, 661 deletions
| diff --git a/ao-tools/altosui/Altos.java b/ao-tools/altosui/Altos.java new file mode 100644 index 00000000..53359e23 --- /dev/null +++ b/ao-tools/altosui/Altos.java @@ -0,0 +1,117 @@ +/* + * 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.util.*; +import java.text.*; + +public class Altos { +	/* EEProm command letters */ +	static final int AO_LOG_FLIGHT = 'F'; +	static final int AO_LOG_SENSOR = 'A'; +	static final int AO_LOG_TEMP_VOLT = 'T'; +	static final int AO_LOG_DEPLOY = 'D'; +	static final int AO_LOG_STATE = 'S'; +	static final int AO_LOG_GPS_TIME = 'G'; +	static final int AO_LOG_GPS_LAT = 'N'; +	static final int AO_LOG_GPS_LON = 'W'; +	static final int AO_LOG_GPS_ALT = 'H'; +	static final int AO_LOG_GPS_SAT = 'V'; +	static final int AO_LOG_GPS_DATE = 'Y'; + +	/* Added for header fields in eeprom files */ +	static final int AO_LOG_CONFIG_VERSION = 1000; +	static final int AO_LOG_MAIN_DEPLOY = 1001; +	static final int AO_LOG_APOGEE_DELAY = 1002; +	static final int AO_LOG_RADIO_CHANNEL = 1003; +	static final int AO_LOG_CALLSIGN = 1004; +	static final int AO_LOG_ACCEL_CAL = 1005; +	static final int AO_LOG_RADIO_CAL = 1006; +	static final int AO_LOG_MANUFACTURER = 1007; +	static final int AO_LOG_PRODUCT = 1008; +	static final int AO_LOG_SERIAL_NUMBER = 1009; +	static final int AO_LOG_SOFTWARE_VERSION = 1010; + +	/* Added to flag invalid records */ +	static final int AO_LOG_INVALID = -1; + +	/* Flight state numbers and names */ +	static final int ao_flight_startup = 0; +	static final int ao_flight_idle = 1; +	static final int ao_flight_pad = 2; +	static final int ao_flight_boost = 3; +	static final int ao_flight_fast = 4; +	static final int ao_flight_coast = 5; +	static final int ao_flight_drogue = 6; +	static final int ao_flight_main = 7; +	static final int ao_flight_landed = 8; +	static final int ao_flight_invalid = 9; + +	static HashMap<String,Integer>	string_to_state = new HashMap<String,Integer>(); + +	static boolean map_initialized = false; + +	static void initialize_map() +	{ +		string_to_state.put("startup", ao_flight_startup); +		string_to_state.put("idle", ao_flight_idle); +		string_to_state.put("pad", ao_flight_pad); +		string_to_state.put("boost", ao_flight_boost); +		string_to_state.put("fast", ao_flight_fast); +		string_to_state.put("coast", ao_flight_coast); +		string_to_state.put("drogue", ao_flight_drogue); +		string_to_state.put("main", ao_flight_main); +		string_to_state.put("landed", ao_flight_landed); +		string_to_state.put("invalid", ao_flight_invalid); +		map_initialized = true; +	} + +	static String[] state_to_string = { +		"startup", +		"idle", +		"pad", +		"boost", +		"fast", +		"coast", +		"drogue", +		"main", +		"landed", +		"invalid", +	}; + +	static public int state(String state) { +		if (!map_initialized) +			initialize_map(); +		if (string_to_state.containsKey(state)) +			return string_to_state.get(state); +		return ao_flight_invalid; +	} + +	static public String state_name(int state) { +		if (state < 0 || state_to_string.length <= state) +			return "invalid"; +		return state_to_string[state]; +	} + +	static final int AO_GPS_VALID = (1 << 4); +	static final int AO_GPS_RUNNING = (1 << 5); +	static final int AO_GPS_DATE_VALID = (1 << 6); +	static final int AO_GPS_NUM_SAT_SHIFT = 0; +	static final int AO_GPS_NUM_SAT_MASK = 0xf; +} diff --git a/ao-tools/altosui/AltosCSV.java b/ao-tools/altosui/AltosCSV.java new file mode 100644 index 00000000..24936758 --- /dev/null +++ b/ao-tools/altosui/AltosCSV.java @@ -0,0 +1,115 @@ +/* + * 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.lang.*; +import java.io.*; +import altosui.AltosRecord; + +public class AltosCSV { +	File		name; +	PrintStream	out; +	boolean		header_written; + +	static final int ALTOS_CSV_VERSION = 1; + +	/* Version 1 format: +	 * +	 * General info +	 *	version number +	 *	serial number +	 *	flight number +	 *	callsign +	 *	time (seconds since boost) +	 * +	 * Flight status +	 *	state +	 *	state name +	 * +	 * Basic sensors +	 *	acceleration (m/s²) +	 *	pressure (mBar) +	 *	altitude (m) +	 *	accelerometer speed (m/s) +	 *	barometer speed (m/s) +	 *	temp (°C) +	 *	battery (V) +	 *	drogue (V) +	 *	main (V) +	 * +	 * GPS data +	 *	connected (1/0) +	 *	locked (1/0) +	 *	nsat (used for solution) +	 *	latitude (°) +	 *	longitude (°) +	 *	altitude (m) +	 *	year (e.g. 2010) +	 *	month (1-12) +	 *	day (1-31) +	 *	hour (0-23) +	 *	minute (0-59) +	 *	second (0-59) +	 * +	 * GPS Sat data +	 *	hdop +	 *	C/N0 data for all 32 valid SDIDs +	 */ + +	void write_general_header() { +		out.printf("version serial flight call time"); +	} + +	void write_general(AltosRecord record) { +		out.printf("%s,%d,%d,%s,%d", +			   record.version, record.serial, record.flight, record.callsign, record.tick); +	} + +	void write_flight_header() { +		out.printf("state state_name"); +	} + +	void write_flight(AltosRecord record) { +		out.printf("%d,%s", record.state, record.state()); +	} + +	void write_header() { +		out.printf("# "); write_general_header(); +		out.printf(" "); write_flight_header(); +		out.printf ("\n"); +	} + +	public void write(AltosRecord record) { +		if (!header_written) { +			write_header(); +			header_written = true; +		} +		write_general(record); out.printf(","); +		write_flight(record); +		out.printf ("\n"); +	} + +	public PrintStream out() { +		return out; +	} + +	public AltosCSV(File in_name) throws FileNotFoundException { +		name = in_name; +		out = new PrintStream(name); +	} +} diff --git a/ao-tools/altosui/AltosChannelMenu.java b/ao-tools/altosui/AltosChannelMenu.java new file mode 100644 index 00000000..504c13c6 --- /dev/null +++ b/ao-tools/altosui/AltosChannelMenu.java @@ -0,0 +1,70 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosChannelMenu extends JMenu implements ActionListener { +	ButtonGroup			group; +	int				channel; +	LinkedList<ActionListener>	listeners; + +	public void addActionListener(ActionListener l) { +		listeners.add(l); +	} + +	public void actionPerformed(ActionEvent e) { +		channel = Integer.parseInt(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); +		} +	} + +	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); +		} +	} + +} diff --git a/ao-tools/altosui/AltosConfig.java b/ao-tools/altosui/AltosConfig.java new file mode 100644 index 00000000..ac73e7c5 --- /dev/null +++ b/ao-tools/altosui/AltosConfig.java @@ -0,0 +1,264 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +import altosui.Altos; +import altosui.AltosSerial; +import altosui.AltosSerialMonitor; +import altosui.AltosRecord; +import altosui.AltosTelemetry; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosFlightStatusTableModel; +import altosui.AltosFlightInfoTableModel; +import altosui.AltosConfigUI; + +import libaltosJNI.*; + +public class AltosConfig implements Runnable, ActionListener { + +	class int_ref { +		int	value; + +		public int get() { +			return value; +		} +		public void set(int i) { +			value = i; +		} +		public int_ref(int i) { +			value = i; +		} +	} + +	class string_ref { +		String	value; + +		public String get() { +			return value; +		} +		public void set(String i) { +			value = i; +		} +		public string_ref(String i) { +			value = i; +		} +	} + +	JFrame		owner; +	AltosDevice	device; +	AltosSerial	serial_line; +	boolean		remote; +	Thread		config_thread; +	int_ref		serial; +	int_ref		main_deploy; +	int_ref		apogee_delay; +	int_ref		radio_channel; +	string_ref	version; +	string_ref	product; +	string_ref	callsign; +	AltosConfigUI	config_ui; + + +	boolean get_int(String line, String label, int_ref x) { +		if (line.startsWith(label)) { +			try { +				String tail = line.substring(label.length()).trim(); +				String[] tokens = tail.split("\\s+"); +				if (tokens.length > 0) { +					int	i = Integer.parseInt(tokens[0]); +					x.set(i); +					return true; +				} +			} catch (NumberFormatException ne) { +			} +		} +		return false; +	} + +	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; +		} +	} + +	void start_serial() throws InterruptedException { +		if (remote) { +			serial_line.printf("m 0\n"); +			serial_line.set_channel(AltosPreferences.channel()); +			serial_line.set_callsign(AltosPreferences.callsign()); +			serial_line.printf("p\n"); +		} +	} + +	void stop_serial() throws InterruptedException { +		if (remote) { +			serial_line.printf("~\n"); +			serial_line.flush(); +		} +	} + +	void get_data() throws InterruptedException { +		try { +			start_serial(); +			serial_line.printf("c s\nv\n"); +			for (;;) { +				String line = serial_line.get_reply(); +				get_int(line, "serial-number", serial); +				get_int(line, "Main deploy:", main_deploy); +				get_int(line, "Apogee delay:", apogee_delay); +				get_int(line, "Radio channel:", radio_channel); +				get_string(line, "Callsign:", callsign); +				get_string(line,"software-version", version); +				get_string(line,"product", product); + +				/* signals the end of the version info */ +				if (line.startsWith("software-version")) +					break; +			} +		} finally { +			stop_serial(); +		} +	} + +	void init_ui () { +		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_callsign(callsign.get()); +			config_ui.set_clean(); +		} catch (InterruptedException ie) { +		} +	} + +	void run_dialog() { +	} + +	void save_data() { +		main_deploy.set(config_ui.main_deploy()); +		apogee_delay.set(config_ui.apogee_delay()); +		radio_channel.set(config_ui.radio_channel()); +		callsign.set(config_ui.callsign()); +		try { +			start_serial(); +			serial_line.printf("c m %d\n", main_deploy.get()); +			serial_line.printf("c d %d\n", apogee_delay.get()); +			serial_line.printf("c r %d\n", radio_channel.get()); +			serial_line.printf("c c %s\n", callsign.get()); +			serial_line.printf("c w\n"); +		} catch (InterruptedException ie) { +		} finally { +			try { +				stop_serial(); +			} catch (InterruptedException ie) { +			} +		} +	} + +	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("close")) { +			if (serial_line != null) +				serial_line.close(); +		} +	} + +	public void run () { +		try { +			init_ui(); +			config_ui.make_visible(); +//		} catch (InterruptedException ie) { +		} finally { +		} +	} + +	public AltosConfig(JFrame given_owner) { +		owner = given_owner; + +		serial = new int_ref(0); +		main_deploy = new int_ref(250); +		apogee_delay = new int_ref(0); +		radio_channel = new int_ref(0); +		callsign = new string_ref("N0CALL"); +		version = new string_ref("unknown"); +		product = new string_ref("unknown"); + +		device = AltosDeviceDialog.show(owner, AltosDevice.Any); +		serial_line = new AltosSerial(); +		if (device != null) { +			try { +				serial_line.open(device); +				if (!device.matchProduct(AltosDevice.TeleMetrum)) +					remote = true; +				config_thread = new Thread(this); +				config_thread.start(); +			} catch (FileNotFoundException ee) { +				JOptionPane.showMessageDialog(owner, +							      String.format("Cannot open device \"%s\"", +									    device.getPath()), +							      "Cannot open target device", +							      JOptionPane.ERROR_MESSAGE); +			} catch (IOException ee) { +				JOptionPane.showMessageDialog(owner, +							      device.getPath(), +							      ee.getLocalizedMessage(), +							      JOptionPane.ERROR_MESSAGE); +			} +		} +	} +}
\ No newline at end of file diff --git a/ao-tools/altosui/AltosConfigUI.java b/ao-tools/altosui/AltosConfigUI.java new file mode 100644 index 00000000..1d8c579a --- /dev/null +++ b/ao-tools/altosui/AltosConfigUI.java @@ -0,0 +1,423 @@ +/* + * 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.LinkedBlockingQueue; + +import altosui.Altos; +import altosui.AltosSerial; +import altosui.AltosSerialMonitor; +import altosui.AltosRecord; +import altosui.AltosTelemetry; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosFlightStatusTableModel; +import altosui.AltosFlightInfoTableModel; + +import libaltosJNI.*; + +public class AltosConfigUI extends JDialog implements ActionListener, ItemListener, DocumentListener { + +	Container	pane; +	Box		box; +	JLabel		product_label; +	JLabel		version_label; +	JLabel		serial_label; +	JLabel		main_deploy_label; +	JLabel		apogee_delay_label; +	JLabel		radio_channel_label; +	JLabel		callsign_label; + +	public boolean		dirty; + +	JFrame		owner; +	JLabel		product_value; +	JLabel		version_value; +	JLabel		serial_value; +	JComboBox	main_deploy_value; +	JComboBox	apogee_delay_value; +	JComboBox	radio_channel_value; +	JTextField	callsign_value; + +	JButton		save; +	JButton		reset; +	JButton		close; + +	ActionListener	listener; + +	static String[] main_deploy_values = { +		"100", "150", "200", "250", "300", "350", +		"400", "450", "500" +	}; + +	static String[] apogee_delay_values = { +		"0", "1", "2", "3", "4", "5" +	}; + +	static String[] radio_channel_values = new String[10]; +		{ +			for (int i = 0; i <= 9; i++) +				radio_channel_values[i] = String.format("Channel %1d (%7.3fMHz)", +									i, 434.550 + i * 0.1); +		} + +	/* A window listener to catch closing events and tell the config code */ +	class ConfigListener extends WindowAdapter { +		AltosConfigUI	ui; + +		public ConfigListener(AltosConfigUI this_ui) { +			ui = this_ui; +		} + +		public void windowClosing(WindowEvent e) { +			ui.actionPerformed(new ActionEvent(e.getSource(), +							   ActionEvent.ACTION_PERFORMED, +							   "close")); +		} +	} + +	/* Build the UI using a grid bag */ +	public AltosConfigUI(JFrame in_owner) { +		super (in_owner, "Configure TeleMetrum", false); + +		owner = in_owner; +		GridBagConstraints c; + +		Insets il = new Insets(4,4,4,4); +		Insets ir = new Insets(4,4,4,4); + +		pane = getContentPane(); +		pane.setLayout(new GridBagLayout()); + +		/* Product */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 0; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		product_label = new JLabel("Product:"); +		pane.add(product_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 0; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		product_value = new JLabel(""); +		pane.add(product_value, c); + +		/* Version */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 1; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		version_label = new JLabel("Software version:"); +		pane.add(version_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 1; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		version_value = new JLabel(""); +		pane.add(version_value, c); + +		/* Serial */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 2; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		serial_label = new JLabel("Serial:"); +		pane.add(serial_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 2; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		serial_value = new JLabel(""); +		pane.add(serial_value, c); + +		/* Main deploy */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 3; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		c.ipady = 3; +		main_deploy_label = new JLabel("Main Deploy Altitude(m):"); +		pane.add(main_deploy_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 3; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		c.ipady = 5; +		main_deploy_value = new JComboBox(main_deploy_values); +		main_deploy_value.setEditable(true); +		main_deploy_value.addItemListener(this); +		pane.add(main_deploy_value, c); + +		/* Apogee delay */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 4; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		c.ipady = 5; +		apogee_delay_label = new JLabel("Apogee Delay(s):"); +		pane.add(apogee_delay_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 4; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		c.ipady = 5; +		apogee_delay_value = new JComboBox(apogee_delay_values); +		apogee_delay_value.setEditable(true); +		apogee_delay_value.addItemListener(this); +		pane.add(apogee_delay_value, c); + +		/* Radio channel */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 5; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		c.ipady = 5; +		radio_channel_label = new JLabel("Radio Channel:"); +		pane.add(radio_channel_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 5; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		c.ipady = 5; +		radio_channel_value = new JComboBox(radio_channel_values); +		radio_channel_value.setEditable(false); +		radio_channel_value.addItemListener(this); +		pane.add(radio_channel_value, c); + +		/* Callsign */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 6; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		c.ipady = 5; +		callsign_label = new JLabel("Callsign:"); +		pane.add(callsign_label, c); + +		c = new GridBagConstraints(); +		c.gridx = 3; c.gridy = 6; +		c.gridwidth = 3; +		c.fill = GridBagConstraints.HORIZONTAL; +		c.weightx = 1; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = ir; +		c.ipady = 5; +		callsign_value = new JTextField("N0CALL"); +		callsign_value.getDocument().addDocumentListener(this); +		pane.add(callsign_value, c); + +		/* Buttons */ +		c = new GridBagConstraints(); +		c.gridx = 0; c.gridy = 7; +		c.gridwidth = 2; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_START; +		c.insets = il; +		save = new JButton("Save"); +		pane.add(save, c); +		save.addActionListener(this); +		save.setActionCommand("save"); + +		c = new GridBagConstraints(); +		c.gridx = 2; c.gridy = 7; +		c.gridwidth = 2; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.CENTER; +		c.insets = il; +		reset = new JButton("Reset"); +		pane.add(reset, c); +		reset.addActionListener(this); +		reset.setActionCommand("reset"); + +		c = new GridBagConstraints(); +		c.gridx = 4; c.gridy = 7; +		c.gridwidth = 2; +		c.fill = GridBagConstraints.NONE; +		c.anchor = GridBagConstraints.LINE_END; +		c.insets = il; +		close = new JButton("Close"); +		pane.add(close, c); +		close.addActionListener(this); +		close.setActionCommand("close"); + +		addWindowListener(new ConfigListener(this)); +	} + +	/* Once the initial values are set, the config code will show the dialog */ +	public void make_visible() { +		pack(); +		setLocationRelativeTo(owner); +		setVisible(true); +	} + +	/* If any values have been changed, confirm before closing */ +	public boolean check_dirty() { +		if (dirty) { +			Object[] options = { "Close anyway", "Keep editing" }; +			int i; +			i = JOptionPane.showOptionDialog(this, +							 "Configuration modified, close anyway?", +							 "Configuration Modified", +							 JOptionPane.DEFAULT_OPTION, +							 JOptionPane.WARNING_MESSAGE, +							 null, options, options[1]); +			if (i != 0) +				return false; +		} +		return true; +	} + +	/* Listen for events from our buttons */ +	public void actionPerformed(ActionEvent e) { +		String	cmd = e.getActionCommand(); + +		if (cmd.equals("close")) +			if (!check_dirty()) +				return; +		listener.actionPerformed(e); +		if (cmd.equals("close")) { +			setVisible(false); +			dispose(); +		} +		dirty = false; +	} + +	/* ItemListener interface method */ +	public void itemStateChanged(ItemEvent e) { +		dirty = true; +	} + +	/* DocumentListener interface methods */ +	public void changedUpdate(DocumentEvent e) { +		dirty = true; +	} + +	public void insertUpdate(DocumentEvent e) { +		dirty = true; +	} + +	public void removeUpdate(DocumentEvent e) { +		dirty = true; +	} + +	/* Let the config code hook on a listener */ +	public void addActionListener(ActionListener l) { +		listener = l; +	} + +	/* set and get all of the dialog values */ +	public void set_product(String product) { +		product_value.setText(product); +	} + +	public void set_version(String version) { +		version_value.setText(version); +	} + +	public void set_serial(int serial) { +		serial_value.setText(String.format("%d", serial)); +	} + +	public void set_main_deploy(int new_main_deploy) { +		main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy)); +	} + +	public int main_deploy() { +		return Integer.parseInt(main_deploy_value.getSelectedItem().toString()); +	} + +	public void set_apogee_delay(int new_apogee_delay) { +		apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay)); +	} + +	public int apogee_delay() { +		return Integer.parseInt(apogee_delay_value.getSelectedItem().toString()); +	} + +	public void set_radio_channel(int new_radio_channel) { +		radio_channel_value.setSelectedIndex(new_radio_channel); +	} + +	public int radio_channel() { +		return radio_channel_value.getSelectedIndex(); +	} + +	public void set_callsign(String new_callsign) { +		callsign_value.setText(new_callsign); +	} + +	public String callsign() { +		return callsign_value.getText(); +	} + +	public void set_clean() { +		dirty = false; +	} + + }
\ No newline at end of file diff --git a/ao-tools/altosui/AltosConvert.java b/ao-tools/altosui/AltosConvert.java index 3be0716c..8cc1df27 100644 --- a/ao-tools/altosui/AltosConvert.java +++ b/ao-tools/altosui/AltosConvert.java @@ -62,7 +62,7 @@ public class AltosConvert {  	 * altitudes are measured with respect to the mean sea level  	 */  	static double -	cc_altitude_to_pressure(double altitude) +	altitude_to_pressure(double altitude)  	{  		double base_temperature = LAYER0_BASE_TEMPERATURE;  		double base_pressure = LAYER0_BASE_PRESSURE; @@ -115,7 +115,7 @@ public class AltosConvert {  /* outputs the altitude associated with the given pressure. the altitude     returned is measured with respect to the mean sea level */  	static double -	cc_pressure_to_altitude(double pressure) +	pressure_to_altitude(double pressure)  	{  		double next_base_temperature = LAYER0_BASE_TEMPERATURE; @@ -178,59 +178,6 @@ public class AltosConvert {  		return altitude;  	} -	/* -	 * Values for our MP3H6115A pressure sensor -	 * -	 * From the data sheet: -	 * -	 * Pressure range: 15-115 kPa -	 * Voltage at 115kPa: 2.82 -	 * Output scale: 27mV/kPa -	 * -	 * -	 * 27 mV/kPa * 2047 / 3300 counts/mV = 16.75 counts/kPa -	 * 2.82V * 2047 / 3.3 counts/V = 1749 counts/115 kPa -	 */ - -	static final double counts_per_kPa = 27 * 2047 / 3300; -	static final double counts_at_101_3kPa = 1674.0; - -	static double -	cc_barometer_to_pressure(double count) -	{ -		return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0; -	} - -	static double -	cc_barometer_to_altitude(double baro) -	{ -		double Pa = cc_barometer_to_pressure(baro); -		return cc_pressure_to_altitude(Pa); -	} - -	static final double count_per_mss = 27.0; - -	static double -	cc_accelerometer_to_acceleration(double accel, double ground_accel) -	{ -		return (ground_accel - accel) / count_per_mss; -	} - -	/* Value for the CC1111 built-in temperature sensor -	 * Output voltage at 0°C = 0.755V -	 * Coefficient = 0.00247V/°C -	 * Reference voltage = 1.25V -	 * -	 * temp = ((value / 32767) * 1.25 - 0.755) / 0.00247 -	 *      = (value - 19791.268) / 32768 * 1.25 / 0.00247 -	 */ - -	static double -	cc_thermometer_to_temperature(double thermo) -	{ -		return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247; -	} -  	static double  	cc_battery_to_voltage(double battery)  	{ diff --git a/ao-tools/altosui/AltosDevice.java b/ao-tools/altosui/AltosDevice.java index f488174c..3daf0742 100644 --- a/ao-tools/altosui/AltosDevice.java +++ b/ao-tools/altosui/AltosDevice.java @@ -22,16 +22,76 @@ import libaltosJNI.*;  public class AltosDevice extends altos_device { +	static boolean initialized = false; +	static { +		try { +			System.loadLibrary("altos"); +			libaltos.altos_init(); +			initialized = true; +		} catch (UnsatisfiedLinkError e) { +			System.err.println("Native library failed to load.\n" + e); +		} +	} +	public final static int TeleMetrum = libaltosConstants.USB_PRODUCT_TELEMETRUM; +	public final static int TeleDongle = libaltosConstants.USB_PRODUCT_TELEDONGLE; +	public final static int TeleTerra = libaltosConstants.USB_PRODUCT_TELETERRA; +	public final static int Any = 0x10000; +	public final static int BaseStation = 0x10000 + 1; +  	public String toString() { +		String	name = getName(); +		if (name == null) +			name = "Altus Metrum";  		return String.format("%-20.20s %4d %s", -				     getProduct(), getSerial(), getPath()); +				     getName(), getSerial(), getPath());  	} -	static { -		System.loadLibrary("altos"); -		libaltos.altos_init(); +	public boolean isAltusMetrum() { +		if (getVendor() != libaltosConstants.USB_VENDOR_ALTUSMETRUM) +			return false; +		if (getProduct() < libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MIN) +			return false; +		if (getProduct() > libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MAX) +			return false; +		return true;  	} -	static AltosDevice[] list(String product) { + +	public boolean matchProduct(int want_product) { + +		if (want_product == Any) +			return true; + +		if (want_product == BaseStation) +			return matchProduct(TeleDongle) || matchProduct(TeleTerra); + +		if (!isAltusMetrum()) +			return false; + +		int have_product = getProduct(); + +		if (want_product == have_product) +			return true; + +		if (have_product != libaltosConstants.USB_PRODUCT_ALTUSMETRUM) +			return false; + +		String name = getName(); + +		if (name == null) +			return false; +		if (want_product == libaltosConstants.USB_PRODUCT_TELEMETRUM) +			return name.startsWith("TeleMetrum"); +		if (want_product == libaltosConstants.USB_PRODUCT_TELEDONGLE) +			return name.startsWith("TeleDongle"); +		if (want_product == libaltosConstants.USB_PRODUCT_TELETERRA) +			return name.startsWith("TeleTerra"); +		return false; +	} + +	static AltosDevice[] list(int product) { +		if (!initialized) +			return null; +  		SWIGTYPE_p_altos_list list = libaltos.altos_list_start();  		ArrayList<AltosDevice> device_list = new ArrayList<AltosDevice>(); @@ -42,7 +102,7 @@ public class AltosDevice extends altos_device {  				AltosDevice device = new AltosDevice();  				if (libaltos.altos_list_next(list, device) == 0)  					break; -				if (product == null || device.getProduct().startsWith(product)) +				if (device.matchProduct(product))  					device_list.add(device);  			}  			libaltos.altos_list_finish(list); diff --git a/ao-tools/altosui/AltosDeviceDialog.java b/ao-tools/altosui/AltosDeviceDialog.java index c60bd7c3..3df4c6eb 100644 --- a/ao-tools/altosui/AltosDeviceDialog.java +++ b/ao-tools/altosui/AltosDeviceDialog.java @@ -31,16 +31,16 @@ import altosui.AltosDevice;  public class AltosDeviceDialog extends JDialog implements ActionListener {  	private static AltosDeviceDialog	dialog; -	private static altos_device		value = null; +	private static AltosDevice		value = null;  	private JList				list; -	public static altos_device show (Component frameComp, String product) { +	public static AltosDevice show (Component frameComp, int product) {  		Frame frame = JOptionPane.getFrameForComponent(frameComp);  		AltosDevice[]	devices;  		devices = AltosDevice.list(product); -		if (devices != null & devices.length > 0) { +		if (devices != null && devices.length > 0) {  			value = null;  			dialog = new AltosDeviceDialog(frame, frameComp,  						       devices, @@ -153,7 +153,7 @@ public class AltosDeviceDialog extends JDialog implements ActionListener {  	//Handle clicks on the Set and Cancel buttons.  	public void actionPerformed(ActionEvent e) {  		if ("select".equals(e.getActionCommand())) -			AltosDeviceDialog.value = (altos_device)(list.getSelectedValue()); +			AltosDeviceDialog.value = (AltosDevice)(list.getSelectedValue());  		AltosDeviceDialog.dialog.setVisible(false);  	} diff --git a/ao-tools/altosui/AltosEeprom.java b/ao-tools/altosui/AltosEepromDownload.java index 4c537a89..02a71118 100644 --- a/ao-tools/altosui/AltosEeprom.java +++ b/ao-tools/altosui/AltosEepromDownload.java @@ -28,8 +28,10 @@ import java.text.*;  import java.util.prefs.*;  import java.util.concurrent.LinkedBlockingQueue; +import altosui.Altos;  import altosui.AltosSerial;  import altosui.AltosSerialMonitor; +import altosui.AltosRecord;  import altosui.AltosTelemetry;  import altosui.AltosState;  import altosui.AltosDeviceDialog; @@ -40,30 +42,7 @@ import altosui.AltosEepromMonitor;  import libaltosJNI.*; -public class AltosEeprom implements Runnable { - -	static final int AO_LOG_FLIGHT = 'F'; -	static final int AO_LOG_SENSOR = 'A'; -	static final int AO_LOG_TEMP_VOLT = 'T'; -	static final int AO_LOG_DEPLOY = 'D'; -	static final int AO_LOG_STATE = 'S'; -	static final int AO_LOG_GPS_TIME = 'G'; -	static final int AO_LOG_GPS_LAT = 'N'; -	static final int AO_LOG_GPS_LON = 'W'; -	static final int AO_LOG_GPS_ALT = 'H'; -	static final int AO_LOG_GPS_SAT = 'V'; -	static final int AO_LOG_GPS_DATE = 'Y'; - -	static final int ao_flight_startup = 0; -	static final int ao_flight_idle = 1; -	static final int ao_flight_pad = 2; -	static final int ao_flight_boost = 3; -	static final int ao_flight_fast = 4; -	static final int ao_flight_coast = 5; -	static final int ao_flight_drogue = 6; -	static final int ao_flight_main = 7; -	static final int ao_flight_landed = 8; -	static final int ao_flight_invalid = 9; +public class AltosEepromDownload implements Runnable {  	static final String[] state_names = {  		"startup", @@ -105,7 +84,7 @@ public class AltosEeprom implements Runnable {  	}  	JFrame			frame; -	altos_device		device; +	AltosDevice		device;  	AltosSerial		serial_line;  	boolean			remote;  	Thread			eeprom_thread; @@ -125,7 +104,7 @@ public class AltosEeprom implements Runnable {  		AltosFile		eeprom_name;  		LinkedList<String>	eeprom_pending = new LinkedList<String>(); -		serial_line.printf("v\n"); +		serial_line.printf("\nc s\nv\n");  		/* Pull the serial number out of the version information */ @@ -135,12 +114,13 @@ public class AltosEeprom implements Runnable {  			if (line.startsWith("serial-number")) {  				try {  					serial = Integer.parseInt(line.substring(13).trim()); -					eeprom_pending.add(String.format("%s\n", line));  				} catch (NumberFormatException ne) {  					serial = 0;  				}  			} +			eeprom_pending.add(String.format("%s\n", line)); +  			/* signals the end of the version info */  			if (line.startsWith("software-version"))  				break; @@ -175,21 +155,21 @@ public class AltosEeprom implements Runnable {  					int	a = values[5] + (values[6] << 8);  					int	b = values[7] + (values[8] << 8); -					if (cmd == AO_LOG_FLIGHT) { +					if (cmd == Altos.AO_LOG_FLIGHT) {  						flight = b;  						monitor.set_flight(flight);  					}  					/* Monitor state transitions to update display */ -					if (cmd == AO_LOG_STATE && a <= ao_flight_landed) { -						if (a > ao_flight_pad) +					if (cmd == Altos.AO_LOG_STATE && a <= Altos.ao_flight_landed) { +						if (a > Altos.ao_flight_pad)  							want_file = true;  						if (a > state)  							state_block = block;  						state = a;  					} -					if (cmd == AO_LOG_GPS_DATE) { +					if (cmd == Altos.AO_LOG_GPS_DATE) {  						year = 2000 + (a & 0xff);  						month = (a >> 8) & 0xff;  						day = (b & 0xff); @@ -219,7 +199,7 @@ public class AltosEeprom implements Runnable {  					else  						eeprom_pending.add(log_line); -					if (cmd == AO_LOG_STATE && a == ao_flight_landed) { +					if (cmd == Altos.AO_LOG_STATE && a == Altos.ao_flight_landed) {  						done = true;  					}  				} @@ -245,10 +225,11 @@ public class AltosEeprom implements Runnable {  		if (remote) {  			serial_line.printf("m 0\n");  			serial_line.set_channel(AltosPreferences.channel()); +			serial_line.set_callsign(AltosPreferences.callsign());  			serial_line.printf("p\n");  		} -		monitor = new AltosEepromMonitor(frame, ao_flight_boost, ao_flight_landed); +		monitor = new AltosEepromMonitor(frame, Altos.ao_flight_boost, Altos.ao_flight_landed);  		monitor.addActionListener(new ActionListener() {  				public void actionPerformed(ActionEvent e) {  					eeprom_thread.interrupt(); @@ -269,9 +250,9 @@ public class AltosEeprom implements Runnable {  		serial_line.close();  	} -	public AltosEeprom(JFrame given_frame) { +	public AltosEepromDownload(JFrame given_frame) {  		frame = given_frame; -		device = AltosDeviceDialog.show(frame, null); +		device = AltosDeviceDialog.show(frame, AltosDevice.Any);  		serial_line = new AltosSerial();  		remote = false; @@ -279,7 +260,7 @@ public class AltosEeprom implements Runnable {  		if (device != null) {  			try {  				serial_line.open(device); -				if (!device.getProduct().startsWith("TeleMetrum")) +				if (!device.matchProduct(AltosDevice.TeleMetrum))  					remote = true;  				eeprom_thread = new Thread(this);  				eeprom_thread.start(); diff --git a/ao-tools/altosui/AltosEepromMonitor.java b/ao-tools/altosui/AltosEepromMonitor.java index e110a354..b88fdd29 100644 --- a/ao-tools/altosui/AltosEepromMonitor.java +++ b/ao-tools/altosui/AltosEepromMonitor.java @@ -30,6 +30,7 @@ import java.util.concurrent.LinkedBlockingQueue;  import altosui.AltosSerial;  import altosui.AltosSerialMonitor; +import altosui.AltosRecord;  import altosui.AltosTelemetry;  import altosui.AltosState;  import altosui.AltosDeviceDialog; diff --git a/ao-tools/altosui/AltosEepromReader.java b/ao-tools/altosui/AltosEepromReader.java new file mode 100644 index 00000000..c29fd90b --- /dev/null +++ b/ao-tools/altosui/AltosEepromReader.java @@ -0,0 +1,315 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +import altosui.AltosRecord; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosEepromMonitor; + +/* + * AltosRecords with an index field so they can be sorted by tick while preserving + * the original ordering for elements with matching ticks + */ +class AltosOrderedRecord extends AltosEepromRecord implements Comparable<AltosOrderedRecord> { + +	int	index; + +	public AltosOrderedRecord(String line, int in_index, int prev_tick) +		throws ParseException { +		super(line); +		int new_tick = tick | (prev_tick & ~0xffff); +		if (new_tick < prev_tick) { +			if (prev_tick - new_tick > 0x8000) +				new_tick += 0x10000; +		} +		tick = new_tick; +		index = in_index; +	} + +	public int compareTo(AltosOrderedRecord o) { +		int	tick_diff = tick - o.tick; +		if (tick_diff != 0) +			return tick_diff; +		return index - o.index; +	} +} + +public class AltosEepromReader extends AltosReader { + +	static final int	seen_flight = 1; +	static final int	seen_sensor = 2; +	static final int	seen_temp_volt = 4; +	static final int	seen_deploy = 8; +	static final int	seen_gps_time = 16; +	static final int	seen_gps_lat = 32; +	static final int	seen_gps_lon = 64; + +	static final int	seen_basic = seen_flight|seen_sensor|seen_temp_volt|seen_deploy; + +	AltosRecord		state; +	AltosOrderedRecord	record; + +	TreeSet<AltosOrderedRecord>	records; + +	Iterator<AltosOrderedRecord>			record_iterator; + +	int			seen; + +	int			index; + +	boolean			last_reported; + +	double			ground_pres; +	double			ground_accel; + +	int			n_pad_samples; + +	int			gps_tick; + +	boolean			saw_boost; + +	int			boost_tick; + +	public AltosRecord read() throws IOException, ParseException { +		for (;;) { +			if (record == null) { +				if (!record_iterator.hasNext()) { +					if (last_reported) +						return null; +					last_reported = true; +					return state; +				} +				record = record_iterator.next(); + +				if ((seen & seen_basic) == seen_basic && record.tick != state.tick) { +					AltosRecord r = new AltosRecord(state); +					r.time = (r.tick - boost_tick) / 100.0; +					return r; +				} +			} + +			state.tick = record.tick; +			switch (record.cmd) { +			case Altos.AO_LOG_FLIGHT: +				state.ground_accel = record.a; +				state.flight = record.b; +				seen |= seen_flight; +				break; +			case Altos.AO_LOG_SENSOR: +				state.accel = record.a; +				state.pres = record.b; +				if (state.state < Altos.ao_flight_boost) { +					n_pad_samples++; +					ground_pres += state.pres; +					state.ground_pres = (int) (ground_pres / n_pad_samples); +					state.flight_pres = state.ground_pres; +					System.out.printf("ground pressure %d altitude %f\n", +							  record.b, state.altitude()); +					ground_accel += state.accel; +					state.ground_accel = (int) (ground_accel / n_pad_samples); +					state.flight_accel = state.ground_accel; +				} else { +					state.flight_pres = (state.flight_pres * 15 + state.pres) / 16; +					state.flight_accel = (state.flight_accel * 15 + state.accel) / 16; +					state.flight_vel += (state.accel_plus_g - state.accel); +				} +				seen |= seen_sensor; +				break; +			case Altos.AO_LOG_TEMP_VOLT: +				state.temp = record.a; +				state.batt = record.b; +				seen |= seen_temp_volt; +				break; +			case Altos.AO_LOG_DEPLOY: +				state.drogue = record.a; +				state.main = record.b; +				seen |= seen_deploy; +				break; +			case Altos.AO_LOG_STATE: +				System.out.printf("state %d\n", record.a); +				state.state = record.a; +				break; +			case Altos.AO_LOG_GPS_TIME: +				gps_tick = state.tick; +				state.gps = new AltosGPS(); +				state.gps.hour = (record.a & 0xff); +				state.gps.minute = (record.a >> 8); +				state.gps.second = (record.b & 0xff); +				int flags = (record.b >> 8); +				state.gps.connected = (flags & Altos.AO_GPS_RUNNING) != 0; +				state.gps.locked = (flags & Altos.AO_GPS_VALID) != 0; +				state.gps.date_valid = (flags & Altos.AO_GPS_DATE_VALID) != 0; +				state.gps.nsat = (flags & Altos.AO_GPS_NUM_SAT_MASK) >> +					Altos.AO_GPS_NUM_SAT_SHIFT; +				break; +			case Altos.AO_LOG_GPS_LAT: +				int lat32 = record.a | (record.b << 16); +				state.gps.lat = (double) lat32 / 1e7; +				break; +			case Altos.AO_LOG_GPS_LON: +				int lon32 = record.a | (record.b << 16); +				state.gps.lon = (double) lon32 / 1e7; +				break; +			case Altos.AO_LOG_GPS_ALT: +				state.gps.alt = record.a; +				break; +			case Altos.AO_LOG_GPS_SAT: +				if (state.tick == gps_tick) { +					int svid = record.a; +					int c_n0 = record.b >> 8; +					state.gps.add_sat(svid, c_n0); +				} +				break; +			case Altos.AO_LOG_GPS_DATE: +				state.gps.year = record.a & 0xff; +				state.gps.month = record.a >> 8; +				state.gps.day = record.b & 0xff; +				break; + +			case Altos.AO_LOG_CONFIG_VERSION: +				break; +			case Altos.AO_LOG_MAIN_DEPLOY: +				break; +			case Altos.AO_LOG_APOGEE_DELAY: +				break; +			case Altos.AO_LOG_RADIO_CHANNEL: +				break; +			case Altos.AO_LOG_CALLSIGN: +				state.callsign = record.data; +				break; +			case Altos.AO_LOG_ACCEL_CAL: +				state.accel_plus_g = record.a; +				state.accel_minus_g = record.b; +				break; +			case Altos.AO_LOG_RADIO_CAL: +				break; +			case Altos.AO_LOG_MANUFACTURER: +				break; +			case Altos.AO_LOG_PRODUCT: +				break; +			case Altos.AO_LOG_SERIAL_NUMBER: +				break; +			case Altos.AO_LOG_SOFTWARE_VERSION: +				break; +			} +			record = null; +		} +	} + +	public void write_comments(PrintStream out) { +		Iterator<AltosOrderedRecord>	iterator = records.iterator(); +		while (iterator.hasNext()) { +			AltosOrderedRecord	record = iterator.next(); +			switch (record.cmd) { +			case Altos.AO_LOG_CONFIG_VERSION: +				out.printf("# Config version: %s\n", record.data); +				break; +			case Altos.AO_LOG_MAIN_DEPLOY: +				out.printf("# Main deploy: %s\n", record.a); +				break; +			case Altos.AO_LOG_APOGEE_DELAY: +				out.printf("# Apogee delay: %s\n", record.a); +				break; +			case Altos.AO_LOG_RADIO_CHANNEL: +				out.printf("# Radio channel: %s\n", record.a); +				break; +			case Altos.AO_LOG_CALLSIGN: +				out.printf("# Callsign: %s\n", record.data); +				break; +			case Altos.AO_LOG_ACCEL_CAL: +				out.printf ("# Accel cal: %d %d\n", record.a, record.b); +				break; +			case Altos.AO_LOG_RADIO_CAL: +				out.printf ("# Radio cal: %d %d\n", record.a); +				break; +			case Altos.AO_LOG_MANUFACTURER: +				out.printf ("# Manufacturer: %s\n", record.data); +				break; +			case Altos.AO_LOG_PRODUCT: +				out.printf ("# Product: %s\n", record.data); +				break; +			case Altos.AO_LOG_SERIAL_NUMBER: +				out.printf ("# Serial number: %d\n", record.a); +				break; +			case Altos.AO_LOG_SOFTWARE_VERSION: +				out.printf ("# Software version: %s\n", record.data); +				break; +			} +		} +	} + +	/* +	 * Read the whole file, dumping records into a RB tree so +	 * we can enumerate them in time order -- the eeprom data +	 * are sometimes out of order with GPS data getting timestamps +	 * matching the first packet out of the GPS unit but not +	 * written until the final GPS packet has been received. +	 */ +	public AltosEepromReader (FileInputStream input) { +		state = new AltosRecord(); +		state.state = Altos.ao_flight_pad; +		state.accel_plus_g = 15758; +		state.accel_minus_g = 16294; +		seen = 0; +		records = new TreeSet<AltosOrderedRecord>(); + +		int index = 0; +		int tick = 0; + +		try { +			for (;;) { +				String line = AltosRecord.gets(input); +				if (line == null) +					break; +				AltosOrderedRecord record = new AltosOrderedRecord(line, index++, tick); +				if (record == null) +					break; +				tick = record.tick; +				if (!saw_boost && record.cmd == Altos.AO_LOG_STATE && +				    record.a == Altos.ao_flight_boost) +				{ +					saw_boost = true; +					boost_tick = state.tick; +				} +				records.add(record); +			} +		} catch (IOException io) { +		} catch (ParseException pe) { +		} +		record_iterator = records.iterator(); +		try { +			input.close(); +		} catch (IOException ie) { +		} +	} +} diff --git a/ao-tools/altosui/AltosEepromRecord.java b/ao-tools/altosui/AltosEepromRecord.java new file mode 100644 index 00000000..86ac1fd2 --- /dev/null +++ b/ao-tools/altosui/AltosEepromRecord.java @@ -0,0 +1,110 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +import altosui.AltosSerial; +import altosui.AltosSerialMonitor; +import altosui.AltosRecord; +import altosui.AltosTelemetry; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosEepromMonitor; + +public class AltosEepromRecord { +	public int	cmd; +	public int	tick; +	public int	a; +	public int	b; +	String		data; +	public boolean	tick_valid; + +	public AltosEepromRecord (String line) throws ParseException { +		tick_valid = false; +		tick = 0; +		a = 0; +		b = 0; +		data = null; +		if (line == null) { +			cmd = Altos.AO_LOG_INVALID; +		} else { +			String[] tokens = line.split("\\s+"); + +			if (tokens[0].length() == 1) { +				if (tokens.length != 4) +					throw new ParseException(line, 0); +				cmd = tokens[0].codePointAt(0); +				tick = Integer.parseInt(tokens[1],16); +				tick_valid = true; +				a = Integer.parseInt(tokens[2],16); +				b = Integer.parseInt(tokens[3],16); +			} else if (tokens[0].equals("Config") && tokens[1].equals("version:")) { +				cmd = Altos.AO_LOG_CONFIG_VERSION; +				data = tokens[2]; +			} else if (tokens[0].equals("Main") && tokens[1].equals("deploy:")) { +				cmd = Altos.AO_LOG_MAIN_DEPLOY; +				a = Integer.parseInt(tokens[2]); +			} else if (tokens[0].equals("Apogee") && tokens[1].equals("delay:")) { +				cmd = Altos.AO_LOG_APOGEE_DELAY; +				a = Integer.parseInt(tokens[2]); +			} else if (tokens[0].equals("Radio") && tokens[1].equals("channel:")) { +				cmd = Altos.AO_LOG_RADIO_CHANNEL; +				a = Integer.parseInt(tokens[2]); +			} else if (tokens[0].equals("Callsign:")) { +				cmd = Altos.AO_LOG_CALLSIGN; +				data = tokens[1].replaceAll("\"",""); +			} else if (tokens[0].equals("Accel") && tokens[1].equals("cal")) { +				cmd = Altos.AO_LOG_ACCEL_CAL; +				a = Integer.parseInt(tokens[3]); +				b = Integer.parseInt(tokens[5]); +			} else if (tokens[0].equals("Radio") && tokens[1].equals("cal:")) { +				cmd = Altos.AO_LOG_RADIO_CAL; +				a = Integer.parseInt(tokens[2]); +			} else if (tokens[0].equals("manufacturer")) { +				cmd = Altos.AO_LOG_MANUFACTURER; +				data = tokens[1]; +			} else if (tokens[0].equals("product")) { +				cmd = Altos.AO_LOG_PRODUCT; +				data = tokens[1]; +			} else if (tokens[0].equals("serial-number")) { +				cmd = Altos.AO_LOG_SERIAL_NUMBER; +				a = Integer.parseInt(tokens[1]); +			} else if (tokens[0].equals("software-version")) { +				cmd = Altos.AO_LOG_SOFTWARE_VERSION; +				data = tokens[1]; +			} else { +				cmd = Altos.AO_LOG_INVALID; +				data = line; +			} +		} +	} + +} diff --git a/ao-tools/altosui/AltosFlightInfoTableModel.java b/ao-tools/altosui/AltosFlightInfoTableModel.java new file mode 100644 index 00000000..2a22e3e5 --- /dev/null +++ b/ao-tools/altosui/AltosFlightInfoTableModel.java @@ -0,0 +1,81 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightInfoTableModel extends AbstractTableModel { +	private String[] columnNames = {"Field", "Value"}; + +	class InfoLine { +		String	name; +		String	value; + +		public InfoLine(String n, String v) { +			name = n; +			value = v; +		} +	} + +	private ArrayList<InfoLine> rows = new ArrayList<InfoLine>(); + +	public int getColumnCount() { return columnNames.length; } +	public String getColumnName(int col) { return columnNames[col]; } + +	public int getRowCount() { return 20; } + +	int	current_row = 0; +	int	prev_num_rows = 0; + +	public Object getValueAt(int row, int col) { +		if (row >= rows.size()) +			return ""; +		if (col == 0) +			return rows.get(row).name; +		else +			return rows.get(row).value; +	} + +	public void resetRow() { +		current_row = 0; +	} +	public void addRow(String name, String value) { +		if (current_row >= rows.size()) +			rows.add(current_row, new InfoLine(name, value)); +		else +			rows.set(current_row, new InfoLine(name, value)); +		current_row++; +	} +	public void finish() { +		if (current_row > prev_num_rows) +			fireTableRowsInserted(prev_num_rows, current_row - 1); +		while (rows.size() > current_row) +			rows.remove(rows.size() - 1); +		prev_num_rows = current_row; +		fireTableDataChanged(); +	} +} diff --git a/ao-tools/altosui/AltosFlightStatusTableModel.java b/ao-tools/altosui/AltosFlightStatusTableModel.java new file mode 100644 index 00000000..4c24b6ac --- /dev/null +++ b/ao-tools/altosui/AltosFlightStatusTableModel.java @@ -0,0 +1,61 @@ +/* + * 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.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightStatusTableModel extends AbstractTableModel { +	private String[] columnNames = {"Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" }; +	private Object[] data = { 0, "idle", 0, 0 }; + +	public int getColumnCount() { return columnNames.length; } +	public int getRowCount() { return 2; } +	public Object getValueAt(int row, int col) { +		if (row == 0) +			return columnNames[col]; +		return data[col]; +	} + +	public void setValueAt(Object value, int col) { +		data[col] = value; +		fireTableCellUpdated(1, col); +	} + +	public void setValueAt(Object value, int row, int col) { +		setValueAt(value, col); +	} + +	public void set(AltosState state) { +		setValueAt(String.format("%1.0f", state.height), 0); +		setValueAt(state.data.state(), 1); +		setValueAt(state.data.rssi, 2); +		double speed = state.baro_speed; +		if (state.ascent) +			speed = state.speed; +		setValueAt(String.format("%1.0f", speed), 3); +	} +} diff --git a/ao-tools/altosui/AltosGPS.java b/ao-tools/altosui/AltosGPS.java index f8eb5f48..b3ee67e8 100644 --- a/ao-tools/altosui/AltosGPS.java +++ b/ao-tools/altosui/AltosGPS.java @@ -23,49 +23,24 @@ import altosui.AltosParse;  public class AltosGPS { -	public class AltosGPSTime { -		int year; -		int month; -		int day; -		int hour; -		int minute; -		int second; - -		public AltosGPSTime(String date, String time) throws ParseException { -			String[] ymd = date.split("-"); -			if (ymd.length != 3) -				throw new ParseException("error parsing GPS date " + date + " got " + ymd.length, 0); -			year = AltosParse.parse_int(ymd[0]); -			month = AltosParse.parse_int(ymd[1]); -			day = AltosParse.parse_int(ymd[2]); - -			String[] hms = time.split(":"); -			if (hms.length != 3) -				throw new ParseException("Error parsing GPS time " + time + " got " + hms.length, 0); -			hour = AltosParse.parse_int(hms[0]); -			minute = AltosParse.parse_int(hms[1]); -			second = AltosParse.parse_int(hms[2]); -		} - -		public AltosGPSTime() { -			year = month = day = 0; -			hour = minute = second = 0; -		} - -	} -  	public class AltosGPSSat {  		int	svid;  		int	c_n0;  	}  	int	nsat; -	boolean	gps_locked; -	boolean	gps_connected; -	AltosGPSTime gps_time; +	boolean	locked; +	boolean	connected; +	boolean date_valid;  	double	lat;		/* degrees (+N -S) */  	double	lon;		/* degrees (+E -W) */  	int	alt;		/* m */ +	int	year; +	int	month; +	int	day; +	int	hour; +	int	minute; +	int	second;  	int	gps_extended;	/* has extra data */  	double	ground_speed;	/* m/s */ @@ -77,27 +52,47 @@ public class AltosGPS {  	AltosGPSSat[] cc_gps_sat;	/* tracking data */ +	void ParseGPSTime(String date, String time) throws ParseException { +		String[] ymd = date.split("-"); +		if (ymd.length != 3) +			throw new ParseException("error parsing GPS date " + date + " got " + ymd.length, 0); +		year = AltosParse.parse_int(ymd[0]); +		month = AltosParse.parse_int(ymd[1]); +		day = AltosParse.parse_int(ymd[2]); + +		String[] hms = time.split(":"); +		if (hms.length != 3) +			throw new ParseException("Error parsing GPS time " + time + " got " + hms.length, 0); +		hour = AltosParse.parse_int(hms[0]); +		minute = AltosParse.parse_int(hms[1]); +		second = AltosParse.parse_int(hms[2]); +	} + +	void ClearGPSTime() { +		year = month = day = 0; +		hour = minute = second = 0; +	} +  	public AltosGPS(String[] words, int i) throws ParseException {  		AltosParse.word(words[i++], "GPS");  		nsat = AltosParse.parse_int(words[i++]);  		AltosParse.word(words[i++], "sat"); -		gps_connected = false; -		gps_locked = false; +		connected = false; +		locked = false;  		lat = lon = 0;  		alt = 0; +		ClearGPSTime();  		if ((words[i]).equals("unlocked")) { -			gps_connected = true; -			gps_time = new AltosGPSTime(); +			connected = true;  			i++;  		} else if ((words[i]).equals("not-connected")) { -			gps_time = new AltosGPSTime();  			i++;  		} else if (words.length >= 40) { -			gps_locked = true; -			gps_connected = true; +			locked = true; +			connected = true; -			gps_time = new AltosGPSTime(words[i], words[i+1]); i += 2; +			ParseGPSTime(words[i], words[i+1]); i += 2;  			lat = AltosParse.parse_coord(words[i++]);  			lon = AltosParse.parse_coord(words[i++]);  			alt = AltosParse.parse_int(AltosParse.strip_suffix(words[i++], "m")); @@ -108,7 +103,6 @@ public class AltosGPS {  			h_error = AltosParse.parse_int(AltosParse.strip_suffix(words[i++], "(herr)"));  			v_error = AltosParse.parse_int(AltosParse.strip_suffix(words[i++], "(verr)"));  		} else { -			gps_time = new AltosGPSTime();  			i++;  		}  		AltosParse.word(words[i++], "SAT"); @@ -125,4 +119,84 @@ public class AltosGPS {  			cc_gps_sat[chan].c_n0 = AltosParse.parse_int(words[i++]);  		}  	} + +	public void set_latitude(int in_lat) { +		lat = in_lat / 10.0e7; +	} + +	public void set_longitude(int in_lon) { +		lon = in_lon / 10.0e7; +	} + +	public void set_time(int hour, int minute, int second) { +		hour = hour; +		minute = minute; +		second = second; +	} + +	public void set_date(int year, int month, int day) { +		year = year; +		month = month; +		day = day; +	} + +	public void set_flags(int flags) { +		flags = flags; +	} + +	public void set_altitude(int altitude) { +		altitude = altitude; +	} + +	public void add_sat(int svid, int c_n0) { +		if (cc_gps_sat == null) { +			cc_gps_sat = new AltosGPS.AltosGPSSat[1]; +		} else { +			AltosGPSSat[] new_gps_sat = new AltosGPS.AltosGPSSat[cc_gps_sat.length + 1]; +			for (int i = 0; i < cc_gps_sat.length; i++) +				new_gps_sat[i] = cc_gps_sat[i]; +			cc_gps_sat = new_gps_sat; +		} +		AltosGPS.AltosGPSSat	sat = new AltosGPS.AltosGPSSat(); +		sat.svid = svid; +		sat.c_n0 = c_n0; +		cc_gps_sat[cc_gps_sat.length - 1] = sat; +	} + +	public AltosGPS() { +		ClearGPSTime(); +		cc_gps_sat = null; +	} + +	public AltosGPS(AltosGPS old) { +		nsat = old.nsat; +		locked = old.locked; +		connected = old.connected; +		lat = old.lat;		/* degrees (+N -S) */ +		lon = old.lon;		/* degrees (+E -W) */ +		alt = old.alt;		/* m */ +		year = old.year; +		month = old.month; +		day = old.day; +		hour = old.hour; +		minute = old.minute; +		second = old.second; + +		gps_extended = old.gps_extended;	/* has extra data */ +		ground_speed = old.ground_speed;	/* m/s */ +		course = old.course;		/* degrees */ +		climb_rate = old.climb_rate;	/* m/s */ +		hdop = old.hdop;		/* unitless? */ +		h_error = old.h_error;	/* m */ +		v_error = old.v_error;	/* m */ + +		if (old.cc_gps_sat != null) { +			cc_gps_sat = new AltosGPSSat[old.cc_gps_sat.length]; +			for (int i = 0; i < old.cc_gps_sat.length; i++) { +				cc_gps_sat[i] = new AltosGPSSat(); +				cc_gps_sat[i].svid = old.cc_gps_sat[i].svid; +				cc_gps_sat[i].c_n0 = old.cc_gps_sat[i].c_n0; +			} +		} +	}  } diff --git a/ao-tools/altosui/AltosPreferences.java b/ao-tools/altosui/AltosPreferences.java index 297e1aae..690f8f1e 100644 --- a/ao-tools/altosui/AltosPreferences.java +++ b/ao-tools/altosui/AltosPreferences.java @@ -37,6 +37,9 @@ class AltosPreferences {  	/* voice preference name */  	final static String voicePreference = "VOICE"; +	/* callsign preference name */ +	final static String callsignPreference = "CALLSIGN"; +  	/* Default logdir is ~/TeleMetrum */  	final static String logdirName = "TeleMetrum"; @@ -52,6 +55,8 @@ class AltosPreferences {  	/* Voice preference */  	static boolean voice; +	static String callsign; +  	public static void init(Component ui) {  		preferences = Preferences.userRoot().node("/org/altusmetrum/altosui"); @@ -71,6 +76,8 @@ class AltosPreferences {  		channel = preferences.getInt(channelPreference, 0);  		voice = preferences.getBoolean(voicePreference, true); + +		callsign = preferences.get(callsignPreference,"N0CALL");  	}  	static void flush_preferences() { @@ -154,4 +161,16 @@ class AltosPreferences {  	public static boolean voice() {  		return voice;  	} + +	public static void set_callsign(String new_callsign) { +		callsign = new_callsign; +		synchronized(preferences) { +			preferences.put(callsignPreference, callsign); +			flush_preferences(); +		} +	} + +	public static String callsign() { +		return callsign; +	}  } diff --git a/ao-tools/altosui/AltosReader.java b/ao-tools/altosui/AltosReader.java new file mode 100644 index 00000000..81779e2b --- /dev/null +++ b/ao-tools/altosui/AltosReader.java @@ -0,0 +1,28 @@ +/* + * 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.*; +import java.text.*; + +import altosui.AltosRecord; + +public class AltosReader { +	public AltosRecord read() throws IOException, ParseException { return null; } +} diff --git a/ao-tools/altosui/AltosRecord.java b/ao-tools/altosui/AltosRecord.java new file mode 100644 index 00000000..b670ee37 --- /dev/null +++ b/ao-tools/altosui/AltosRecord.java @@ -0,0 +1,209 @@ +/* + * 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.lang.*; +import java.text.*; +import java.util.HashMap; +import java.io.*; +import altosui.AltosConvert; +import altosui.AltosGPS; + +public class AltosRecord { +	int	version; +	String 	callsign; +	int	serial; +	int	flight; +	int	rssi; +	int	status; +	int	state; +	int	tick; +	int	accel; +	int	pres; +	int	temp; +	int	batt; +	int	drogue; +	int	main; +	int	flight_accel; +	int	ground_accel; +	int	flight_vel; +	int	flight_pres; +	int	ground_pres; +	int	accel_plus_g; +	int	accel_minus_g; +	AltosGPS	gps; + +	double	time;	/* seconds since boost */ + +	/* +	 * Values for our MP3H6115A pressure sensor +	 * +	 * From the data sheet: +	 * +	 * Pressure range: 15-115 kPa +	 * Voltage at 115kPa: 2.82 +	 * Output scale: 27mV/kPa +	 * +	 * +	 * 27 mV/kPa * 2047 / 3300 counts/mV = 16.75 counts/kPa +	 * 2.82V * 2047 / 3.3 counts/V = 1749 counts/115 kPa +	 */ + +	static final double counts_per_kPa = 27 * 2047 / 3300; +	static final double counts_at_101_3kPa = 1674.0; + +	static double +	barometer_to_pressure(double count) +	{ +		return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0; +	} + +	public double pressure() { +		return barometer_to_pressure(flight_pres); +	} + +	public double ground_pressure() { +		return barometer_to_pressure(ground_pres); +	} + +	public double altitude() { +		return AltosConvert.pressure_to_altitude(pressure()); +	} + +	public double ground_altitude() { +		return AltosConvert.pressure_to_altitude(ground_pressure()); +	} + +	public double height() { +		return altitude() - ground_altitude(); +	} + +	public double battery_voltage() { +		return AltosConvert.cc_battery_to_voltage(batt); +	} + +	public double main_voltage() { +		return AltosConvert.cc_ignitor_to_voltage(main); +	} + +	public double drogue_voltage() { +		return AltosConvert.cc_ignitor_to_voltage(drogue); +	} + +	/* Value for the CC1111 built-in temperature sensor +	 * Output voltage at 0°C = 0.755V +	 * Coefficient = 0.00247V/°C +	 * Reference voltage = 1.25V +	 * +	 * temp = ((value / 32767) * 1.25 - 0.755) / 0.00247 +	 *      = (value - 19791.268) / 32768 * 1.25 / 0.00247 +	 */ + +	static double +	thermometer_to_temperature(double thermo) +	{ +		return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247; +	} + +	public double temperature() { +		return thermometer_to_temperature(temp); +	} + +	double accel_counts_per_mss() { +		double	counts_per_g = Math.abs(accel_minus_g - accel_plus_g) / 2; + +		return counts_per_g / 9.80665; +	} +	public double acceleration() { +		return (accel_plus_g - accel) / accel_counts_per_mss(); +	} + +	public double accel_speed() { +		double speed = flight_vel / (accel_counts_per_mss() * 100.0); +		return speed; +	} + +	public String state() { +		return Altos.state_name(state); +	} + +	public static String gets(FileInputStream s) throws IOException { +		int c; +		String	line = ""; + +		while ((c = s.read()) != -1) { +			if (c == '\r') +				continue; +			if (c == '\n') { +				return line; +			} +			line = line + (char) c; +		} +		return null; +	} + +	public AltosRecord(AltosRecord old) { +		version = old.version; +		callsign = old.callsign; +		serial = old.serial; +		flight = old.flight; +		rssi = old.rssi; +		status = old.status; +		state = old.state; +		tick = old.tick; +		accel = old.accel; +		pres = old.pres; +		temp = old.temp; +		batt = old.batt; +		drogue = old.drogue; +		main = old.main; +		flight_accel = old.flight_accel; +		ground_accel = old.ground_accel; +		flight_vel = old.flight_vel; +		flight_pres = old.flight_pres; +		ground_pres = old.ground_pres; +		accel_plus_g = old.accel_plus_g; +		accel_minus_g = old.accel_minus_g; +		gps = new AltosGPS(old.gps); +	} + +	public AltosRecord() { +		version = 0; +		callsign = "N0CALL"; +		serial = 0; +		flight = 0; +		rssi = 0; +		status = 0; +		state = Altos.ao_flight_startup; +		tick = 0; +		accel = 0; +		pres = 0; +		temp = 0; +		batt = 0; +		drogue = 0; +		main = 0; +		flight_accel = 0; +		ground_accel = 0; +		flight_vel = 0; +		flight_pres = 0; +		ground_pres = 0; +		accel_plus_g = 0; +		accel_minus_g = 0; +		gps = new AltosGPS(); +	} +} diff --git a/ao-tools/altosui/AltosSerial.java b/ao-tools/altosui/AltosSerial.java index efa63f68..ba00b55e 100644 --- a/ao-tools/altosui/AltosSerial.java +++ b/ao-tools/altosui/AltosSerial.java @@ -154,6 +154,11 @@ public class AltosSerial implements Runnable {  			printf("m 0\nc r %d\nm 1\n", channel);  	} +	public void set_callsign(String callsign) { +		if (altos != null) +			printf ("c c %s\n", callsign); +	} +  	public AltosSerial() {  		altos = null;  		input_thread = null; diff --git a/ao-tools/altosui/AltosState.java b/ao-tools/altosui/AltosState.java index 9aa10a08..deeb4c77 100644 --- a/ao-tools/altosui/AltosState.java +++ b/ao-tools/altosui/AltosState.java @@ -16,16 +16,16 @@   */  /* - * Track flight state from telemetry data stream + * Track flight state from telemetry or eeprom data stream   */  package altosui; -import altosui.AltosTelemetry; +import altosui.AltosRecord;  import altosui.AltosGPS;  public class AltosState { -	AltosTelemetry data; +	AltosRecord data;  	/* derived data */ @@ -71,27 +71,25 @@ public class AltosState {  	double	speak_altitude; -	void init (AltosTelemetry cur, AltosState prev_state) { +	void init (AltosRecord cur, AltosState prev_state) {  		int		i; -		AltosTelemetry prev; -		double	accel_counts_per_mss; +		AltosRecord prev;  		data = cur; -		ground_altitude = AltosConvert.cc_barometer_to_altitude(data.ground_pres); -		height = AltosConvert.cc_barometer_to_altitude(data.flight_pres) - ground_altitude; +		ground_altitude = data.ground_altitude(); +		height = data.altitude() - ground_altitude;  		report_time = System.currentTimeMillis(); -		accel_counts_per_mss = ((data.accel_minus_g - data.accel_plus_g) / 2.0) / 9.80665; -		acceleration = (data.ground_accel - data.flight_accel) / accel_counts_per_mss; -		speed = data.flight_vel / (accel_counts_per_mss * 100.0); -		temperature = AltosConvert.cc_thermometer_to_temperature(data.temp); -		drogue_sense = AltosConvert.cc_ignitor_to_voltage(data.drogue); -		main_sense = AltosConvert.cc_ignitor_to_voltage(data.main); -		battery = AltosConvert.cc_battery_to_voltage(data.batt); +		acceleration = data.acceleration(); +		speed = data.accel_speed(); +		temperature = data.temperature(); +		drogue_sense = data.drogue_voltage(); +		main_sense = data.main_voltage(); +		battery = data.battery_voltage();  		tick = data.tick; -		state = data.state(); +		state = data.state;  		if (prev_state != null) { @@ -125,8 +123,13 @@ public class AltosState {  			time_change = 0;  		} -		if (state == AltosTelemetry.ao_flight_pad) { -			if (data.gps != null && data.gps.gps_locked && data.gps.nsat >= 4) { +		if (state == Altos.ao_flight_pad) { +			if (data.gps == null) +				System.out.printf("on pad, gps null\n"); +			else +				System.out.printf ("on pad gps lat %f lon %f locked %d nsat %d\n", +						   data.gps.lat, data.gps.lon, data.gps.locked ? 1 : 0, data.gps.nsat); +			if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) {  				npad++;  				if (npad > 1) {  					/* filter pad position */ @@ -149,8 +152,8 @@ public class AltosState {  		gps_ready = gps_waiting == 0; -		ascent = (AltosTelemetry.ao_flight_boost <= state && -			  state <= AltosTelemetry.ao_flight_coast); +		ascent = (Altos.ao_flight_boost <= state && +			  state <= Altos.ao_flight_coast);  		/* Only look at accelerometer data on the way up */  		if (ascent && acceleration > max_acceleration) @@ -161,9 +164,9 @@ public class AltosState {  		if (height > max_height)  			max_height = height;  		if (data.gps != null) { -			if (gps == null || !gps.gps_locked || data.gps.gps_locked) +			if (gps == null || !gps.locked || data.gps.locked)  				gps = data.gps; -			if (npad > 0 && gps.gps_locked) +			if (npad > 0 && gps.locked)  				from_pad = new AltosGreatCircle(pad_lat, pad_lon, gps.lat, gps.lon);  		}  		if (npad > 0) { @@ -173,11 +176,11 @@ public class AltosState {  		}  	} -	public AltosState(AltosTelemetry cur) { +	public AltosState(AltosRecord cur) {  		init(cur, null);  	} -	public AltosState (AltosTelemetry cur, AltosState prev) { +	public AltosState (AltosRecord cur, AltosState prev) {  		init(cur, prev);  	}  } diff --git a/ao-tools/altosui/AltosTelemetry.java b/ao-tools/altosui/AltosTelemetry.java index e13b42e2..af29b8c0 100644 --- a/ao-tools/altosui/AltosTelemetry.java +++ b/ao-tools/altosui/AltosTelemetry.java @@ -21,6 +21,7 @@ import java.lang.*;  import java.text.*;  import java.util.HashMap;  import altosui.AltosConvert; +import altosui.AltosRecord;  import altosui.AltosGPS;  /* @@ -51,72 +52,9 @@ import altosui.AltosGPS;   *     SAT 10   29  30  24  28   5  25  21  20  15  33   1  23  30  24  18  26  10  29   2  26   */ -public class AltosTelemetry { -	int	version; -	String 	callsign; -	int	serial; -	int	flight; -	int	rssi; -	int	status; -	String	state; -	int	tick; -	int	accel; -	int	pres; -	int	temp; -	int	batt; -	int	drogue; -	int	main; -	int	flight_accel; -	int	ground_accel; -	int	flight_vel; -	int	flight_pres; -	int	ground_pres; -	int	accel_plus_g; -	int	accel_minus_g; -	AltosGPS	gps; - -	public static final int ao_flight_startup = 0; -	public static final int ao_flight_idle = 1; -	public static final int ao_flight_pad = 2; -	public static final int ao_flight_boost = 3; -	public static final int ao_flight_fast = 4; -	public static final int ao_flight_coast = 5; -	public static final int ao_flight_drogue = 6; -	public static final int ao_flight_main = 7; -	public static final int ao_flight_landed = 8; -	public static final int ao_flight_invalid = 9; - -	static HashMap<String,Integer>	states = new HashMap<String,Integer>(); -	{ -		states.put("startup", ao_flight_startup); -		states.put("idle", ao_flight_idle); -		states.put("pad", ao_flight_pad); -		states.put("boost", ao_flight_boost); -		states.put("fast", ao_flight_fast); -		states.put("coast", ao_flight_coast); -		states.put("drogue", ao_flight_drogue); -		states.put("main", ao_flight_main); -		states.put("landed", ao_flight_landed); -		states.put("invalid", ao_flight_invalid); -	} - -	public int state() { -		if (states.containsKey(state)) -			return states.get(state); -		return ao_flight_invalid; -	} - -	public double altitude() { -		return AltosConvert.cc_pressure_to_altitude(AltosConvert.cc_barometer_to_pressure(pres)); -	} - -	public double pad_altitude() { -		return AltosConvert.cc_pressure_to_altitude(AltosConvert.cc_barometer_to_pressure(ground_pres)); -	} - +public class AltosTelemetry extends AltosRecord {  	public AltosTelemetry(String line) throws ParseException {  		String[] words = line.split("\\s+"); -  		int	i = 0;  		AltosParse.word (words[i++], "VERSION"); @@ -138,7 +76,7 @@ public class AltosTelemetry {  		status = AltosParse.parse_hex(words[i++]);  		AltosParse.word(words[i++], "STATE"); -		state = words[i++]; +		state = Altos.state(words[i++]);  		tick = AltosParse.parse_int(words[i++]); diff --git a/ao-tools/altosui/AltosTelemetryReader.java b/ao-tools/altosui/AltosTelemetryReader.java new file mode 100644 index 00000000..f1f6788c --- /dev/null +++ b/ao-tools/altosui/AltosTelemetryReader.java @@ -0,0 +1,71 @@ +/* + * 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.*; +import java.text.*; +import altosui.AltosTelemetry; + +public class AltosTelemetryReader extends AltosReader { +	LinkedList<AltosRecord>	records; + +	Iterator<AltosRecord> record_iterator; + +	int	boost_tick; + +	public AltosRecord read() throws IOException, ParseException { +		AltosRecord	r; +		if (!record_iterator.hasNext()) +			return null; + +		r = record_iterator.next(); +		r.time = (r.tick - boost_tick) / 100.0; +		return r; +	} + +	public AltosTelemetryReader (FileInputStream input) { +		boolean saw_boost = false; + +		records = new LinkedList<AltosRecord> (); + +		try { +			for (;;) { +				String line = AltosRecord.gets(input); +				if (line == null) +					break; +				AltosTelemetry record = new AltosTelemetry(line); +				if (record == null) +					break; +				if (!saw_boost && record.state >= Altos.ao_flight_boost) +				{ +					saw_boost = true; +					boost_tick = record.tick; +				} +				records.add(record); +			} +		} catch (IOException io) { +		} catch (ParseException pe) { +		} +		record_iterator = records.iterator(); +		try { +			input.close(); +		} catch (IOException ie) { +		} +	} +} diff --git a/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java index 4994f093..49d1f11a 100644 --- a/ao-tools/altosui/AltosUI.java +++ b/ao-tools/altosui/AltosUI.java @@ -28,100 +28,22 @@ import java.text.*;  import java.util.prefs.*;  import java.util.concurrent.LinkedBlockingQueue; +import altosui.Altos;  import altosui.AltosSerial;  import altosui.AltosSerialMonitor; +import altosui.AltosRecord;  import altosui.AltosTelemetry;  import altosui.AltosState;  import altosui.AltosDeviceDialog;  import altosui.AltosPreferences;  import altosui.AltosLog;  import altosui.AltosVoice; +import altosui.AltosFlightStatusTableModel; +import altosui.AltosFlightInfoTableModel; +import altosui.AltosChannelMenu;  import libaltosJNI.*; -class AltosFlightStatusTableModel extends AbstractTableModel { -	private String[] columnNames = {"Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" }; -	private Object[] data = { 0, "idle", 0, 0 }; - -	public int getColumnCount() { return columnNames.length; } -	public int getRowCount() { return 2; } -	public Object getValueAt(int row, int col) { -		if (row == 0) -			return columnNames[col]; -		return data[col]; -	} - -	public void setValueAt(Object value, int col) { -		data[col] = value; -		fireTableCellUpdated(1, col); -	} - -	public void setValueAt(Object value, int row, int col) { -		setValueAt(value, col); -	} - -	public void set(AltosState state) { -		setValueAt(String.format("%1.0f", state.height), 0); -		setValueAt(state.data.state, 1); -		setValueAt(state.data.rssi, 2); -		double speed = state.baro_speed; -		if (state.ascent) -			speed = state.speed; -		setValueAt(String.format("%1.0f", speed), 3); -	} -} - -class AltosFlightInfoTableModel extends AbstractTableModel { -	private String[] columnNames = {"Field", "Value"}; - -	class InfoLine { -		String	name; -		String	value; - -		public InfoLine(String n, String v) { -			name = n; -			value = v; -		} -	} - -	private ArrayList<InfoLine> rows = new ArrayList<InfoLine>(); - -	public int getColumnCount() { return columnNames.length; } -	public String getColumnName(int col) { return columnNames[col]; } - -	public int getRowCount() { return 20; } - -	public Object getValueAt(int row, int col) { -		if (row >= rows.size()) -			return ""; -		if (col == 0) -			return rows.get(row).name; -		else -			return rows.get(row).value; -	} - -	int	current_row = 0; -	int	prev_num_rows = 0; - -	public void resetRow() { -		current_row = 0; -	} -	public void addRow(String name, String value) { -		if (current_row >= rows.size()) -			rows.add(current_row, new InfoLine(name, value)); -		else -			rows.set(current_row, new InfoLine(name, value)); -		current_row++; -	} -	public void finish() { -		if (current_row > prev_num_rows) { -			fireTableRowsInserted(prev_num_rows, current_row - 1); -			prev_num_rows = current_row; -		} -		fireTableDataChanged(); -	} -} -  public class AltosUI extends JFrame {  	private int channel = -1; @@ -252,7 +174,7 @@ public class AltosUI extends JFrame {  		else  			info_add_row(0, "Ground state", "wait (%d)",  				     state.gps_waiting); -		info_add_row(0, "Rocket state", "%s", state.data.state); +		info_add_row(0, "Rocket state", "%s", state.data.state());  		info_add_row(0, "Callsign", "%s", state.data.callsign);  		info_add_row(0, "Rocket serial", "%6d", state.data.serial);  		info_add_row(0, "Rocket flight", "%6d", state.data.flight); @@ -272,9 +194,9 @@ public class AltosUI extends JFrame {  		if (state.gps == null) {  			info_add_row(1, "GPS", "not available");  		} else { -			if (state.data.gps.gps_locked) +			if (state.data.gps.locked)  				info_add_row(1, "GPS", "   locked"); -			else if (state.data.gps.gps_connected) +			else if (state.data.gps.connected)  				info_add_row(1, "GPS", " unlocked");  			else  				info_add_row(1, "GPS", "  missing"); @@ -309,13 +231,13 @@ public class AltosUI extends JFrame {  				info_add_row(1, "Pad GPS alt", "%6.0f m", state.pad_alt);  			}  			info_add_row(1, "GPS date", "%04d-%02d-%02d", -				       state.gps.gps_time.year, -				       state.gps.gps_time.month, -				       state.gps.gps_time.day); +				       state.gps.year, +				       state.gps.month, +				       state.gps.day);  			info_add_row(1, "GPS time", "  %02d:%02d:%02d", -				       state.gps.gps_time.hour, -				       state.gps.gps_time.minute, -				       state.gps.gps_time.second); +				       state.gps.hour, +				       state.gps.minute, +				       state.gps.second);  			int	nsat_vis = 0;  			int	c; @@ -344,7 +266,7 @@ public class AltosUI extends JFrame {  				return;  			/* reset the landing count once we hear about a new flight */ -			if (state.state < AltosTelemetry.ao_flight_drogue) +			if (state.state < Altos.ao_flight_drogue)  				reported_landing = 0;  			/* Shut up once the rocket is on the ground */ @@ -353,7 +275,7 @@ public class AltosUI extends JFrame {  			}  			/* If the rocket isn't on the pad, then report height */ -			if (state.state > AltosTelemetry.ao_flight_pad) { +			if (state.state > Altos.ao_flight_pad) {  				voice.speak("%d meters", (int) (state.height + 0.5));  			} else {  				reported_landing = 0; @@ -366,7 +288,7 @@ public class AltosUI extends JFrame {  			if (!state.ascent &&  			    (last ||  			     System.currentTimeMillis() - state.report_time >= 15000 || -			     state.state == AltosTelemetry.ao_flight_landed)) +			     state.state == Altos.ao_flight_landed))  			{  				if (Math.abs(state.baro_speed) < 20 && state.height < 100)  					voice.speak("rocket landed safely"); @@ -400,13 +322,13 @@ public class AltosUI extends JFrame {  	private void tell(AltosState state, AltosState old_state) {  		if (old_state == null || old_state.state != state.state) { -			voice.speak(state.data.state); -			if ((old_state == null || old_state.state <= AltosTelemetry.ao_flight_boost) && -			    state.state > AltosTelemetry.ao_flight_boost) { +			voice.speak(state.data.state()); +			if ((old_state == null || old_state.state <= Altos.ao_flight_boost) && +			    state.state > Altos.ao_flight_boost) {  				voice.speak("max speed: %d meters per second.",  					    (int) (state.max_speed + 0.5)); -			} else if ((old_state == null || old_state.state < AltosTelemetry.ao_flight_drogue) && -				   state.state >= AltosTelemetry.ao_flight_drogue) { +			} else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) && +				   state.state >= Altos.ao_flight_drogue) {  				voice.speak("max height: %d meters.",  					    (int) (state.max_height + 0.5));  			} @@ -423,7 +345,9 @@ public class AltosUI extends JFrame {  	class DisplayThread extends Thread {  		IdleThread	idle_thread; -		String read() throws InterruptedException { return null; } +		String		name; + +		AltosRecord read() throws InterruptedException, ParseException { return null; }  		void close() { } @@ -440,18 +364,19 @@ public class AltosUI extends JFrame {  			info_finish();  			idle_thread.start();  			try { -				while ((line = read()) != null) { +				for (;;) {  					try { -						AltosTelemetry	t = new AltosTelemetry(line); +						AltosRecord record = read(); +						if (record == null) +							break;  						old_state = state; -						state = new AltosState(t, state); +						state = new AltosState(record, state);  						update(state);  						show(state);  						tell(state, old_state);  						idle_thread.notice(state);  					} catch (ParseException pp) { -						System.out.printf("Parse error on %s\n", line); -						System.out.println("exception " + pp); +						System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage());  					}  				}  			} catch (InterruptedException ee) { @@ -471,8 +396,8 @@ public class AltosUI extends JFrame {  		AltosSerial	serial;  		LinkedBlockingQueue<String> telem; -		String read() throws InterruptedException { -			return telem.take(); +		AltosRecord read() throws InterruptedException, ParseException { +			return new AltosTelemetry(telem.take());  		}  		void close() { @@ -484,17 +409,19 @@ public class AltosUI extends JFrame {  			serial = s;  			telem = new LinkedBlockingQueue<String>();  			serial.add_monitor(telem); +			name = "telemetry";  		}  	}  	private void ConnectToDevice() { -		altos_device	device = AltosDeviceDialog.show(AltosUI.this, "TeleDongle"); +		AltosDevice	device = AltosDeviceDialog.show(AltosUI.this, AltosDevice.BaseStation);  		if (device != null) {  			try {  				serial_line.open(device);  				DeviceThread thread = new DeviceThread(serial_line);  				serial_line.set_channel(AltosPreferences.channel()); +				serial_line.set_callsign(AltosPreferences.callsign());  				run_display(thread);  			} catch (FileNotFoundException ee) {  				JOptionPane.showMessageDialog(AltosUI.this, @@ -515,61 +442,69 @@ public class AltosUI extends JFrame {  		stop_display();  	} -	String readline(FileInputStream s) throws IOException { -		int c; -		String	line = ""; - -		while ((c = s.read()) != -1) { -			if (c == '\r') -				continue; -			if (c == '\n') { -				return line; -			} -			line = line + (char) c; +	void ConfigureCallsign() { +		String	result; +		result = JOptionPane.showInputDialog(AltosUI.this, +						     "Configure Callsign", +						     AltosPreferences.callsign()); +		if (result != null) { +			AltosPreferences.set_callsign(result); +			if (serial_line != null) +				serial_line.set_callsign(result);  		} -		return null;  	} +	void ConfigureTeleMetrum() { +		new AltosConfig(AltosUI.this); +	}  	/*  	 * Open an existing telemetry file and replay it in realtime  	 */  	class ReplayThread extends DisplayThread { -		FileInputStream	replay; -		String filename; +		AltosReader	reader; +		String		name; -		ReplayThread(FileInputStream in, String name) { -			replay = in; -			filename = name; -		} - -		String read() { +		public AltosRecord read() {  			try { -				return readline(replay); -			} catch (IOException ee) { +				return reader.read(); +			} catch (IOException ie) {  				JOptionPane.showMessageDialog(AltosUI.this, -							      filename, +							      name,  							      "error reading",  							      JOptionPane.ERROR_MESSAGE); +			} catch (ParseException pe) {  			}  			return null;  		} -		void close () { -			try { -				replay.close(); -			} catch (IOException ee) { -			} +		public void close () {  			report();  		} +		public ReplayThread(AltosReader in_reader, String in_name) { +			reader = in_reader; +		}  		void update(AltosState state) throws InterruptedException {  			/* Make it run in realtime after the rocket leaves the pad */ -			if (state.state > AltosTelemetry.ao_flight_pad) +			if (state.state > Altos.ao_flight_pad)  				Thread.sleep((int) (Math.min(state.time_change,10) * 1000));  		}  	} +	class ReplayTelemetryThread extends ReplayThread { +		ReplayTelemetryThread(FileInputStream in, String in_name) { +			super(new AltosTelemetryReader(in), in_name); +		} + +	} + +	class ReplayEepromThread extends ReplayThread { +		ReplayEepromThread(FileInputStream in, String in_name) { +			super(new AltosEepromReader(in), in_name); +		} +	} +  	Thread		display_thread;  	private void stop_display() { @@ -590,8 +525,8 @@ public class AltosUI extends JFrame {  	private void Replay() {  		JFileChooser	logfile_chooser = new JFileChooser(); -		logfile_chooser.setDialogTitle("Select Telemetry File"); -		logfile_chooser.setFileFilter(new FileNameExtensionFilter("Telemetry file", "telem")); +		logfile_chooser.setDialogTitle("Select Flight Record File"); +		logfile_chooser.setFileFilter(new FileNameExtensionFilter("Flight data file", "eeprom", "telem"));  		logfile_chooser.setCurrentDirectory(AltosPreferences.logdir());  		int returnVal = logfile_chooser.showOpenDialog(AltosUI.this); @@ -602,7 +537,11 @@ public class AltosUI extends JFrame {  			String	filename = file.getName();  			try {  				FileInputStream	replay = new FileInputStream(file); -				ReplayThread	thread = new ReplayThread(replay, filename); +				DisplayThread	thread; +				if (filename.endsWith("eeprom")) +				    thread = new ReplayEepromThread(replay, filename); +				else +				    thread = new ReplayTelemetryThread(replay, filename);  				run_display(thread);  			} catch (FileNotFoundException ee) {  				JOptionPane.showMessageDialog(AltosUI.this, @@ -617,7 +556,7 @@ public class AltosUI extends JFrame {  	 * a TeleDongle over the packet link  	 */  	private void SaveFlightData() { -		new AltosEeprom(AltosUI.this); +		new AltosEepromDownload(AltosUI.this);  	}  	/* Create the AltosUI menus @@ -634,6 +573,22 @@ public class AltosUI extends JFrame {  			menu.setMnemonic(KeyEvent.VK_F);  			menubar.add(menu); +			item = new JMenuItem("Replay File",KeyEvent.VK_R); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						Replay(); +					} +				}); +			menu.add(item); + +			item = new JMenuItem("Save Flight Data",KeyEvent.VK_S); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						SaveFlightData(); +					} +				}); +			menu.add(item); +  			item = new JMenuItem("Quit",KeyEvent.VK_Q);  			item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,  								   ActionEvent.CTRL_MASK)); @@ -669,20 +624,22 @@ public class AltosUI extends JFrame {  			menu.addSeparator(); -			item = new JMenuItem("Save Flight Data",KeyEvent.VK_S); +			item = new JMenuItem("Set Callsign",KeyEvent.VK_S);  			item.addActionListener(new ActionListener() {  					public void actionPerformed(ActionEvent e) { -						SaveFlightData(); +						ConfigureCallsign();  					}  				}); +  			menu.add(item); -			item = new JMenuItem("Replay",KeyEvent.VK_R); +			item = new JMenuItem("Configure TeleMetrum device",KeyEvent.VK_T);  			item.addActionListener(new ActionListener() {  					public void actionPerformed(ActionEvent e) { -						Replay(); +						ConfigureTeleMetrum();  					}  				}); +  			menu.add(item);  		}  		// Log menu @@ -736,26 +693,16 @@ public class AltosUI extends JFrame {  		// Channel menu  		{ -			menu = new JMenu("Channel", true); -			menu.setMnemonic(KeyEvent.VK_C); -			menubar.add(menu); -			ButtonGroup group = new ButtonGroup(); - -			for (int c = 0; c <= 9; c++) { -				radioitem = new JRadioButtonMenuItem(String.format("Channel %1d (%7.3fMHz)", c, -										   434.550 + c * 0.1), -								     c == AltosPreferences.channel()); -				radioitem.setActionCommand(String.format("%d", c)); -				radioitem.addActionListener(new ActionListener() { +			menu = new AltosChannelMenu(AltosPreferences.channel()); +			menu.addActionListener(new ActionListener() {  						public void actionPerformed(ActionEvent e) {  							int new_channel = Integer.parseInt(e.getActionCommand());  							AltosPreferences.set_channel(new_channel);  							serial_line.set_channel(new_channel);  						} -					}); -				menu.add(radioitem); -				group.add(radioitem); -			} +				}); +			menu.setMnemonic(KeyEvent.VK_C); +			menubar.add(menu);  		}  		this.setJMenuBar(menubar); diff --git a/ao-tools/altosui/Makefile b/ao-tools/altosui/Makefile index bae42c9a..63359fbb 100644 --- a/ao-tools/altosui/Makefile +++ b/ao-tools/altosui/Makefile @@ -2,19 +2,30 @@  CLASSPATH=classes:./*:/usr/share/java/*  CLASSFILES=\ +	Altos.class \ +	AltosChannelMenu.class \ +	AltosConfig.class \ +	AltosConfigUI.class \  	AltosConvert.class \ -	AltosEeprom.class \ +	AltosCSV.class \ +	AltosEepromDownload.class \  	AltosEepromMonitor.class \ +	AltosEepromReader.class \ +	AltosEepromRecord.class \  	AltosFile.class \ +	AltosFlightInfoTableModel.class \ +	AltosFlightStatusTableModel.class \  	AltosGPS.class \  	AltosGreatCircle.class \  	AltosLog.class \  	AltosParse.class \  	AltosPreferences.class \ +	AltosRecord.class \  	AltosSerialMonitor.class \  	AltosSerial.class \  	AltosState.class \  	AltosTelemetry.class \ +	AltosTelemetryReader.class \  	AltosUI.class \  	AltosDevice.class \  	AltosDeviceDialog.class \ @@ -32,7 +43,7 @@ CLASSFILES=\  #	en_us.jar \  #	freetts.jar -JAVAFLAGS=-Xlint:unchecked +JAVAFLAGS=-Xlint:unchecked -Xlint:deprecation  OS:=$(shell uname) diff --git a/ao-tools/ao-dumplog/ao-dumplog.c b/ao-tools/ao-dumplog/ao-dumplog.c index 440a02b5..57c43290 100644 --- a/ao-tools/ao-dumplog/ao-dumplog.c +++ b/ao-tools/ao-dumplog/ao-dumplog.c @@ -30,12 +30,13 @@ static const struct option options[] = {  	{ .name = "tty", .has_arg = 1, .val = 'T' },  	{ .name = "device", .has_arg = 1, .val = 'D' },  	{ .name = "remote", .has_arg = 1, .val = 'R' }, +	{ .name = "channel", .has_arg = 1, .val = 'C' },  	{ 0, 0, 0, 0},  };  static void usage(char *program)  { -	fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>] [-R]\n", program); +	fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>] [--remote] [--channel <radio-channel>]\n", program);  	exit(1);  } @@ -75,6 +76,7 @@ main (int argc, char **argv)  	FILE		*out;  	char		*filename;  	int		serial_number = 0; +	int		channel = 0;  	int		flight = 0;  	char		cmd;  	int		tick, a, b; @@ -100,6 +102,9 @@ main (int argc, char **argv)  		case 'R':  			remote = 1;  			break; +		case 'C': +			channel = atoi(optarg); +			break;  		default:  			usage(argv[0]);  			break; @@ -119,7 +124,7 @@ main (int argc, char **argv)  	if (!cc)  		exit(1);  	if (remote) -		cc_usb_open_remote(cc); +		cc_usb_open_remote(cc, channel);  	/* send a 'version' command followed by a 'log' command */  	cc_usb_printf(cc, "v\n");  	out = NULL; diff --git a/ao-tools/lib/cc-usb.c b/ao-tools/lib/cc-usb.c index 53a50741..1580c6d9 100644 --- a/ao-tools/lib/cc-usb.c +++ b/ao-tools/lib/cc-usb.c @@ -375,10 +375,11 @@ cc_usb_reset(struct cc_usb *cc)  }  void -cc_usb_open_remote(struct cc_usb *cc) +cc_usb_open_remote(struct cc_usb *cc, int channel)  {  	if (!cc->remote) { -		cc_usb_printf(cc, "\np\nE 0\n"); +		printf ("channel %d\n", channel); +		cc_usb_printf(cc, "\nc r %d\np\nE 0\n", channel);  		do {  			cc->in_count = cc->in_pos = 0;  			_cc_usb_sync(cc, 100); diff --git a/ao-tools/lib/cc-usb.h b/ao-tools/lib/cc-usb.h index 627f1b5d..d3539281 100644 --- a/ao-tools/lib/cc-usb.h +++ b/ao-tools/lib/cc-usb.h @@ -63,7 +63,7 @@ void  cc_usb_printf(struct cc_usb *cc, char *format, ...);  void -cc_usb_open_remote(struct cc_usb *cc); +cc_usb_open_remote(struct cc_usb *cc, int channel);  void  cc_usb_close_remote(struct cc_usb *cc); diff --git a/ao-tools/libaltos/Makefile b/ao-tools/libaltos/Makefile index fa5127eb..a251e54e 100644 --- a/ao-tools/libaltos/Makefile +++ b/ao-tools/libaltos/Makefile @@ -1,27 +1,56 @@  OS:=$(shell uname) +# +# Linux +#  ifeq ($(OS),Linux)  JAVA_CFLAGS=-I/usr/lib/jvm/java-6-openjdk/include  OS_CFLAGS=-DLINUX -DPOSIX_TTY $(JAVA_CFLAGS) -LIBEXT=so +OS_LDFLAGS= + +LIBNAME=libaltos.so +EXEEXT=  endif +# +# Darwin (Mac OS X) +#  ifeq ($(OS),Darwin) -DARWIN_CFLAGS=\ +OS_CFLAGS=\ +	-DDARWIN -DPOSIX_TTY -arch i386 -arch x86_64 \  	--sysroot=/Developer/SDKs/MacOSX10.5.sdk -mmacosx-version-min=10.5 \  	-iwithsysroot /System/Library/Frameworks/JavaVM.framework/Headers \  	-iwithsysroot /System/Library/Frameworks/IOKit.framework/Headers \  	-iwithsysroot /System/Library/Frameworks/CoreFoundation.framework/Headers -DARWIN_LIBS=\ + +OS_LDFLAGS =\  	-framework IOKit -framework CoreFoundation -OS_CFLAGS = $(DARWIN_CFLAGS) -DDARWIN -DPOSIX_TTY -arch i386 -arch x86_64 -LIBEXT=dylib +LIBNAME=libaltos.dylib +EXEEXT= + +endif + +# +# Windows +# +ifneq (,$(findstring MINGW,$(OS))) + +CC=gcc + +OS_CFLAGS = -DWINDOWS -mconsole + +OS_LDFLAGS = -lgdi32 -luser32 -lcfgmgr32 -lsetupapi -lole32 \ +	-ladvapi32 -lcomctl32 -mconsole -Wl,--add-stdcall-alias + +LIBNAME=altos.dll + +EXEEXT=.exe  endif @@ -48,26 +77,30 @@ CLASSFILES = $(JAVAFILES:%.java=%.class)  JAVAFLAGS=-Xlint:unchecked -all: libaltos.$(LIBEXT) cjnitest $(CLASSFILES) +CJNITEST=cjnitest$(EXEEXT) + +all: $(LIBNAME) $(CJNITEST) $(CLASSFILES)  .java.class:  	javac -encoding UTF8 -classpath "$(CLASSPATH)" $(JAVAFLAGS) $*.java  CFLAGS=$(OS_CFLAGS) -O0 -g -I. +LDFLAGS=$(OS_LDFLAGS) +  HEADERS=libaltos.h  SRCS = libaltos.c $(SWIG_WRAP)  OBJS = $(SRCS:%.c=%.o)  LIBS = $(DARWIN_LIBS) -cjnitest: cjnitest.o $(OBJS) +$(CJNITEST): cjnitest.o $(OBJS)  	cc -o $@ $(CFLAGS) cjnitest.o $(OBJS) $(LIBS) -libaltos.$(LIBEXT): $(OBJS) -	gcc -shared $(CFLAGS) -o $@ $(OBJS) $(LIBS) +$(LIBNAME): $(OBJS) +	gcc -shared $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS)  clean: -	rm -f $(CLASSFILES) $(OBJS) libaltos.$(LIBEXT) cjnitest cjnitest.o +	rm -f $(CLASSFILES) $(OBJS) $(LIBNAME) $(CJNITEST) cjnitest.o  	rm -rf swig_bindings libaltosJNI  $(JNI_FILE): libaltos.i0 $(HEADERS) diff --git a/ao-tools/libaltos/cjnitest.c b/ao-tools/libaltos/cjnitest.c index cd3898ed..93d1f376 100644 --- a/ao-tools/libaltos/cjnitest.c +++ b/ao-tools/libaltos/cjnitest.c @@ -1,6 +1,15 @@  #include <stdio.h>  #include "libaltos.h" +static void +altos_puts(struct altos_file *file, char *string) +{ +	char	c; + +	while ((c = *string++)) +		altos_putchar(file, c); +} +  main ()  {  	struct altos_device	device; @@ -12,12 +21,20 @@ main ()  		struct altos_file	*file;  		int			c; +		printf ("%04x:%04x %-20s %4d %s\n", device.vendor, device.product, +			device.name, device.serial, device.path); +  		file = altos_open(&device); -		altos_putchar(file, '?'); altos_putchar(file, '\n'); altos_flush(file); +		if (!file) { +			printf("altos_open failed\n"); +			continue; +		} +		altos_puts(file,"v\nc s\n");  		while ((c = altos_getchar(file, 100)) >= 0) {  			putchar (c);  		} -		printf ("getchar returns %d\n", c); +		if (c != LIBALTOS_TIMEOUT) +			printf ("getchar returns %d\n", c);  		altos_close(file);  	}  	altos_list_finish(list); diff --git a/ao-tools/libaltos/libaltos.c b/ao-tools/libaltos/libaltos.c index 00fb2125..3e8485e4 100644 --- a/ao-tools/libaltos/libaltos.c +++ b/ao-tools/libaltos/libaltos.c @@ -15,29 +15,21 @@   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.   */ +#define BUILD_DLL  #include "libaltos.h"  #include <stdio.h>  #include <stdlib.h>  #include <string.h> -static int -match_dev(char *product, int serial, struct altos_device *device) +PUBLIC int +altos_init(void)  { -	struct altos_list	*list; -	int			i; +	return LIBALTOS_SUCCESS; +} -	list = altos_list_start(); -	if (!list) -		return 0; -	while ((i = altos_list_next(list, device)) != 0) { -		if (product && strncmp (product, device->product, strlen(product)) != 0) -			continue; -		if (serial && serial != device->serial) -			continue; -		break; -	} -	altos_list_finish(list); -	return i; +PUBLIC void +altos_fini(void) +{  }  #ifdef DARWIN @@ -60,48 +52,9 @@ altos_strndup (const char *s, size_t n)  #define altos_strndup strndup  #endif -int -altos_find_by_arg(char *arg, char *default_product, struct altos_device *device) -{ -	char	*product; -	int	serial; -	char	*end; -	char	*colon; -	int	ret; - -	if (arg) -	{ -		/* check for <serial> */ -		serial = strtol(arg, &end, 0); -		if (end != arg) { -			if (*end != '\0') -				return 0; -			product = NULL; -		} else { -			/* check for <product>:<serial> */ -			colon = strchr(arg, ':'); -			if (colon) { -				product = altos_strndup(arg, colon - arg); -				serial = strtol(colon + 1, &end, 0); -				if (*end != '\0') -					return 0; -			} else { -				product = arg; -				serial = 0; -			} -		} -	} else { -		product = NULL; -		serial = 0; -	} -	if (!product && default_product) -		ret = match_dev(default_product, serial, device); -	if (!ret) -		ret = match_dev(product, serial, device); -	if (product && product != arg) -		free(product); -	return ret; -} +/* + * Scan for Altus Metrum devices by looking through /sys + */  #ifdef LINUX @@ -216,7 +169,7 @@ struct altos_usbdev {  	char	*sys;  	char	*tty;  	char	*manufacturer; -	char	*product; +	char	*product_name;  	int	serial;	/* AltOS always uses simple integer serial numbers */  	int	idProduct;  	int	idVendor; @@ -286,7 +239,7 @@ usb_scan_device(char *sys)  		return NULL;  	usbdev->sys = strdup(sys);  	usbdev->manufacturer = load_string(sys, "manufacturer"); -	usbdev->product = load_string(sys, "product"); +	usbdev->product_name = load_string(sys, "product");  	usbdev->serial = load_dec(sys, "serial");  	usbdev->idProduct = load_hex(sys, "idProduct");  	usbdev->idVendor = load_hex(sys, "idVendor"); @@ -299,7 +252,7 @@ usbdev_free(struct altos_usbdev *usbdev)  {  	free(usbdev->sys);  	free(usbdev->manufacturer); -	free(usbdev->product); +	free(usbdev->product_name);  	/* this can get used as a return value */  	if (usbdev->tty)  		free(usbdev->tty); @@ -332,17 +285,6 @@ struct altos_list {  	int			ndev;  }; -int -altos_init(void) -{ -	return 1; -} - -void -altos_fini(void) -{ -} -  struct altos_list *  altos_list_start(void)  { @@ -366,7 +308,7 @@ altos_list_start(void)  		dir = cc_fullname(USB_DEVICES, ents[e]->d_name);  		dev = usb_scan_device(dir);  		free(dir); -		if (dev->idVendor == 0xfffe && dev->tty) { +		if (USB_IS_ALTUSMETRUM(dev->idVendor, dev->idProduct)) {  			if (devs->dev)  				devs->dev = realloc(devs->dev,  						    devs->ndev + 1 * sizeof (struct usbdev *)); @@ -387,7 +329,9 @@ altos_list_next(struct altos_list *list, struct altos_device *device)  	if (list->current >= list->ndev)  		return 0;  	dev = list->dev[list->current]; -	strcpy(device->product, dev->product); +	strcpy(device->name, dev->product_name); +	device->vendor = dev->idVendor; +	device->product = dev->idProduct;  	strcpy(device->path, dev->tty);  	device->serial = dev->serial;  	list->current++; @@ -447,17 +391,6 @@ get_string(io_object_t object, CFStringRef entry, char *result, int result_len)  	return 0;  } -int -altos_init(void) -{ -	return 1; -} - -void -altos_fini(void) -{ -} -  struct altos_list *  altos_list_start(void)  { @@ -566,15 +499,16 @@ altos_open(struct altos_device *device)  void  altos_close(struct altos_file *file)  { -	close(file->fd); -	file->fd = -1; +	if (file->fd != -1) { +		close(file->fd); +		file->fd = -1; +	}  }  void  altos_free(struct altos_file *file)  { -	if (file->fd != -1) -		close(file->fd); +	altos_close(file);  	free(file);  } @@ -613,6 +547,8 @@ altos_flush(struct altos_file *file)  	}  } +#include <poll.h> +  int  altos_getchar(struct altos_file *file, int timeout)  { @@ -622,9 +558,18 @@ altos_getchar(struct altos_file *file, int timeout)  		altos_flush(file);  		if (file->fd < 0)  			return -EBADF; +		if (timeout) { +			struct pollfd fd; +			int ret; +			fd.fd = file->fd; +			fd.events = POLLIN; +			ret = poll(&fd, 1, timeout); +			if (ret == 0) +				return LIBALTOS_TIMEOUT; +		}  		ret = read(file->fd, file->in_data, USB_BUF_SIZE);  		if (ret < 0) -			return -errno; +			return LIBALTOS_ERROR;  		file->in_read = 0;  		file->in_used = ret;  	} @@ -633,143 +578,255 @@ altos_getchar(struct altos_file *file, int timeout)  #endif /* POSIX_TTY */ -#ifdef USE_LIBUSB -#include <libusb.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> +#ifdef WINDOWS -libusb_context	*usb_context; +#include <windows.h> +#include <setupapi.h> -int altos_init(void) -{ -	int	ret; -	ret = libusb_init(&usb_context); -	if (ret) -		return ret; -	libusb_set_debug(usb_context, 3); -	return 0; -} +struct altos_list { +	HDEVINFO	dev_info; +	int		index; +}; -void altos_fini(void) -{ -	libusb_exit(usb_context); -	usb_context = NULL; -} +#define USB_BUF_SIZE	64 -static libusb_device **list; -static ssize_t num, current; +struct altos_file { +	HANDLE				handle; +	unsigned char			out_data[USB_BUF_SIZE]; +	int				out_used; +	unsigned char			in_data[USB_BUF_SIZE]; +	int				in_used; +	int				in_read; +}; -int altos_list_start(void) + +PUBLIC struct altos_list * +altos_list_start(void)  { -	if (list) -		altos_list_finish(); -	current = 0; -	num = libusb_get_device_list(usb_context, &list); -	if (num == 0) { -		current = num = 0; -		list = NULL; -		return 0; +	struct altos_list	*list = calloc(1, sizeof (struct altos_list)); + +	if (!list) +		return NULL; +	list->dev_info = SetupDiGetClassDevs(NULL, "USB", NULL, +					     DIGCF_ALLCLASSES|DIGCF_PRESENT); +	if (list->dev_info == INVALID_HANDLE_VALUE) { +		printf("SetupDiGetClassDevs failed %d\n", GetLastError()); +		free(list); +		return NULL;  	} -	return 1; +	list->index = 0; +	return list;  } -int altos_list_next(struct altos_device *device) -{ -	while (current < num) { -		struct libusb_device_descriptor descriptor; -		libusb_device *usb_device = list[current++]; - -		if (libusb_get_device_descriptor(usb_device, &descriptor) == 0) { -			if (descriptor.idVendor == 0xfffe) -			{ -				libusb_device_handle	*handle; -				if (libusb_open(usb_device, &handle) == 0) { -					char	serial_number[256]; -					libusb_get_string_descriptor_ascii(handle, descriptor.iProduct, -									   device->product, -									   sizeof(device->product)); -					libusb_get_string_descriptor_ascii(handle, descriptor.iSerialNumber, -									   serial_number, -									   sizeof (serial_number)); -					libusb_close(handle); -					device->serial = atoi(serial_number); -					device->device = usb_device; -					return 1; -				} -			} +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device) +{ +	SP_DEVINFO_DATA dev_info_data; +	char		port[128]; +	DWORD		port_len; +	char		location[256]; +	char		symbolic[256]; +	DWORD		symbolic_len; +	HKEY		dev_key; +	int		vid, pid; +	int		serial; +	HRESULT 	result; +	DWORD		location_type; +	DWORD		location_len; + +	dev_info_data.cbSize = sizeof (SP_DEVINFO_DATA); +	while(SetupDiEnumDeviceInfo(list->dev_info, list->index, +				    &dev_info_data)) +	{ +		list->index++; + +		dev_key = SetupDiOpenDevRegKey(list->dev_info, &dev_info_data, +					       DICS_FLAG_GLOBAL, 0, DIREG_DEV, +					       KEY_READ); +		if (dev_key == INVALID_HANDLE_VALUE) { +			printf("cannot open device registry key\n"); +			continue;  		} + +		/* Fetch symbolic name for this device and parse out +		 * the vid/pid/serial info */ +		symbolic_len = sizeof(symbolic); +		result = RegQueryValueEx(dev_key, "SymbolicName", NULL, NULL, +					 symbolic, &symbolic_len); +		if (result != 0) { +			printf("cannot find SymbolicName value\n"); +			RegCloseKey(dev_key); +			continue; +		} +		vid = pid = serial = 0; +		sscanf(symbolic + sizeof("\\??\\USB#VID_") - 1, +		       "%04X", &vid); +		sscanf(symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1, +		       "%04X", &pid); +		sscanf(symbolic + sizeof("\\??\\USB#VID_XXXX&PID_XXXX#") - 1, +		       "%d", &serial); +		if (!USB_IS_ALTUSMETRUM(vid, pid)) { +			printf("Not Altus Metrum symbolic name: %s\n", +			       symbolic); +			RegCloseKey(dev_key); +			continue; +		} + +		/* Fetch the com port name */ +		port_len = sizeof (port); +		result = RegQueryValueEx(dev_key, "PortName", NULL, NULL, +					 port, &port_len); +		RegCloseKey(dev_key); +		if (result != 0) { +			printf("failed to get PortName\n"); +			continue; +		} + +		/* Fetch the 'location information' which is the device name, +		 * at least on XP */ +		location_len = sizeof (location); +		if(!SetupDiGetDeviceRegistryProperty(list->dev_info, +						     &dev_info_data, +						     SPDRP_LOCATION_INFORMATION, +						     &location_type, +						     (BYTE *)location, +						     sizeof(location), +						     &location_len)) +		{ +			printf("Failed to get location\n"); +			continue; +		} +		device->vendor = vid; +		device->product = pid; +		device->serial = serial; + +		if (strcasestr(location, "tele")) +			strcpy(device->name, location); +		else +			strcpy(device->name, ""); + +		strcpy(device->path, port); +		printf ("product: %04x:%04x (%s)  path: %s serial %d\n", +			device->vendor, device->product, device->name, +			device->path, device->serial); +		return 1;  	} +	result = GetLastError(); +	if (result != ERROR_NO_MORE_ITEMS) +		printf ("SetupDiEnumDeviceInfo failed error %d\n", result);  	return 0;  } -void altos_list_finish(void) +PUBLIC void +altos_list_finish(struct altos_list *list) +{ +	SetupDiDestroyDeviceInfoList(list->dev_info); +	free(list); +} + +static int +altos_fill(struct altos_file *file, int timeout)  { -	if (list) { -		libusb_free_device_list(list, 1); -		list = NULL; +	DWORD	result; +	DWORD	got; +	COMMTIMEOUTS timeouts; + +	if (file->in_read < file->in_used) +		return LIBALTOS_SUCCESS; +	file->in_read = file->in_used = 0; + +	if (timeout) { +		timeouts.ReadIntervalTimeout = MAXDWORD; +		timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; +		timeouts.ReadTotalTimeoutConstant = timeout; +	} else { +		timeouts.ReadIntervalTimeout = 0; +		timeouts.ReadTotalTimeoutMultiplier = 0; +		timeouts.ReadTotalTimeoutConstant = 0; +	} +	timeouts.WriteTotalTimeoutMultiplier = 0; +	timeouts.WriteTotalTimeoutConstant = 0; + +	if (!SetCommTimeouts(file->handle, &timeouts)) { +		printf("SetCommTimeouts failed %d\n", GetLastError());  	} + +	if (!ReadFile(file->handle, file->in_data, USB_BUF_SIZE, &got, NULL)) { +		result = GetLastError(); +		printf ("read failed %d\n", result); +		return LIBALTOS_ERROR; +		got = 0; +	} +	if (got) +		return LIBALTOS_SUCCESS; +	return LIBALTOS_TIMEOUT;  } -#define USB_BUF_SIZE	64 +PUBLIC int +altos_flush(struct altos_file *file) +{ +	DWORD	put; +	char	*data = file->out_data; +	char	used = file->out_used; +	DWORD	result; -struct altos_file { -	struct libusb_device		*device; -	struct libusb_device_handle	*handle; -	int				out_ep; -	int				out_size; -	int				in_ep; -	int				in_size; -	unsigned char			out_data[USB_BUF_SIZE]; -	int				out_used; -	unsigned char			in_data[USB_BUF_SIZE]; -	int				in_used; -	int				in_read; -}; +	while (used) { +		if (!WriteFile(file->handle, data, used, &put, NULL)) { +			result = GetLastError(); +			printf ("write failed %d\n", result); +			return LIBALTOS_ERROR; +		} +		data += put; +		used -= put; +	} +	file->out_used = 0; +	return LIBALTOS_SUCCESS; +} -struct altos_file * +PUBLIC struct altos_file *  altos_open(struct altos_device *device)  { -	struct altos_file		*file; -	struct libusb_device_handle	*handle; -	if (libusb_open(device->device, &handle) == 0) { -		int	ret; +	struct altos_file	*file = calloc (sizeof (struct altos_file), 1); +	char	full_name[64]; -		ret = libusb_claim_interface(handle, 1); -#if 0 -		if (ret) { -			libusb_close(handle); -			return NULL; -		} -#endif -		ret = libusb_detach_kernel_driver(handle, 1); -#if 0 -		if (ret) { -			libusb_close(handle); -			return NULL; -		} -#endif +	if (!file) +		return NULL; -		file = calloc(sizeof (struct altos_file), 1); -		file->device = libusb_ref_device(device->device); -		file->handle = handle; -		/* XXX should get these from the endpoint descriptors */ -		file->out_ep = 4 | LIBUSB_ENDPOINT_OUT; -		file->out_size = 64; -		file->in_ep = 5 | LIBUSB_ENDPOINT_IN; -		file->in_size = 64; +	strcpy(full_name, "\\\\.\\"); +	strcat(full_name, device->path); +	file->handle = CreateFile(full_name, GENERIC_READ|GENERIC_WRITE, +				  0, NULL, OPEN_EXISTING, +				  FILE_ATTRIBUTE_NORMAL, NULL); +	if (file->handle == INVALID_HANDLE_VALUE) { +		free(file); +		return NULL; +	} -		return file; +	timeouts.ReadIntervalTimeout = MAXDWORD; +	timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; +	timeouts.ReadTotalTimeoutConstant = 100; +	timeouts.WriteTotalTimeoutMultiplier = 0; +	timeouts.WriteTotalTimeoutConstant = 10000; +	if (!SetCommTimeouts(file->handle, &timeouts)) { +		printf("SetCommTimeouts failed %d\n", GetLastError());  	} -	return NULL; + +	return file;  } -void +PUBLIC void  altos_close(struct altos_file *file)  { -	libusb_close(file->handle); -	libusb_unref_device(file->device); -	file->handle = NULL; +	if (file->handle != INVALID_HANDLE_VALUE) { +		CloseHandle(file->handle); +		file->handle = INVALID_HANDLE_VALUE; +	} +} + +PUBLIC void +altos_free(struct altos_file *file) +{ +	altos_close(file);  	free(file);  } @@ -778,60 +835,32 @@ altos_putchar(struct altos_file *file, char c)  {  	int	ret; -	if (file->out_used == file->out_size) { +	if (file->out_used == USB_BUF_SIZE) {  		ret = altos_flush(file);  		if (ret)  			return ret;  	}  	file->out_data[file->out_used++] = c; -	if (file->out_used == file->out_size) +	if (file->out_used == USB_BUF_SIZE)  		return altos_flush(file); -	return 0; -} - -int -altos_flush(struct altos_file *file) -{ -	while (file->out_used) { -		int	transferred; -		int	ret; - -		ret = libusb_bulk_transfer(file->handle, -					   file->out_ep, -					   file->out_data, -					   file->out_used, -					   &transferred, -					   0); -		if (ret) -			return ret; -		if (transferred) { -			memmove(file->out_data, file->out_data + transferred, -				file->out_used - transferred); -			file->out_used -= transferred; -		} -	} +	return LIBALTOS_SUCCESS;  }  int  altos_getchar(struct altos_file *file, int timeout)  { +	int	ret;  	while (file->in_read == file->in_used) { -		int	ret; -		int	transferred; - -		altos_flush(file); -		ret = libusb_bulk_transfer(file->handle, -					   file->in_ep, -					   file->in_data, -					   file->in_size, -					   &transferred, -					   (unsigned int) timeout); +		ret = altos_flush(file); +		if (ret) +			return ret; +		if (file->handle == INVALID_HANDLE_VALUE) +			return LIBALTOS_ERROR; +		ret = altos_fill(file, timeout);  		if (ret)  			return ret; -		file->in_read = 0; -		file->in_used = transferred;  	}  	return file->in_data[file->in_read++];  } -#endif /* USE_LIBUSB */ +#endif diff --git a/ao-tools/libaltos/libaltos.h b/ao-tools/libaltos/libaltos.h index 53026e0a..fe2c483c 100644 --- a/ao-tools/libaltos/libaltos.h +++ b/ao-tools/libaltos/libaltos.h @@ -18,39 +18,83 @@  #ifndef _LIBALTOS_H_  #define _LIBALTOS_H_ +#if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) +# ifndef BUILD_STATIC +#  ifdef BUILD_DLL +#   define PUBLIC __declspec(dllexport) +#  else +#   define PUBLIC __declspec(dllimport) +#  endif +# endif /* BUILD_STATIC */ +#endif + +#ifndef PUBLIC +# define PUBLIC +#endif + +#define USB_VENDOR_FSF			0xfffe +#define USB_VENDOR_ALTUSMETRUM		USB_VENDOR_FSF +#define USB_PRODUCT_ALTUSMETRUM		0x000a +#define USB_PRODUCT_TELEMETRUM		0x000b +#define USB_PRODUCT_TELEDONGLE		0x000c +#define USB_PRODUCT_TELETERRA		0x000d +#define USB_PRODUCT_ALTUSMETRUM_MIN	0x000a +#define USB_PRODUCT_ALTUSMETRUM_MAX	0x0013 + +#define USB_IS_ALTUSMETRUM(v,p)	((v) == USB_VENDOR_ALTUSMETRUM && \ +		(USB_PRODUCT_ALTUSMETRUM_MIN <= (p) && \ +		 (p) <= USB_PRODUCT_ALTUSMETRUM_MAX)) +  struct altos_device {  	//%immutable; -	char				product[256]; +	int				vendor; +	int				product;  	int				serial; +	char				name[256];  	char				path[256];  	//%mutable;  }; -int altos_init(void); +#define LIBALTOS_SUCCESS	0 +#define LIBALTOS_ERROR		-1 +#define LIBALTOS_TIMEOUT	-2 + +/* Returns 0 for success, < 0 on error */ +PUBLIC int +altos_init(void); -void altos_fini(void); +PUBLIC void +altos_fini(void); -struct altos_list * +PUBLIC struct altos_list *  altos_list_start(void); -int altos_list_next(struct altos_list *list, struct altos_device *device); +/* Returns 1 for success, zero on end of list */ +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device); -void altos_list_finish(struct altos_list *list); +PUBLIC void +altos_list_finish(struct altos_list *list); -struct altos_file * +PUBLIC struct altos_file *  altos_open(struct altos_device *device); -void altos_close(struct altos_file *file); +PUBLIC void +altos_close(struct altos_file *file); -void altos_free(struct altos_file *file); +PUBLIC void +altos_free(struct altos_file *file); -int +/* Returns < 0 for error */ +PUBLIC int  altos_putchar(struct altos_file *file, char c); -int +/* Returns < 0 for error */ +PUBLIC int  altos_flush(struct altos_file *file); -int +/* Returns < 0 for error or timeout. timeout of 0 == wait forever */ +PUBLIC int  altos_getchar(struct altos_file *file, int timeout);  #endif /* _LIBALTOS_H_ */ | 
