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