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