diff options
| author | Keith Packard <keithp@keithp.com> | 2012-01-04 20:28:42 -0800 | 
|---|---|---|
| committer | Keith Packard <keithp@keithp.com> | 2012-06-02 19:37:29 -0700 | 
| commit | 81465a20049da40cd8d3cda920d6585ffe87bfe3 (patch) | |
| tree | fa5ebc28e4dac0670ca16979c3ce1199e30f571e /altoslib/src | |
| parent | d6df16525927d8096d1c0cccf4c86bf4c6599d53 (diff) | |
altosui: Move java altoslib to top level
This will be shared with other (android) java code.
Signed-off-by: Keith Packard <keithp@keithp.com>
Diffstat (limited to 'altoslib/src')
38 files changed, 5368 insertions, 0 deletions
| diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosCRCException.java b/altoslib/src/org/altusmetrum/AltosLib/AltosCRCException.java new file mode 100644 index 00000000..101c5363 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosCRCException.java @@ -0,0 +1,26 @@ +/* + * 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 org.altusmetrum.AltosLib; + +public class AltosCRCException extends Exception { +	public int rssi; + +	public AltosCRCException (int in_rssi) { +		rssi = in_rssi; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosConfigData.java b/altoslib/src/org/altusmetrum/AltosLib/AltosConfigData.java new file mode 100644 index 00000000..4ad4e58a --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosConfigData.java @@ -0,0 +1,184 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosConfigData implements Iterable<String> { + +	/* Version information */ +	public String	manufacturer; +	public String	product; +	public String	version; +	public int	log_format; +	public int	serial; + +	/* Strings returned */ +	public LinkedList<String>	lines; + +	/* Config information */ +	public int	config_major; +	public int	config_minor; +	public int	main_deploy; +	public int	apogee_delay; +	public int	radio_channel; +	public int	radio_setting; +	public int	radio_frequency; +	public String	callsign; +	public int	accel_cal_plus, accel_cal_minus; +	public int	radio_calibration; +	public int	flight_log_max; +	public int	ignite_mode; +	public int	stored_flight; +	public int	storage_size; +	public int	storage_erase_unit; + +	public static String get_string(String line, String label) throws  ParseException { +		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); +			return quoted; +		} +		throw new ParseException("mismatch", 0); +	} + +	public static int get_int(String line, String label) throws NumberFormatException, ParseException { +		if (line.startsWith(label)) { +			String tail = line.substring(label.length()).trim(); +			String[] tokens = tail.split("\\s+"); +			if (tokens.length > 0) +				return  Integer.parseInt(tokens[0]); +		} +		throw new ParseException("mismatch", 0); +	} + +	public Iterator<String> iterator() { +		return lines.iterator(); +	} + +	public int log_available() { +		switch (log_format) { +		case AltosLib.AO_LOG_FORMAT_TINY: +			if (stored_flight == 0) +				return 1; +			return 0; +		default: +			if (flight_log_max <= 0) +				return 1; +			int	log_space = storage_size - storage_erase_unit; +			int	log_used = stored_flight * flight_log_max; + +			if (log_used >= log_space) +				return 0; +			return (log_space - log_used) / flight_log_max; +		} +	} + +	int[] parse_version(String v) { +		String[] parts = v.split("\\."); +		int r[] = new int[parts.length]; + +		for (int i = 0; i < parts.length; i++) { +			try { +				r[i] = Altos.fromdec(parts[i]); +			} catch (NumberFormatException n) { +				r[i] = 0; +			} +		} + +		return r; +	} +	 +	public int compare_version(String other) { +		int[]	me = parse_version(version); +		int[]	them = parse_version(other); + +		int	l = Math.min(me.length, them.length); + +		for (int i = 0; i < l; i++) { +			int	d = me[i] - them[i]; +			if (d != 0) +				return d; +		} +		if (me.length > l) +			return 1; +		if (them.length > l) +			return -1; +		return 0; +	} + +	public AltosConfigData(AltosLink link) throws InterruptedException, TimeoutException { +		link.printf("c s\np\nf\nl\nv\n"); +		lines = new LinkedList<String>(); +		radio_setting = 0; +		radio_frequency = 0; +		stored_flight = 0; +		for (;;) { +			String line = link.get_reply(); +			if (line == null) +				throw new TimeoutException(); +			if (line.contains("Syntax error")) +				continue; +			lines.add(line); +			try { serial = get_int(line, "serial-number"); } catch (Exception e) {} +			try { log_format = get_int(line, "log-format"); } catch (Exception e) {} +			try { main_deploy = get_int(line, "Main deploy:"); } catch (Exception e) {} +			try { apogee_delay = get_int(line, "Apogee delay:"); } catch (Exception e) {} +			try { radio_channel = get_int(line, "Radio channel:"); } catch (Exception e) {} +			try { radio_setting = get_int(line, "Radio setting:"); } catch (Exception e) {} +			try { +				radio_frequency = get_int(line, "Frequency:"); +				if (radio_frequency < 0) +					radio_frequency = 434550; +			} catch (Exception e) {} +			try { +				if (line.startsWith("Accel cal")) { +					String[] bits = line.split("\\s+"); +					if (bits.length >= 6) { +						accel_cal_plus = Integer.parseInt(bits[3]); +						accel_cal_minus = Integer.parseInt(bits[5]); +					} +				} +			} catch (Exception e) {} +			try { radio_calibration = get_int(line, "Radio cal:"); } catch (Exception e) {} +			try { flight_log_max = get_int(line, "Max flight log:"); } catch (Exception e) {} +			try { ignite_mode = get_int(line, "Ignite mode:"); } catch (Exception e) {} +			try { callsign = get_string(line, "Callsign:"); } catch (Exception e) {} +			try { version = get_string(line,"software-version"); } catch (Exception e) {} +			try { product = get_string(line,"product"); } catch (Exception e) {} + +			try { get_int(line, "flight"); stored_flight++; }  catch (Exception e) {} +			try { storage_size = get_int(line, "Storage size:"); } catch (Exception e) {} +			try { storage_erase_unit = get_int(line, "Storage erase unit"); } catch (Exception e) {} + +			/* signals the end of the version info */ +			if (line.startsWith("software-version")) +				break; +		} +	} + +}
\ No newline at end of file diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosConvert.java b/altoslib/src/org/altusmetrum/AltosLib/AltosConvert.java new file mode 100644 index 00000000..3527b575 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosConvert.java @@ -0,0 +1,259 @@ +/* + * 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 org.altusmetrum.AltosLib; + +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). +	 */ + +	public static final double GRAVITATIONAL_ACCELERATION = -9.80665; +	public static final double AIR_GAS_CONSTANT		= 287.053; +	public static final double NUMBER_OF_LAYERS		= 7; +	public static final double MAXIMUM_ALTITUDE		= 84852.0; +	public static final double MINIMUM_PRESSURE		= 0.3734; +	public static final double LAYER0_BASE_TEMPERATURE	= 288.15; +	public static final double LAYER0_BASE_PRESSURE	= 101325; + +	/* lapse rate and base altitude for each layer in the atmosphere */ +	public static final double[] lapse_rate = { +		-0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002 +	}; + +	public 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 +	 */ +	public static double +	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 */ +	public static double +	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; +	} + +	public static double +	cc_battery_to_voltage(double battery) +	{ +		return battery / 32767.0 * 5.0; +	} + +	public static double +	cc_ignitor_to_voltage(double ignite) +	{ +		return ignite / 32767 * 15.0; +	} + +	public static double radio_to_frequency(int freq, int setting, int cal, int channel) { +		double	f; + +		if (freq > 0) +			f = freq / 1000.0; +		else { +			if (setting <= 0) +				setting = cal; +			f = 434.550 * setting / cal; +			/* Round to nearest 50KHz */ +			f = Math.floor (20.0 * f + 0.5) / 20.0; +		} +		return f + channel * 0.100; +	} + +	public static int radio_frequency_to_setting(double frequency, int cal) { +		double	set = frequency / 434.550 * cal; + +		return (int) Math.floor (set + 0.5); +	} + +	public static int radio_frequency_to_channel(double frequency) { +		int	channel = (int) Math.floor ((frequency - 434.550) / 0.100 + 0.5); + +		if (channel < 0) +			channel = 0; +		if (channel > 9) +			channel = 9; +		return channel; +	} + +	public static double radio_channel_to_frequency(int channel) { +		return 434.550 + channel * 0.100; +	} + +	public static int[] ParseHex(String line) { +		String[] tokens = line.split("\\s+"); +		int[] array = new int[tokens.length]; + +		for (int i = 0; i < tokens.length; i++) +			try { +				array[i] = Integer.parseInt(tokens[i], 16); +			} catch (NumberFormatException ne) { +				return null; +			} +		return array; +	} + +	public static double meters_to_feet(double meters) { +		return meters * (100 / (2.54 * 12)); +	} + +	public static double meters_to_mach(double meters) { +		return meters / 343;		/* something close to mach at usual rocket sites */ +	} + +	public static double meters_to_g(double meters) { +		return meters / 9.80665; +	} + +	public static int checksum(int[] data, int start, int length) { +		int	csum = 0x5a; +		for (int i = 0; i < length; i++) +			csum += data[i + start]; +		return csum & 0xff; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosEepromChunk.java b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromChunk.java new file mode 100644 index 00000000..6d889723 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromChunk.java @@ -0,0 +1,102 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.concurrent.*; + +public class AltosEepromChunk { + +	public static final int	chunk_size = 256; +	public static final int	per_line = 8; + +	public int		data[]; +	public int		address; +	public ParseException	parse_exception = null; + +	int[] ParseHex(String line) { +		String[] tokens = line.split("\\s+"); +		int[] array = new int[tokens.length]; + +		for (int i = 0; i < tokens.length; i++) +			try { +				array[i] = Integer.parseInt(tokens[i], 16); +			} catch (NumberFormatException ne) { +				return null; +			} +		return array; +	} + +	public int data(int offset) { +		return data[offset]; +	} + +	public int data16(int offset) { +		return data[offset] | (data[offset + 1] << 8); +	} + +	public int data32(int offset) { +		return data[offset] | (data[offset + 1] << 8) | +			(data[offset+2] << 16) | (data[offset+3] << 24); +	} + +	public boolean erased(int start, int len) { +		for (int i = 0; i < len; i++) +			if (data[start+i] != 0xff) +				return false; +		return true; +	} + +	public AltosEepromChunk(AltosLink link, int block, boolean flush) +		throws TimeoutException, InterruptedException { + +		int	offset; + +		data = new int[chunk_size]; +		address = block * chunk_size; +		if (flush) +			link.flush_input(); +		link.printf("e %x\n", block); + +		for (offset = 0; offset < chunk_size; offset += per_line) { +			try { +				String	line = link.get_reply(5000); + +				if (line == null) +					throw new TimeoutException(); + +				int[] values = ParseHex(line); + +				if (values == null || values.length != per_line + 1) +					throw new ParseException(String.format("invalid line %s", line), 0); +				if (values[0] != offset) +					throw new ParseException(String.format("data address out of sync at 0x%x", +									       address + offset), 0); +				for (int i = 0; i < per_line; i++) +					data[offset + i] = values[1 + i]; +			} catch (ParseException pe) { +				for (int i = 0; i < per_line; i++) +					data[offset + i] = 0xff; +				if (parse_exception == null) +					parse_exception = pe; +			} +		} +	} +}
\ No newline at end of file diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosEepromIterable.java b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromIterable.java new file mode 100644 index 00000000..f1397c7b --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromIterable.java @@ -0,0 +1,475 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * 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> { + +	public int	index; + +	public AltosOrderedRecord(String line, int in_index, int prev_tick, boolean prev_tick_valid) +		throws ParseException { +		super(line); +		if (prev_tick_valid) { +			tick |= (prev_tick & ~0xffff); +			if (tick < prev_tick) { +				if (prev_tick - tick > 0x8000) +					tick += 0x10000; +			} else { +				if (tick - prev_tick > 0x8000) +					tick -= 0x10000; +			} +		} +		index = in_index; +	} + +	public AltosOrderedRecord(int in_cmd, int in_tick, int in_a, int in_b, int in_index) { +		super(in_cmd, in_tick, in_a, in_b); +		index = in_index; +	} + +	public String toString() { +		return String.format("%d.%d %04x %04x %04x", +				     cmd, index, tick, a, b); +	} + +	public int compareTo(AltosOrderedRecord o) { +		int	tick_diff = tick - o.tick; +		if (tick_diff != 0) +			return tick_diff; +		return index - o.index; +	} +} + +public class AltosEepromIterable extends AltosRecordIterable { + +	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; + +	boolean			has_accel; +	boolean			has_gps; +	boolean			has_ignite; + +	AltosEepromRecord	flight_record; +	AltosEepromRecord	gps_date_record; + +	TreeSet<AltosOrderedRecord>	records; + +	LinkedList<AltosRecord>	list; + +	class EepromState { +		int	seen; +		int	n_pad_samples; +		double	ground_pres; +		int	gps_tick; +		int	boost_tick; +		int	sensor_tick; + +		EepromState() { +			seen = 0; +			n_pad_samples = 0; +			ground_pres = 0.0; +			gps_tick = 0; +		} +	} + +	void update_state(AltosRecord state, AltosEepromRecord record, EepromState eeprom) { +		state.tick = record.tick; +		switch (record.cmd) { +		case AltosLib.AO_LOG_FLIGHT: +			eeprom.seen |= seen_flight; +			state.ground_accel = record.a; +			state.flight_accel = record.a; +			state.flight = record.b; +			eeprom.boost_tick = record.tick; +			break; +		case AltosLib.AO_LOG_SENSOR: +			state.accel = record.a; +			state.pres = record.b; +			if (state.state < AltosLib.ao_flight_boost) { +				eeprom.n_pad_samples++; +				eeprom.ground_pres += state.pres; +				state.ground_pres = (int) (eeprom.ground_pres / eeprom.n_pad_samples); +				state.flight_pres = state.ground_pres; +			} else { +				state.flight_pres = (state.flight_pres * 15 + state.pres) / 16; +			} +			state.flight_accel = (state.flight_accel * 15 + state.accel) / 16; +			if ((eeprom.seen & seen_sensor) == 0) +				eeprom.sensor_tick = record.tick - 1; +			state.flight_vel += (state.accel_plus_g - state.accel) * (record.tick - eeprom.sensor_tick); +			eeprom.seen |= seen_sensor; +			eeprom.sensor_tick = record.tick; +			has_accel = true; +			break; +		case AltosLib.AO_LOG_PRESSURE: +			state.pres = record.b; +			state.flight_pres = state.pres; +			if (eeprom.n_pad_samples == 0) { +				eeprom.n_pad_samples++; +				state.ground_pres = state.pres; +			} +			eeprom.seen |= seen_sensor; +			break; +		case AltosLib.AO_LOG_TEMP_VOLT: +			state.temp = record.a; +			state.batt = record.b; +			eeprom.seen |= seen_temp_volt; +			break; +		case AltosLib.AO_LOG_DEPLOY: +			state.drogue = record.a; +			state.main = record.b; +			eeprom.seen |= seen_deploy; +			has_ignite = true; +			break; +		case AltosLib.AO_LOG_STATE: +			state.state = record.a; +			break; +		case AltosLib.AO_LOG_GPS_TIME: +			eeprom.gps_tick = state.tick; +			AltosGPS old = state.gps; +			state.gps = new AltosGPS(); + +			/* GPS date doesn't get repeated through the file */ +			if (old != null) { +				state.gps.year = old.year; +				state.gps.month = old.month; +				state.gps.day = old.day; +			} +			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 & AltosLib.AO_GPS_RUNNING) != 0; +			state.gps.locked = (flags & AltosLib.AO_GPS_VALID) != 0; +			state.gps.nsat = (flags & AltosLib.AO_GPS_NUM_SAT_MASK) >> +				AltosLib.AO_GPS_NUM_SAT_SHIFT; +			state.new_gps = true; +			has_gps = true; +			break; +		case AltosLib.AO_LOG_GPS_LAT: +			int lat32 = record.a | (record.b << 16); +			state.gps.lat = (double) lat32 / 1e7; +			break; +		case AltosLib.AO_LOG_GPS_LON: +			int lon32 = record.a | (record.b << 16); +			state.gps.lon = (double) lon32 / 1e7; +			break; +		case AltosLib.AO_LOG_GPS_ALT: +			state.gps.alt = record.a; +			break; +		case AltosLib.AO_LOG_GPS_SAT: +			if (state.tick == eeprom.gps_tick) { +				int svid = record.a; +				int c_n0 = record.b >> 8; +				state.gps.add_sat(svid, c_n0); +			} +			break; +		case AltosLib.AO_LOG_GPS_DATE: +			state.gps.year = (record.a & 0xff) + 2000; +			state.gps.month = record.a >> 8; +			state.gps.day = record.b & 0xff; +			break; + +		case AltosLib.AO_LOG_CONFIG_VERSION: +			break; +		case AltosLib.AO_LOG_MAIN_DEPLOY: +			break; +		case AltosLib.AO_LOG_APOGEE_DELAY: +			break; +		case AltosLib.AO_LOG_RADIO_CHANNEL: +			break; +		case AltosLib.AO_LOG_CALLSIGN: +			state.callsign = record.data; +			break; +		case AltosLib.AO_LOG_ACCEL_CAL: +			state.accel_plus_g = record.a; +			state.accel_minus_g = record.b; +			break; +		case AltosLib.AO_LOG_RADIO_CAL: +			break; +		case AltosLib.AO_LOG_MANUFACTURER: +			break; +		case AltosLib.AO_LOG_PRODUCT: +			break; +		case AltosLib.AO_LOG_SERIAL_NUMBER: +			state.serial = record.a; +			break; +		case AltosLib.AO_LOG_SOFTWARE_VERSION: +			break; +		} +		state.seen |= eeprom.seen; +	} + +	LinkedList<AltosRecord> make_list() { +		LinkedList<AltosRecord>		list = new LinkedList<AltosRecord>(); +		Iterator<AltosOrderedRecord>	iterator = records.iterator(); +		AltosOrderedRecord		record = null; +		AltosRecord			state = new AltosRecord(); +		boolean				last_reported = false; +		EepromState			eeprom = new EepromState(); + +		state.state = AltosLib.ao_flight_pad; +		state.accel_plus_g = 15758; +		state.accel_minus_g = 16294; + +		/* Pull in static data from the flight and gps_date records */ +		if (flight_record != null) +			update_state(state, flight_record, eeprom); +		if (gps_date_record != null) +			update_state(state, gps_date_record, eeprom); + +		while (iterator.hasNext()) { +			record = iterator.next(); +			if ((eeprom.seen & seen_basic) == seen_basic && record.tick != state.tick) { +				AltosRecord r = new AltosRecord(state); +				r.time = (r.tick - eeprom.boost_tick) / 100.0; +				list.add(r); +			} +			update_state(state, record, eeprom); +		} +		AltosRecord r = new AltosRecord(state); +		r.time = (r.tick - eeprom.boost_tick) / 100.0; +		list.add(r); +	return list; +	} + +	public Iterator<AltosRecord> iterator() { +		if (list == null) +			list = make_list(); +		return list.iterator(); +	} + +	public boolean has_gps() { return has_gps; } +	public boolean has_accel() { return has_accel; } +	public boolean has_ignite() { return has_ignite; } + +	public void write_comments(PrintStream out) { +		Iterator<AltosOrderedRecord>	iterator = records.iterator(); +		out.printf("# Comments\n"); +		while (iterator.hasNext()) { +			AltosOrderedRecord	record = iterator.next(); +			switch (record.cmd) { +			case AltosLib.AO_LOG_CONFIG_VERSION: +				out.printf("# Config version: %s\n", record.data); +				break; +			case AltosLib.AO_LOG_MAIN_DEPLOY: +				out.printf("# Main deploy: %s\n", record.a); +				break; +			case AltosLib.AO_LOG_APOGEE_DELAY: +				out.printf("# Apogee delay: %s\n", record.a); +				break; +			case AltosLib.AO_LOG_RADIO_CHANNEL: +				out.printf("# Radio channel: %s\n", record.a); +				break; +			case AltosLib.AO_LOG_CALLSIGN: +				out.printf("# Callsign: %s\n", record.data); +				break; +			case AltosLib.AO_LOG_ACCEL_CAL: +				out.printf ("# Accel cal: %d %d\n", record.a, record.b); +				break; +			case AltosLib.AO_LOG_RADIO_CAL: +				out.printf ("# Radio cal: %d\n", record.a); +				break; +			case AltosLib.AO_LOG_MAX_FLIGHT_LOG: +				out.printf ("# Max flight log: %d\n", record.a); +				break; +			case AltosLib.AO_LOG_MANUFACTURER: +				out.printf ("# Manufacturer: %s\n", record.data); +				break; +			case AltosLib.AO_LOG_PRODUCT: +				out.printf ("# Product: %s\n", record.data); +				break; +			case AltosLib.AO_LOG_SERIAL_NUMBER: +				out.printf ("# Serial number: %d\n", record.a); +				break; +			case AltosLib.AO_LOG_SOFTWARE_VERSION: +				out.printf ("# Software version: %s\n", record.data); +				break; +			case Altos.AO_LOG_BARO_RESERVED: +				out.printf ("# Baro reserved: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_SENS: +				out.printf ("# Baro sens: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_OFF: +				out.printf ("# Baro off: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_TCS: +				out.printf ("# Baro tcs: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_TCO: +				out.printf ("# Baro tco: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_TREF: +				out.printf ("# Baro tref: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_TEMPSENS: +				out.printf ("# Baro tempsens: %d\n", record.a); +				break; +			case Altos.AO_LOG_BARO_CRC: +				out.printf ("# Baro crc: %d\n", record.a); +				break; +			} +		} +	} + +	/* +	 * Given an AO_LOG_GPS_TIME record with correct time, and one +	 * missing time, rewrite the missing time values with the good +	 * ones, assuming that the difference between them is 'diff' seconds +	 */ +	void update_time(AltosOrderedRecord good, AltosOrderedRecord bad) { + +		int diff = (bad.tick - good.tick + 50) / 100; + +		int hour = (good.a & 0xff); +		int minute = (good.a >> 8); +		int second = (good.b & 0xff); +		int flags = (good.b >> 8); +		int seconds = hour * 3600 + minute * 60 + second; + +		/* Make sure this looks like a good GPS value */ +		if ((flags & AltosLib.AO_GPS_NUM_SAT_MASK) >> AltosLib.AO_GPS_NUM_SAT_SHIFT < 4) +			flags = (flags & ~AltosLib.AO_GPS_NUM_SAT_MASK) | (4 << AltosLib.AO_GPS_NUM_SAT_SHIFT); +		flags |= AltosLib.AO_GPS_RUNNING; +		flags |= AltosLib.AO_GPS_VALID; + +		int new_seconds = seconds + diff; +		if (new_seconds < 0) +			new_seconds += 24 * 3600; +		int new_second = (new_seconds % 60); +		int new_minutes = (new_seconds / 60); +		int new_minute = (new_minutes % 60); +		int new_hours = (new_minutes / 60); +		int new_hour = (new_hours % 24); + +		bad.a = new_hour + (new_minute << 8); +		bad.b = new_second + (flags << 8); +	} + +	/* +	 * 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 AltosEepromIterable (FileInputStream input) { +		records = new TreeSet<AltosOrderedRecord>(); + +		AltosOrderedRecord last_gps_time = null; + +		int index = 0; +		int prev_tick = 0; +		boolean prev_tick_valid = false; +		boolean missing_time = false; + +		try { +			for (;;) { +				String line = AltosRecord.gets(input); +				if (line == null) +					break; +				AltosOrderedRecord record = new AltosOrderedRecord(line, index++, prev_tick, prev_tick_valid); +				if (record == null) +					break; +				if (record.cmd == AltosLib.AO_LOG_INVALID) +					continue; +				prev_tick = record.tick; +				if (record.cmd < AltosLib.AO_LOG_CONFIG_VERSION) +					prev_tick_valid = true; +				if (record.cmd == AltosLib.AO_LOG_FLIGHT) { +					flight_record = record; +					continue; +				} + +				/* Two firmware bugs caused the loss of some GPS data. +				 * The flight date would never be recorded, and often +				 * the flight time would get overwritten by another +				 * record. Detect the loss of the GPS date and fix up the +				 * missing time records +				 */ +				if (record.cmd == AltosLib.AO_LOG_GPS_DATE) { +					gps_date_record = record; +					continue; +				} + +				/* go back and fix up any missing time values */ +				if (record.cmd == AltosLib.AO_LOG_GPS_TIME) { +					last_gps_time = record; +					if (missing_time) { +						Iterator<AltosOrderedRecord> iterator = records.iterator(); +						while (iterator.hasNext()) { +							AltosOrderedRecord old = iterator.next(); +							if (old.cmd == AltosLib.AO_LOG_GPS_TIME && +							    old.a == -1 && old.b == -1) +							{ +								update_time(record, old); +							} +						} +						missing_time = false; +					} +				} + +				if (record.cmd == AltosLib.AO_LOG_GPS_LAT) { +					if (last_gps_time == null || last_gps_time.tick != record.tick) { +						AltosOrderedRecord add_gps_time = new AltosOrderedRecord(AltosLib.AO_LOG_GPS_TIME, +													 record.tick, +													 -1, -1, index-1); +						if (last_gps_time != null) +							update_time(last_gps_time, add_gps_time); +						else +							missing_time = true; + +						records.add(add_gps_time); +						record.index = index++; +					} +				} +				records.add(record); + +				/* Bail after reading the 'landed' record; we're all done */ +				if (record.cmd == AltosLib.AO_LOG_STATE && +				    record.a == AltosLib.ao_flight_landed) +					break; +			} +		} catch (IOException io) { +		} catch (ParseException pe) { +		} +		try { +			input.close(); +		} catch (IOException ie) { +		} +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosEepromLog.java b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromLog.java new file mode 100644 index 00000000..7fca4bd9 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromLog.java @@ -0,0 +1,100 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +/* + * Extract a bit of information from an eeprom-stored flight log. + */ + +public class AltosEepromLog { +	public int		serial; +	public boolean		has_flight; +	public int		flight; +	public int		start_block; +	public int		end_block; + +	public int		year, month, day; + +	public boolean		selected; + +	public AltosEepromLog(AltosConfigData config_data, +			      AltosLink link, +			      int in_flight, int in_start_block, +			      int in_end_block) +		throws InterruptedException, TimeoutException { + +		int		block; +		boolean		has_date = false; + +		flight = in_flight; +		if (flight != 0) +			has_flight = true; +		start_block = in_start_block; +		end_block = in_end_block; +		serial = config_data.serial; + +		/* +		 * Select all flights for download +		 */ +		selected = true; + +		/* +		 * Look in TeleMetrum log data for date +		 */ +		if (config_data.log_format == AltosLib.AO_LOG_FORMAT_UNKNOWN || +		    config_data.log_format == AltosLib.AO_LOG_FORMAT_FULL) +		{ +			/* +			 * Only look in the first two blocks so that this +			 * process doesn't take a long time +			 */ +			if (in_end_block > in_start_block + 2) +				in_end_block = in_start_block + 2; + +			for (block = in_start_block; block < in_end_block; block++) { +				AltosEepromChunk eechunk = new AltosEepromChunk(link, block, block == in_start_block); + +				for (int i = 0; i < eechunk.chunk_size; i += AltosEepromRecord.record_length) { +					try { +						AltosEepromRecord r = new AltosEepromRecord(eechunk, i); + +						if (r.cmd == AltosLib.AO_LOG_FLIGHT) { +							flight = r.b; +							has_flight = true; +						} +						if (r.cmd == AltosLib.AO_LOG_GPS_DATE) { +							year = 2000 + (r.a & 0xff); +							month = (r.a >> 8) & 0xff; +							day = (r.b & 0xff); +							has_date = true; +						} +					} catch (ParseException pe) { +					} +				} +				if (has_date && has_flight) +					break; +			} +		} +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosEepromRecord.java b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromRecord.java new file mode 100644 index 00000000..1e845f46 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromRecord.java @@ -0,0 +1,139 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +public class AltosEepromRecord { +	public int	cmd; +	public int	tick; +	public int	a; +	public int	b; +	public String	data; +	public boolean	tick_valid; + +	public static final int	record_length = 8; + +	public AltosEepromRecord (AltosEepromChunk chunk, int start) throws ParseException { + +		cmd = chunk.data(start); +		tick_valid = true; + +		tick_valid = !chunk.erased(start, record_length); +		if (tick_valid) { +			if (AltosConvert.checksum(chunk.data, start, record_length) != 0) +				throw new ParseException(String.format("invalid checksum at 0x%x", +								       chunk.address + start), 0); +		} else { +			cmd = AltosLib.AO_LOG_INVALID; +		} + +		tick = chunk.data16(start + 2); +		a = chunk.data16(start + 4); +		b = chunk.data16(start + 6); + +		data = null; +	} + +	public AltosEepromRecord (String line) { +		tick_valid = false; +		tick = 0; +		a = 0; +		b = 0; +		data = null; +		if (line == null) { +			cmd = AltosLib.AO_LOG_INVALID; +			data = ""; +		} else { +			try { +				String[] tokens = line.split("\\s+"); + +				if (tokens[0].length() == 1) { +					if (tokens.length != 4) { +						cmd = AltosLib.AO_LOG_INVALID; +						data = line; +					} else { +						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 = AltosLib.AO_LOG_CONFIG_VERSION; +					data = tokens[2]; +				} else if (tokens[0].equals("Main") && tokens[1].equals("deploy:")) { +					cmd = AltosLib.AO_LOG_MAIN_DEPLOY; +					a = Integer.parseInt(tokens[2]); +				} else if (tokens[0].equals("Apogee") && tokens[1].equals("delay:")) { +					cmd = AltosLib.AO_LOG_APOGEE_DELAY; +					a = Integer.parseInt(tokens[2]); +				} else if (tokens[0].equals("Radio") && tokens[1].equals("channel:")) { +					cmd = AltosLib.AO_LOG_RADIO_CHANNEL; +					a = Integer.parseInt(tokens[2]); +				} else if (tokens[0].equals("Callsign:")) { +					cmd = AltosLib.AO_LOG_CALLSIGN; +					data = tokens[1].replaceAll("\"",""); +				} else if (tokens[0].equals("Accel") && tokens[1].equals("cal")) { +					cmd = AltosLib.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 = AltosLib.AO_LOG_RADIO_CAL; +					a = Integer.parseInt(tokens[2]); +				} else if (tokens[0].equals("Max") && tokens[1].equals("flight") && tokens[2].equals("log:")) { +					cmd = AltosLib.AO_LOG_MAX_FLIGHT_LOG; +					a = Integer.parseInt(tokens[3]); +				} else if (tokens[0].equals("manufacturer")) { +					cmd = AltosLib.AO_LOG_MANUFACTURER; +					data = tokens[1]; +				} else if (tokens[0].equals("product")) { +					cmd = AltosLib.AO_LOG_PRODUCT; +					data = tokens[1]; +				} else if (tokens[0].equals("serial-number")) { +					cmd = AltosLib.AO_LOG_SERIAL_NUMBER; +					a = Integer.parseInt(tokens[1]); +				} else if (tokens[0].equals("log-format")) { +					cmd = AltosLib.AO_LOG_LOG_FORMAT; +					a = Integer.parseInt(tokens[1]); +				} else if (tokens[0].equals("software-version")) { +					cmd = AltosLib.AO_LOG_SOFTWARE_VERSION; +					data = tokens[1]; +				} else { +					cmd = AltosLib.AO_LOG_INVALID; +					data = line; +				} +			} catch (NumberFormatException ne) { +				cmd = AltosLib.AO_LOG_INVALID; +				data = line; +			} +		} +	} + +	public AltosEepromRecord(int in_cmd, int in_tick, int in_a, int in_b) { +		tick_valid = true; +		cmd = in_cmd; +		tick = in_tick; +		a = in_a; +		b = in_b; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosEepromTeleScience.java b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromTeleScience.java new file mode 100644 index 00000000..1758fa34 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosEepromTeleScience.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +public class AltosEepromTeleScience { +	public int	type; +	public int	tick; +	public int	tm_state; +	public int	tm_tick; +	public int[]	data; +	public boolean	valid; + +	public static final int AO_LOG_TELESCIENCE_START = 's'; +	public static final int AO_LOG_TELESCIENCE_DATA = 'd'; + +	static final int	max_data = 12; +	public static final int	record_length = 32; + +	public AltosEepromTeleScience (AltosEepromChunk chunk, int start) throws ParseException { +		type = chunk.data(start); + +		valid = !chunk.erased(start, record_length); +		if (valid) { +			if (AltosConvert.checksum(chunk.data, start, record_length) != 0) +				throw new ParseException(String.format("invalid checksum at 0x%x", +								       chunk.address + start), 0); +		} else { +			type = AltosLib.AO_LOG_INVALID; +		} + +		tick = chunk.data16(start+2); +		tm_tick = chunk.data16(start+4); +		tm_state = chunk.data(start+6); +		data = new int[max_data]; +		for (int i = 0; i < max_data; i++) +			data[i] = chunk.data16(start + 8 + i * 2); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosFile.java b/altoslib/src/org/altusmetrum/AltosLib/AltosFile.java new file mode 100644 index 00000000..d2e4f2f7 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosFile.java @@ -0,0 +1,44 @@ +/* + * 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 org.altusmetrum.AltosLib;  + +import java.lang.*; +import java.io.File; +import java.util.*; + +public class AltosFile extends File { + +	public AltosFile(int year, int month, int day, int serial, int flight, String extension) { +		super (AltosPreferences.logdir(), +		       String.format("%04d-%02d-%02d-serial-%03d-flight-%03d.%s", +				     year, month, day, serial, flight, extension)); +	} + +	public AltosFile(int serial, int flight, String extension) { +		this(Calendar.getInstance().get(Calendar.YEAR), +		     Calendar.getInstance().get(Calendar.MONTH) + 1, +		     Calendar.getInstance().get(Calendar.DAY_OF_MONTH), +		     serial, +		     flight, +		     extension); +	} + +	public AltosFile(AltosRecord telem) { +		this(telem.serial, telem.flight, "telem"); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosFlightReader.java b/altoslib/src/org/altusmetrum/AltosLib/AltosFlightReader.java new file mode 100644 index 00000000..3fdea469 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosFlightReader.java @@ -0,0 +1,49 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.io.*; +import java.util.concurrent.*; + +public class AltosFlightReader { +	public String name; + +	public int serial; + +	public void init() { } + +	public AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; } + +	public void close(boolean interrupted) { } + +	public void set_frequency(double frequency) throws InterruptedException, TimeoutException { } + +	public void save_frequency() { } + +	public void set_telemetry(int telemetry) { } + +	public void save_telemetry() { } + +	public void update(AltosState state) throws InterruptedException { } + +	public boolean supports_telemetry(int telemetry) { return false; } + +	public File backing_file() { return null; } +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosFrequency.java b/altoslib/src/org/altusmetrum/AltosLib/AltosFrequency.java new file mode 100644 index 00000000..f08ff116 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosFrequency.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; + +public class AltosFrequency { +	public double	frequency; +	public String	description; + +	public String toString() { +		return String.format("%7.3f MHz %-20s", +				     frequency, description); +	} + +	public String toShortString() { +		return String.format("%7.3f MHz %s", +				     frequency, description); +	} + +	public boolean close(double f) { +		double	diff = Math.abs(frequency - f); + +		return diff < 0.010; +	} + +	public AltosFrequency(double f, String d) { +		frequency = f; +		description = d; +	} +}
\ No newline at end of file diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosGPS.java b/altoslib/src/org/altusmetrum/AltosLib/AltosGPS.java new file mode 100644 index 00000000..f078a469 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosGPS.java @@ -0,0 +1,248 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; + +public class AltosGPS { + +	public final static int MISSING = AltosRecord.MISSING; + +	public int	nsat; +	public boolean	locked; +	public boolean	connected; +	public double	lat;		/* degrees (+N -S) */ +	public double	lon;		/* degrees (+E -W) */ +	public int	alt;		/* m */ +	public int	year; +	public int	month; +	public int	day; +	public int	hour; +	public int	minute; +	public int	second; + +	public double	ground_speed;	/* m/s */ +	public int	course;		/* degrees */ +	public double	climb_rate;	/* m/s */ +	public double	hdop;		/* unitless */ +	public double	vdop;		/* unitless */ +	public int	h_error;	/* m */ +	public int	v_error;	/* m */ + +	public AltosGPSSat[] cc_gps_sat;	/* tracking data */ + +	public void ParseGPSDate(String date) 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]); +	} + +	public void ParseGPSTime(String time) throws ParseException { +		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 void ClearGPSTime() { +		year = month = day = 0; +		hour = minute = second = 0; +	} + +	public AltosGPS(AltosTelemetryMap map) throws ParseException { +		String	state = map.get_string(AltosTelemetry.AO_TELEM_GPS_STATE, +					       AltosTelemetry.AO_TELEM_GPS_STATE_ERROR); + +		nsat = map.get_int(AltosTelemetry.AO_TELEM_GPS_NUM_SAT, 0); +		if (state.equals(AltosTelemetry.AO_TELEM_GPS_STATE_LOCKED)) { +			connected = true; +			locked = true; +			lat = map.get_double(AltosTelemetry.AO_TELEM_GPS_LATITUDE, MISSING, 1.0e-7); +			lon = map.get_double(AltosTelemetry.AO_TELEM_GPS_LONGITUDE, MISSING, 1.0e-7); +			alt = map.get_int(AltosTelemetry.AO_TELEM_GPS_ALTITUDE, MISSING); +			year = map.get_int(AltosTelemetry.AO_TELEM_GPS_YEAR, MISSING); +			if (year != MISSING) +				year += 2000; +			month = map.get_int(AltosTelemetry.AO_TELEM_GPS_MONTH, MISSING); +			day = map.get_int(AltosTelemetry.AO_TELEM_GPS_DAY, MISSING); + +			hour = map.get_int(AltosTelemetry.AO_TELEM_GPS_HOUR, 0); +			minute = map.get_int(AltosTelemetry.AO_TELEM_GPS_MINUTE, 0); +			second = map.get_int(AltosTelemetry.AO_TELEM_GPS_SECOND, 0); + +			ground_speed = map.get_double(AltosTelemetry.AO_TELEM_GPS_HORIZONTAL_SPEED, +						      AltosRecord.MISSING, 1/100.0); +			course = map.get_int(AltosTelemetry.AO_TELEM_GPS_COURSE, +					     AltosRecord.MISSING); +			hdop = map.get_double(AltosTelemetry.AO_TELEM_GPS_HDOP, MISSING, 1.0); +			vdop = map.get_double(AltosTelemetry.AO_TELEM_GPS_VDOP, MISSING, 1.0); +			h_error = map.get_int(AltosTelemetry.AO_TELEM_GPS_HERROR, MISSING); +			v_error = map.get_int(AltosTelemetry.AO_TELEM_GPS_VERROR, MISSING); +		} else if (state.equals(AltosTelemetry.AO_TELEM_GPS_STATE_UNLOCKED)) { +			connected = true; +			locked = false; +		} else { +			connected = false; +			locked = false; +		} +	} + +	public AltosGPS(String[] words, int i, int version) throws ParseException { +		AltosParse.word(words[i++], "GPS"); +		nsat = AltosParse.parse_int(words[i++]); +		AltosParse.word(words[i++], "sat"); + +		connected = false; +		locked = false; +		lat = lon = 0; +		alt = 0; +		ClearGPSTime(); +		if ((words[i]).equals("unlocked")) { +			connected = true; +			i++; +		} else if ((words[i]).equals("not-connected")) { +			i++; +		} else if (words.length >= 40) { +			locked = true; +			connected = true; + +			if (version > 1) +				ParseGPSDate(words[i++]); +			else +				year = month = day = 0; +			ParseGPSTime(words[i++]); +			lat = AltosParse.parse_coord(words[i++]); +			lon = AltosParse.parse_coord(words[i++]); +			alt = AltosParse.parse_int(words[i++]); +			if (version > 1 || (i < words.length && !words[i].equals("SAT"))) { +				ground_speed = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "m/s(H)")); +				course = AltosParse.parse_int(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(words[i++]); +				v_error = AltosParse.parse_int(words[i++]); +			} +		} else { +			i++; +		} +		if (i < words.length) { +			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 AltosGPSSat[tracking_channels]; +			for (int chan = 0; chan < tracking_channels; chan++) { +				cc_gps_sat[chan] = new AltosGPSSat(); +				cc_gps_sat[chan].svid = AltosParse.parse_int(words[i++]); +				/* Older versions included SiRF status bits */ +				if (version < 2) +					i++; +				cc_gps_sat[chan].c_n0 = AltosParse.parse_int(words[i++]); +			} +		} else +			cc_gps_sat = new AltosGPSSat[0]; +	} + +	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 AltosGPSSat[1]; +		} else { +			AltosGPSSat[] new_gps_sat = new 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; +		} +		AltosGPSSat	sat = new 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; + +		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/altoslib/src/org/altusmetrum/AltosLib/AltosGPSSat.java b/altoslib/src/org/altusmetrum/AltosLib/AltosGPSSat.java new file mode 100644 index 00000000..faa1ec8d --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosGPSSat.java @@ -0,0 +1,32 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +public class AltosGPSSat { +	public int	svid; +	public int	c_n0; + +	public AltosGPSSat(int s, int c) { +		svid = s; +		c_n0= c; +	} + +	public AltosGPSSat() { +	} +} + diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosGreatCircle.java b/altoslib/src/org/altusmetrum/AltosLib/AltosGreatCircle.java new file mode 100644 index 00000000..76b71859 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosGreatCircle.java @@ -0,0 +1,101 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.Math; + +public class AltosGreatCircle { +	public double	distance; +	public 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 */ + +	public static final int BEARING_LONG = 0; +	public static final int BEARING_SHORT = 1; +	public static final int BEARING_VOICE = 2; + +	public String bearing_words(int length) { +		String [][] bearing_string = { +			{ +				"North", "North North East", "North East", "East North East", +				"East", "East South East", "South East", "South South East", +				"South", "South South West", "South West", "West South West", +				"West", "West North West", "North West", "North North West" +			}, { +				"N", "NNE", "NE", "ENE", +				"E", "ESE", "SE", "SSE", +				"S", "SSW", "SW", "WSW", +				"W", "WNW", "NW", "NNW" +			}, { +				"north", "nor nor east", "north east", "east nor east", +				"east", "east sow east", "south east", "sow sow east", +				"south", "sow sow west", "south west", "west sow west", +				"west", "west nor west", "north west", "nor nor west " +			} +		}; +		return bearing_string[length][(int)((bearing / 90 * 8 + 1) / 2)%16]; +	} + +	public 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; +	} + +	public AltosGreatCircle(AltosGPS start, AltosGPS end) { +		this(start.lat, start.lon, end.lat, end.lon); +	} + +	public AltosGreatCircle() { +		distance = 0; +		bearing = 0; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosLib.java b/altoslib/src/org/altusmetrum/AltosLib/AltosLib.java new file mode 100644 index 00000000..2921d040 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosLib.java @@ -0,0 +1,348 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.awt.*; +import java.util.*; +import java.text.*; +import java.nio.charset.Charset; + +public class AltosLib { +	/* EEProm command letters */ +	public static final int AO_LOG_FLIGHT = 'F'; +	public static final int AO_LOG_SENSOR = 'A'; +	public static final int AO_LOG_TEMP_VOLT = 'T'; +	public static final int AO_LOG_DEPLOY = 'D'; +	public static final int AO_LOG_STATE = 'S'; +	public static final int AO_LOG_GPS_TIME = 'G'; +	public static final int AO_LOG_GPS_LAT = 'N'; +	public static final int AO_LOG_GPS_LON = 'W'; +	public static final int AO_LOG_GPS_ALT = 'H'; +	public static final int AO_LOG_GPS_SAT = 'V'; +	public static final int AO_LOG_GPS_DATE = 'Y'; +	public static final int AO_LOG_PRESSURE = 'P'; + +	/* Added for header fields in eeprom files */ +	public static final int AO_LOG_CONFIG_VERSION = 1000; +	public static final int AO_LOG_MAIN_DEPLOY = 1001; +	public static final int AO_LOG_APOGEE_DELAY = 1002; +	public static final int AO_LOG_RADIO_CHANNEL = 1003; +	public static final int AO_LOG_CALLSIGN = 1004; +	public static final int AO_LOG_ACCEL_CAL = 1005; +	public static final int AO_LOG_RADIO_CAL = 1006; +	public static final int AO_LOG_MAX_FLIGHT_LOG = 1007; +	public static final int AO_LOG_MANUFACTURER = 2000; +	public static final int AO_LOG_PRODUCT = 2001; +	public static final int AO_LOG_SERIAL_NUMBER = 2002; +	public static final int AO_LOG_LOG_FORMAT = 2003; +	public static final int AO_LOG_SOFTWARE_VERSION = 9999; + +	/* Added to flag invalid records */ +	public static final int AO_LOG_INVALID = -1; + +	/* Flight state numbers and names */ +	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; + +	/* Telemetry modes */ +	public static final int ao_telemetry_off = 0; +	public static final int ao_telemetry_min = 1; +	public static final int ao_telemetry_standard = 1; +	public static final int ao_telemetry_0_9 = 2; +	public static final int ao_telemetry_0_8 = 3; +	public static final int ao_telemetry_max = 3; + +	public static final String[] ao_telemetry_name = { +		"Off", "Standard Telemetry", "TeleMetrum v0.9", "TeleMetrum v0.8" +	}; + +	public static final String launch_sites_url = "http://www.altusmetrum.org/AltOS/launch-sites.txt"; + +	public static final int ao_telemetry_standard_len = 32; +	public static final int ao_telemetry_0_9_len = 95; +	public static final int ao_telemetry_0_8_len = 94; + +	public static final int[] ao_telemetry_len = { +		0, 32, 95, 94 +	}; + +	public static HashMap<String,Integer>	string_to_state = new HashMap<String,Integer>(); + +	public static boolean map_initialized = false; + +	public 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("apogee", ao_flight_coast); +		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; +	} + +	public static int telemetry_len(int telemetry) { +		if (telemetry <= ao_telemetry_max) +			return ao_telemetry_len[telemetry]; +		throw new IllegalArgumentException(String.format("Invalid telemetry %d", +								 telemetry)); +	} + +	public static String telemetry_name(int telemetry) { +		if (telemetry <= ao_telemetry_max) +			return ao_telemetry_name[telemetry]; +		throw new IllegalArgumentException(String.format("Invalid telemetry %d", +								 telemetry)); +	} +	 +	public static String[] state_to_string = { +		"startup", +		"idle", +		"pad", +		"boost", +		"fast", +		"coast", +		"drogue", +		"main", +		"landed", +		"invalid", +	}; + +	public static String[] state_to_string_capital = { +		"Startup", +		"Idle", +		"Pad", +		"Boost", +		"Fast", +		"Coast", +		"Drogue", +		"Main", +		"Landed", +		"Invalid", +	}; + +	public static 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; +	} + +	public static String state_name(int state) { +		if (state < 0 || state_to_string.length <= state) +			return "invalid"; +		return state_to_string[state]; +	} + +	public static final int AO_GPS_VALID = (1 << 4); +	public static final int AO_GPS_RUNNING = (1 << 5); +	public static final int AO_GPS_DATE_VALID = (1 << 6); +	public static final int AO_GPS_NUM_SAT_SHIFT = 0; +	public static final int AO_GPS_NUM_SAT_MASK = 0xf; + +	public static final int AO_LOG_FORMAT_UNKNOWN = 0; +	public static final int AO_LOG_FORMAT_FULL = 1; +	public static final int AO_LOG_FORMAT_TINY = 2; +	public static final int AO_LOG_FORMAT_TELEMETRY = 3; +	public static final int AO_LOG_FORMAT_TELESCIENCE = 4; +	public static final int AO_LOG_FORMAT_NONE = 127; + +	public static boolean isspace(int c) { +		switch (c) { +		case ' ': +		case '\t': +			return true; +		} +		return false; +	} + +	public static boolean ishex(int c) { +		if ('0' <= c && c <= '9') +			return true; +		if ('a' <= c && c <= 'f') +			return true; +		if ('A' <= c && c <= 'F') +			return true; +		return false; +	} + +	public static boolean ishex(String s) { +		for (int i = 0; i < s.length(); i++) +			if (!ishex(s.charAt(i))) +				return false; +		return true; +	} + +	public static int fromhex(int c) { +		if ('0' <= c && c <= '9') +			return c - '0'; +		if ('a' <= c && c <= 'f') +			return c - 'a' + 10; +		if ('A' <= c && c <= 'F') +			return c - 'A' + 10; +		return -1; +	} + +	public static int fromhex(String s) throws NumberFormatException { +		int c, v = 0; +		for (int i = 0; i < s.length(); i++) { +			c = s.charAt(i); +			if (!ishex(c)) { +				if (i == 0) +					throw new NumberFormatException(String.format("invalid hex \"%s\"", s)); +				return v; +			} +			v = v * 16 + fromhex(c); +		} +		return v; +	} + +	public static boolean isdec(int c) { +		if ('0' <= c && c <= '9') +			return true; +		return false; +	} + +	public static boolean isdec(String s) { +		for (int i = 0; i < s.length(); i++) +			if (!isdec(s.charAt(i))) +				return false; +		return true; +	} + +	public static int fromdec(int c) { +		if ('0' <= c && c <= '9') +			return c - '0'; +		return -1; +	} + +	public static int int8(int[] bytes, int i) { +		return (int) (byte) bytes[i]; +	} + +	public static int uint8(int[] bytes, int i) { +		return bytes[i]; +	} + +	public static int int16(int[] bytes, int i) { +		return (int) (short) (bytes[i] + (bytes[i+1] << 8)); +	} + +	public static int uint16(int[] bytes, int i) { +		return bytes[i] + (bytes[i+1] << 8); +	} + +	public static int uint32(int[] bytes, int i) { +		return bytes[i] + +			(bytes[i+1] << 8) + +			(bytes[i+2] << 16) + +			(bytes[i+3] << 24); +	} + +	public static final Charset	unicode_set = Charset.forName("UTF-8"); + +	public static String string(int[] bytes, int s, int l) { +		if (s + l > bytes.length) { +			if (s > bytes.length) { +				s = bytes.length; +				l = 0; +			} else { +				l = bytes.length - s; +			} +		} + +		int i; +		for (i = l - 1; i >= 0; i--) +			if (bytes[s+i] != 0) +				break; + +		l = i + 1; +		byte[]	b = new byte[l]; + +		for (i = 0; i < l; i++) +			b[i] = (byte) bytes[s+i]; +		String n = new String(b, unicode_set); +		return n; +	} + +	public static int hexbyte(String s, int i) { +		int c0, c1; + +		if (s.length() < i + 2) +			throw new NumberFormatException(String.format("invalid hex \"%s\"", s)); +		c0 = s.charAt(i); +		if (!ishex(c0)) +			throw new NumberFormatException(String.format("invalid hex \"%c\"", c0)); +		c1 = s.charAt(i+1); +		if (!ishex(c1)) +			throw new NumberFormatException(String.format("invalid hex \"%c\"", c1)); +		return fromhex(c0) * 16 + fromhex(c1); +	} + +	public static int[] hexbytes(String s) { +		int	n; +		int[]	r; +		int	i; + +		if ((s.length() & 1) != 0) +			throw new NumberFormatException(String.format("invalid line \"%s\"", s)); +		n = s.length() / 2; +		r = new int[n]; +		for (i = 0; i < n; i++) +			r[i] = hexbyte(s, i * 2); +		return r; +	} + +	public static int fromdec(String s) throws NumberFormatException { +		int c, v = 0; +		int sign = 1; +		for (int i = 0; i < s.length(); i++) { +			c = s.charAt(i); +			if (i == 0 && c == '-') { +				sign = -1; +			} else if (!isdec(c)) { +				if (i == 0) +					throw new NumberFormatException(String.format("invalid number \"%s\"", s)); +				return v; +			} else +				v = v * 10 + fromdec(c); +		} +		return v * sign; +	} + +	public static String replace_extension(String input, String extension) { +		int dot = input.lastIndexOf("."); +		if (dot > 0) +			input = input.substring(0,dot); +		return input.concat(extension); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosLine.java b/altoslib/src/org/altusmetrum/AltosLib/AltosLine.java new file mode 100644 index 00000000..5627795a --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosLine.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 org.altusmetrum.AltosLib; + +public class AltosLine { +	public String	line; + +	public AltosLine() { +		line = null; +	} + +	public AltosLine(String s) { +		line = s; +	} +}
\ No newline at end of file diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosLink.java b/altoslib/src/org/altusmetrum/AltosLib/AltosLink.java new file mode 100644 index 00000000..9b80e916 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosLink.java @@ -0,0 +1,238 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; +import java.text.*; + +public abstract class AltosLink { +	public abstract void print(String data); +	public abstract void close(); + +	public static boolean debug = false; +	public static void set_debug(boolean in_debug) { debug = in_debug; } +	LinkedList<String> pending_output = new LinkedList<String>(); + +	public LinkedList<LinkedBlockingQueue<AltosLine>> monitors = new LinkedList<LinkedBlockingQueue<AltosLine>> ();; +	public LinkedBlockingQueue<AltosLine> reply_queue = new LinkedBlockingQueue<AltosLine>(); + +	public void add_monitor(LinkedBlockingQueue<AltosLine> q) { +		set_monitor(true); +		monitors.add(q); +	} + +	public void remove_monitor(LinkedBlockingQueue<AltosLine> q) { +		monitors.remove(q); +		if (monitors.isEmpty()) +			set_monitor(false); +	} + +	public void printf(String format, Object ... arguments) { +		String	line = String.format(format, arguments); +		if (debug) +			pending_output.add(line); +		print(line); +	} + +	public String get_reply_no_dialog(int timeout) throws InterruptedException, TimeoutException { +		flush_output(); +		AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS); +		if (line != null) +			return line.line; +		return null; +	} + +	public String get_reply(int timeout) throws InterruptedException { +		try { +			return get_reply_no_dialog(timeout); +		} catch (TimeoutException te) { +			return null; +		} +	} + +	public String get_reply() throws InterruptedException { +		return get_reply(5000); +	} + +	public void add_telem(AltosLine line) throws InterruptedException { +		for (int e = 0; e < monitors.size(); e++) { +			LinkedBlockingQueue<AltosLine> q = monitors.get(e); +			q.put(line); +		} +	} + +	public void add_reply(AltosLine line) throws InterruptedException { +		reply_queue.put (line); +	} + +	public void add_string(String line) throws InterruptedException { +		if (line.startsWith("TELEM") || line.startsWith("VERSION") || line.startsWith("CRC")) { +			add_telem(new AltosLine(line)); +		} else { +			add_reply(new AltosLine(line)); +		} +	} + +	public void add_bytes(byte[] bytes, int len) throws InterruptedException { +		String	line; +		try { +			line = new String(bytes, 0, len, "UTF-8"); +		} catch (UnsupportedEncodingException ue) { +			line = ""; +			for (int i = 0; i < len; i++) +				line = line + bytes[i]; +		} +		if (debug) +			System.out.printf("\t\t\t\t\t%s\n", line); +		add_string(line); +	} + +	public void flush_output() { +		for (String s : pending_output) +			System.out.print(s); +		pending_output.clear(); +	} + +	public void flush_input(int timeout) throws InterruptedException { +		flush_output(); +		boolean	got_some; + +		do { +			Thread.sleep(timeout); +			got_some = !reply_queue.isEmpty(); +			reply_queue.clear(); +		} while (got_some); +	} + + +	public void flush_input() throws InterruptedException { +		flush_input(100); +	} + + +	/* +	 * Various command-level operations on +	 * the link +	 */ +	public boolean monitor_mode = false; +	public int telemetry = AltosLib.ao_telemetry_standard; +	public double frequency; +	AltosConfigData	config_data; + +	private int telemetry_len() { +		return AltosLib.telemetry_len(telemetry); +	} + +	public void set_telemetry(int in_telemetry) { +		telemetry = in_telemetry; +		if (monitor_mode) +			printf("m 0\nm %x\n", telemetry_len()); +		flush_output(); +	} + +	public void set_monitor(boolean monitor) { +		monitor_mode = monitor; +		if (monitor) +			printf("m %x\n", telemetry_len()); +		else +			printf("m 0\n"); +		flush_output(); +	} + +	private void set_channel(int channel) { +		if (monitor_mode) +			printf("m 0\nc r %d\nm %x\n", +			       channel, telemetry_len()); +		else +			printf("c r %d\n", channel); +		flush_output(); +	} + +	private void set_radio_setting(int setting) { +		if (monitor_mode) +			printf("m 0\nc R %d\nm %x\n", +			       setting, telemetry_len()); +		else +			printf("c R %d\n", setting); +		flush_output(); +	} + +	public void set_radio_frequency(double frequency, +					boolean has_setting, +					int cal) { +		if (debug) +			System.out.printf("set_radio_frequency %7.3f %b %d\n", frequency, has_setting, cal); +		if (has_setting) +			set_radio_setting(AltosConvert.radio_frequency_to_setting(frequency, cal)); +		else +			set_channel(AltosConvert.radio_frequency_to_channel(frequency)); +	} + +	public AltosConfigData config_data() throws InterruptedException, TimeoutException { +		if (config_data == null) +			config_data = new AltosConfigData(this); +		return config_data; +	} + +	public void set_radio_frequency(double in_frequency) throws InterruptedException, TimeoutException { +		frequency = in_frequency; +		config_data(); +		set_radio_frequency(frequency, +				    config_data.radio_setting != 0, +				    config_data.radio_calibration); +	} + +	public void set_callsign(String callsign) { +		printf ("c c %s\n", callsign); +		flush_output(); +	} + +	public boolean remote; +	public int serial; +	public String name; + +	public void start_remote() throws TimeoutException, InterruptedException { +		if (debug) +			System.out.printf("start remote %7.3f\n", frequency); +		if (frequency == 0.0) +			frequency = AltosPreferences.frequency(serial); +		set_radio_frequency(frequency); +		set_callsign(AltosPreferences.callsign()); +		printf("p\nE 0\n"); +		flush_input(); +		remote = true; +	} + +	public void stop_remote() throws InterruptedException { +		if (debug) +			System.out.printf("stop remote\n"); +		try { +			flush_input(); +		} finally { +			printf ("~\n"); +			flush_output(); +		} +		remote = false; +	} + +	public AltosLink() { +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosLog.java b/altoslib/src/org/altusmetrum/AltosLib/AltosLog.java new file mode 100644 index 00000000..08c45ca8 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosLog.java @@ -0,0 +1,126 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.lang.*; +import java.util.*; +import java.text.ParseException; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * This creates a thread to capture telemetry data and write it to + * a log file + */ +class AltosLog implements Runnable { + +	LinkedBlockingQueue<AltosLine>	input_queue; +	LinkedBlockingQueue<String>	pending_queue; +	int				serial; +	int				flight; +	FileWriter			log_file; +	Thread				log_thread; +	AltosFile			file; + +	private void close_log_file() { +		if (log_file != null) { +			try { +				log_file.close(); +			} catch (IOException io) { +			} +			log_file = null; +		} +	} + +	void close() { +		close_log_file(); +		if (log_thread != null) { +			log_thread.interrupt(); +			log_thread = null; +		} +	} + +	File file() { +		return file; +	} + +	boolean open (AltosRecord telem) throws IOException { +		AltosFile	a = new AltosFile(telem); + +		System.out.printf("open %s\n", a.toString()); +		log_file = new FileWriter(a, true); +		if (log_file != null) { +			while (!pending_queue.isEmpty()) { +				try { +					String s = pending_queue.take(); +					log_file.write(s); +					log_file.write('\n'); +				} catch (InterruptedException ie) { +				} +			} +			log_file.flush(); +			file = a; +		} +		return log_file != null; +	} + +	public void run () { +		try { +			AltosRecord	previous = null; +			for (;;) { +				AltosLine	line = input_queue.take(); +				if (line.line == null) +					continue; +				try { +					AltosRecord	telem = AltosTelemetry.parse(line.line, previous); +					if (telem.serial != 0 && telem.flight != 0 && +					    (telem.serial != serial || telem.flight != flight || log_file == null)) +					{ +						close_log_file(); +						serial = telem.serial; +						flight = telem.flight; +						open(telem); +					} +					previous = telem; +				} catch (ParseException pe) { +				} catch (AltosCRCException ce) { +				} +				if (log_file != null) { +					log_file.write(line.line); +					log_file.write('\n'); +					log_file.flush(); +				} else +					pending_queue.put(line.line); +			} +		} catch (InterruptedException ie) { +		} catch (IOException ie) { +		} +		close(); +	} + +	public AltosLog (AltosLink link) { +		pending_queue = new LinkedBlockingQueue<String> (); +		input_queue = new LinkedBlockingQueue<AltosLine> (); +		link.add_monitor(input_queue); +		serial = -1; +		flight = -1; +		log_file = null; +		log_thread = new Thread(this); +		log_thread.start(); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosParse.java b/altoslib/src/org/altusmetrum/AltosLib/AltosParse.java new file mode 100644 index 00000000..7d832f1a --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosParse.java @@ -0,0 +1,79 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.text.*; +import java.lang.*; + +public class AltosParse { +	public static boolean isdigit(char c) { +		return '0' <= c && c <= '9'; +	} + +	public static int parse_int(String v) throws ParseException { +		try { +			return AltosLib.fromdec(v); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing int " + v, 0); +		} +	} + +	public static int parse_hex(String v) throws ParseException { +		try { +			return AltosLib.fromhex(v); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing hex " + v, 0); +		} +	} + +	public static double parse_double(String v) throws ParseException { +		try { +			return Double.parseDouble(v); +		} catch (NumberFormatException e) { +			throw new ParseException("error parsing double " + v, 0); +		} +	} + +	public 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; +	} + +	public static String strip_suffix(String v, String suffix) { +		if (v.endsWith(suffix)) +			return v.substring(0, v.length() - suffix.length()); +		return v; +	} + +	public static void word(String v, String m) throws ParseException { +		if (!v.equals(m)) { +			throw new ParseException("error matching '" + v + "' '" + m + "'", 0); +		} +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosPreferences.java b/altoslib/src/org/altusmetrum/AltosLib/AltosPreferences.java new file mode 100644 index 00000000..43c7088d --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosPreferences.java @@ -0,0 +1,365 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.awt.Component; +import javax.swing.*; +import javax.swing.filechooser.FileSystemView; + +public class AltosPreferences { +	public static Preferences preferences; + +	/* logdir preference name */ +	public final static String logdirPreference = "LOGDIR"; + +	/* channel preference name */ +	public final static String channelPreferenceFormat = "CHANNEL-%d"; + +	/* frequency preference name */ +	public final static String frequencyPreferenceFormat = "FREQUENCY-%d"; + +	/* telemetry format preference name */ +	public final static String telemetryPreferenceFormat = "TELEMETRY-%d"; + +	/* voice preference name */ +	public final static String voicePreference = "VOICE"; + +	/* callsign preference name */ +	public final static String callsignPreference = "CALLSIGN"; + +	/* firmware directory preference name */ +	public final static String firmwaredirPreference = "FIRMWARE"; + +	/* serial debug preference name */ +	public final static String serialDebugPreference = "SERIAL-DEBUG"; + +	/* scanning telemetry preferences name */ +	public final static String scanningTelemetryPreference = "SCANNING-TELEMETRY"; + +	/* Launcher serial preference name */ +	public final static String launcherSerialPreference = "LAUNCHER-SERIAL"; + +	/* Launcher channel preference name */ +	public final static String launcherChannelPreference = "LAUNCHER-CHANNEL"; +	 +	/* Default logdir is ~/TeleMetrum */ +	public final static String logdirName = "TeleMetrum"; + +	/* Log directory */ +	public static File logdir; + +	/* Map directory -- hangs of logdir */ +	public static File mapdir; + +	/* Frequency (map serial to frequency) */ +	public static Hashtable<Integer, Double> frequencies; + +	/* Telemetry (map serial to telemetry format) */ +	public static Hashtable<Integer, Integer> telemetries; + +	/* Voice preference */ +	public static boolean voice; + +	/* Callsign preference */ +	public static String callsign; + +	/* Firmware directory */ +	public static File firmwaredir; + +	/* Scanning telemetry */ +	public static int scanning_telemetry; + +	/* List of frequencies */ +	public final static String common_frequencies_node_name = "COMMON-FREQUENCIES"; +	public static AltosFrequency[] common_frequencies; + +	public final static String	frequency_count = "COUNT"; +	public final static String	frequency_format = "FREQUENCY-%d"; +	public final static String	description_format = "DESCRIPTION-%d"; + +	public static AltosFrequency[] load_common_frequencies() { +		AltosFrequency[] frequencies = null; +		boolean	existing = false; +		try { +			existing = preferences.nodeExists(common_frequencies_node_name); +		} catch (BackingStoreException be) { +			existing = false; +		} +		if (existing) { +			Preferences	node = preferences.node(common_frequencies_node_name); +			int		count = node.getInt(frequency_count, 0); + +			frequencies = new AltosFrequency[count]; +			for (int i = 0; i < count; i++) { +				double	frequency; +				String	description; + +				frequency = node.getDouble(String.format(frequency_format, i), 0.0); +				description = node.get(String.format(description_format, i), null); +				frequencies[i] = new AltosFrequency(frequency, description); +			} +		} else { +			frequencies = new AltosFrequency[10]; +			for (int i = 0; i < 10; i++) { +				frequencies[i] = new AltosFrequency(434.550 + i * .1, +									   String.format("Channel %d", i)); +			} +		} +		return frequencies; +	} + +	public static void save_common_frequencies(AltosFrequency[] frequencies) { +		Preferences	node = preferences.node(common_frequencies_node_name); + +		node.putInt(frequency_count, frequencies.length); +		for (int i = 0; i < frequencies.length; i++) { +			node.putDouble(String.format(frequency_format, i), frequencies[i].frequency); +			node.put(String.format(description_format, i), frequencies[i].description); +		} +	} +	public static int launcher_serial; + +	public static int launcher_channel; + +	public static void init() { +		preferences = Preferences.userRoot().node("/org/altusmetrum/altosui"); + +		/* Initialize logdir from preferences */ +		String logdir_string = preferences.get(logdirPreference, null); +		if (logdir_string != null) +			logdir = new File(logdir_string); +		else { +			/* Use the file system view default directory */ +			logdir = new File(FileSystemView.getFileSystemView().getDefaultDirectory(), logdirName); +			if (!logdir.exists()) +				logdir.mkdirs(); +		} +		mapdir = new File(logdir, "maps"); +		if (!mapdir.exists()) +			mapdir.mkdirs(); + +		frequencies = new Hashtable<Integer, Double>(); + +		telemetries = new Hashtable<Integer,Integer>(); + +		voice = preferences.getBoolean(voicePreference, true); + +		callsign = preferences.get(callsignPreference,"N0CALL"); + +		scanning_telemetry = preferences.getInt(scanningTelemetryPreference,(1 << AltosLib.ao_telemetry_standard)); + +		launcher_serial = preferences.getInt(launcherSerialPreference, 0); + +		launcher_channel = preferences.getInt(launcherChannelPreference, 0); + +		String firmwaredir_string = preferences.get(firmwaredirPreference, null); +		if (firmwaredir_string != null) +			firmwaredir = new File(firmwaredir_string); +		else +			firmwaredir = null; + +		common_frequencies = load_common_frequencies(); + +	} + +	static { init(); } + +	public static void flush_preferences() { +		try { +			preferences.flush(); +		} catch (BackingStoreException ee) { +/* +			if (component != null) +				JOptionPane.showMessageDialog(component, +							      preferences.absolutePath(), +							      "Cannot save prefernces", +							      JOptionPane.ERROR_MESSAGE); +			else +*/ +				System.err.printf("Cannot save preferences\n"); +		} +	} + +	public static void set_logdir(File new_logdir) { +		logdir = new_logdir; +		mapdir = new File(logdir, "maps"); +		if (!mapdir.exists()) +			mapdir.mkdirs(); +		synchronized (preferences) { +			preferences.put(logdirPreference, logdir.getPath()); +			flush_preferences(); +		} +	} + +	public static File logdir() { +		return logdir; +	} + +	public static File mapdir() { +		return mapdir; +	} + +	public static void set_frequency(int serial, double new_frequency) { +		frequencies.put(serial, new_frequency); +		synchronized (preferences) { +			preferences.putDouble(String.format(frequencyPreferenceFormat, serial), new_frequency); +			flush_preferences(); +		} +	} + +	public static double frequency(int serial) { +		if (frequencies.containsKey(serial)) +			return frequencies.get(serial); +		double frequency = preferences.getDouble(String.format(frequencyPreferenceFormat, serial), 0); +		if (frequency == 0.0) { +			int channel = preferences.getInt(String.format(channelPreferenceFormat, serial), 0); +			frequency = AltosConvert.radio_channel_to_frequency(channel); +		} +		frequencies.put(serial, frequency); +		return frequency; +	} + +	public static void set_telemetry(int serial, int new_telemetry) { +		telemetries.put(serial, new_telemetry); +		synchronized (preferences) { +			preferences.putInt(String.format(telemetryPreferenceFormat, serial), new_telemetry); +			flush_preferences(); +		} +	} + +	public static int telemetry(int serial) { +		if (telemetries.containsKey(serial)) +			return telemetries.get(serial); +		int telemetry = preferences.getInt(String.format(telemetryPreferenceFormat, serial), +						   AltosLib.ao_telemetry_standard); +		telemetries.put(serial, telemetry); +		return telemetry; +	} + +	public static void set_scanning_telemetry(int new_scanning_telemetry) { +		scanning_telemetry = new_scanning_telemetry; +		synchronized (preferences) { +			preferences.putInt(scanningTelemetryPreference, scanning_telemetry); +			flush_preferences(); +		} +	} + +	public static int scanning_telemetry() { +		return scanning_telemetry; +	} + +	public static void set_voice(boolean new_voice) { +		voice = new_voice; +		synchronized (preferences) { +			preferences.putBoolean(voicePreference, voice); +			flush_preferences(); +		} +	} + +	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; +	} + +	public static void set_firmwaredir(File new_firmwaredir) { +		firmwaredir = new_firmwaredir; +		synchronized (preferences) { +			preferences.put(firmwaredirPreference, firmwaredir.getPath()); +			flush_preferences(); +		} +	} + +	public static File firmwaredir() { +		return firmwaredir; +	} + +	public static void set_launcher_serial(int new_launcher_serial) { +		launcher_serial = new_launcher_serial; +		System.out.printf("set launcher serial to %d\n", new_launcher_serial); +		synchronized (preferences) { +			preferences.putInt(launcherSerialPreference, launcher_serial); +			flush_preferences(); +		} +	} + +	public static int launcher_serial() { +		return launcher_serial; +	} + +	public static void set_launcher_channel(int new_launcher_channel) { +		launcher_channel = new_launcher_channel; +		System.out.printf("set launcher channel to %d\n", new_launcher_channel); +		synchronized (preferences) { +			preferences.putInt(launcherChannelPreference, launcher_channel); +			flush_preferences(); +		} +	} + +	public static int launcher_channel() { +		return launcher_channel; +	} +	 +	public static Preferences bt_devices() { +		return preferences.node("bt_devices"); +	} + +	public static AltosFrequency[] common_frequencies() { +		return common_frequencies; +	} + +	public static void set_common_frequencies(AltosFrequency[] frequencies) { +		common_frequencies = frequencies; +		synchronized(preferences) { +			save_common_frequencies(frequencies); +			flush_preferences(); +		} +	} + +	public static void add_common_frequency(AltosFrequency frequency) { +		AltosFrequency[]	new_frequencies = new AltosFrequency[common_frequencies.length + 1]; +		int			i; + +		for (i = 0; i < common_frequencies.length; i++) { +			if (frequency.frequency == common_frequencies[i].frequency) +				return; +			if (frequency.frequency < common_frequencies[i].frequency) +				break; +			new_frequencies[i] = common_frequencies[i]; +		} +		new_frequencies[i] = frequency; +		for (; i < common_frequencies.length; i++) +			new_frequencies[i+1] = common_frequencies[i]; +		set_common_frequencies(new_frequencies); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosRecord.java b/altoslib/src/org/altusmetrum/AltosLib/AltosRecord.java new file mode 100644 index 00000000..e4915af0 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosRecord.java @@ -0,0 +1,319 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; +import java.io.*; + +public class AltosRecord implements Comparable <AltosRecord> { +	public final static int	MISSING = 0x7fffffff; + +	public static final int	seen_flight = 1; +	public static final int	seen_sensor = 2; +	public static final int	seen_temp_volt = 4; +	public static final int	seen_deploy = 8; +	public static final int	seen_gps_time = 16; +	public static final int	seen_gps_lat = 32; +	public static final int	seen_gps_lon = 64; +	public static final int	seen_companion = 128; +	public int			seen; + +	public int	version; +	public String 	callsign; +	public int	serial; +	public int	flight; +	public int	rssi; +	public int	status; +	public int	state; +	public int	tick; + +	public int	accel; +	public int	pres; +	public int	temp; +	public int	batt; +	public int	drogue; +	public int	main; + +	public int	ground_accel; +	public int	ground_pres; +	public int	accel_plus_g; +	public int	accel_minus_g; + +	public double	acceleration; +	public double	speed; +	public double	height; + +	public int	flight_accel; +	public int	flight_vel; +	public int	flight_pres; + +	public AltosGPS	gps; +	public boolean		new_gps; + +	public AltosIMU	imu; +	public AltosMag	mag; + +	public double	time;	/* seconds since boost */ + +	public int	device_type; +	public int	config_major; +	public int	config_minor; +	public int	apogee_delay; +	public int	main_deploy; +	public int	flight_log_max; +	public String	firmware_version; + +	public AltosRecordCompanion companion; + +>>>>>>> 5a249bc... altosui: Complete split out of separate java library +	/* +	 * 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 +	 */ + +	public static final double counts_per_kPa = 27 * 2047 / 3300; +	public static final double counts_at_101_3kPa = 1674.0; + +	public static double +	barometer_to_pressure(double count) +	{ +		return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0; +	} + +	public double raw_pressure() { +		if (pres == MISSING) +			return MISSING; +		return barometer_to_pressure(pres); +	} + +	public double filtered_pressure() { +		if (flight_pres == MISSING) +			return MISSING; +		return barometer_to_pressure(flight_pres); +	} + +	public double ground_pressure() { +		if (ground_pres == MISSING) +			return MISSING; +		return barometer_to_pressure(ground_pres); +	} + +	public double raw_altitude() { +		double p = raw_pressure(); +		if (p == MISSING) +			return MISSING; +		return AltosConvert.pressure_to_altitude(p); +	} + +	public double ground_altitude() { +		double p = ground_pressure(); +		if (p == MISSING) +			return MISSING; +		return AltosConvert.pressure_to_altitude(p); +	} + +	public double filtered_altitude() { +		if (height != MISSING && ground_pres != MISSING) +			return height + ground_altitude(); + +		double	p = filtered_pressure(); +		if (p == MISSING) +			return MISSING; +		return AltosConvert.pressure_to_altitude(p); +	} + +	public double filtered_height() { +		if (height != MISSING) +			return height; + +		double f = filtered_altitude(); +		double g = ground_altitude(); +		if (f == MISSING || g == MISSING) +			return MISSING; +		return f - g; +	} + +	public double raw_height() { +		double r = raw_altitude(); +		double g = ground_altitude(); + +		if (r == MISSING || g == MISSING) +			return height; +		return r - g; +	} + +	public double battery_voltage() { +		if (batt == MISSING) +			return MISSING; +		return AltosConvert.cc_battery_to_voltage(batt); +	} + +	public double main_voltage() { +		if (main == MISSING) +			return MISSING; +		return AltosConvert.cc_ignitor_to_voltage(main); +	} + +	public double drogue_voltage() { +		if (drogue == MISSING) +			return MISSING; +		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 +	 */ + +	public static double +	thermometer_to_temperature(double thermo) +	{ +		return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247; +	} + +	public double temperature() { +		if (temp == MISSING) +			return MISSING; +		return thermometer_to_temperature(temp); +	} + +	public 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() { +		if (acceleration != MISSING) +			return acceleration; + +		if (ground_accel == MISSING || accel == MISSING) +			return MISSING; +		return (ground_accel - accel) / accel_counts_per_mss(); +	} + +	public double accel_speed() { +		if (speed != MISSING) +			return speed; +		if (flight_vel == MISSING) +			return MISSING; +		return flight_vel / (accel_counts_per_mss() * 100.0); +	} + +	public String state() { +		return AltosLib.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 int compareTo(AltosRecord o) { +		return tick - o.tick; +	} + +	public AltosRecord(AltosRecord old) { +		version = old.version; +		seen = old.seen; +		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; +		acceleration = old.acceleration; +		speed = old.speed; +		height = old.height; +		gps = new AltosGPS(old.gps); +		new_gps = false; +		companion = old.companion; +		imu = old.imu; +		mag = old.mag; +	} + +	public AltosRecord() { +		version = 0; +		seen = 0; +		callsign = "N0CALL"; +		serial = 0; +		flight = 0; +		rssi = 0; +		status = 0; +		state = AltosLib.ao_flight_startup; +		tick = 0; +		accel = MISSING; +		pres = MISSING; +		temp = MISSING; +		batt = MISSING; +		drogue = MISSING; +		main = MISSING; +		flight_accel = 0; +		ground_accel = 0; +		flight_vel = 0; +		flight_pres = 0; +		ground_pres = 0; +		accel_plus_g = 0; +		accel_minus_g = 0; +		acceleration = MISSING; +		speed = MISSING; +		height = MISSING; +		gps = new AltosGPS(); +		new_gps = false; +		companion = null; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosRecordCompanion.java b/altoslib/src/org/altusmetrum/AltosLib/AltosRecordCompanion.java new file mode 100644 index 00000000..c8cc6cac --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosRecordCompanion.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +public class AltosRecordCompanion { +	public final static int	board_id_telescience = 0x0a; +	public final static int	MAX_CHANNELS = 12; + +	public int	tick; +	public int	board_id; +	public int	update_period; +	public int	channels; +	public int[]	companion_data; + +	public AltosRecordCompanion(int in_channels) { +		channels = in_channels; +		if (channels < 0) +			channels = 0; +		if (channels > MAX_CHANNELS) +			channels = MAX_CHANNELS; +		companion_data = new int[channels]; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosRecordIterable.java b/altoslib/src/org/altusmetrum/AltosLib/AltosRecordIterable.java new file mode 100644 index 00000000..ed1787ed --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosRecordIterable.java @@ -0,0 +1,29 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; + +public abstract class AltosRecordIterable implements Iterable<AltosRecord> { +	public abstract Iterator<AltosRecord> iterator(); +	public void write_comments(PrintStream out) { } +	public boolean has_accel() { return false; } +	public boolean has_gps() { return false; } +	public boolean has_ignite() { return false; }; +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosReplayReader.java b/altoslib/src/org/altusmetrum/AltosLib/AltosReplayReader.java new file mode 100644 index 00000000..1585f9eb --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosReplayReader.java @@ -0,0 +1,56 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * Open an existing telemetry file and replay it in realtime + */ + +public class AltosReplayReader extends AltosFlightReader { +	Iterator<AltosRecord>	iterator; +	File	file; + +	public AltosRecord read() { +		if (iterator.hasNext()) +			return iterator.next(); +		return null; +	} + +	public void close (boolean interrupted) { +	} + +	public void update(AltosState state) throws InterruptedException { +		/* Make it run in realtime after the rocket leaves the pad */ +		if (state.state > AltosLib.ao_flight_pad) +			Thread.sleep((int) (Math.min(state.time_change,10) * 1000)); +	} + +	public File backing_file() { return file; } + +	public AltosReplayReader(Iterator<AltosRecord> in_iterator, File in_file) { +		iterator = in_iterator; +		file = in_file; +		name = file.getName(); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosState.java b/altoslib/src/org/altusmetrum/AltosLib/AltosState.java new file mode 100644 index 00000000..0645e448 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosState.java @@ -0,0 +1,210 @@ +/* + * 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 or eeprom data stream + */ + +package org.altusmetrum.AltosLib; + +public class AltosState { +	public AltosRecord data; + +	/* derived data */ + +	public long  	report_time; + +	public double	time; +	public double	time_change; +	public int	tick; + +	public int	state; +	public boolean	landed; +	public boolean	ascent;	/* going up? */ +	public boolean boost;	/* under power */ + +	public double	ground_altitude; +	public double	height; +	public double	speed; +	public double	acceleration; +	public double	battery; +	public double	temperature; +	public double	main_sense; +	public double	drogue_sense; +	public double	baro_speed; + +	public double	max_height; +	public double	max_acceleration; +	public double	max_speed; +	public double	max_baro_speed; + +	public AltosGPS	gps; + +	public AltosIMU	imu; +	public AltosMag	mag; + +	public static final int MIN_PAD_SAMPLES = 10; + +	public int	npad; +	public int	ngps; +	public int	gps_waiting; +	public boolean	gps_ready; + +	public AltosGreatCircle from_pad; +	public double	elevation;	/* from pad */ +	public double	range;		/* total distance */ + +	public double	gps_height; + +	public int	speak_tick; +	public double	speak_altitude; + +	public void init (AltosRecord cur, AltosState prev_state) { +		int		i; +		AltosRecord prev; + +		data = cur; + +		ground_altitude = data.ground_altitude(); +		height = data.filtered_height(); + +		report_time = System.currentTimeMillis(); + +		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; + +		if (prev_state != null) { + +			/* Preserve any existing gps data */ +			npad = prev_state.npad; +			ngps = prev_state.ngps; +			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; +			max_baro_speed = prev_state.max_baro_speed; +			imu = prev_state.imu; +			mag = prev_state.mag; + +			/* 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 (data.speed != AltosRecord.MISSING) +				baro_speed = data.speed; +			else { +				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; +			ngps = 0; +			gps = null; +			baro_speed = 0; +			time_change = 0; +		} + +		time = tick / 100.0; + +		if (cur.new_gps && (state == AltosLib.ao_flight_pad || state == AltosLib.ao_flight_idle)) { + +			/* Track consecutive 'good' gps reports, waiting for 10 of them */ +			if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) +				npad++; +			else +				npad = 0; + +			/* Average GPS data while on the pad */ +			if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) { +				if (ngps > 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; +				} +				ngps++; +			} +		} + +		gps_waiting = MIN_PAD_SAMPLES - npad; +		if (gps_waiting < 0) +			gps_waiting = 0; + +		gps_ready = gps_waiting == 0; + +		ascent = (AltosLib.ao_flight_boost <= state && +			  state <= AltosLib.ao_flight_coast); +		boost = (AltosLib.ao_flight_boost == state); + +		/* Only look at accelerometer data under boost */ +		if (boost && acceleration > max_acceleration) +			max_acceleration = acceleration; +		if (boost && speed > max_speed) +			max_speed = speed; +		if (boost && baro_speed > max_baro_speed) +			max_baro_speed = baro_speed; + +		if (height > max_height) +			max_height = height; +		if (data.gps != null) { +			if (gps == null || !gps.locked || data.gps.locked) +				gps = data.gps; +			if (ngps > 0 && gps.locked) { +				from_pad = new AltosGreatCircle(pad_lat, pad_lon, gps.lat, gps.lon); +			} +		} +		elevation = 0; +		range = -1; +		if (ngps > 0) { +			gps_height = gps.alt - pad_alt; +			if (from_pad != null) { +				elevation = Math.atan2(height, from_pad.distance) * 180 / Math.PI; +				range = Math.sqrt(height * height + from_pad.distance * from_pad.distance); +			} +		} else { +			gps_height = 0; +		} +	} + +	public AltosState(AltosRecord cur) { +		init(cur, null); +	} + +	public AltosState (AltosRecord cur, AltosState prev) { +		init(cur, prev); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetry.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetry.java new file mode 100644 index 00000000..04abb1f3 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetry.java @@ -0,0 +1,241 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +/* + * Telemetry data contents + */ + + +/* + * The packet format is a simple hex dump of the raw telemetry frame. + * It starts with 'TELEM', then contains hex digits with a checksum as the last + * byte on the line. + * + * Version 4 is a replacement with consistent syntax. Each telemetry line + * contains a sequence of space-separated names and values, the values are + * either integers or strings. The names are all unique. All values are + * optional + * + * VERSION 4 c KD7SQG n 236 f 18 r -25 s pad t 513 r_a 15756 r_b 26444 r_t 20944 + *   r_v 26640 r_d 512 r_m 208 c_a 15775 c_b 26439 c_p 15749 c_m 16281 a_a 15764 + *   a_s 0 a_b 26439 g_s u g_n 0 s_n 0 + * + * VERSION 4 c KD7SQG n 19 f 0 r -23 s pad t 513 r_b 26372 r_t 21292 r_v 26788 + *   r_d 136 r_m 140 c_b 26370 k_h 0 k_s 0 k_a 0 + * + * General header fields + * + *	Name		Value + * + *	VERSION		Telemetry version number (4 or more). Must be first. + * 	c		Callsign (string, no spaces allowed) + *	n		Flight unit serial number (integer) + * 	f		Flight number (integer) + *	r		Packet RSSI value (integer) + * 	s		Flight computer state (string, no spaces allowed) + *	t		Flight computer clock (integer in centiseconds) + * + * Version 3 is Version 2 with fixed RSSI numbers -- the radio reports + * in 1/2dB increments while this protocol provides only integers. So, + * the syntax didn't change just the interpretation of the RSSI + * values. + * + * Version 2 of the telemetry data stream is a bit of a mess, 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 extends AltosRecord { + +	/* +	 * General header fields +	 * +	 *	Name		Value +	 * +	 *	VERSION		Telemetry version number (4 or more). Must be first. +	 * 	c		Callsign (string, no spaces allowed) +	 *	n		Flight unit serial number (integer) +	 * 	f		Flight number (integer) +	 *	r		Packet RSSI value (integer) +	 * 	s		Flight computer state (string, no spaces allowed) +	 *	t		Flight computer clock (integer in centiseconds) +	 */ + +	final static String AO_TELEM_VERSION	= "VERSION"; +	final static String AO_TELEM_CALL	= "c"; +	final static String AO_TELEM_SERIAL	= "n"; +	final static String AO_TELEM_FLIGHT	= "f"; +	final static String AO_TELEM_RSSI	= "r"; +	final static String AO_TELEM_STATE	= "s"; +	final static String AO_TELEM_TICK	= "t"; + +	/* +	 * Raw sensor values +	 * +	 *	Name		Value +	 *	r_a		Accelerometer reading (integer) +	 *	r_b		Barometer reading (integer) +	 *	r_t		Thermometer reading (integer) +	 *	r_v		Battery reading (integer) +	 *	r_d		Drogue continuity (integer) +	 *	r_m		Main continuity (integer) +	 */ + +	final static String AO_TELEM_RAW_ACCEL	= "r_a"; +	final static String AO_TELEM_RAW_BARO	= "r_b"; +	final static String AO_TELEM_RAW_THERMO	= "r_t"; +	final static String AO_TELEM_RAW_BATT	= "r_v"; +	final static String AO_TELEM_RAW_DROGUE	= "r_d"; +	final static String AO_TELEM_RAW_MAIN	= "r_m"; + +	/* +	 * Sensor calibration values +	 * +	 *	Name		Value +	 *	c_a		Ground accelerometer reading (integer) +	 *	c_b		Ground barometer reading (integer) +	 *	c_p		Accelerometer reading for +1g +	 *	c_m		Accelerometer reading for -1g +	 */ + +	final static String AO_TELEM_CAL_ACCEL_GROUND	= "c_a"; +	final static String AO_TELEM_CAL_BARO_GROUND	= "c_b"; +	final static String AO_TELEM_CAL_ACCEL_PLUS	= "c_p"; +	final static String AO_TELEM_CAL_ACCEL_MINUS	= "c_m"; + +	/* +	 * Kalman state values +	 * +	 *	Name		Value +	 *	k_h		Height above pad (integer, meters) +	 *	k_s		Vertical speeed (integer, m/s * 16) +	 *	k_a		Vertical acceleration (integer, m/s² * 16) +	 */ + +	final static String AO_TELEM_KALMAN_HEIGHT	= "k_h"; +	final static String AO_TELEM_KALMAN_SPEED	= "k_s"; +	final static String AO_TELEM_KALMAN_ACCEL	= "k_a"; + +	/* +	 * Ad-hoc flight values +	 * +	 *	Name		Value +	 *	a_a		Acceleration (integer, sensor units) +	 *	a_s		Speed (integer, integrated acceleration value) +	 *	a_b		Barometer reading (integer, sensor units) +	 */ + +	final static String AO_TELEM_ADHOC_ACCEL	= "a_a"; +	final static String AO_TELEM_ADHOC_SPEED	= "a_s"; +	final static String AO_TELEM_ADHOC_BARO		= "a_b"; + +	/* +	 * GPS values +	 * +	 *	Name		Value +	 *	g_s		GPS state (string): +	 *				l	locked +	 *				u	unlocked +	 *				e	error (missing or broken) +	 *	g_n		Number of sats used in solution +	 *	g_ns		Latitude (degrees * 10e7) +	 *	g_ew		Longitude (degrees * 10e7) +	 *	g_a		Altitude (integer meters) +	 *	g_Y		GPS year (integer) +	 *	g_M		GPS month (integer - 1-12) +	 *	g_D		GPS day (integer - 1-31) +	 *	g_h		GPS hour (integer - 0-23) +	 *	g_m		GPS minute (integer - 0-59) +	 *	g_s		GPS second (integer - 0-59) +	 *	g_v		GPS vertical speed (integer, cm/sec) +	 *	g_s		GPS horizontal speed (integer, cm/sec) +	 *	g_c		GPS course (integer, 0-359) +	 *	g_hd		GPS hdop (integer * 10) +	 *	g_vd		GPS vdop (integer * 10) +	 *	g_he		GPS h error (integer) +	 *	g_ve		GPS v error (integer) +	 */ + +	final static String AO_TELEM_GPS_STATE	 		= "g"; +	final static String AO_TELEM_GPS_STATE_LOCKED		= "l"; +	final static String AO_TELEM_GPS_STATE_UNLOCKED		= "u"; +	final static String AO_TELEM_GPS_STATE_ERROR		= "e"; +	final static String AO_TELEM_GPS_NUM_SAT		= "g_n"; +	final static String AO_TELEM_GPS_LATITUDE		= "g_ns"; +	final static String AO_TELEM_GPS_LONGITUDE		= "g_ew"; +	final static String AO_TELEM_GPS_ALTITUDE		= "g_a"; +	final static String AO_TELEM_GPS_YEAR			= "g_Y"; +	final static String AO_TELEM_GPS_MONTH			= "g_M"; +	final static String AO_TELEM_GPS_DAY			= "g_D"; +	final static String AO_TELEM_GPS_HOUR			= "g_h"; +	final static String AO_TELEM_GPS_MINUTE			= "g_m"; +	final static String AO_TELEM_GPS_SECOND			= "g_s"; +	final static String AO_TELEM_GPS_VERTICAL_SPEED		= "g_v"; +	final static String AO_TELEM_GPS_HORIZONTAL_SPEED	= "g_g"; +	final static String AO_TELEM_GPS_COURSE			= "g_c"; +	final static String AO_TELEM_GPS_HDOP			= "g_hd"; +	final static String AO_TELEM_GPS_VDOP			= "g_vd"; +	final static String AO_TELEM_GPS_HERROR			= "g_he"; +	final static String AO_TELEM_GPS_VERROR			= "g_ve"; + +	/* +	 * GPS satellite values +	 * +	 *	Name		Value +	 *	s_n		Number of satellites reported (integer) +	 *	s_v0		Space vehicle ID (integer) for report 0 +	 *	s_c0		C/N0 number (integer) for report 0 +	 *	s_v1		Space vehicle ID (integer) for report 1 +	 *	s_c1		C/N0 number (integer) for report 1 +	 *	... +	 */ + +	final static String AO_TELEM_SAT_NUM	= "s_n"; +	final static String AO_TELEM_SAT_SVID	= "s_v"; +	final static String AO_TELEM_SAT_C_N_0	= "s_c"; + +	static public AltosRecord parse(String line, AltosRecord previous) throws ParseException, AltosCRCException { +		AltosTelemetryRecord	r = AltosTelemetryRecord.parse(line); + +		return r.update_state(previous); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryIterable.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryIterable.java new file mode 100644 index 00000000..f4b4029f --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryIterable.java @@ -0,0 +1,109 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.io.*; +import java.util.*; +import java.text.*; + +public class AltosTelemetryIterable extends AltosRecordIterable { +	TreeSet<AltosRecord>	records; + +	public Iterator<AltosRecord> iterator () { +		return records.iterator(); +	} + +	boolean has_gps = false; +	boolean has_accel = false; +	boolean has_ignite = false; +	public boolean has_gps() { return has_gps; } +	public boolean has_accel() { return has_accel; } +	public boolean has_ignite() { return has_ignite; }; + +	public AltosTelemetryIterable (FileInputStream input) { +		boolean saw_boost = false; +		int	current_tick = 0; +		int	boost_tick = 0; + +		AltosRecord	previous = null; +		records = new TreeSet<AltosRecord> (); + +		try { +			for (;;) { +				String line = AltosRecord.gets(input); +				if (line == null) { +					break; +				} +				try { +					AltosRecord record = AltosTelemetry.parse(line, previous); +					if (record == null) +						break; +					if (records.isEmpty()) { +						current_tick = record.tick; +					} else { +						int tick = record.tick; +						while (tick < current_tick - 0x1000) +							tick += 0x10000; +						current_tick = tick; +						record.tick = current_tick; +					} +					if (!saw_boost && record.state >= AltosLib.ao_flight_boost) +					{ +						saw_boost = true; +						boost_tick = record.tick; +					} +					if (record.accel != AltosRecord.MISSING) +						has_accel = true; +					if (record.gps != null) +						has_gps = true; +					if (record.main != AltosRecord.MISSING) +						has_ignite = true; +					if (previous != null && previous.tick != record.tick) +						records.add(previous); +					previous = record; +				} catch (ParseException pe) { +					System.out.printf("parse exception %s\n", pe.getMessage()); +				} catch (AltosCRCException ce) { +				} +			} +		} catch (IOException io) { +			System.out.printf("io exception\n"); +		} + +		if (previous != null) +			records.add(previous); + +		/* Adjust all tick counts to match expected eeprom values, +		 * which starts with a 16-bit tick count 16 samples before boost +		 */ + +		int tick_adjust = (boost_tick - 16) & 0xffff0000; +		for (AltosRecord r : this) +			r.tick -= tick_adjust; +		boost_tick -= tick_adjust; + +		/* adjust all tick counts to be relative to boost time */ +		for (AltosRecord r : this) +			r.time = (r.tick - boost_tick) / 100.0; + +		try { +			input.close(); +		} catch (IOException ie) { +		} +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryMap.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryMap.java new file mode 100644 index 00000000..003cb6a9 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryMap.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +public class AltosTelemetryMap extends HashMap<String,String> { +	public boolean has(String key) { +		return containsKey(key); +	} + +	public String get_string(String key) throws ParseException { +		if (!has(key)) +			throw new ParseException ("missing " + key, 0); +		return (String) get(key); +	} + +	public String get_string(String key, String def) { +		if (has(key)) +			return get(key); +		else +			return def; +	} + +	public int get_int(String key) throws ParseException { +		return AltosParse.parse_int(get_string(key)); +	} + +	public int get_int(String key, int def) throws ParseException { +		if (has(key)) +			return get_int(key); +		else +			return def; +	} + +	public double get_double(String key, double def, double scale) throws ParseException { +		if (has(key)) +			return get_int(key) * scale; +		else +			return def; +	} + +	public AltosTelemetryMap(String[] words, int start) { +		for (int i = start; i < words.length - 1; i += 2) +			put(words[i], words[i+1]); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryReader.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryReader.java new file mode 100644 index 00000000..67ac1b65 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryReader.java @@ -0,0 +1,119 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.io.*; +import java.util.concurrent.*; + +public class AltosTelemetryReader extends AltosFlightReader { +	AltosLink	link; +	AltosLog	log; +	AltosRecord	previous; +	double		frequency; +	int		telemetry; + +	LinkedBlockingQueue<AltosLine> telem; + +	public AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { +		AltosLine l = telem.take(); +		if (l.line == null) +			throw new IOException("IO error"); +		AltosRecord	next = AltosTelemetry.parse(l.line, previous); +		previous = next; +		return next; +	} + +	public void flush() { +		telem.clear(); +	} + +	public void close(boolean interrupted) { +		link.remove_monitor(telem); +		log.close(); +		link.close(); +	} + +	public void set_frequency(double in_frequency) throws InterruptedException, TimeoutException { +		frequency = in_frequency; +		link.set_radio_frequency(frequency); +	} + +	public boolean supports_telemetry(int telemetry) { + +		try { +			/* Version 1.0 or later firmware supports all telemetry formats */ +			if (serial.config_data().compare_version("1.0") >= 0) +				return true; + +			/* Version 0.9 firmware only supports 0.9 telemetry */ +			if (serial.config_data().compare_version("0.9") >= 0) { +				if (telemetry == Altos.ao_telemetry_0_9) +					return true; +				else +					return false; +			} + +			/* Version 0.8 firmware only supports 0.8 telemetry */ +			if (telemetry == Altos.ao_telemetry_0_8) +				return true; +			else +				return false; +		} catch (InterruptedException ie) { +			return true; +		} catch (TimeoutException te) { +			return true; +		} +	} + +	public void save_frequency() { +		AltosPreferences.set_frequency(link.serial, frequency); +	} + +	public void set_telemetry(int in_telemetry) { +		telemetry = in_telemetry; +		link.set_telemetry(telemetry); +	} + +	public void save_telemetry() { +		AltosPreferences.set_telemetry(link.serial, telemetry); +	} + +	public void set_monitor(boolean monitor) { +		link.set_monitor(monitor); +	} + +	public File backing_file() { +		return log.file(); +	} + +	public AltosTelemetryReader (AltosLink in_link) +		throws IOException, InterruptedException, TimeoutException { +		link = in_link; +		log = new AltosLog(link); +		name = link.name; +		previous = null; +		telem = new LinkedBlockingQueue<AltosLine>(); +		frequency = AltosPreferences.frequency(link.serial); +		set_frequency(frequency); +		telemetry = AltosPreferences.telemetry(link.serial); +		set_telemetry(telemetry); +		link.add_monitor(telem); +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecord.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecord.java new file mode 100644 index 00000000..367c148d --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecord.java @@ -0,0 +1,126 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +public abstract class AltosTelemetryRecord { + +	long	received_time; +	abstract public AltosRecord update_state(AltosRecord previous); + +	static boolean cksum(int[] bytes) { +		int	sum = 0x5a; +		for (int i = 1; i < bytes.length - 1; i++) +			sum += bytes[i]; +		sum &= 0xff; +		return sum == bytes[bytes.length - 1]; +	} + +	final static int PKT_APPEND_STATUS_1_CRC_OK		= (1 << 7); +	final static int PKT_APPEND_STATUS_1_LQI_MASK		= (0x7f); +	final static int PKT_APPEND_STATUS_1_LQI_SHIFT		= 0; + +	final static int packet_type_TM_sensor = 0x01; +	final static int packet_type_Tm_sensor = 0x02; +	final static int packet_type_Tn_sensor = 0x03; +	final static int packet_type_configuration = 0x04; +	final static int packet_type_location = 0x05; +	final static int packet_type_satellite = 0x06; +	final static int packet_type_companion = 0x07; +	 +	static AltosTelemetryRecord parse_hex(String hex)  throws ParseException, AltosCRCException { +		AltosTelemetryRecord	r; + +		int[] bytes; +		try { +			bytes = Altos.hexbytes(hex); +		} catch (NumberFormatException ne) { +			throw new ParseException(ne.getMessage(), 0); +		} + +		/* one for length, one for checksum */ +		if (bytes[0] != bytes.length - 2) +			throw new ParseException(String.format("invalid length %d != %d\n", +							       bytes[0], +							       bytes.length - 2), 0); +		if (!cksum(bytes)) +			throw new ParseException(String.format("invalid line \"%s\"", hex), 0); + +		int	rssi = Altos.int8(bytes, bytes.length - 3) / 2 - 74; +		int	status = Altos.uint8(bytes, bytes.length - 2); + +		if ((status & PKT_APPEND_STATUS_1_CRC_OK) == 0) +			throw new AltosCRCException(rssi); + +		/* length, data ..., rssi, status, checksum -- 4 bytes extra */ +		switch (bytes.length) { +		case Altos.ao_telemetry_standard_len + 4: +			int	type = Altos.uint8(bytes, 4 + 1); +			switch (type) { +			case packet_type_TM_sensor: +			case packet_type_Tm_sensor: +			case packet_type_Tn_sensor: +				r = new AltosTelemetryRecordSensor(bytes, rssi); +				break; +			case packet_type_configuration: +				r = new AltosTelemetryRecordConfiguration(bytes); +				break; +			case packet_type_location: +				r = new AltosTelemetryRecordLocation(bytes); +				break; +			case packet_type_satellite: +				r = new AltosTelemetryRecordSatellite(bytes); +				break; +			case packet_type_companion: +				r = new AltosTelemetryRecordCompanion(bytes); +				break; +			default: +				r = new AltosTelemetryRecordRaw(bytes); +				break; +			} +			break; +		case Altos.ao_telemetry_0_9_len + 4: +			r = new AltosTelemetryRecordLegacy(bytes, rssi, status); +			break; +		case Altos.ao_telemetry_0_8_len + 4: +			r = new AltosTelemetryRecordLegacy(bytes, rssi, status); +			break; +		default: +			throw new ParseException(String.format("Invalid packet length %d", bytes.length), 0); +		} +		r.received_time = System.currentTimeMillis(); +		return r; +	} + +	public static AltosTelemetryRecord parse(String line) throws ParseException, AltosCRCException { +		AltosTelemetryRecord	r; + +		String[] word = line.split("\\s+"); +		int i =0; +		if (word[i].equals("CRC") && word[i+1].equals("INVALID")) { +			i += 2; +			AltosParse.word(word[i++], "RSSI"); +			throw new AltosCRCException(AltosParse.parse_int(word[i++])); +		} + +		if (word[i].equals("TELEM")) +			r = parse_hex(word[i+1]); +		else +			r = new AltosTelemetryRecordLegacy(line); +		return r; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordCompanion.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordCompanion.java new file mode 100644 index 00000000..6ad17244 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordCompanion.java @@ -0,0 +1,52 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +public class AltosTelemetryRecordCompanion extends AltosTelemetryRecordRaw { + +	AltosRecordCompanion	companion; + +	public AltosTelemetryRecordCompanion(int[] in_bytes) { +		super(in_bytes); + +		int	off = 0; +		if (uint8(6) == 0) +			off = 1; +		int channels = uint8(7+off); + +		if (off != 0 && channels >= 12) +			channels = 11; + +		companion = new AltosRecordCompanion(channels); +		companion.tick		= tick; +		companion.board_id      = uint8(5); +		companion.update_period = uint8(6+off); +		for (int i = 0; i < companion.companion_data.length; i++) +			companion.companion_data[i] = uint16(8 + off + i * 2); +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next = super.update_state(previous); + +		next.companion = companion; +		next.seen |= AltosRecord.seen_sensor | AltosRecord.seen_temp_volt; + +		companion.tick = tick; +		return next; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordConfiguration.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordConfiguration.java new file mode 100644 index 00000000..25242edc --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + + +public class AltosTelemetryRecordConfiguration extends AltosTelemetryRecordRaw { +	int	device_type; +	int	flight; +	int	config_major; +	int	config_minor; +	int	apogee_delay; +	int	main_deploy; +	int	flight_log_max; +	String	callsign; +	String	version; + +	public AltosTelemetryRecordConfiguration(int[] in_bytes) { +		super(in_bytes); + +		device_type    = uint8(5); +		flight         = uint16(6); +		config_major   = uint8(8); +		config_minor   = uint8(9); +		apogee_delay   = uint16(10); +		main_deploy    = uint16(12); +		flight_log_max = uint16(14); +		callsign       = string(16, 8); +		version        = string(24, 8); +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next = super.update_state(previous); + +		next.device_type = device_type; +		next.flight = flight; +		next.config_major = config_major; +		next.config_minor = config_minor; +		next.apogee_delay = apogee_delay; +		next.main_deploy = main_deploy; +		next.flight_log_max = flight_log_max; + +		next.callsign = callsign; +		next.firmware_version = version; + +		next.seen |= AltosRecord.seen_deploy | AltosRecord.seen_flight; + +		return next; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordGeneral.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordGeneral.java new file mode 100644 index 00000000..5e157a54 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordGeneral.java @@ -0,0 +1,43 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +public class AltosTelemetryRecordGeneral { + +	static AltosTelemetryRecord parse(String line) throws ParseException, AltosCRCException { +		AltosTelemetryRecord	r; + +		String[] word = line.split("\\s+"); +		int i =0; +		if (word[i].equals("CRC") && word[i+1].equals("INVALID")) { +			i += 2; +			AltosParse.word(word[i++], "RSSI"); +			throw new AltosCRCException(AltosParse.parse_int(word[i++])); +		} + +		if (word[i].equals("TELEM")) +			r = AltosTelemetryRecordRaw.parse(word[i+1]); +		else +			r = new AltosTelemetryRecordLegacy(line); +		return r; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLegacy.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLegacy.java new file mode 100644 index 00000000..8e3713cc --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLegacy.java @@ -0,0 +1,521 @@ +/* + * 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +/* + * Telemetry data contents + */ + + +/* + * The packet format is a simple hex dump of the raw telemetry frame. + * It starts with 'TELEM', then contains hex digits with a checksum as the last + * byte on the line. + * + * Version 4 is a replacement with consistent syntax. Each telemetry line + * contains a sequence of space-separated names and values, the values are + * either integers or strings. The names are all unique. All values are + * optional + * + * VERSION 4 c KD7SQG n 236 f 18 r -25 s pad t 513 r_a 15756 r_b 26444 r_t 20944 + *   r_v 26640 r_d 512 r_m 208 c_a 15775 c_b 26439 c_p 15749 c_m 16281 a_a 15764 + *   a_s 0 a_b 26439 g_s u g_n 0 s_n 0 + * + * VERSION 4 c KD7SQG n 19 f 0 r -23 s pad t 513 r_b 26372 r_t 21292 r_v 26788 + *   r_d 136 r_m 140 c_b 26370 k_h 0 k_s 0 k_a 0 + * + * General header fields + * + *	Name		Value + * + *	VERSION		Telemetry version number (4 or more). Must be first. + * 	c		Callsign (string, no spaces allowed) + *	n		Flight unit serial number (integer) + * 	f		Flight number (integer) + *	r		Packet RSSI value (integer) + * 	s		Flight computer state (string, no spaces allowed) + *	t		Flight computer clock (integer in centiseconds) + * + * Version 3 is Version 2 with fixed RSSI numbers -- the radio reports + * in 1/2dB increments while this protocol provides only integers. So, + * the syntax didn't change just the interpretation of the RSSI + * values. + * + * Version 2 of the telemetry data stream is a bit of a mess, 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 AltosTelemetryRecordLegacy extends AltosTelemetryRecord { +	/* +	 * General header fields +	 * +	 *	Name		Value +	 * +	 *	VERSION		Telemetry version number (4 or more). Must be first. +	 * 	c		Callsign (string, no spaces allowed) +	 *	n		Flight unit serial number (integer) +	 * 	f		Flight number (integer) +	 *	r		Packet RSSI value (integer) +	 * 	s		Flight computer state (string, no spaces allowed) +	 *	t		Flight computer clock (integer in centiseconds) +	 */ + +	final static String AO_TELEM_VERSION	= "VERSION"; +	final static String AO_TELEM_CALL	= "c"; +	final static String AO_TELEM_SERIAL	= "n"; +	final static String AO_TELEM_FLIGHT	= "f"; +	final static String AO_TELEM_RSSI	= "r"; +	final static String AO_TELEM_STATE	= "s"; +	final static String AO_TELEM_TICK	= "t"; + +	/* +	 * Raw sensor values +	 * +	 *	Name		Value +	 *	r_a		Accelerometer reading (integer) +	 *	r_b		Barometer reading (integer) +	 *	r_t		Thermometer reading (integer) +	 *	r_v		Battery reading (integer) +	 *	r_d		Drogue continuity (integer) +	 *	r_m		Main continuity (integer) +	 */ + +	final static String AO_TELEM_RAW_ACCEL	= "r_a"; +	final static String AO_TELEM_RAW_BARO	= "r_b"; +	final static String AO_TELEM_RAW_THERMO	= "r_t"; +	final static String AO_TELEM_RAW_BATT	= "r_v"; +	final static String AO_TELEM_RAW_DROGUE	= "r_d"; +	final static String AO_TELEM_RAW_MAIN	= "r_m"; + +	/* +	 * Sensor calibration values +	 * +	 *	Name		Value +	 *	c_a		Ground accelerometer reading (integer) +	 *	c_b		Ground barometer reading (integer) +	 *	c_p		Accelerometer reading for +1g +	 *	c_m		Accelerometer reading for -1g +	 */ + +	final static String AO_TELEM_CAL_ACCEL_GROUND	= "c_a"; +	final static String AO_TELEM_CAL_BARO_GROUND	= "c_b"; +	final static String AO_TELEM_CAL_ACCEL_PLUS	= "c_p"; +	final static String AO_TELEM_CAL_ACCEL_MINUS	= "c_m"; + +	/* +	 * Kalman state values +	 * +	 *	Name		Value +	 *	k_h		Height above pad (integer, meters) +	 *	k_s		Vertical speeed (integer, m/s * 16) +	 *	k_a		Vertical acceleration (integer, m/s² * 16) +	 */ + +	final static String AO_TELEM_KALMAN_HEIGHT	= "k_h"; +	final static String AO_TELEM_KALMAN_SPEED	= "k_s"; +	final static String AO_TELEM_KALMAN_ACCEL	= "k_a"; + +	/* +	 * Ad-hoc flight values +	 * +	 *	Name		Value +	 *	a_a		Acceleration (integer, sensor units) +	 *	a_s		Speed (integer, integrated acceleration value) +	 *	a_b		Barometer reading (integer, sensor units) +	 */ + +	final static String AO_TELEM_ADHOC_ACCEL	= "a_a"; +	final static String AO_TELEM_ADHOC_SPEED	= "a_s"; +	final static String AO_TELEM_ADHOC_BARO		= "a_b"; + +	/* +	 * GPS values +	 * +	 *	Name		Value +	 *	g_s		GPS state (string): +	 *				l	locked +	 *				u	unlocked +	 *				e	error (missing or broken) +	 *	g_n		Number of sats used in solution +	 *	g_ns		Latitude (degrees * 10e7) +	 *	g_ew		Longitude (degrees * 10e7) +	 *	g_a		Altitude (integer meters) +	 *	g_Y		GPS year (integer) +	 *	g_M		GPS month (integer - 1-12) +	 *	g_D		GPS day (integer - 1-31) +	 *	g_h		GPS hour (integer - 0-23) +	 *	g_m		GPS minute (integer - 0-59) +	 *	g_s		GPS second (integer - 0-59) +	 *	g_v		GPS vertical speed (integer, cm/sec) +	 *	g_s		GPS horizontal speed (integer, cm/sec) +	 *	g_c		GPS course (integer, 0-359) +	 *	g_hd		GPS hdop (integer * 10) +	 *	g_vd		GPS vdop (integer * 10) +	 *	g_he		GPS h error (integer) +	 *	g_ve		GPS v error (integer) +	 */ + +	final static String AO_TELEM_GPS_STATE	 		= "g"; +	final static String AO_TELEM_GPS_STATE_LOCKED		= "l"; +	final static String AO_TELEM_GPS_STATE_UNLOCKED		= "u"; +	final static String AO_TELEM_GPS_STATE_ERROR		= "e"; +	final static String AO_TELEM_GPS_NUM_SAT		= "g_n"; +	final static String AO_TELEM_GPS_LATITUDE		= "g_ns"; +	final static String AO_TELEM_GPS_LONGITUDE		= "g_ew"; +	final static String AO_TELEM_GPS_ALTITUDE		= "g_a"; +	final static String AO_TELEM_GPS_YEAR			= "g_Y"; +	final static String AO_TELEM_GPS_MONTH			= "g_M"; +	final static String AO_TELEM_GPS_DAY			= "g_D"; +	final static String AO_TELEM_GPS_HOUR			= "g_h"; +	final static String AO_TELEM_GPS_MINUTE			= "g_m"; +	final static String AO_TELEM_GPS_SECOND			= "g_s"; +	final static String AO_TELEM_GPS_VERTICAL_SPEED		= "g_v"; +	final static String AO_TELEM_GPS_HORIZONTAL_SPEED	= "g_g"; +	final static String AO_TELEM_GPS_COURSE			= "g_c"; +	final static String AO_TELEM_GPS_HDOP			= "g_hd"; +	final static String AO_TELEM_GPS_VDOP			= "g_vd"; +	final static String AO_TELEM_GPS_HERROR			= "g_he"; +	final static String AO_TELEM_GPS_VERROR			= "g_ve"; + +	/* +	 * GPS satellite values +	 * +	 *	Name		Value +	 *	s_n		Number of satellites reported (integer) +	 *	s_v0		Space vehicle ID (integer) for report 0 +	 *	s_c0		C/N0 number (integer) for report 0 +	 *	s_v1		Space vehicle ID (integer) for report 1 +	 *	s_c1		C/N0 number (integer) for report 1 +	 *	... +	 */ + +	final static String AO_TELEM_SAT_NUM	= "s_n"; +	final static String AO_TELEM_SAT_SVID	= "s_v"; +	final static String AO_TELEM_SAT_C_N_0	= "s_c"; + +	AltosRecord	record; + +	private void parse_v4(String[] words, int i) throws ParseException { +		AltosTelemetryMap	map = new AltosTelemetryMap(words, i); + +		record.callsign = map.get_string(AO_TELEM_CALL, "N0CALL"); +		record.serial = map.get_int(AO_TELEM_SERIAL, AltosRecord.MISSING); +		record.flight = map.get_int(AO_TELEM_FLIGHT, AltosRecord.MISSING); +		record.rssi = map.get_int(AO_TELEM_RSSI, AltosRecord.MISSING); +		record.state = Altos.state(map.get_string(AO_TELEM_STATE, "invalid")); +		record.tick = map.get_int(AO_TELEM_TICK, 0); + +		/* raw sensor values */ +		record.accel = map.get_int(AO_TELEM_RAW_ACCEL, AltosRecord.MISSING); +		record.pres = map.get_int(AO_TELEM_RAW_BARO, AltosRecord.MISSING); +		record.temp = map.get_int(AO_TELEM_RAW_THERMO, AltosRecord.MISSING); +		record.batt = map.get_int(AO_TELEM_RAW_BATT, AltosRecord.MISSING); +		record.drogue = map.get_int(AO_TELEM_RAW_DROGUE, AltosRecord.MISSING); +		record.main = map.get_int(AO_TELEM_RAW_MAIN, AltosRecord.MISSING); + +		/* sensor calibration information */ +		record.ground_accel = map.get_int(AO_TELEM_CAL_ACCEL_GROUND, AltosRecord.MISSING); +		record.ground_pres = map.get_int(AO_TELEM_CAL_BARO_GROUND, AltosRecord.MISSING); +		record.accel_plus_g = map.get_int(AO_TELEM_CAL_ACCEL_PLUS, AltosRecord.MISSING); +		record.accel_minus_g = map.get_int(AO_TELEM_CAL_ACCEL_MINUS, AltosRecord.MISSING); + +		/* flight computer values */ +		record.acceleration = map.get_double(AO_TELEM_KALMAN_ACCEL, AltosRecord.MISSING, 1/16.0); +		record.speed = map.get_double(AO_TELEM_KALMAN_SPEED, AltosRecord.MISSING, 1/16.0); +		record.height = map.get_int(AO_TELEM_KALMAN_HEIGHT, AltosRecord.MISSING); + +		record.flight_accel = map.get_int(AO_TELEM_ADHOC_ACCEL, AltosRecord.MISSING); +		record.flight_vel = map.get_int(AO_TELEM_ADHOC_SPEED, AltosRecord.MISSING); +		record.flight_pres = map.get_int(AO_TELEM_ADHOC_BARO, AltosRecord.MISSING); + +		if (map.has(AO_TELEM_GPS_STATE)) { +		record.gps = new AltosGPS(map); +		record.new_gps = true; +		} +		else +		record.gps = null; +	} + +	private void parse_legacy(String[] words, int i) throws ParseException { + +		AltosParse.word (words[i++], "CALL"); +		record.callsign = words[i++]; + +		AltosParse.word (words[i++], "SERIAL"); +		record.serial = AltosParse.parse_int(words[i++]); + +		if (record.version >= 2) { +			AltosParse.word (words[i++], "FLIGHT"); +			record.flight = AltosParse.parse_int(words[i++]); +		} else +			record.flight = 0; + +		AltosParse.word(words[i++], "RSSI"); +		record.rssi = AltosParse.parse_int(words[i++]); + +		/* Older telemetry data had mis-computed RSSI value */ +		if (record.version <= 2) +			record.rssi = (record.rssi + 74) / 2 - 74; + +		AltosParse.word(words[i++], "STATUS"); +		record.status = AltosParse.parse_hex(words[i++]); + +		AltosParse.word(words[i++], "STATE"); +		record.state = Altos.state(words[i++]); + +		record.tick = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "a:"); +		record.accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "p:"); +		record.pres = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "t:"); +		record.temp = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "v:"); +		record.batt = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "d:"); +		record.drogue = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "m:"); +		record.main = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fa:"); +		record.flight_accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "ga:"); +		record.ground_accel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fv:"); +		record.flight_vel = AltosParse.parse_int(words[i++]); + +		AltosParse.word(words[i++], "fp:"); +		record.flight_pres = AltosParse.parse_int(words[i++]); + +		/* Old TeleDongle code with kalman-reporting TeleMetrum code */ +		if ((record.flight_vel & 0xffff0000) == 0x80000000) { +			record.speed = ((short) record.flight_vel) / 16.0; +			record.acceleration = record.flight_accel / 16.0; +			record.height = record.flight_pres; +			record.flight_vel = AltosRecord.MISSING; +			record.flight_pres = AltosRecord.MISSING; +			record.flight_accel = AltosRecord.MISSING; +		} + +		AltosParse.word(words[i++], "gp:"); +		record.ground_pres = AltosParse.parse_int(words[i++]); + +		if (record.version >= 1) { +			AltosParse.word(words[i++], "a+:"); +			record.accel_plus_g = AltosParse.parse_int(words[i++]); + +			AltosParse.word(words[i++], "a-:"); +			record.accel_minus_g = AltosParse.parse_int(words[i++]); +		} else { +			record.accel_plus_g = record.ground_accel; +			record.accel_minus_g = record.ground_accel + 530; +		} + +		record.gps = new AltosGPS(words, i, record.version); +		record.new_gps = true; +	} + +	public AltosTelemetryRecordLegacy(String line) throws ParseException, AltosCRCException { +		String[] words = line.split("\\s+"); +		int	i = 0; + +		record = new AltosRecord(); + +		if (words[i].equals("CRC") && words[i+1].equals("INVALID")) { +			i += 2; +			AltosParse.word(words[i++], "RSSI"); +			record.rssi = AltosParse.parse_int(words[i++]); +			throw new AltosCRCException(record.rssi); +		} +		if (words[i].equals("CALL")) { +			record.version = 0; +		} else { +			AltosParse.word (words[i++], "VERSION"); +			record.version = AltosParse.parse_int(words[i++]); +		} + +		if (record.version < 4) +			parse_legacy(words, i); +		else +			parse_v4(words, i); +	} + +	/* +	 * Given a hex dump of a legacy telemetry line, construct an AltosRecord from that +	 */ + +	int[]	bytes; +	int	adjust; + +	private int int8(int i) { +		return AltosLib.int8(bytes, i + 1 + adjust); +	} +	private int uint8(int i) { +		return AltosLib.uint8(bytes, i + 1 + adjust); +	} +	private int int16(int i) { +		return AltosLib.int16(bytes, i + 1 + adjust); +	} +	private int uint16(int i) { +		return AltosLib.uint16(bytes, i + 1 + adjust); +	} +	private int uint32(int i) { +		return AltosLib.uint32(bytes, i + 1 + adjust); +	} +	private String string(int i, int l) { +		return AltosLib.string(bytes, i + 1 + adjust, l); +	} + +	static final int AO_GPS_NUM_SAT_MASK	= (0xf << 0); +	static final int AO_GPS_NUM_SAT_SHIFT	= (0); + +	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_COURSE_VALID	= (1 << 7); + +	public AltosTelemetryRecordLegacy(int[] in_bytes, int in_rssi, int in_status) { +		record = new AltosRecord(); + +		bytes = in_bytes; +		record.version = 4; +		adjust = 0; + +		if (bytes.length == AltosLib.ao_telemetry_0_8_len + 4) { +			record.serial = uint8(0); +			adjust = -1; +		} else +			record.serial = uint16(0); + +		record.seen = AltosRecord.seen_flight | AltosRecord.seen_sensor | AltosRecord.seen_temp_volt | AltosRecord.seen_deploy; + +		record.callsign = string(62, 8); +		record.flight = uint16(2); +		record.rssi = in_rssi; +		record.status = in_status; +		record.state = uint8(4); +		record.tick = uint16(21); +		record.accel = int16(23); +		record.pres = int16(25); +		record.temp = int16(27); +		record.batt = int16(29); +		record.drogue = int16(31); +		record.main = int16(33); +		 +		record.ground_accel = int16(7); +		record.ground_pres = int16(15); +		record.accel_plus_g = int16(17); +		record.accel_minus_g = int16(19); + +		if (uint16(11) == 0x8000) { +			record.acceleration = int16(5); +			record.speed = int16(9); +			record.height = int16(13); +			record.flight_accel = AltosRecord.MISSING; +			record.flight_vel = AltosRecord.MISSING; +			record.flight_pres = AltosRecord.MISSING; +		} else { +			record.flight_accel = int16(5); +			record.flight_vel = uint32(9); +			record.flight_pres = int16(13); +			record.acceleration = AltosRecord.MISSING; +			record.speed = AltosRecord.MISSING; +			record.height = AltosRecord.MISSING; +		} + +		record.gps = null; + +		int gps_flags = uint8(41); + +		if ((gps_flags & (AO_GPS_VALID|AO_GPS_RUNNING)) != 0) { +			record.gps = new AltosGPS(); +			record.new_gps = true; + +			record.seen |= record.seen_gps_time | record.seen_gps_lat | record.seen_gps_lon; +			record.gps.nsat = (gps_flags & AO_GPS_NUM_SAT_MASK); +			record.gps.locked = (gps_flags & AO_GPS_VALID) != 0; +			record.gps.connected = true; +			record.gps.lat = uint32(42) / 1.0e7; +			record.gps.lon = uint32(46) / 1.0e7; +			record.gps.alt = int16(50); +			record.gps.ground_speed = uint16(52) / 100.0; +			record.gps.course = uint8(54) * 2; +			record.gps.hdop = uint8(55) / 5.0; +			record.gps.h_error = uint16(58); +			record.gps.v_error = uint16(60); + +			int	n_tracking_reported = uint8(70); +			if (n_tracking_reported > 12) +				n_tracking_reported = 12; +			int	n_tracking_actual = 0; +			for (int i = 0; i < n_tracking_reported; i++) { +				if (uint8(71 + i*2) != 0) +					n_tracking_actual++; +			} +			if (n_tracking_actual > 0) { +				record.gps.cc_gps_sat = new AltosGPSSat[n_tracking_actual]; + +				n_tracking_actual = 0; +				for (int i = 0; i < n_tracking_reported; i++) { +					int	svid = uint8(71 + i*2); +					int	c_n0 = uint8(72 + i*2); +					if (svid != 0) +						record.gps.cc_gps_sat[n_tracking_actual++] = new AltosGPSSat(svid, c_n0); +				} +			} +		} + +		record.time = 0.0; +	} + +	public AltosRecord update_state(AltosRecord previous) { +		return record; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLocation.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLocation.java new file mode 100644 index 00000000..cddb773d --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordLocation.java @@ -0,0 +1,93 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + + +public class AltosTelemetryRecordLocation extends AltosTelemetryRecordRaw { +	int	flags; +	int	altitude; +	int	latitude; +	int	longitude; +	int	year; +	int	month; +	int	day; +	int	hour; +	int	minute; +	int	second; +	int	pdop; +	int	hdop; +	int	vdop; +	int	mode; +	int	ground_speed; +	int	climb_rate; +	int	course; + +	public AltosTelemetryRecordLocation(int[] in_bytes) { +		super(in_bytes); + +		flags          = uint8(5); +		altitude       = int16(6); +		latitude       = uint32(8); +		longitude      = uint32(12); +		year	       = uint8(16); +		month	       = uint8(17); +		day	       = uint8(18); +		hour	       = uint8(19); +		minute	       = uint8(20); +		second	       = uint8(21); +		pdop	       = uint8(22); +		hdop	       = uint8(23); +		vdop	       = uint8(24); +		mode	       = uint8(25); +		ground_speed   = uint16(26); +		climb_rate     = int16(28); +		course	       = uint8(30); +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next = super.update_state(previous); + +		if (next.gps == null) +			next.gps = new AltosGPS(); + +		next.gps.nsat = flags & 0xf; +		next.gps.locked = (flags & (1 << 4)) != 0; +		next.gps.connected = (flags & (1 << 5)) != 0; + +		if (next.gps.locked) { +			next.gps.lat = latitude * 1.0e-7; +			next.gps.lon = longitude * 1.0e-7; +			next.gps.alt = altitude; +			next.gps.year = 2000 + year; +			next.gps.month = month; +			next.gps.day = day; +			next.gps.hour = hour; +			next.gps.minute = minute; +			next.gps.second = second; +			next.gps.ground_speed = ground_speed * 1.0e-2; +			next.gps.course = course * 2; +			next.gps.climb_rate = climb_rate * 1.0e-2; +			next.gps.hdop = hdop; +			next.gps.vdop = vdop; +			next.seen |= AltosRecord.seen_gps_time | AltosRecord.seen_gps_lat | AltosRecord.seen_gps_lon; +			next.new_gps = true; +		} + +		return next; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordRaw.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordRaw.java new file mode 100644 index 00000000..43d0f17a --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordRaw.java @@ -0,0 +1,77 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +public class AltosTelemetryRecordRaw extends AltosTelemetryRecord { +	int[]	bytes; +	int	serial; +	int	tick; +	int	type; + +	long	received_time; + +	public int int8(int off) { +		return AltosLib.int8(bytes, off + 1); +	} + +	public int uint8(int off) { +		return AltosLib.uint8(bytes, off + 1); +	} + +	public int int16(int off) { +		return AltosLib.int16(bytes, off + 1); +	} + +	public int uint16(int off) { +		return AltosLib.uint16(bytes, off + 1); +	} + +	public int uint32(int off) { +		return AltosLib.uint32(bytes, off + 1); +	} + +	public String string(int off, int l) { +		return AltosLib.string(bytes, off + 1, l); +	} + +	public AltosTelemetryRecordRaw(int[] in_bytes) { +		bytes = in_bytes; +		serial = uint16(0); +		tick   = uint16(2); +		type   = uint8(4); +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next; +		if (previous != null) +			next = new AltosRecord(previous); +		else +			next = new AltosRecord(); +		next.serial = serial; +		next.tick = tick; +		return next; +	} + +	public long received_time() { +		return received_time; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSatellite.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSatellite.java new file mode 100644 index 00000000..2526afb6 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSatellite.java @@ -0,0 +1,52 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + +public class AltosTelemetryRecordSatellite extends AltosTelemetryRecordRaw { +	int		channels; +	AltosGPSSat[]	sats; + +	public AltosTelemetryRecordSatellite(int[] in_bytes) { +		super(in_bytes); + +		channels = uint8(5); +		if (channels > 12) +			channels = 12; +		if (channels == 0) +			sats = null; +		else { +			sats = new AltosGPSSat[channels]; +			for (int i = 0; i < channels; i++) { +				int	svid =  uint8(6 + i * 2 + 0); +				int	c_n_1 = uint8(6 + i * 2 + 1); +				sats[i] = new AltosGPSSat(svid, c_n_1); +			} +		} +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next = super.update_state(previous); + +		if (next.gps == null) +			next.gps = new AltosGPS(); + +		next.gps.cc_gps_sat = sats; + +		return next; +	} +} diff --git a/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSensor.java b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSensor.java new file mode 100644 index 00000000..cfaf90b0 --- /dev/null +++ b/altoslib/src/org/altusmetrum/AltosLib/AltosTelemetryRecordSensor.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2011 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 org.altusmetrum.AltosLib; + + +public class AltosTelemetryRecordSensor extends AltosTelemetryRecordRaw { +	int	state; +	int	accel; +	int	pres; +	int	temp; +	int	v_batt; +	int	sense_d; +	int	sense_m; + +	int	acceleration; +	int	speed; +	int	height; + +	int	ground_accel; +	int	ground_pres; +	int	accel_plus_g; +	int	accel_minus_g; + +	int	rssi; + +	public AltosTelemetryRecordSensor(int[] in_bytes, int in_rssi) { +		super(in_bytes); +		state         = uint8(5); + +		accel         = int16(6); +		pres          = int16(8); +		temp          = int16(10); +		v_batt        = int16(12); +		sense_d       = int16(14); +		sense_m       = int16(16); + +		acceleration  = int16(18); +		speed         = int16(20); +		height        = int16(22); + +		ground_pres   = int16(24); +		ground_accel  = int16(26); +		accel_plus_g  = int16(28); +		accel_minus_g = int16(30); + +		rssi	      = in_rssi; +	} + +	public AltosRecord update_state(AltosRecord previous) { +		AltosRecord	next = super.update_state(previous); + +		next.state = state; +		if (type == packet_type_TM_sensor) +			next.accel = accel; +		else +			next.accel = AltosRecord.MISSING; +		next.pres = pres; +		next.temp = temp; +		next.batt = v_batt; +		if (type == packet_type_TM_sensor || type == packet_type_Tm_sensor) { +			next.drogue = sense_d; +			next.main = sense_m; +		} else { +			next.drogue = AltosRecord.MISSING; +			next.main = AltosRecord.MISSING; +		} + +		next.acceleration = acceleration / 16.0; +		next.speed = speed / 16.0; +		next.height = height; + +		next.ground_pres = ground_pres; +		if (type == packet_type_TM_sensor) { +			next.ground_accel = ground_accel; +			next.accel_plus_g = accel_plus_g; +			next.accel_minus_g = accel_minus_g; +		} else { +			next.ground_accel = AltosRecord.MISSING; +			next.accel_plus_g = AltosRecord.MISSING; +			next.accel_minus_g = AltosRecord.MISSING; +		} + +		next.rssi = rssi; + +		next.seen |= AltosRecord.seen_sensor | AltosRecord.seen_temp_volt; + +		return next; +	} +} | 
