diff options
25 files changed, 2171 insertions, 9 deletions
diff --git a/ao-tools/altosui/.gitignore b/ao-tools/altosui/.gitignore new file mode 100644 index 00000000..59913193 --- /dev/null +++ b/ao-tools/altosui/.gitignore @@ -0,0 +1,2 @@ +*.class +altosui diff --git a/ao-tools/altosui/AltosConvert.java b/ao-tools/altosui/AltosConvert.java new file mode 100644 index 00000000..3be0716c --- /dev/null +++ b/ao-tools/altosui/AltosConvert.java @@ -0,0 +1,245 @@ +/* + * 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. + */ + +/* + * Sensor data conversion functions + */ +package altosui; + +public class AltosConvert { +	/* +	 * Pressure Sensor Model, version 1.1 +	 * +	 * written by Holly Grimes +	 * +	 * Uses the International Standard Atmosphere as described in +	 *   "A Quick Derivation relating altitude to air pressure" (version 1.03) +	 *    from the Portland State Aerospace Society, except that the atmosphere +	 *    is divided into layers with each layer having a different lapse rate. +	 * +	 * Lapse rate data for each layer was obtained from Wikipedia on Sept. 1, 2007 +	 *    at site <http://en.wikipedia.org/wiki/International_Standard_Atmosphere +	 * +	 * Height measurements use the local tangent plane.  The postive z-direction is up. +	 * +	 * All measurements are given in SI units (Kelvin, Pascal, meter, meters/second^2). +	 *   The lapse rate is given in Kelvin/meter, the gas constant for air is given +	 *   in Joules/(kilogram-Kelvin). +	 */ + +	static final double GRAVITATIONAL_ACCELERATION = -9.80665; +	static final double AIR_GAS_CONSTANT		= 287.053; +	static final double NUMBER_OF_LAYERS		= 7; +	static final double MAXIMUM_ALTITUDE		= 84852.0; +	static final double MINIMUM_PRESSURE		= 0.3734; +	static final double LAYER0_BASE_TEMPERATURE	= 288.15; +	static final double LAYER0_BASE_PRESSURE	= 101325; + +	/* lapse rate and base altitude for each layer in the atmosphere */ +	static final double[] lapse_rate = { +		-0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002 +	}; + +	static final int[] base_altitude = { +		0, 11000, 20000, 32000, 47000, 51000, 71000 +	}; + +	/* outputs atmospheric pressure associated with the given altitude. +	 * altitudes are measured with respect to the mean sea level +	 */ +	static double +	cc_altitude_to_pressure(double altitude) +	{ +		double base_temperature = LAYER0_BASE_TEMPERATURE; +		double base_pressure = LAYER0_BASE_PRESSURE; + +		double pressure; +		double base; /* base for function to determine pressure */ +		double exponent; /* exponent for function to determine pressure */ +		int layer_number; /* identifies layer in the atmosphere */ +		double delta_z; /* difference between two altitudes */ + +		if (altitude > MAXIMUM_ALTITUDE) /* FIX ME: use sensor data to improve model */ +			return 0; + +		/* calculate the base temperature and pressure for the atmospheric layer +		   associated with the inputted altitude */ +		for(layer_number = 0; layer_number < NUMBER_OF_LAYERS - 1 && altitude > base_altitude[layer_number + 1]; layer_number++) { +			delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number]; +			if (lapse_rate[layer_number] == 0.0) { +				exponent = GRAVITATIONAL_ACCELERATION * delta_z +					/ AIR_GAS_CONSTANT / base_temperature; +				base_pressure *= Math.exp(exponent); +			} +			else { +				base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; +				exponent = GRAVITATIONAL_ACCELERATION / +					(AIR_GAS_CONSTANT * lapse_rate[layer_number]); +				base_pressure *= Math.pow(base, exponent); +			} +			base_temperature += delta_z * lapse_rate[layer_number]; +		} + +		/* calculate the pressure at the inputted altitude */ +		delta_z = altitude - base_altitude[layer_number]; +		if (lapse_rate[layer_number] == 0.0) { +			exponent = GRAVITATIONAL_ACCELERATION * delta_z +				/ AIR_GAS_CONSTANT / base_temperature; +			pressure = base_pressure * Math.exp(exponent); +		} +		else { +			base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; +			exponent = GRAVITATIONAL_ACCELERATION / +				(AIR_GAS_CONSTANT * lapse_rate[layer_number]); +			pressure = base_pressure * Math.pow(base, exponent); +		} + +		return pressure; +	} + + +/* 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) +	{ + +		double next_base_temperature = LAYER0_BASE_TEMPERATURE; +		double next_base_pressure = LAYER0_BASE_PRESSURE; + +		double altitude; +		double base_pressure; +		double base_temperature; +		double base; /* base for function to determine base pressure of next layer */ +		double exponent; /* exponent for function to determine base pressure +				    of next layer */ +		double coefficient; +		int layer_number; /* identifies layer in the atmosphere */ +		int delta_z; /* difference between two altitudes */ + +		if (pressure < 0)  /* illegal pressure */ +			return -1; +		if (pressure < MINIMUM_PRESSURE) /* FIX ME: use sensor data to improve model */ +			return MAXIMUM_ALTITUDE; + +		/* calculate the base temperature and pressure for the atmospheric layer +		   associated with the inputted pressure. */ +		layer_number = -1; +		do { +			layer_number++; +			base_pressure = next_base_pressure; +			base_temperature = next_base_temperature; +			delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number]; +			if (lapse_rate[layer_number] == 0.0) { +				exponent = GRAVITATIONAL_ACCELERATION * delta_z +					/ AIR_GAS_CONSTANT / base_temperature; +				next_base_pressure *= Math.exp(exponent); +			} +			else { +				base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; +				exponent = GRAVITATIONAL_ACCELERATION / +					(AIR_GAS_CONSTANT * lapse_rate[layer_number]); +				next_base_pressure *= Math.pow(base, exponent); +			} +			next_base_temperature += delta_z * lapse_rate[layer_number]; +		} +		while(layer_number < NUMBER_OF_LAYERS - 1 && pressure < next_base_pressure); + +		/* calculate the altitude associated with the inputted pressure */ +		if (lapse_rate[layer_number] == 0.0) { +			coefficient = (AIR_GAS_CONSTANT / GRAVITATIONAL_ACCELERATION) +				* base_temperature; +			altitude = base_altitude[layer_number] +				+ coefficient * Math.log(pressure / base_pressure); +		} +		else { +			base = pressure / base_pressure; +			exponent = AIR_GAS_CONSTANT * lapse_rate[layer_number] +				/ GRAVITATIONAL_ACCELERATION; +			coefficient = base_temperature / lapse_rate[layer_number]; +			altitude = base_altitude[layer_number] +				+ coefficient * (Math.pow(base, exponent) - 1); +		} + +		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) +	{ +		return battery / 32767.0 * 5.0; +	} + +	static double +	cc_ignitor_to_voltage(double ignite) +	{ +		return ignite / 32767 * 15.0; +	} +} diff --git a/ao-tools/altosui/AltosDevice.java b/ao-tools/altosui/AltosDevice.java new file mode 100644 index 00000000..66800c5c --- /dev/null +++ b/ao-tools/altosui/AltosDevice.java @@ -0,0 +1,30 @@ +/* + * 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.util.*; + +public class AltosDevice { +	String	tty;	/* suitable to be passed to AltosSerial.connect */ +	String	manufacturer; +	String	product; +	int	serial; +	int	idProduct; +	int	idVendor; + +}
\ No newline at end of file diff --git a/ao-tools/altosui/AltosDeviceDialog.java b/ao-tools/altosui/AltosDeviceDialog.java new file mode 100644 index 00000000..cb1eef8b --- /dev/null +++ b/ao-tools/altosui/AltosDeviceDialog.java @@ -0,0 +1,45 @@ +/* + * 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.util.*; +import javax.swing.*; +import altosui.AltosDevice; +import altosui.AltosDeviceLinux; + +public class AltosDeviceDialog { + +	static AltosDevice show (JFrame frame, String product) { +		AltosDevice[]	devices = null; +		if (System.getProperty("os.name").startsWith("Linux")) +			devices = AltosDeviceLinux.list(product); +		if (devices != null & devices.length > 0) { +			Object o = JOptionPane.showInputDialog(frame, +							       "Select a device", +							       "Device Selection", +							       JOptionPane.PLAIN_MESSAGE, +							       null, +							       devices, +							       devices[0]); +			return (AltosDevice) o; +		} else { +			return null; +		} +	} +} diff --git a/ao-tools/altosui/AltosDeviceLinux.java b/ao-tools/altosui/AltosDeviceLinux.java new file mode 100644 index 00000000..ffc70aff --- /dev/null +++ b/ao-tools/altosui/AltosDeviceLinux.java @@ -0,0 +1,172 @@ +/* + * 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 java.util.*; +import altosui.AltosDevice; + +public class AltosDeviceLinux extends AltosDevice { + +	String load_string(File file) { +		try { +			FileInputStream	in = new FileInputStream(file); +			String result = ""; +			int c; +			try { +				while ((c = in.read()) != -1) { +					if (c == '\n') +						break; +					result = result + (char) c; +				} +				return result; +			} catch (IOException ee) { +				return ""; +			} +		} catch (FileNotFoundException ee) { +			return ""; +		} +	} +	String load_string(File dir, String name) { +		return load_string(new File(dir, name)); +	} + +	int load_hex(File file) { +		try { +			return Integer.parseInt(load_string(file).trim(), 16); +		} catch (NumberFormatException ee) { +			return -1; +		} +	} + +	int load_hex(File dir, String name) { +		return load_hex(new File(dir, name)); +	} + +	int load_dec(File file) { +		try { +			return Integer.parseInt(load_string(file).trim()); +		} catch (NumberFormatException ee) { +			return -1; +		} +	} + +	int load_dec(File dir, String name) { +		return load_dec(new File(dir, name)); +	} + +	String usb_tty(File sys_dir) { +		String base = sys_dir.getName(); +		int num_configs = load_hex(sys_dir, "bNumConfigurations"); +		int num_inters = load_hex(sys_dir, "bNumInterfaces"); +		for (int config = 1; config <= num_configs; config++) { +			for (int inter = 0; inter < num_inters; inter++) { +				String endpoint_base = String.format("%s:%d.%d", +								     base, config, inter); +				File endpoint_full = new File(sys_dir, endpoint_base); + +				File[] namelist; + +				/* Check for tty:ttyACMx style names */ +				class tty_colon_filter implements FilenameFilter { +					public boolean accept(File dir, String name) { +						return name.startsWith("tty:"); +					} +				} +				namelist = endpoint_full.listFiles(new tty_colon_filter()); +				if (namelist != null && namelist.length > 0) +					return new File ("/dev", namelist[0].getName().substring(4)).getPath(); + +				/* Check for tty/ttyACMx style names */ +				class tty_filter implements FilenameFilter { +					public boolean accept(File dir, String name) { +						return name.startsWith("tty"); +					} +				} +				File tty_dir = new File(endpoint_full, "tty"); +				namelist = tty_dir.listFiles(new tty_filter()); +				if (namelist != null && namelist.length > 0) +					return new File ("/dev", namelist[0].getName()).getPath(); +			} +		} +		return null; +	} + +	public AltosDeviceLinux (File sys) { +		sys = sys; +		manufacturer = load_string(sys, "manufacturer"); +		product = load_string(sys, "product"); +		serial = load_dec(sys, "serial"); +		idProduct = load_hex(sys, "idProduct"); +		idVendor = load_hex(sys, "idVendor"); +		tty = usb_tty(sys); +	} + +	public String toString() { +		return String.format("%-20s %6d %-15s", product, serial, tty == null ? "" : tty); +	} +	static public AltosDeviceLinux[] list() { +		LinkedList<AltosDeviceLinux> devices = new LinkedList<AltosDeviceLinux>(); + +		class dev_filter implements FilenameFilter{ +			public boolean accept(File dir, String name) { +				for (int i = 0; i < name.length(); i++) { +					char c = name.charAt(i); +					if (Character.isDigit(c)) +						continue; +					if (c == '-') +						continue; +					if (c == '.' && i != 1) +						continue; +					return false; +				} +				return true; +			} +		} + +		File usb_devices = new File("/sys/bus/usb/devices"); +		File[] devs = usb_devices.listFiles(new dev_filter()); +		if (devs != null) { +			for (int e = 0; e < devs.length; e++) { +				AltosDeviceLinux	dev = new AltosDeviceLinux(devs[e]); +				if (dev.idVendor == 0xfffe && dev.tty != null) { +					devices.add(dev); +				} +			} +		} +		AltosDeviceLinux[] foo = new AltosDeviceLinux[devices.size()]; +		for (int e = 0; e < devices.size(); e++) +			foo[e] = devices.get(e); +		return foo; +	} + +	static public AltosDeviceLinux[] list(String model) { +		AltosDeviceLinux[] devices = list(); +		if (model != null) { +			LinkedList<AltosDeviceLinux> subset = new LinkedList<AltosDeviceLinux>(); +			for (int i = 0; i < devices.length; i++) { +				if (devices[i].product.startsWith(model)) +					subset.add(devices[i]); +			} +			devices = new AltosDeviceLinux[subset.size()]; +			for (int e = 0; e < subset.size(); e++) +				devices[e] = subset.get(e); +		} +		return devices; +	} +} diff --git a/ao-tools/altosui/AltosGPS.java b/ao-tools/altosui/AltosGPS.java new file mode 100644 index 00000000..c3b368e2 --- /dev/null +++ b/ao-tools/altosui/AltosGPS.java @@ -0,0 +1,124 @@ +/* + * 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 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; +	double	lat;		/* degrees (+N -S) */ +	double	lon;		/* degrees (+E -W) */ +	int	alt;		/* m */ + +	int	gps_extended;	/* has extra data */ +	double	ground_speed;	/* m/s */ +	int	course;		/* degrees */ +	double	climb_rate;	/* m/s */ +	double	hdop;		/* unitless? */ +	int	h_error;	/* m */ +	int	v_error;	/* m */ + +	AltosGPSSat[] cc_gps_sat;	/* tracking data */ + +	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; +		lat = lon = 0; +		alt = 0; +		if ((words[i]).equals("unlocked")) { +			gps_connected = true; +			gps_time = new AltosGPSTime(); +			i++; +		} else if (words.length >= 40) { +			gps_locked = true; +			gps_connected = true; + +			gps_time = new AltosGPSTime(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")); +			ground_speed = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "m/s(H)")); +			course = AltosParse.parse_int(AltosParse.strip_suffix(words[i++], "°")); +			climb_rate = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "m/s(V)")); +			hdop = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "(hdop)")); +			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(); +		} +		AltosParse.word(words[i++], "SAT"); +		int tracking_channels = 0; +		if (words[i].equals("not-connected")) +			tracking_channels = 0; +		else +			tracking_channels = AltosParse.parse_int(words[i]); +		i++; +		cc_gps_sat = new AltosGPS.AltosGPSSat[tracking_channels]; +		for (int chan = 0; chan < tracking_channels; chan++) { +			cc_gps_sat[chan] = new AltosGPS.AltosGPSSat(); +			cc_gps_sat[chan].svid = AltosParse.parse_int(words[i++]); +			cc_gps_sat[chan].c_n0 = AltosParse.parse_int(words[i++]); +		} +	} +} diff --git a/ao-tools/altosui/AltosGreatCircle.java b/ao-tools/altosui/AltosGreatCircle.java new file mode 100644 index 00000000..878da03e --- /dev/null +++ b/ao-tools/altosui/AltosGreatCircle.java @@ -0,0 +1,66 @@ +/* + * 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.Math; + +public class AltosGreatCircle { +	double	distance; +	double	bearing; + +	double sqr(double a) { return a * a; } + +	static final double rad = Math.PI / 180; +	static final double earth_radius = 6371.2 * 1000;	/* in meters */ + +	AltosGreatCircle (double start_lat, double start_lon, +			  double end_lat, double end_lon) +	{ +		double lat1 = rad * start_lat; +		double lon1 = rad * -start_lon; +		double lat2 = rad * end_lat; +		double lon2 = rad * -end_lon; + +		double d_lon = lon2 - lon1; + +		/* From http://en.wikipedia.org/wiki/Great-circle_distance */ +		double vdn = Math.sqrt(sqr(Math.cos(lat2) * Math.sin(d_lon)) + +				       sqr(Math.cos(lat1) * Math.sin(lat2) - +					   Math.sin(lat1) * Math.cos(lat2) * Math.cos(d_lon))); +		double vdd = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(d_lon); +		double d = Math.atan2(vdn,vdd); +		double course; + +		if (Math.cos(lat1) < 1e-20) { +			if (lat1 > 0) +				course = Math.PI; +			else +				course = -Math.PI; +		} else { +			if (d < 1e-10) +				course = 0; +			else +				course = Math.acos((Math.sin(lat2)-Math.sin(lat1)*Math.cos(d)) / +						   (Math.sin(d)*Math.cos(lat1))); +			if (Math.sin(lon2-lon1) > 0) +				course = 2 * Math.PI-course; +		} +		distance = d * earth_radius; +		bearing = course * 180/Math.PI; +	} +} diff --git a/ao-tools/altosui/AltosParse.java b/ao-tools/altosui/AltosParse.java new file mode 100644 index 00000000..a60dc694 --- /dev/null +++ b/ao-tools/altosui/AltosParse.java @@ -0,0 +1,75 @@ +/* + * 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.text.*; +import java.lang.*; + +public class AltosParse { +	static int parse_int(String v) throws ParseException { +		try { +			return Integer.parseInt(v); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing int " + v, 0); +		} +	} + +	static int parse_hex(String v) throws ParseException { +		try { +			return Integer.parseInt(v, 16); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing hex " + v, 0); +		} +	} + +	static double parse_double(String v) throws ParseException { +		try { +			return Double.parseDouble(v); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing double " + v, 0); +		} +	} + +	static double parse_coord(String coord) throws ParseException { +		String[]	dsf = coord.split("\\D+"); + +		if (dsf.length != 3) { +			throw new ParseException("error parsing coord " + coord, 0); +		} +		int deg = parse_int(dsf[0]); +		int min = parse_int(dsf[1]); +		int frac = parse_int(dsf[2]); + +		double r = deg + (min + frac / 10000.0) / 60.0; +		if (coord.endsWith("S") || coord.endsWith("W")) +			r = -r; +		return r; +	} + +	static String strip_suffix(String v, String suffix) { +		if (v.endsWith(suffix)) +			return v.substring(0, v.length() - suffix.length()); +		return v; +	} + +	static void word(String v, String m) throws ParseException { +		if (!v.equals(m)) { +			throw new ParseException("error matching '" + v + "' '" + m + "'", 0); +		} +	} +} diff --git a/ao-tools/altosui/AltosSerial.java b/ao-tools/altosui/AltosSerial.java new file mode 100644 index 00000000..e4cedde2 --- /dev/null +++ b/ao-tools/altosui/AltosSerial.java @@ -0,0 +1,194 @@ +/* + * 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. + */ + +/* + * Deal with TeleDongle on a serial port + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.LinkedList; +import java.util.Iterator; +import gnu.io.*; +import altosui.AltosSerialMonitor; + +/* + * This class reads from the serial port and places each received + * line in a queue. Dealing with that queue is left up to other + * threads. + */ +class AltosSerialReader implements Runnable { +	InputStream	serial_in; +	LinkedBlockingQueue<String> monitor_queue; +	LinkedBlockingQueue<String> reply_queue; +	Thread input_thread; +	String line; + +	public void run () { +		int c; + +		try { +			for (;;) { +				c = serial_in.read(); +				if (Thread.interrupted()) +					break; +				if (c == -1) +					continue; +				if (c == '\r') +					continue; +				synchronized(this) { +					if (c == '\n') { +						if (line != "") { +							if (line.startsWith("VERSION")) +								monitor_queue.put(line); +							else +								reply_queue.put(line); +							line = ""; +						} +					} else { +						line = line + (char) c; +					} +				} +			} +		} catch (IOException e) { +		} catch (InterruptedException e) { +		} +	} + +	public String get_telem() throws InterruptedException { +		String s = monitor_queue.take(); +		System.out.println(s); +		return s; +	} + +	public String get_reply() throws InterruptedException { +		return reply_queue.take(); +	} + +	public void flush () { +		synchronized(this) { +			if (!"VERSION".startsWith(line) && !line.startsWith("VERSION")) +				line = ""; +			reply_queue.clear(); +		} +	} + +	public boolean opened() { +		return serial_in != null; +	} + +	public void close() { +		if (serial_in != null) { +			try { +				serial_in.close(); +			} catch (IOException e) { +			} +			serial_in = null; +		} +		if (input_thread != null) { +			try { +				input_thread.interrupt(); +				input_thread.join(); +			} catch (InterruptedException e) { +			} +			input_thread = null; +		} +	} + +	public void open(File name) throws FileNotFoundException { +		close(); +		serial_in = new FileInputStream(name); +		input_thread = new Thread(this); +		input_thread.start(); +	} +	public void open(CommPort c) throws IOException { +		close(); +		try { +		c.enableReceiveTimeout(1000);	/* icky. the read method cannot be interrupted */ +		} catch (UnsupportedCommOperationException ee) { +		} +		serial_in = c.getInputStream(); +		input_thread = new Thread(this); +		input_thread.start(); +	} +	public AltosSerialReader () { +		serial_in = null; +		input_thread = null; +		line = ""; +		monitor_queue = new LinkedBlockingQueue<String> (); +		reply_queue = new LinkedBlockingQueue<String> (); +	} + +} + +public class AltosSerial { +	OutputStream serial_out = null; +	AltosSerialReader reader = null; + +	public String get_telem() throws InterruptedException { +		return reader.get_telem(); +	} + +	CommPort comm_port = null; + +	public void close() { +		try { +			serial_out.close(); +		} catch (IOException ee) { +		} +		reader.close(); +		if (comm_port != null) { +			comm_port.close(); +		} +	} + +	public void open(File serial_name) throws FileNotFoundException { +		reader.open(serial_name); +		serial_out = new FileOutputStream(serial_name); +	} + +	public void open(CommPort c) throws IOException { +		reader.open(c); +		serial_out = c.getOutputStream(); +	} + +	public void connect(String port_name) throws IOException, NoSuchPortException, PortInUseException { +		comm_port = new RXTXPort(port_name); +		open(comm_port); +	} + +	void init() { +		reader = new AltosSerialReader(); +	} + +	public AltosSerial() { +		init(); +	} + +	public AltosSerial(File serial_name) throws FileNotFoundException { +		init(); +		open(serial_name); +	} + +	public AltosSerial(CommPort comm_port) throws IOException { +		init(); +		open(comm_port); +	} +} diff --git a/ao-tools/altosui/AltosSerialMonitor.java b/ao-tools/altosui/AltosSerialMonitor.java new file mode 100644 index 00000000..ad0e9295 --- /dev/null +++ b/ao-tools/altosui/AltosSerialMonitor.java @@ -0,0 +1,22 @@ +/* + * 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; + +public interface AltosSerialMonitor { +	void data(String data); +} diff --git a/ao-tools/altosui/AltosState.java b/ao-tools/altosui/AltosState.java new file mode 100644 index 00000000..192011d0 --- /dev/null +++ b/ao-tools/altosui/AltosState.java @@ -0,0 +1,175 @@ +/* + * 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. + */ + +/* + * Track flight state from telemetry data stream + */ + +package altosui; + +import altosui.AltosTelemetry; +import altosui.AltosGPS; + +public class AltosState { +	AltosTelemetry data; + +	/* derived data */ + +	double  report_time; + +	double	time_change; +	int	tick; + +	int	state; +	boolean	ascent;	/* going up? */ + +	double	ground_altitude; +	double	height; +	double	speed; +	double	acceleration; +	double	battery; +	double	temperature; +	double	main_sense; +	double	drogue_sense; +	double	baro_speed; + +	double	max_height; +	double	max_acceleration; +	double	max_speed; + +	AltosGPS	gps; + +	double	pad_lat; +	double	pad_lon; +	double	pad_alt; +	int	npad; +	int	prev_npad; + +	AltosGreatCircle from_pad; + +	double	gps_height; + +	int	speak_tick; +	double	speak_altitude; + +	static double +	aoview_time() +	{ +		return System.currentTimeMillis() / 1000.0; +	} + +	void init (AltosTelemetry cur, AltosState prev_state) { +		int		i; +		AltosTelemetry prev; +		double	accel_counts_per_mss; + +		data = cur; + +		ground_altitude = AltosConvert.cc_barometer_to_altitude(data.ground_pres); +		height = AltosConvert.cc_barometer_to_altitude(data.flight_pres) - ground_altitude; + +		report_time = aoview_time(); + +		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); +		tick = data.tick; +		state = data.state(); + +		if (prev_state != null) { + +			/* Preserve any existing gps data */ +			npad = prev_state.npad; +			gps = prev_state.gps; +			pad_lat = prev_state.pad_lat; +			pad_lon = prev_state.pad_lon; +			pad_alt = prev_state.pad_alt; +			max_height = prev_state.max_height; +			max_acceleration = prev_state.max_acceleration; +			max_speed = prev_state.max_speed; + +			/* make sure the clock is monotonic */ +			while (tick < prev_state.tick) +				tick += 65536; + +			time_change = (tick - prev_state.tick) / 100.0; + +			/* compute barometric speed */ + +			double height_change = height - prev_state.height; +			if (time_change > 0) +				baro_speed = (prev_state.baro_speed * 3 + (height_change / time_change)) / 4.0; +			else +				baro_speed = prev_state.baro_speed; +		} else { +			npad = 0; +			gps = null; +			baro_speed = 0; +			time_change = 0; +		} + +		if (state == AltosTelemetry.ao_flight_pad) { +			if (data.gps != null && data.gps.gps_locked && data.gps.nsat >= 4) { +				npad++; +				if (npad > 1) { +					/* filter pad position */ +					pad_lat = (pad_lat * 31.0 + data.gps.lat) / 32.0; +					pad_lon = (pad_lon * 31.0 + data.gps.lon) / 32.0; +					pad_alt = (pad_alt * 31.0 + data.gps.alt) / 32.0; +				} else { +					pad_lat = data.gps.lat; +					pad_lon = data.gps.lon; +					pad_alt = data.gps.alt; +				} +			} +		} +		ascent = (AltosTelemetry.ao_flight_boost <= state && +			  state <= AltosTelemetry.ao_flight_coast); + +		/* Only look at accelerometer data on the way up */ +		if (ascent && acceleration > max_acceleration) +			max_acceleration = acceleration; +		if (ascent && speed > max_speed) +			max_speed = speed; + +		if (height > max_height) +			max_height = height; +		if (data.gps != null) { +			if (gps == null || !gps.gps_locked || data.gps.gps_locked) +				gps = data.gps; +			if (npad > 0 && gps.gps_locked) +				from_pad = new AltosGreatCircle(pad_lat, pad_lon, gps.lat, gps.lon); +		} +		if (npad > 0) { +			gps_height = gps.alt - pad_alt; +		} else { +			gps_height = 0; +		} +	} + +	public AltosState(AltosTelemetry cur) { +		init(cur, null); +	} + +	public AltosState (AltosTelemetry cur, AltosState prev) { +		init(cur, prev); +	} +} diff --git a/ao-tools/altosui/AltosTelemetry.java b/ao-tools/altosui/AltosTelemetry.java new file mode 100644 index 00000000..e13b42e2 --- /dev/null +++ b/ao-tools/altosui/AltosTelemetry.java @@ -0,0 +1,186 @@ +/* + * 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 altosui.AltosConvert; +import altosui.AltosGPS; + +/* + * Telemetry data contents + */ + + +/* + * The telemetry data stream is a bit of a mess at present, with no consistent + * formatting. In particular, the GPS data is formatted for viewing instead of parsing. + * However, the key feature is that every telemetry line contains all of the information + * necessary to describe the current rocket state, including the calibration values + * for accelerometer and barometer. + * + * GPS unlocked: + * + * VERSION 2 CALL KB0G SERIAL  51 FLIGHT     2 RSSI  -68 STATUS ff STATE     pad  1001 \ + *    a: 16032 p: 21232 t: 20284 v: 25160 d:   204 m:   204 fa: 16038 ga: 16032 fv:       0 \ + *    fp: 21232 gp: 21230 a+: 16049 a-: 16304 GPS  0 sat unlocked SAT 1   15  30 + * + * GPS locked: + * + * VERSION 2 CALL KB0G SERIAL  51 FLIGHT     2 RSSI  -71 STATUS ff STATE     pad  2504 \ + *     a: 16028 p: 21220 t: 20360 v: 25004 d:   208 m:   200 fa: 16031 ga: 16032 fv:     330 \ + *     fp: 21231 gp: 21230 a+: 16049 a-: 16304 \ + *     GPS  9 sat 2010-02-13 17:16:51 35°20.0803'N 106°45.2235'W  1790m  \ + *     0.00m/s(H) 0°     0.00m/s(V) 1.0(hdop)     0(herr)     0(verr) \ + *     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 AltosTelemetry(String line) throws ParseException { +		String[] words = line.split("\\s+"); + +		int	i = 0; + +		AltosParse.word (words[i++], "VERSION"); +		version = AltosParse.parse_int(words[i++]); + +		AltosParse.word (words[i++], "CALL"); +		callsign = words[i++]; + +		AltosParse.word (words[i++], "SERIAL"); +		serial = AltosParse.parse_int(words[i++]); + +		AltosParse.word (words[i++], "FLIGHT"); +		flight = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "RSSI"); +		rssi = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "STATUS"); +		status = AltosParse.parse_hex(words[i++]); + +		AltosParse.word(words[i++], "STATE"); +		state = words[i++]; + +		tick = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "a:"); +		accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "p:"); +		pres = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "t:"); +		temp = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "v:"); +		batt = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "d:"); +		drogue = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "m:"); +		main = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fa:"); +		flight_accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "ga:"); +		ground_accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fv:"); +		flight_vel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fp:"); +		flight_pres = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "gp:"); +		ground_pres = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "a+:"); +		accel_plus_g = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "a-:"); +		accel_minus_g = AltosParse.parse_int(words[i++]); + +		gps = new AltosGPS(words, i); +	} +} diff --git a/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java new file mode 100644 index 00000000..7f008f3a --- /dev/null +++ b/ao-tools/altosui/AltosUI.java @@ -0,0 +1,739 @@ +/* + * 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 gnu.io.*; + +import altosui.AltosSerial; +import altosui.AltosSerialMonitor; +import altosui.AltosTelemetry; +import altosui.AltosState; +import altosui.AltosDeviceDialog; + +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; + +	private AltosFlightStatusTableModel flightStatusModel; +	private JTable flightStatus; + +	static final int info_columns = 3; + +	private AltosFlightInfoTableModel[] flightInfoModel; +	private JTable[] flightInfo; +	private AltosSerial serialLine; +	private Box[] ibox; +	private Box vbox; +	private Box hbox; + +	private Font statusFont = new Font("SansSerif", Font.BOLD, 24); +	private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 14); +	private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 14); + +	public AltosUI() { + +		String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" }; +		Object[][] statusData = { { "0", "pad", "-50", "0" } }; + +		vbox = Box.createVerticalBox(); +		this.add(vbox); + +		flightStatusModel = new AltosFlightStatusTableModel(); +		flightStatus = new JTable(flightStatusModel); +		flightStatus.setFont(statusFont); +		TableColumnModel tcm = flightStatus.getColumnModel(); +		for (int i = 0; i < flightStatusModel.getColumnCount(); i++) { +			DefaultTableCellRenderer       r = new DefaultTableCellRenderer(); +			r.setFont(statusFont); +			r.setHorizontalAlignment(SwingConstants.CENTER); +			tcm.getColumn(i).setCellRenderer(r); +		} + +		FontMetrics	statusMetrics = flightStatus.getFontMetrics(statusFont); +		int statusHeight = (statusMetrics.getHeight() + statusMetrics.getLeading()) * 15 / 10; +		flightStatus.setRowHeight(statusHeight); +		flightStatus.setShowGrid(false); + +		vbox.add(flightStatus); + +		hbox = Box.createHorizontalBox(); +		vbox.add(hbox); + +		flightInfo = new JTable[3]; +		flightInfoModel = new AltosFlightInfoTableModel[3]; +		ibox = new Box[3]; +		FontMetrics	infoValueMetrics = flightStatus.getFontMetrics(infoValueFont); +		int infoHeight = (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 20 / 10; + +		for (int i = 0; i < info_columns; i++) { +			ibox[i] = Box.createVerticalBox(); +			flightInfoModel[i] = new AltosFlightInfoTableModel(); +			flightInfo[i] = new JTable(flightInfoModel[i]); +			flightInfo[i].setFont(infoValueFont); +			flightInfo[i].setRowHeight(infoHeight); +			flightInfo[i].setShowGrid(true); +			ibox[i].add(flightInfo[i].getTableHeader()); +			ibox[i].add(flightInfo[i]); +			hbox.add(ibox[i]); +		} + +		setTitle("AltOS"); + +		createMenu(); + +		serialLine = new AltosSerial(); +		int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); +		this.setSize(new Dimension (infoValueMetrics.charWidth('0') * 6 * 20, +					    statusHeight * 4 + infoHeight * 17)); +		this.validate(); +		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); +		addWindowListener(new WindowAdapter() { +			@Override +			public void windowClosing(WindowEvent e) { +				System.exit(0); +			} +		}); +	} + +	public void info_reset() { +		for (int i = 0; i < info_columns; i++) +			flightInfoModel[i].resetRow(); +	} + +	public void info_add_row(int col, String name, String value) { +		flightInfoModel[col].addRow(name, value); +	} + +	public void info_add_row(int col, String name, String format, Object value) { +		flightInfoModel[col].addRow(name, String.format(format, value)); +	} + +	public void info_add_row(int col, String name, String format, Object v1, Object v2) { +		flightInfoModel[col].addRow(name, String.format(format, v1, v2)); +	} + +	public void info_add_row(int col, String name, String format, Object v1, Object v2, Object v3) { +		flightInfoModel[col].addRow(name, String.format(format, v1, v2, v3)); +	} + +	public void info_add_deg(int col, String name, double v, int pos, int neg) { +		int	c = pos; +		if (v < 0) { +			c = neg; +			v = -v; +		} +		double	deg = Math.floor(v); +		double	min = (v - deg) * 60; + +		flightInfoModel[col].addRow(name, String.format("%3.0f°%08.5f'", deg, min)); +	} + +	public void info_finish() { +		for (int i = 0; i < info_columns; i++) +			flightInfoModel[i].finish(); +	} + +	static final int MIN_PAD_SAMPLES = 10; + +	public void show(AltosState state) { +		flightStatusModel.set(state); + +		info_reset(); +		if (state.npad >= MIN_PAD_SAMPLES) +			info_add_row(0, "Ground state", "%s", "ready"); +		else +			info_add_row(0, "Ground state", "wait (%d)", +				     MIN_PAD_SAMPLES - state.npad); +		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); + +		info_add_row(0, "RSSI", "%6d    dBm", state.data.rssi); +		info_add_row(0, "Height", "%6.0f    m", state.height); +		info_add_row(0, "Max height", "%6.0f    m", state.max_height); +		info_add_row(0, "Acceleration", "%8.1f  m/s²", state.acceleration); +		info_add_row(0, "Max acceleration", "%8.1f  m/s²", state.max_acceleration); +		info_add_row(0, "Speed", "%8.1f  m/s", state.ascent ? state.speed : state.baro_speed); +		info_add_row(0, "Max Speed", "%8.1f  m/s", state.max_speed); +		info_add_row(0, "Temperature", "%9.2f °C", state.temperature); +		info_add_row(0, "Battery", "%9.2f V", state.battery); +		info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense); +		info_add_row(0, "Main", "%9.2f V", state.main_sense); +		info_add_row(0, "Pad altitude", "%6.0f    m", state.ground_altitude); +		if (state.gps == null) { +			info_add_row(1, "GPS", "not available"); +		} else { +			if (state.data.gps.gps_locked) +				info_add_row(1, "GPS", "   locked"); +			else if (state.data.gps.gps_connected) +				info_add_row(1, "GPS", " unlocked"); +			else +				info_add_row(1, "GPS", "  missing"); +			info_add_row(1, "Satellites", "%6d", state.data.gps.nsat); +			info_add_deg(1, "Latitude", state.gps.lat, 'N', 'S'); +			info_add_deg(1, "Longitude", state.gps.lon, 'E', 'W'); +			info_add_row(1, "GPS altitude", "%6d", state.gps.alt); +			info_add_row(1, "GPS height", "%6.0f", state.gps_height); + +			/* The SkyTraq GPS doesn't report these values */ +			if (false) { +				info_add_row(1, "GPS ground speed", "%8.1f m/s %3d°", +					     state.gps.ground_speed, +					     state.gps.course); +				info_add_row(1, "GPS climb rate", "%8.1f m/s", +					     state.gps.climb_rate); +				info_add_row(1, "GPS error", "%6d m(h)%3d m(v)", +					     state.gps.h_error, state.gps.v_error); +			} +			info_add_row(1, "GPS hdop", "%8.1f", state.gps.hdop); + +			if (state.npad > 0) { +				if (state.from_pad != null) { +					info_add_row(1, "Distance from pad", "%6.0f m", state.from_pad.distance); +					info_add_row(1, "Direction from pad", "%6.0f°", state.from_pad.bearing); +				} else { +					info_add_row(1, "Distance from pad", "unknown"); +					info_add_row(1, "Direction from pad", "unknown"); +				} +				info_add_deg(1, "Pad latitude", state.pad_lat, 'N', 'S'); +				info_add_deg(1, "Pad longitude", state.pad_lon, 'E', 'W'); +				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); +			info_add_row(1, "GPS time", "  %02d:%02d:%02d", +				       state.gps.gps_time.hour, +				       state.gps.gps_time.minute, +				       state.gps.gps_time.second); +			int	nsat_vis = 0; +			int	c; + +			if (state.gps.cc_gps_sat == null) +				info_add_row(2, "Satellites Visible", "%4d", 0); +			else { +				info_add_row(2, "Satellites Visible", "%4d", state.gps.cc_gps_sat.length); +				for (c = 0; c < state.gps.cc_gps_sat.length; c++) { +					info_add_row(2, "Satellite id,C/N0", +						     "%4d, %4d", +						     state.gps.cc_gps_sat[c].svid, +						     state.gps.cc_gps_sat[c].c_n0); +				} +			} +		} +		info_finish(); +	} + +	/* User Preferences */ +	Preferences altosui_preferences = Preferences.userNodeForPackage(this.getClass()); + +	/* Log directory */ +	private File logdir = null; + +	/* logdir preference name */ +	final static String logdirPreference = "LOGDIR"; + +	/* Default logdir is ~/TeleMetrum */ +	final static String logdirName = "TeleMetrum"; + +	/* Initialize logdir from preferences */ +	{ +		String logdir_string = altosui_preferences.get(logdirPreference, null); +		if (logdir_string != null) +			logdir = new File(logdir_string); +		else +			/* a hack -- make the file chooser tell us what the default directory +			 * would be and stick our logdir in a subdirectory of that. +			 */ +			logdir = new File(new JFileChooser().getCurrentDirectory(), logdirName); +	} + +	private void set_logdir(File new_logdir) { +		logdir = new_logdir; +		System.out.printf("Set logdir to %s\n", logdir.toString()); +		synchronized (altosui_preferences) { +			altosui_preferences.put(logdirPreference, logdir.getPath()); +			try { +				altosui_preferences.flush(); +			} catch (BackingStoreException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      altosui_preferences.absolutePath(), +							      "Cannot save prefernces", +							      JOptionPane.ERROR_MESSAGE); +			} +		} +	} + +	private boolean check_dir(File dir) { +		if (!dir.exists()) { +			if (!dir.mkdirs()) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      dir.getName(), +							      "Cannot create directory", +							      JOptionPane.ERROR_MESSAGE); +				return false; +			} +		} else if (!dir.isDirectory()) { +			JOptionPane.showMessageDialog(AltosUI.this, +						      dir.getName(), +						      "Is not a directory", +						      JOptionPane.ERROR_MESSAGE); +			return false; +		} +		return true; +	} + +	private void PickSerialDevice() { +		java.util.Enumeration<CommPortIdentifier> port_list = CommPortIdentifier.getPortIdentifiers(); +		while (port_list.hasMoreElements()) { +			CommPortIdentifier identifier = port_list.nextElement(); +			System.out.println("Serial port " + identifier.getName()); +		} +	} + +	class DisplayThread extends Thread { +		String read() throws InterruptedException { return null; } + +		void close() { } + +		void update(AltosState state) throws InterruptedException { } + +		public void run() { +			String		line; +			AltosState	state = null; + +			info_reset(); +			info_finish(); +			try { +				while ((line = read()) != null) { +					try { +						AltosTelemetry	t = new AltosTelemetry(line); +						state = new AltosState(t, state); +						update(state); +						show(state); +					} catch (ParseException pp) { +						System.out.printf("Parse error on %s\n", line); +						System.out.println("exception " + pp); +					} +				} +			} catch (InterruptedException ee) { +			} finally { +				close(); +			} +		} +	} + +	class DeviceThread extends DisplayThread { +		AltosSerial	serial; + +		String read() throws InterruptedException { +			System.out.println("Waiting for telemetry"); +			String s = serial.get_telem(); +			System.out.println("Got telemetry " + s); +			return s; +		} + +		void close() { +			serial.close(); +			System.out.println("DisplayThread done"); +		} + +		public DeviceThread(AltosSerial s) { +			serial = s; +		} +	} + +	private void ConnectToDevice() { +		AltosDevice	device = AltosDeviceDialog.show(AltosUI.this, "TeleDongle"); + +		if (device != null) { +			try { +				serialLine.connect(device.tty); +				DeviceThread thread = new DeviceThread(serialLine); +				run_display(thread); +			} catch (FileNotFoundException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      device.tty, +							      "Cannot open serial port", +							      JOptionPane.ERROR_MESSAGE); +			} catch (NoSuchPortException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      device.tty, +							      "No such serial port", +							      JOptionPane.ERROR_MESSAGE); +			} catch (PortInUseException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      device.tty, +							      "Port in use", +							      JOptionPane.ERROR_MESSAGE); +			} catch (IOException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      device.tty, +							      "Unkonwn I/O error", +							      JOptionPane.ERROR_MESSAGE); +			} +		} +	} + +	void DisconnectFromDevice () { +		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; +		} +		return null; +	} + +	/* +	 * Open an existing telemetry file and replay it in realtime +	 */ + +	class ReplayThread extends DisplayThread { +		FileInputStream	replay; +		String filename; + +		ReplayThread(FileInputStream in, String name) { +			replay = in; +			filename = name; +		} + +		String read() { +			try { +				return readline(replay); +			} catch (IOException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      filename, +							      "error reading", +							      JOptionPane.ERROR_MESSAGE); +			} +			return null; +		} + +		void close () { +			try { +				replay.close(); +			} catch (IOException ee) { +			} +		} + +		void update(AltosState state) throws InterruptedException { +			/* Make it run in realtime after the rocket leaves the pad */ +			if (state.state > AltosTelemetry.ao_flight_pad) +				Thread.sleep((int) (state.time_change * 1000)); +		} +	} + +	Thread		display_thread; + +	private void stop_display() { +		if (display_thread != null && display_thread.isAlive()) +			display_thread.interrupt(); +		display_thread = null; +	} + +	private void run_display(Thread thread) { +		stop_display(); +		display_thread = thread; +		display_thread.start(); +	} + +	/* +	 * Replay a flight from telemetry data +	 */ +	private void Replay() { +		JFileChooser	logfile_chooser = new JFileChooser(); + +		logfile_chooser.setDialogTitle("Select Telemetry File"); +		logfile_chooser.setFileFilter(new FileNameExtensionFilter("Telemetry file", "telem")); +		logfile_chooser.setCurrentDirectory(logdir); +		int returnVal = logfile_chooser.showOpenDialog(AltosUI.this); + +		if (returnVal == JFileChooser.APPROVE_OPTION) { +			File file = logfile_chooser.getSelectedFile(); +			if (file == null) +				System.out.println("No file selected?"); +			String	filename = file.getName(); +			try { +				FileInputStream	replay = new FileInputStream(file); +				ReplayThread	thread = new ReplayThread(replay, filename); +				run_display(thread); +			} catch (FileNotFoundException ee) { +				JOptionPane.showMessageDialog(AltosUI.this, +							      filename, +							      "Cannot open serial port", +							      JOptionPane.ERROR_MESSAGE); +			} +		} +	} + +	/* Connect to TeleMetrum, either directly or through +	 * a TeleDongle over the packet link +	 */ +	private void SaveFlightData() { +	} + +	/* Configure the log directory. This is where all telemetry and eeprom files +	 * will be written to, and where replay will look for telemetry files +	 */ +	private void ConfigureLog() { +		JFileChooser	logdir_chooser = new JFileChooser(); + +		logdir_chooser.setDialogTitle("Configure Data Logging Directory"); +		logdir_chooser.setCurrentDirectory(logdir.getParentFile()); +		logdir_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + +		if (logdir_chooser.showDialog(AltosUI.this, "Select Directory") == JFileChooser.APPROVE_OPTION) { +			File dir = logdir_chooser.getSelectedFile(); +			if (check_dir(dir)) +				set_logdir(dir); +		} +	} + +	/* Create the AltosUI menus +	 */ +	private void createMenu() { +		JMenuBar menubar = new JMenuBar(); +		JMenu menu; +		JMenuItem item; +		JRadioButtonMenuItem radioitem; + +		// File menu +		{ +			menu = new JMenu("File"); +			menu.setMnemonic(KeyEvent.VK_F); +			menubar.add(menu); + +			item = new JMenuItem("Quit",KeyEvent.VK_Q); +			item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, +								   ActionEvent.CTRL_MASK)); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						System.exit(0); +					} +				}); +			menu.add(item); +		} + +		// Device menu +		{ +			menu = new JMenu("Device"); +			menu.setMnemonic(KeyEvent.VK_D); +			menubar.add(menu); + +			item = new JMenuItem("Connect to Device",KeyEvent.VK_C); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						ConnectToDevice(); +					} +				}); +			menu.add(item); + +			item = new JMenuItem("Disconnect from Device",KeyEvent.VK_D); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						DisconnectFromDevice(); +					} +				}); +			menu.add(item); + +			menu.addSeparator(); + +			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("Replay",KeyEvent.VK_R); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						Replay(); +					} +				}); +			menu.add(item); +		} +		// Log menu +		{ +			menu = new JMenu("Log"); +			menu.setMnemonic(KeyEvent.VK_L); +			menubar.add(menu); + +			item = new JMenuItem("New Log",KeyEvent.VK_N); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +					} +				}); +			menu.add(item); + +			item = new JMenuItem("Configure Log",KeyEvent.VK_C); +			item.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +						ConfigureLog(); +					} +				}); +			menu.add(item); +		} +		// Voice menu +		{ +			menu = new JMenu("Voice", true); +			menu.setMnemonic(KeyEvent.VK_V); +			menubar.add(menu); + +			radioitem = new JRadioButtonMenuItem("Enable Voice"); +			radioitem.addActionListener(new ActionListener() { +					public void actionPerformed(ActionEvent e) { +					} +				}); +			menu.add(radioitem); +		} + +		// 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 == 0); +				radioitem.setActionCommand(String.format("%d", c)); +				radioitem.addActionListener(new ActionListener() { +						public void actionPerformed(ActionEvent e) { +							System.out.println("Command: " + e.getActionCommand() + " param: " + +									   e.paramString()); +						} +					}); +				menu.add(radioitem); +				group.add(radioitem); +			} +		} + +		this.setJMenuBar(menubar); + +	} +	public static void main(final String[] args) { +		AltosUI altosui = new AltosUI(); +		altosui.setVisible(true); +	} +}
\ No newline at end of file diff --git a/ao-tools/altosui/Makefile b/ao-tools/altosui/Makefile new file mode 100644 index 00000000..fbe0a5eb --- /dev/null +++ b/ao-tools/altosui/Makefile @@ -0,0 +1,34 @@ +.SUFFIXES: .java .class + +CLASSPATH=..:/usr/share/java/* +CLASSFILES=\ +	AltosConvert.class \ +	AltosGPS.class \ +	AltosGreatCircle.class \ +	AltosParse.class \ +	AltosSerialMonitor.class \ +	AltosSerial.class \ +	AltosState.class \ +	AltosTelemetry.class \ +	AltosUI.class \ +	AltosDevice.class \ +	AltosDeviceLinux.class \ +	AltosDeviceDialog.class + +JAVAFLAGS=-Xlint:unchecked + +all: $(CLASSFILES) altosui altosui.jar + +.java.class: +	javac -cp "$(CLASSPATH)" $(JAVAFLAGS) $*.java + +altosui: Makefile +	(echo '#!/bin/sh'; \ +	echo exec java -cp '"$(CLASSPATH)"' altosui/AltosUI) > $@ +	chmod +x $@ + +altosui.jar: $(CLASSFILES) Manifest.txt +	cd .. && jar cfm altosui/$@ altosui/Manifest.txt altosui/*.class + +clean: +	rm -f *.class diff --git a/ao-tools/altosui/Manifest.txt b/ao-tools/altosui/Manifest.txt new file mode 100644 index 00000000..0305fcfb --- /dev/null +++ b/ao-tools/altosui/Manifest.txt @@ -0,0 +1 @@ +Main-Class: altosui.AltosUI diff --git a/src/Makefile b/src/Makefile index e2699ee6..24f562e1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,7 +4,7 @@  #  CC=sdcc -SUBDIRS=telemetrum-v0.2 teledongle-v0.2 telemetrum-v0.1-sky telemetrum-v0.1-sirf teledongle-v0.1 tidongle test +SUBDIRS=telemetrum-v1.0 teledongle-v0.2 telemetrum-v0.1-sky telemetrum-v0.1-sirf teledongle-v0.1 tidongle test  all: all-recursive @@ -43,7 +43,7 @@ struct ao_task {  	__xdata void *wchan;		/* current wait channel (NULL if running) */  	uint16_t alarm;			/* abort ao_sleep time */  	uint8_t	stack_count;		/* amount of saved stack */ -	uint8_t task_id;		/* index in the task array */ +	uint8_t task_id;		/* unique id */  	__code char *name;		/* task name */  	uint8_t	stack[AO_STACK_SIZE];	/* saved stack */  }; diff --git a/src/ao_monitor.c b/src/ao_monitor.c index 628b6e67..f2f3fc2e 100644 --- a/src/ao_monitor.c +++ b/src/ao_monitor.c @@ -80,7 +80,8 @@ ao_set_monitor(uint8_t monitoring)  {  	ao_monitoring = monitoring;  	ao_wakeup(&ao_monitoring); -	ao_radio_abort(); +	if (!ao_monitoring) +		ao_radio_abort();  }  static void diff --git a/src/ao_pins.h b/src/ao_pins.h index 771d9c7f..e9a265b0 100644 --- a/src/ao_pins.h +++ b/src/ao_pins.h @@ -18,7 +18,7 @@  #ifndef _AO_PINS_H_  #define _AO_PINS_H_ -#if defined(TELEMETRUM_V_0_2) +#if defined(TELEMETRUM_V_1_0)  	#define HAS_SERIAL_1		1  	#define HAS_ADC			1  	#define HAS_EEPROM		1 diff --git a/src/ao_radio.c b/src/ao_radio.c index bb19c6a9..0849349e 100644 --- a/src/ao_radio.c +++ b/src/ao_radio.c @@ -317,8 +317,8 @@ ao_radio_idle(void)  {  	if (RF_MARCSTATE != RF_MARCSTATE_IDLE)  	{ -		RFST = RFST_SIDLE;  		do { +			RFST = RFST_SIDLE;  			ao_yield();  		} while (RF_MARCSTATE != RF_MARCSTATE_IDLE);  	} diff --git a/src/ao_task.c b/src/ao_task.c index 4a78766e..72c9d7d6 100644 --- a/src/ao_task.c +++ b/src/ao_task.c @@ -28,10 +28,19 @@ void  ao_add_task(__xdata struct ao_task * task, void (*start)(void), __code char *name) __reentrant  {  	uint8_t	__xdata *stack; +	uint8_t task_id; +	uint8_t t;  	if (ao_num_tasks == AO_NUM_TASKS)  		ao_panic(AO_PANIC_NO_TASK); +	for (task_id = 1; task_id != 0; task_id++) { +		for (t = 0; t < ao_num_tasks; t++) +			if (ao_tasks[t]->task_id == task_id) +				break; +		if (t == ao_num_tasks) +			break; +	}  	ao_tasks[ao_num_tasks++] = task; -	task->task_id = ao_num_tasks; +	task->task_id = task_id;  	task->name = name;  	/*  	 * Construct a stack frame so that it will 'return' diff --git a/src/telemetrum-v0.2/.gitignore b/src/telemetrum-v1.0/.gitignore index 76228093..76228093 100644 --- a/src/telemetrum-v0.2/.gitignore +++ b/src/telemetrum-v1.0/.gitignore diff --git a/src/telemetrum-v0.2/Makefile b/src/telemetrum-v1.0/Makefile index d8867b19..d8867b19 100644 --- a/src/telemetrum-v0.2/Makefile +++ b/src/telemetrum-v1.0/Makefile diff --git a/src/telemetrum-v0.2/Makefile.defs b/src/telemetrum-v1.0/Makefile.defs index eb11e7dc..e3cc5eb2 100644 --- a/src/telemetrum-v0.2/Makefile.defs +++ b/src/telemetrum-v1.0/Makefile.defs @@ -1,4 +1,4 @@ -PROG = telemetrum-v0.2.ihx +PROG = telemetrum-v1.0.ihx  SRC = \  	$(TM_BASE_SRC) \ @@ -6,5 +6,5 @@ SRC = \  	$(SKY_DRIVER_SRC) \  	$(DBG_SRC) -PRODUCT=TeleMetrum-v0.2 -PRODUCT_DEF=-DTELEMETRUM_V_0_2 +PRODUCT=TeleMetrum-v1.0 +PRODUCT_DEF=-DTELEMETRUM_V_1_0 diff --git a/telemetrum.inf b/telemetrum.inf new file mode 100755 index 00000000..4549cfba --- /dev/null +++ b/telemetrum.inf @@ -0,0 +1,42 @@ +; Copyright (C) 2010 Keith Packard (keithp@keithp.com)
 +; released under GNU General Public License version 2
 +
 +[Version]
 +Signature	= "$Windows NT$"
 +Class           = Ports
 +ClassGUID       = {4d36e978-e325-11ce-bfc1-08002be10318}
 +Provider	= %Mfg%
 +DriverVer	= 30/03/2010,4.0.0.8
 +
 +[Manufacturer]
 +%Mfg%           = Models
 +
 +[Models]
 +%TeleMetrum%    = TELEMETRUM, USB\VID_FFFE&PID_000A
 +
 +[DestinationDirs]
 +DefaultDestDir  = 12
 +
 +[ControlFlags]
 +ExcludeFromSelect=USB\VID_0BAF&PID_0303
 +
 +[Strings]
 +Mfg = "altusmetrum.org"
 +TeleMetrum       = "TeleMetrum/TeleDongle"
 +
 +[TELEMETRUM.NT]
 +include=mdmcpq.inf
 +CopyFiles=FakeModemCopyFileSection
 +AddReg=Uninstall
 +
 +[TELEMETRUM.NT.HW]
 +Include=mdmcpq.inf
 +AddReg=LowerFilterAddReg
 +
 +[TELEMETRUM.NT.Services]
 +Include=mdmcpq.inf
 +AddService=usbser, 0x00000000, LowerFilter_Service_Inst
 +
 +[Uninstall]
 +HKLM,Software\Microsoft\Windows\CurrentVersion\Uninstall\%TeleMetrum%,DisplayName,,"%TeleMetrum%"
 +
  | 
