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