From 40c31a36df46b67741e08a12e8bb873dbf208d20 Mon Sep 17 00:00:00 2001 From: Bdale Garbee Date: Sun, 14 Jan 2018 15:43:48 -0300 Subject: [PATCH] create teststand/ starting with a clone of altosui/ --- Makefile.am | 7 +- configure.ac | 3 + teststand/Altos.java | 29 + teststand/AltosAscent.java | 195 +++ teststand/AltosChannelMenu.java | 37 + teststand/AltosCompanionInfo.java | 112 ++ teststand/AltosConfigFC.java | 315 ++++ teststand/AltosConfigFCUI.java | 1476 +++++++++++++++++ teststand/AltosConfigPyroUI.java | 464 ++++++ teststand/AltosConfigTD.java | 380 +++++ teststand/AltosConfigTDUI.java | 378 +++++ teststand/AltosConfigureUI.java | 157 ++ teststand/AltosDescent.java | 165 ++ teststand/AltosDevice.java | 30 + teststand/AltosFlightStatus.java | 325 ++++ teststand/AltosFlightStatusTableModel.java | 69 + teststand/AltosFlightStatusUpdate.java | 42 + teststand/AltosFlightUI.java | 333 ++++ teststand/AltosGraphUI.java | 150 ++ teststand/AltosIdleMonitorUI.java | 306 ++++ teststand/AltosIgniteUI.java | 477 ++++++ teststand/AltosIgnitor.java | 92 + teststand/AltosLanded.java | 193 +++ teststand/AltosLaunch.java | 198 +++ teststand/AltosLaunchUI.java | 513 ++++++ teststand/AltosLib.jar | 1 + teststand/AltosPad.java | 253 +++ teststand/AltosVersion.java | 22 + teststand/Info.plist.in | 73 + .../Instdrv/NSIS/Contrib/InstDrv/Example.nsi | 84 + .../NSIS/Contrib/InstDrv/InstDrv-Test.exe | Bin 0 -> 51831 bytes .../Instdrv/NSIS/Contrib/InstDrv/InstDrv.c | 704 ++++++++ .../Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp | 110 ++ .../Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw | 29 + .../Instdrv/NSIS/Contrib/InstDrv/Readme.txt | 141 ++ .../Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf | 137 ++ .../Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys | Bin 0 -> 30464 bytes teststand/Instdrv/NSIS/Includes/java.nsh | 181 ++ .../Instdrv/NSIS/Includes/refresh-sh.nsh | 14 + teststand/Instdrv/NSIS/Plugins/InstDrv.dll | Bin 0 -> 6656 bytes teststand/Makefile-standalone | 191 +++ teststand/Makefile.am | 394 +++++ teststand/ReadMe-Mac.rtf | 56 + .../Contents/MacOS/JavaApplicationStub | Bin 0 -> 61296 bytes teststand/TestStand.app/Contents/PkgInfo | 1 + teststand/TestStand.java | 646 ++++++++ teststand/altosui-fat | 4 + teststand/altosuilib.jar | 1 + teststand/altusmetrum-teststand.desktop.in | 11 + teststand/altusmetrum.jpg | Bin 0 -> 72868 bytes teststand/linux-install.sh | 244 +++ teststand/mdwn.tmpl | 7 + teststand/teststand-windows.nsi.in | 235 +++ teststand/teststand.1 | 39 + 54 files changed, 10023 insertions(+), 1 deletion(-) create mode 100644 teststand/Altos.java create mode 100644 teststand/AltosAscent.java create mode 100644 teststand/AltosChannelMenu.java create mode 100644 teststand/AltosCompanionInfo.java create mode 100644 teststand/AltosConfigFC.java create mode 100644 teststand/AltosConfigFCUI.java create mode 100644 teststand/AltosConfigPyroUI.java create mode 100644 teststand/AltosConfigTD.java create mode 100644 teststand/AltosConfigTDUI.java create mode 100644 teststand/AltosConfigureUI.java create mode 100644 teststand/AltosDescent.java create mode 100644 teststand/AltosDevice.java create mode 100644 teststand/AltosFlightStatus.java create mode 100644 teststand/AltosFlightStatusTableModel.java create mode 100644 teststand/AltosFlightStatusUpdate.java create mode 100644 teststand/AltosFlightUI.java create mode 100644 teststand/AltosGraphUI.java create mode 100644 teststand/AltosIdleMonitorUI.java create mode 100644 teststand/AltosIgniteUI.java create mode 100644 teststand/AltosIgnitor.java create mode 100644 teststand/AltosLanded.java create mode 100644 teststand/AltosLaunch.java create mode 100644 teststand/AltosLaunchUI.java create mode 120000 teststand/AltosLib.jar create mode 100644 teststand/AltosPad.java create mode 100644 teststand/AltosVersion.java create mode 100644 teststand/Info.plist.in create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/Example.nsi create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/Readme.txt create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf create mode 100644 teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys create mode 100644 teststand/Instdrv/NSIS/Includes/java.nsh create mode 100644 teststand/Instdrv/NSIS/Includes/refresh-sh.nsh create mode 100644 teststand/Instdrv/NSIS/Plugins/InstDrv.dll create mode 100644 teststand/Makefile-standalone create mode 100644 teststand/Makefile.am create mode 100644 teststand/ReadMe-Mac.rtf create mode 100755 teststand/TestStand.app/Contents/MacOS/JavaApplicationStub create mode 100644 teststand/TestStand.app/Contents/PkgInfo create mode 100644 teststand/TestStand.java create mode 100755 teststand/altosui-fat create mode 120000 teststand/altosuilib.jar create mode 100644 teststand/altusmetrum-teststand.desktop.in create mode 100644 teststand/altusmetrum.jpg create mode 100644 teststand/linux-install.sh create mode 100644 teststand/mdwn.tmpl create mode 100644 teststand/teststand-windows.nsi.in create mode 100644 teststand/teststand.1 diff --git a/Makefile.am b/Makefile.am index 4f47417e..36230437 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS=ao-tools src doc icon altoslib libaltos altosuilib altosui micropeak ao-utils altosdroid telegps +SUBDIRS=ao-tools src doc icon altoslib libaltos altosuilib altosui micropeak ao-utils altosdroid telegps teststand EXTRA_DIST = ChangeLog @@ -23,6 +23,7 @@ fat-install: fat cd altosui && $(MAKE) fat-install cd telegps && $(MAKE) fat-install cd micropeak && $(MAKE) fat-install + cd teststand && $(MAKE) fat-install endif fat: @@ -35,19 +36,23 @@ fat: cd altosui && $(MAKE) fat cd micropeak && $(MAKE) fat cd telegps && $(MAKE) fat + cd teststand && $(MAKE) fat fat_linux = \ altosui/Altos-Linux-$(VERSION).sh altosui/Altos-Linux-$(VERSION).tar.bz2 \ + teststand/TestStand-Linux-$(VERSION).sh teststand/TestStand-Linux-$(VERSION).tar.bz2 \ telegps/TeleGPS-Linux-$(VERSION).sh telegps/TeleGPS-Linux-$(VERSION).tar.bz2 \ micropeak/MicroPeak-Linux-$(VERSION).sh micropeak/MicroPeak-Linux-$(VERSION).tar.bz2 fat_mac = \ altosui/Altos-Mac-$(VERSION).dmg \ + teststand/TestStand-Mac-$(VERSION).dmg \ telegps/TeleGPS-Mac-$(VERSION).dmg \ micropeak/MicroPeak-Mac-$(VERSION).dmg fat_windows = \ altosui/Altos-Windows-$(VERSION_DASH).exe \ + teststand/TestStand-Windows-$(VERSION_DASH).exe \ telegps/TeleGPS-Windows-$(VERSION_DASH).exe \ micropeak/MicroPeak-Windows-$(VERSION_DASH).exe diff --git a/configure.ac b/configure.ac index 7f27dfad..7c725678 100644 --- a/configure.ac +++ b/configure.ac @@ -526,6 +526,9 @@ altosuilib/Makefile altosui/Makefile altosui/Info.plist altosui/altos-windows.nsi +teststand/Makefile +teststand/Info.plist +teststand/teststand-windows.nsi libaltos/Makefile micropeak/Makefile micropeak/Info.plist diff --git a/teststand/Altos.java b/teststand/Altos.java new file mode 100644 index 00000000..f6a03060 --- /dev/null +++ b/teststand/Altos.java @@ -0,0 +1,29 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import libaltosJNI.*; + +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class Altos extends AltosUILib { + +} diff --git a/teststand/AltosAscent.java b/teststand/AltosAscent.java new file mode 100644 index 00000000..dfc5927f --- /dev/null +++ b/teststand/AltosAscent.java @@ -0,0 +1,195 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosAscent extends AltosUIFlightTab { + JLabel cur, max; + + class Height extends AltosUIUnitsIndicator { + + public double value(AltosState state, int i) { + if (i == 0) + return state.height(); + else + return state.max_height(); + } + + public Height(Container container, int y) { + super(container, y, AltosConvert.height, "Height", 2, false, 1); + } + } + + class Speed extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { + if (i == 0) + return state.speed(); + else + return state.max_speed(); + } + + public Speed(Container container, int y) { + super(container, y, AltosConvert.speed, "Speed", 2, false, 1); + } + } + + class Accel extends AltosUIUnitsIndicator { + public boolean hide(double v) { return v == AltosLib.MISSING; } + + public double value(AltosState state, int i) { + if (i == 0) + return state.acceleration(); + else + return state.max_acceleration(); + } + + public Accel(Container container, int y) { + super(container, y, AltosConvert.accel, "Acceleration", 2, false, 1); + } + } + + class Orient extends AltosUIUnitsIndicator { + + public boolean hide(double v) { return v == AltosLib.MISSING; } + + public double value(AltosState state, int i) { + if (i == 0) + return state.orient(); + else + return state.max_orient(); + } + + public Orient(Container container, int y) { + super(container, y, AltosConvert.orient, "Tilt Angle", 2, false, 1); + } + + } + + class Apogee extends AltosUIUnitsIndicator { + + public double value(AltosState state, int i) { + return state.apogee_voltage; + } + + public boolean good(double v) { return v >= AltosLib.ao_igniter_good; } + public boolean hide(double v) { return v == AltosLib.MISSING; } + + public Apogee (Container container, int y) { + super(container, y, AltosConvert.voltage, "Apogee Igniter Voltage", 1, true, 2); + } + } + + class Main extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { + return state.main_voltage; + } + + public boolean good(double v) { return v >= AltosLib.ao_igniter_good; } + public boolean hide(double v) { return v == AltosLib.MISSING; } + + public Main (Container container, int y) { + super(container, y, AltosConvert.voltage, "Main Igniter Voltage", 1, true, 2); + } + } + + class Lat extends AltosUIUnitsIndicator { + + public boolean hide(AltosState state, int i) { + return state.gps == null || !state.gps.connected; + } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lat; + } + + Lat (Container container, int y) { + super (container, y, AltosConvert.latitude, "Latitude", 1, false, 2); + } + } + + class Lon extends AltosUIUnitsIndicator { + + public boolean hide(AltosState state, int i) { + return state.gps == null || !state.gps.connected; + } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lon; + } + + Lon (Container container, int y) { + super (container, y, AltosConvert.longitude, "Longitude", 1, false, 2); + } + } + + public void font_size_changed(int font_size) { + super.font_size_changed(font_size); + cur.setFont(AltosUILib.label_font); + max.setFont(AltosUILib.label_font); + } + + public void labels(GridBagLayout layout, int y) { + GridBagConstraints c; + + cur = new JLabel("Current"); + cur.setFont(AltosUILib.label_font); + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + layout.setConstraints(cur, c); + add(cur); + + max = new JLabel("Maximum"); + max.setFont(AltosUILib.label_font); + c.gridx = 3; c.gridy = y; + layout.setConstraints(max, c); + add(max); + } + + public String getName() { + return "Ascent"; + } + + public AltosAscent() { + int y = 0; + labels(layout, y++); + add(new Height(this, y++)); + add(new Speed(this, y++)); + add(new Accel(this, y++)); + add(new Orient(this, y++)); + add(new Lat(this, y++)); + add(new Lon(this, y++)); + add(new Apogee(this, y++)); + add(new Main(this, y++)); + } +} diff --git a/teststand/AltosChannelMenu.java b/teststand/AltosChannelMenu.java new file mode 100644 index 00000000..8eb8ea49 --- /dev/null +++ b/teststand/AltosChannelMenu.java @@ -0,0 +1,37 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.event.*; +import javax.swing.*; + +public class AltosChannelMenu extends JComboBox implements ActionListener { + int channel; + + public AltosChannelMenu(int current_channel) { + + channel = current_channel; + + for (int c = 0; c <= 9; c++) + addItem(String.format("Channel %1d (%7.3fMHz)", c, 434.550 + c * 0.1)); + setSelectedIndex(channel); + setMaximumRowCount(10); + } + +} diff --git a/teststand/AltosCompanionInfo.java b/teststand/AltosCompanionInfo.java new file mode 100644 index 00000000..10fbcd20 --- /dev/null +++ b/teststand/AltosCompanionInfo.java @@ -0,0 +1,112 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosCompanionInfo extends JTable implements AltosFlightDisplay { + private AltosFlightInfoTableModel model; + + static final int info_columns = 2; + static final int info_rows = 17; + + int desired_row_height() { + FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font); + return (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 18 / 10; + } + + public void font_size_changed(int font_size) { + setFont(Altos.table_value_font); + setRowHeight(desired_row_height()); + doLayout(); + } + + public void units_changed(boolean imperial_units) { + } + + public AltosCompanionInfo() { + super(new AltosFlightInfoTableModel(info_rows, info_columns)); + model = (AltosFlightInfoTableModel) getModel(); + setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS); + setShowGrid(true); + font_size_changed(AltosUIPreferences.font_size()); + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + public void reset() { + model.reset(); + } + + void info_add_row(int col, String name, String value) { + model.addRow(col, name, value); + } + + void info_add_row(int col, String name, String format, Object... parameters) { + info_add_row (col, name, String.format(format, parameters)); + } + + void info_finish() { + model.finish(); + } + + public void clear() { + model.clear(); + } + + AltosCompanion companion; + + public String board_name() { + if (companion == null) + return "None"; + switch (companion.board_id) { + case AltosCompanion.board_id_telescience: + return "TeleScience"; + default: + return String.format("%02x\n", companion.board_id); + } + } + + public String getName() { return "Companion"; } + + public void show(AltosState state, AltosListenerState listener_state) { + if (state == null) + return; + if (state.companion != null) + companion = state.companion; + reset(); + info_add_row(0, "Companion board", "%s", board_name()); + if (companion != null) { + info_add_row(0, "Last Data", "%5d", companion.tick); + info_add_row(0, "Update period", "%5.2f s", + companion.update_period / 100.0); + info_add_row(0, "Channels", "%3d", companion.channels); + + for (int i = 0; i < companion.channels; i++) + info_add_row(1, String.format("Channel %2d", i), + "%6d", companion.companion_data[i]); + } + info_finish(); + } +} diff --git a/teststand/AltosConfigFC.java b/teststand/AltosConfigFC.java new file mode 100644 index 00000000..c81f7db9 --- /dev/null +++ b/teststand/AltosConfigFC.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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.concurrent.*; +import java.text.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigFC implements 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; + + AltosConfigData data; + AltosConfigFCUI config_ui; + boolean serial_started; + boolean made_visible; + + void start_serial() throws InterruptedException, TimeoutException { + serial_started = true; + if (remote) + serial_line.start_remote(); + } + + void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (remote) + serial_line.stop_remote(); + } + + void update_ui() { + data.set_values(config_ui); + config_ui.set_clean(); + if (!made_visible) { + made_visible = true; + config_ui.make_visible(); + } + } + + int pyro; + + final static int serial_mode_read = 0; + final static int serial_mode_save = 1; + final static int serial_mode_reboot = 2; + + class SerialData implements Runnable { + AltosConfigFC config; + int serial_mode; + + void callback(String in_cmd) { + final String cmd = in_cmd; + Runnable r = new Runnable() { + public void run() { + if (cmd.equals("abort")) { + abort(); + } else if (cmd.equals("all finished")) { + if (serial_line != null) + update_ui(); + } + } + }; + SwingUtilities.invokeLater(r); + } + + void get_data() { + data = null; + try { + start_serial(); + data = new AltosConfigData(config.serial_line); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + try { + stop_serial(); + callback("abort"); + } catch (InterruptedException ie) { + } + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + callback("all finished"); + } + + void save_data() { + try { + start_serial(); + data.save(serial_line, remote); + if (remote) + AltosUIPreferences.set_frequency(device.getSerial(), + data.frequency()); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + void reboot() { + try { + start_serial(); + serial_line.printf("r eboot\n"); + serial_line.flush_output(); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + serial_line.close(); + } catch (InterruptedException ie) { + } + } + } + + public void run () { + switch (serial_mode) { + case serial_mode_save: + save_data(); + /* fall through ... */ + case serial_mode_read: + get_data(); + break; + case serial_mode_reboot: + reboot(); + break; + } + } + + public SerialData(AltosConfigFC in_config, int in_serial_mode) { + config = in_config; + serial_mode = in_serial_mode; + } + } + + void run_serial_thread(int serial_mode) { + SerialData sd = new SerialData(this, serial_mode); + Thread st = new Thread(sd); + st.start(); + } + + void init_ui () throws InterruptedException, TimeoutException { + config_ui = new AltosConfigFCUI(owner, remote); + config_ui.addActionListener(this); + serial_line.set_frame(owner); + set_ui(); + } + + void abort() { + if (serial_line != null) { + serial_line.close(); + serial_line = null; + } + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + config_ui.setVisible(false); + } + + void set_ui() throws InterruptedException, TimeoutException { + if (serial_line != null) + run_serial_thread(serial_mode_read); + else + update_ui(); + } + + double frequency() { + return AltosConvert.radio_to_frequency(data.radio_frequency, + data.radio_setting, + data.radio_calibration, + data.radio_channel); + } + + void save_data() { + + try { + /* bounds check stuff */ + if (config_ui.flight_log_max() > data.log_space() / 1024) { + JOptionPane.showMessageDialog(owner, + String.format("Requested flight log, %dk, is larger than the available space, %dk.\n", + config_ui.flight_log_max(), + data.log_space() / 1024), + "Maximum Flight Log Too Large", + JOptionPane.ERROR_MESSAGE); + return; + } + + /* Pull data out of the UI and stuff back into our local data record */ + + data.get_values(config_ui); + run_serial_thread(serial_mode_save); + } catch (AltosConfigDataException ae) { + JOptionPane.showMessageDialog(owner, + ae.getMessage(), + "Configuration Data Error", + JOptionPane.ERROR_MESSAGE); + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + try { + if (cmd.equals("Save")) { + save_data(); + } else if (cmd.equals("Reset")) { + set_ui(); + } else if (cmd.equals("Reboot")) { + if (serial_line != null) + run_serial_thread(serial_mode_reboot); + } else if (cmd.equals("Close")) { + if (serial_line != null) + serial_line.close(); + } + else if (cmd.equals("Accel")) { + if (data.pad_orientation != AltosLib.MISSING) { + AltosUIAccelCal accel_ui = new AltosUIAccelCal(owner, serial_line, config_ui); + if (accel_ui != null) + accel_ui.doit(); + } + } + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public AltosConfigFC(JFrame given_owner) { + owner = given_owner; + + device = AltosDeviceUIDialog.show(owner, Altos.product_any); + if (device != null) { + try { + serial_line = new AltosSerial(device); + try { + if (device.matchProduct(Altos.product_basestation)) + remote = true; + init_ui(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/teststand/AltosConfigFCUI.java b/teststand/AltosConfigFCUI.java new file mode 100644 index 00000000..8fcf9ba2 --- /dev/null +++ b/teststand/AltosConfigFCUI.java @@ -0,0 +1,1476 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.text.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigFCUI + extends AltosUIDialog + implements ActionListener, ItemListener, DocumentListener, AltosConfigValues, AltosUnitsListener +{ + + Container pane; + JLabel product_label; + JLabel version_label; + JLabel serial_label; + JLabel main_deploy_label; + JLabel apogee_delay_label; + JLabel apogee_lockout_label; + JLabel frequency_label; + JLabel radio_calibration_label; + JLabel radio_frequency_label; + JLabel radio_enable_label; + JLabel rate_label; + JLabel aprs_interval_label; + JLabel aprs_ssid_label; + JLabel aprs_format_label; + JLabel flight_log_max_label; + JLabel ignite_mode_label; + JLabel pad_orientation_label; + JLabel accel_plus_label; + JLabel accel_minus_label; + JLabel callsign_label; + JLabel beep_label; + JLabel tracker_motion_label; + JLabel tracker_interval_label; + + public boolean dirty; + + JFrame owner; + JLabel product_value; + JLabel version_value; + JLabel serial_value; + JComboBox main_deploy_value; + JComboBox apogee_delay_value; + JComboBox apogee_lockout_value; + AltosUIFreqList radio_frequency_value; + JLabel radio_calibration_value; + JRadioButton radio_enable_value; + AltosUIRateList rate_value; + JComboBox aprs_interval_value; + JComboBox aprs_ssid_value; + JComboBox aprs_format_value; + JComboBox flight_log_max_value; + JComboBox ignite_mode_value; + JComboBox pad_orientation_value; + JTextField accel_plus_value; + JTextField accel_minus_value; + JTextField callsign_value; + JComboBox beep_value; + JComboBox tracker_motion_value; + JComboBox tracker_interval_value; + + JButton pyro; + JButton accel_cal; + + JButton save; + JButton reset; + JButton reboot; + JButton close; + + AltosPyro[] pyros; + double pyro_firing_time; + + ActionListener listener; + + static String[] main_deploy_values_m = { + "100", "150", "200", "250", "300", "350", + "400", "450", "500" + }; + + static String[] main_deploy_values_ft = { + "250", "500", "750", "1000", "1250", "1500", + "1750", "2000" + }; + + static String[] apogee_delay_values = { + "0", "1", "2", "3", "4", "5" + }; + + static String[] apogee_lockout_values = { + "0", "5", "10", "15", "20" + }; + + static String[] ignite_mode_values = { + "Dual Deploy", + "Redundant Apogee", + "Redundant Main", + }; + + static String[] aprs_interval_values = { + "Disabled", + "2", + "5", + "10" + }; + + static Integer[] aprs_ssid_values = { + 0, 1, 2 ,3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }; + + static String[] beep_values = { + "3750", + "4000", + "4250", + }; + + static String[] pad_orientation_values = { + "Antenna Up", + "Antenna Down", + }; + + static String[] tracker_motion_values_m = { + "2", + "5", + "10", + "25", + }; + + static String[] tracker_motion_values_ft = { + "5", + "20", + "50", + "100" + }; + + static String[] tracker_interval_values = { + "1", + "2", + "5", + "10" + }; + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigFCUI ui; + + public ConfigListener(AltosConfigFCUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "Close")); + } + } + + boolean is_telemini_v1() { + String product = product_value.getText(); + return product != null && product.startsWith("TeleMini-v1"); + } + + boolean is_telemini() { + String product = product_value.getText(); + return product != null && product.startsWith("TeleMini"); + } + + boolean is_easymini() { + String product = product_value.getText(); + return product != null && product.startsWith("EasyMini"); + } + + boolean is_telemetrum() { + String product = product_value.getText(); + return product != null && product.startsWith("TeleMetrum"); + } + + void set_radio_enable_tool_tip() { + if (radio_enable_value.isVisible()) + radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions"); + else + radio_enable_value.setToolTipText("Firmware version does not support disabling radio"); + } + + void set_rate_tool_tip() { + if (rate_value.isVisible()) + rate_value.setToolTipText("Select telemetry baud rate"); + else + rate_value.setToolTipText("Firmware version does not support variable telemetry rates"); + } + + void set_aprs_interval_tool_tip() { + if (aprs_interval_value.isVisible()) + aprs_interval_value.setToolTipText("Enable APRS and set the interval between APRS reports"); + else + aprs_interval_value.setToolTipText("Hardware doesn't support APRS"); + } + + void set_aprs_ssid_tool_tip() { + if (aprs_ssid_value.isVisible()) + aprs_ssid_value.setToolTipText("Set the APRS SSID (secondary station identifier)"); + else if (aprs_ssid_value.isVisible()) + aprs_ssid_value.setToolTipText("Software version doesn't support setting the APRS SSID"); + else + aprs_ssid_value.setToolTipText("Hardware doesn't support APRS"); + } + + void set_aprs_format_tool_tip() { + if (aprs_format_value.isVisible()) + aprs_format_value.setToolTipText("Set the APRS format (compressed/uncompressed)"); + else if (aprs_format_value.isVisible()) + aprs_format_value.setToolTipText("Software version doesn't support setting the APRS format"); + else + aprs_format_value.setToolTipText("Hardware doesn't support APRS"); + } + + void set_flight_log_max_tool_tip() { + if (flight_log_max_value.isVisible()) + flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)"); + else { + if (is_telemini_v1()) + flight_log_max_value.setToolTipText("TeleMini-v1 stores only one flight"); + else + flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory"); + } + } + + void set_ignite_mode_tool_tip() { + if (ignite_mode_value.isVisible()) + ignite_mode_value.setToolTipText("Select when igniters will be fired"); + else + ignite_mode_value.setToolTipText("Older firmware could not select ignite mode"); + } + + void set_pad_orientation_tool_tip() { + if (pad_orientation_value.isVisible()) { + pad_orientation_value.setToolTipText("How will the computer be mounted in the airframe"); + } else { + if (is_telemetrum()) + pad_orientation_value.setToolTipText("Older TeleMetrum firmware must fly antenna forward"); + else if (is_telemini() || is_easymini()) + pad_orientation_value.setToolTipText("TeleMini and EasyMini don't care how they are mounted"); + else + pad_orientation_value.setToolTipText("Can't select orientation"); + } + } + + void set_accel_tool_tips() { + if (accel_plus_value.isVisible()) { + accel_plus_value.setToolTipText("Pad acceleration value in flight orientation"); + accel_minus_value.setToolTipText("Upside-down acceleration value"); + } else { + accel_plus_value.setToolTipText("No accelerometer"); + accel_minus_value.setToolTipText("No accelerometer"); + } + } + + void set_beep_tool_tip() { + if (beep_value.isVisible()) + beep_value.setToolTipText("What frequency the beeper will sound at"); + else + beep_value.setToolTipText("Older firmware could not select beeper frequency"); + } + + /* Build the UI using a grid bag */ + public AltosConfigFCUI(JFrame in_owner, boolean remote) { + super (in_owner, "Configure Flight Computer", false); + + owner = in_owner; + GridBagConstraints c; + int row = 0; + + 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 = row; + c.gridwidth = 4; + 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 = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + product_value = new JLabel(""); + pane.add(product_value, c); + row++; + + /* Version */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + version_label = new JLabel("Software version:"); + pane.add(version_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + version_value = new JLabel(""); + pane.add(version_value, c); + row++; + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + serial_value = new JLabel(""); + pane.add(serial_value, c); + row++; + + /* Main deploy */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + main_deploy_label = new JLabel(get_main_deploy_label()); + pane.add(main_deploy_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + 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); + main_deploy_value.setToolTipText("Height above pad altitude to fire main charge"); + row++; + + /* Apogee delay */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + 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 = 4; c.gridy = row; + c.gridwidth = 4; + 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); + apogee_delay_value.setToolTipText("Delay after apogee before charge fires"); + row++; + + /* Apogee lockout */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + apogee_lockout_label = new JLabel("Apogee Lockout(s):"); + pane.add(apogee_lockout_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + apogee_lockout_value = new JComboBox(apogee_lockout_values); + apogee_lockout_value.setEditable(true); + apogee_lockout_value.addItemListener(this); + pane.add(apogee_lockout_value, c); + apogee_lockout_value.setToolTipText("Time after boost while apogee detection is locked out"); + row++; + + /* Frequency */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_frequency_label = new JLabel("Frequency:"); + pane.add(radio_frequency_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_frequency_value = new AltosUIFreqList(); + radio_frequency_value.addItemListener(this); + pane.add(radio_frequency_value, c); + radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency"); + row++; + + /* Radio Calibration */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("RF Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JLabel(String.format("%d", 1186611)); + pane.add(radio_calibration_value, c); + row++; + + /* Radio Enable */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_enable_label = new JLabel("Telemetry/RDF/APRS Enable:"); + pane.add(radio_enable_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_enable_value = new JRadioButton("Enabled"); + radio_enable_value.addItemListener(this); + pane.add(radio_enable_value, c); + set_radio_enable_tool_tip(); + row++; + + /* Telemetry Rate */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + rate_label = new JLabel("Telemetry baud rate:"); + pane.add(rate_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + rate_value = new AltosUIRateList(); + rate_value.addItemListener(this); + pane.add(rate_value, c); + set_rate_tool_tip(); + row++; + + /* APRS interval */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + aprs_interval_label = new JLabel("APRS Interval(s):"); + pane.add(aprs_interval_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + aprs_interval_value = new JComboBox(aprs_interval_values); + aprs_interval_value.setEditable(true); + aprs_interval_value.addItemListener(this); + pane.add(aprs_interval_value, c); + set_aprs_interval_tool_tip(); + row++; + + /* APRS SSID */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + aprs_ssid_label = new JLabel("APRS SSID:"); + pane.add(aprs_ssid_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + aprs_ssid_value = new JComboBox(aprs_ssid_values); + aprs_ssid_value.setEditable(false); + aprs_ssid_value.addItemListener(this); + aprs_ssid_value.setMaximumRowCount(aprs_ssid_values.length); + pane.add(aprs_ssid_value, c); + set_aprs_ssid_tool_tip(); + row++; + + /* APRS format */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + aprs_format_label = new JLabel("APRS format:"); + pane.add(aprs_format_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + aprs_format_value = new JComboBox(AltosLib.ao_aprs_format_name); + aprs_format_value.setEditable(false); + aprs_format_value.addItemListener(this); + aprs_format_value.setMaximumRowCount(AltosLib.ao_aprs_format_name.length); + pane.add(aprs_format_value, c); + set_aprs_format_tool_tip(); + row++; + + /* Callsign */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + 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 = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + callsign_value = new JTextField(AltosUIPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + pane.add(callsign_value, c); + callsign_value.setToolTipText("Callsign reported in telemetry data"); + row++; + + /* Flight log max */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + flight_log_max_label = new JLabel("Maximum Flight Log Size (kB):"); + pane.add(flight_log_max_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + flight_log_max_value = new JComboBox(); + flight_log_max_value.setEditable(true); + flight_log_max_value.addItemListener(this); + pane.add(flight_log_max_value, c); + set_flight_log_max_tool_tip(); + row++; + + /* Ignite mode */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + ignite_mode_label = new JLabel("Igniter Firing Mode:"); + pane.add(ignite_mode_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + ignite_mode_value = new JComboBox(ignite_mode_values); + ignite_mode_value.setEditable(false); + ignite_mode_value.addItemListener(this); + pane.add(ignite_mode_value, c); + set_ignite_mode_tool_tip(); + row++; + + /* Pad orientation */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + pad_orientation_label = new JLabel("Pad Orientation:"); + pane.add(pad_orientation_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + pad_orientation_value = new JComboBox(pad_orientation_values); + pad_orientation_value.setEditable(false); + pad_orientation_value.addItemListener(this); + pane.add(pad_orientation_value, c); + set_pad_orientation_tool_tip(); + row++; + + /* Accel plus */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + accel_plus_label = new JLabel("Accel Plus:"); + pane.add(accel_plus_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + accel_plus_value = new JTextField(10); + accel_plus_value.setEditable(true); + accel_plus_value.getDocument().addDocumentListener(this); + pane.add(accel_plus_value, c); + row++; + + /* Accel minus */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + accel_minus_label = new JLabel("Accel Minus:"); + pane.add(accel_minus_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + accel_minus_value = new JTextField(10); + accel_minus_value.setEditable(true); + accel_minus_value.getDocument().addDocumentListener(this); + pane.add(accel_minus_value, c); + row++; + set_accel_tool_tips(); + + /* Beeper */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + beep_label = new JLabel("Beeper Frequency:"); + pane.add(beep_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + beep_value = new JComboBox(beep_values); + beep_value.setEditable(true); + beep_value.addItemListener(this); + pane.add(beep_value, c); + set_beep_tool_tip(); + row++; + + /* Tracker triger horiz distances */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + tracker_motion_label = new JLabel(get_tracker_motion_label()); + pane.add(tracker_motion_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + tracker_motion_value = new JComboBox(tracker_motion_values()); + tracker_motion_value.setEditable(true); + tracker_motion_value.addItemListener(this); + pane.add(tracker_motion_value, c); + row++; + + /* Tracker triger vert distances */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + tracker_interval_label = new JLabel("Position Reporting Interval(s):"); + pane.add(tracker_interval_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + tracker_interval_value = new JComboBox(tracker_interval_values); + tracker_interval_value.setEditable(true); + tracker_interval_value.addItemListener(this); + pane.add(tracker_interval_value, c); + set_tracker_tool_tip(); + row++; + + /* Pyro channels */ + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + pyro = new JButton("Configure Pyro Channels"); + pane.add(pyro, c); + pyro.addActionListener(this); + pyro.setActionCommand("Pyro"); + row++; + + /* Accel cal */ + c = new GridBagConstraints(); + c.gridx = 5; c.gridy = row; + c.gridwidth = 5; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + accel_cal = new JButton("Calibrate Accelerometer"); + pane.add(accel_cal, c); + accel_cal.addActionListener(this); + accel_cal.setActionCommand("Accel"); + row++; + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + 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 = row; + 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 = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reboot = new JButton("Reboot"); + pane.add(reboot, c); + reboot.addActionListener(this); + reboot.setActionCommand("Reboot"); + + c = new GridBagConstraints(); + c.gridx = 6; c.gridy = row; + 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)); + AltosPreferences.register_units_listener(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(String operation) { + if (dirty) { + Object[] options = { String.format("%s anyway", operation), "Keep editing" }; + int i; + i = JOptionPane.showOptionDialog(this, + String.format("Configuration modified. %s anyway?", operation), + "Configuration Modified", + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, options, options[1]); + if (i != 0) + return false; + } + return true; + } + + public void set_dirty() { + dirty = true; + save.setEnabled(true); + } + + public void set_clean() { + dirty = false; + save.setEnabled(false); + } + + AltosConfigPyroUI pyro_ui; + + public void dispose() { + if (pyro_ui != null) + pyro_ui.dispose(); + AltosPreferences.unregister_units_listener(this); + super.dispose(); + } + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("Pyro")) { + if (pyro_ui == null && pyros != null) + pyro_ui = new AltosConfigPyroUI(this, pyros, pyro_firing_time); + if (pyro_ui != null) + pyro_ui.make_visible(); + return; + } + + if (cmd.equals("Close") || cmd.equals("Reboot")) + if (!check_dirty(cmd)) + return; + listener.actionPerformed(e); + if (cmd.equals("Close") || cmd.equals("Reboot")) { + setVisible(false); + dispose(); + } + if (cmd.equals("Save") || cmd.equals("Reset")) + set_clean(); + } + + /* ItemListener interface method */ + public void itemStateChanged(ItemEvent e) { + set_dirty(); + } + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + set_dirty(); + } + + public void insertUpdate(DocumentEvent e) { + set_dirty(); + } + + public void removeUpdate(DocumentEvent e) { + set_dirty(); + } + + /* 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) { + radio_frequency_value.set_product(product); + product_value.setText(product); + set_pad_orientation_tool_tip(); + set_accel_tool_tips(); + set_flight_log_max_tool_tip(); + } + + public void set_version(String version) { + version_value.setText(version); + } + + public void set_serial(int serial) { + radio_frequency_value.set_serial(serial); + serial_value.setText(String.format("%d", serial)); + } + + public void set_altitude_32(int altitude_32) { + } + + public void set_main_deploy(int new_main_deploy) { + if (new_main_deploy != AltosLib.MISSING) + main_deploy_value.setSelectedItem(AltosConvert.height.say(new_main_deploy)); + main_deploy_value.setVisible(new_main_deploy != AltosLib.MISSING); + main_deploy_label.setVisible(new_main_deploy != AltosLib.MISSING); + } + + public int main_deploy() throws AltosConfigDataException { + String str = main_deploy_value.getSelectedItem().toString(); + try { + return (int) (AltosConvert.height.parse_locale(str) + 0.5); + } catch (ParseException pe) { + throw new AltosConfigDataException("invalid main deploy height %s", str); + } + } + + String get_main_deploy_label() { + return String.format("Main Deploy Altitude(%s):", AltosConvert.height.parse_units()); + } + + String[] main_deploy_values() { + if (AltosConvert.imperial_units) + return main_deploy_values_ft; + else + return main_deploy_values_m; + } + + void set_main_deploy_values() { + String[] v = main_deploy_values(); + while (main_deploy_value.getItemCount() > 0) + main_deploy_value.removeItemAt(0); + for (int i = 0; i < v.length; i++) + main_deploy_value.addItem(v[i]); + main_deploy_value.setMaximumRowCount(v.length); + } + + public void units_changed(boolean imperial_units) { + boolean was_dirty = dirty; + + String v = main_deploy_value.getSelectedItem().toString(); + main_deploy_label.setText(get_main_deploy_label()); + set_main_deploy_values(); + try { + int m = (int) (AltosConvert.height.parse_locale(v, !imperial_units) + 0.5); + set_main_deploy(m); + } catch (ParseException pe) { + } + + if (tracker_motion_value.isVisible()) { + String motion = tracker_motion_value.getSelectedItem().toString(); + tracker_motion_label.setText(get_tracker_motion_label()); + set_tracker_motion_values(); + try { + int m = (int) (AltosConvert.height.parse_locale(motion, !imperial_units) + 0.5); + set_tracker_motion(m); + } catch (ParseException pe) { + } + } + + if (!was_dirty) + set_clean(); + } + + public void set_apogee_delay(int new_apogee_delay) { + if (new_apogee_delay != AltosLib.MISSING) + apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay)); + apogee_delay_value.setVisible(new_apogee_delay != AltosLib.MISSING); + apogee_delay_label.setVisible(new_apogee_delay != AltosLib.MISSING); + } + + private int parse_int(String name, String s, boolean split) throws AltosConfigDataException { + String v = s; + if (split) + v = s.split("\\s+")[0]; + try { + return Integer.parseInt(v); + } catch (NumberFormatException ne) { + throw new AltosConfigDataException("Invalid %s \"%s\"", name, s); + } + } + + public int apogee_delay() throws AltosConfigDataException { + return parse_int("apogee delay", apogee_delay_value.getSelectedItem().toString(), false); + } + + public void set_apogee_lockout(int new_apogee_lockout) { + if (new_apogee_lockout != AltosLib.MISSING) + apogee_lockout_value.setSelectedItem(Integer.toString(new_apogee_lockout)); + + apogee_lockout_value.setVisible(new_apogee_lockout != AltosLib.MISSING); + apogee_lockout_label.setVisible(new_apogee_lockout != AltosLib.MISSING); + } + + public int apogee_lockout() throws AltosConfigDataException { + return parse_int("apogee lockout", apogee_lockout_value.getSelectedItem().toString(), false); + } + + public void set_radio_frequency(double new_radio_frequency) { + if (new_radio_frequency != AltosLib.MISSING) + radio_frequency_value.set_frequency(new_radio_frequency); + radio_frequency_label.setVisible(new_radio_frequency != AltosLib.MISSING); + radio_frequency_value.setVisible(new_radio_frequency != AltosLib.MISSING); + } + + public double radio_frequency() { + return radio_frequency_value.frequency(); + } + + public void set_radio_calibration(int new_radio_calibration) { + if (new_radio_calibration != AltosLib.MISSING) + radio_calibration_value.setText(String.format("%d", new_radio_calibration)); + radio_calibration_value.setVisible(new_radio_calibration != AltosLib.MISSING); + radio_calibration_label.setVisible(new_radio_calibration != AltosLib.MISSING); + } + + public void set_radio_enable(int new_radio_enable) { + if (new_radio_enable != AltosLib.MISSING) + radio_enable_value.setSelected(new_radio_enable != 0); + radio_enable_label.setVisible(new_radio_enable != AltosLib.MISSING); + radio_enable_value.setVisible(new_radio_enable != AltosLib.MISSING); + set_radio_enable_tool_tip(); + } + + public int radio_enable() { + if (radio_enable_value.isVisible()) + return radio_enable_value.isSelected() ? 1 : 0; + else + return AltosLib.MISSING; + } + + public void set_telemetry_rate(int new_rate) { + if (new_rate != AltosLib.MISSING) + rate_value.set_rate(new_rate); + rate_label.setVisible(new_rate != AltosLib.MISSING); + rate_value.setVisible(new_rate != AltosLib.MISSING); + set_rate_tool_tip(); + } + + public int telemetry_rate() { + return rate_value.rate(); + } + + public void set_callsign(String new_callsign) { + if (new_callsign != null) + callsign_value.setText(new_callsign); + callsign_value.setVisible(new_callsign != null); + callsign_label.setVisible(new_callsign != null); + } + + public String callsign() { + if (callsign_value.isVisible()) + return callsign_value.getText(); + return null; + } + + int flight_log_max_limit; + int flight_log_max; + + public String flight_log_max_label(int flight_log_max) { + if (flight_log_max_limit != 0) { + int nflight = flight_log_max_limit / flight_log_max; + String plural = nflight > 1 ? "s" : ""; + + return String.format("%d (%d flight%s)", flight_log_max, nflight, plural); + } + return String.format("%d", flight_log_max); + } + + public void set_flight_log_max(int new_flight_log_max) { + if (new_flight_log_max != AltosLib.MISSING) { + flight_log_max_value.setSelectedItem(flight_log_max_label(new_flight_log_max)); + flight_log_max = new_flight_log_max; + } + flight_log_max_value.setVisible(new_flight_log_max != AltosLib.MISSING); + flight_log_max_label.setVisible(new_flight_log_max != AltosLib.MISSING); + set_flight_log_max_tool_tip(); + } + + public void set_flight_log_max_enabled(boolean enable) { + flight_log_max_value.setEnabled(enable); + set_flight_log_max_tool_tip(); + } + + public int flight_log_max() throws AltosConfigDataException { + if (flight_log_max_value.isVisible()) + return parse_int("flight log max", flight_log_max_value.getSelectedItem().toString(), true); + return AltosLib.MISSING; + } + + public void set_flight_log_max_limit(int new_flight_log_max_limit) { + flight_log_max_limit = new_flight_log_max_limit; + if (new_flight_log_max_limit != AltosLib.MISSING) { + flight_log_max_value.removeAllItems(); + for (int i = 8; i >= 1; i--) { + int size = flight_log_max_limit / i; + flight_log_max_value.addItem(String.format("%d (%d flights)", size, i)); + } + } + if (flight_log_max != 0) + set_flight_log_max(flight_log_max); + } + + public void set_ignite_mode(int new_ignite_mode) { + if (new_ignite_mode != AltosLib.MISSING) { + if (new_ignite_mode >= ignite_mode_values.length) + new_ignite_mode = 0; + if (new_ignite_mode < 0) { + ignite_mode_value.setEnabled(false); + new_ignite_mode = 0; + } else { + ignite_mode_value.setEnabled(true); + } + ignite_mode_value.setSelectedIndex(new_ignite_mode); + } + ignite_mode_value.setVisible(new_ignite_mode != AltosLib.MISSING); + ignite_mode_label.setVisible(new_ignite_mode != AltosLib.MISSING); + + set_ignite_mode_tool_tip(); + } + + public int ignite_mode() { + if (ignite_mode_value.isVisible()) + return ignite_mode_value.getSelectedIndex(); + else + return AltosLib.MISSING; + } + + + public void set_pad_orientation(int new_pad_orientation) { + if (new_pad_orientation != AltosLib.MISSING) { + if (new_pad_orientation >= pad_orientation_values.length) + new_pad_orientation = 0; + if (new_pad_orientation < 0) + new_pad_orientation = 0; + pad_orientation_value.setSelectedIndex(new_pad_orientation); + } + pad_orientation_value.setVisible(new_pad_orientation != AltosLib.MISSING); + pad_orientation_label.setVisible(new_pad_orientation != AltosLib.MISSING); + accel_cal.setVisible(new_pad_orientation != AltosLib.MISSING); + + set_pad_orientation_tool_tip(); + } + + public int pad_orientation() { + if (pad_orientation_value.isVisible()) + return pad_orientation_value.getSelectedIndex(); + else + return AltosLib.MISSING; + } + + public void set_accel_cal(int accel_plus, int accel_minus) { + if (accel_plus != AltosLib.MISSING) { + accel_plus_value.setText(String.format("%d", accel_plus)); + accel_minus_value.setText(String.format("%d", accel_minus)); + } + accel_plus_value.setVisible(accel_plus != AltosLib.MISSING); + accel_plus_label.setVisible(accel_plus != AltosLib.MISSING); + accel_minus_value.setVisible(accel_minus != AltosLib.MISSING); + accel_minus_label.setVisible(accel_minus != AltosLib.MISSING); + + set_accel_tool_tips(); + } + + public int accel_cal_plus() { + if (accel_plus_value.isVisible()) + return Integer.parseInt(accel_plus_value.getText()); + return AltosLib.MISSING; + } + + public int accel_cal_minus() { + if (accel_minus_value.isVisible()) + return Integer.parseInt(accel_minus_value.getText()); + return AltosLib.MISSING; + } + + public void set_beep(int new_beep) { + if (new_beep != AltosLib.MISSING) { + int new_freq = (int) Math.floor (AltosConvert.beep_value_to_freq(new_beep) + 0.5); + for (int i = 0; i < beep_values.length; i++) + if (new_beep == AltosConvert.beep_freq_to_value(Integer.parseInt(beep_values[i]))) { + beep_value.setSelectedIndex(i); + set_beep_tool_tip(); + return; + } + beep_value.setSelectedItem(String.format("%d", new_freq)); + } + beep_value.setVisible(new_beep != AltosLib.MISSING); + beep_label.setVisible(new_beep != AltosLib.MISSING); + set_beep_tool_tip(); + } + + public int beep() { + if (beep_value.isVisible()) + return AltosConvert.beep_freq_to_value(Integer.parseInt(beep_value.getSelectedItem().toString())); + else + return AltosLib.MISSING; + } + + String[] tracker_motion_values() { + if (AltosConvert.imperial_units) + return tracker_motion_values_ft; + else + return tracker_motion_values_m; + } + + void set_tracker_motion_values() { + String[] v = tracker_motion_values(); + while (tracker_motion_value.getItemCount() > 0) + tracker_motion_value.removeItemAt(0); + for (int i = 0; i < v.length; i++) + tracker_motion_value.addItem(v[i]); + tracker_motion_value.setMaximumRowCount(v.length); + } + + String get_tracker_motion_label() { + return String.format("Logging Trigger Motion (%s):", AltosConvert.height.parse_units()); + } + + void set_tracker_tool_tip() { + if (tracker_motion_value.isVisible()) + tracker_motion_value.setToolTipText("How far the device must move before logging"); + else + tracker_motion_value.setToolTipText("This device doesn't disable logging when stationary"); + if (tracker_interval_value.isVisible()) + tracker_interval_value.setToolTipText("How often to report GPS position"); + else + tracker_interval_value.setToolTipText("This device can't configure interval"); + } + + public void set_tracker_motion(int tracker_motion) { + if (tracker_motion != AltosLib.MISSING) + tracker_motion_value.setSelectedItem(AltosConvert.height.say(tracker_motion)); + tracker_motion_label.setVisible(tracker_motion != AltosLib.MISSING); + tracker_motion_value.setVisible(tracker_motion != AltosLib.MISSING); + } + + public int tracker_motion() throws AltosConfigDataException { + if (tracker_motion_value.isVisible()) { + String str = tracker_motion_value.getSelectedItem().toString(); + try { + return (int) (AltosConvert.height.parse_locale(str) + 0.5); + } catch (ParseException pe) { + throw new AltosConfigDataException("invalid tracker motion %s", str); + } + } + return AltosLib.MISSING; + } + + public void set_tracker_interval(int tracker_interval) { + if (tracker_interval != AltosLib.MISSING) + tracker_interval_value.setSelectedItem(String.format("%d", tracker_interval)); + tracker_interval_label.setVisible(tracker_interval != AltosLib.MISSING); + tracker_interval_value.setVisible(tracker_interval != AltosLib.MISSING); + } + + public int tracker_interval() throws AltosConfigDataException { + if (tracker_interval_value.isVisible()) + return parse_int ("tracker interval", tracker_interval_value.getSelectedItem().toString(), false); + return AltosLib.MISSING; + } + + public void set_pyros(AltosPyro[] new_pyros) { + pyros = new_pyros; + if (pyros != null && pyro_ui != null) + pyro_ui.set_pyros(pyros); + pyro.setVisible(pyros != null); + } + + public AltosPyro[] pyros() throws AltosConfigDataException { + if (pyro_ui != null) + pyros = pyro_ui.get_pyros(); + return pyros; + } + + public void set_pyro_firing_time(double new_pyro_firing_time) { + pyro_firing_time = new_pyro_firing_time; + if (pyro_firing_time != AltosLib.MISSING && pyro_ui != null) + pyro_ui.set_pyro_firing_time(pyro_firing_time); + pyro.setVisible(pyro_firing_time != AltosLib.MISSING); + } + + public double pyro_firing_time() throws AltosConfigDataException { + if (pyro_ui != null) + pyro_firing_time = pyro_ui.get_pyro_firing_time(); + return pyro_firing_time; + } + + public void set_aprs_interval(int new_aprs_interval) { + if (new_aprs_interval != AltosLib.MISSING) + aprs_interval_value.setSelectedItem(Integer.toString(new_aprs_interval)); + aprs_interval_value.setVisible(new_aprs_interval != AltosLib.MISSING); + aprs_interval_label.setVisible(new_aprs_interval != AltosLib.MISSING); + set_aprs_interval_tool_tip(); + } + + public int aprs_interval() throws AltosConfigDataException { + if (aprs_interval_value.isVisible()) { + String s = aprs_interval_value.getSelectedItem().toString(); + + return parse_int("aprs interval", s, false); + } + return AltosLib.MISSING; + } + + public void set_aprs_ssid(int new_aprs_ssid) { + if (new_aprs_ssid != AltosLib.MISSING) + aprs_ssid_value.setSelectedItem(new_aprs_ssid); + aprs_ssid_value.setVisible(new_aprs_ssid != AltosLib.MISSING); + aprs_ssid_label.setVisible(new_aprs_ssid != AltosLib.MISSING); + set_aprs_ssid_tool_tip(); + } + + public int aprs_ssid() throws AltosConfigDataException { + if (aprs_ssid_value.isVisible()) { + Integer i = (Integer) aprs_ssid_value.getSelectedItem(); + return i; + } + return AltosLib.MISSING; + } + + public void set_aprs_format(int new_aprs_format) { + if (new_aprs_format != AltosLib.MISSING) + aprs_format_value.setSelectedIndex(new_aprs_format); + aprs_format_value.setVisible(new_aprs_format != AltosLib.MISSING); + aprs_format_label.setVisible(new_aprs_format != AltosLib.MISSING); + set_aprs_format_tool_tip(); + } + + public int aprs_format() throws AltosConfigDataException { + if (aprs_format_value.isVisible()) + return aprs_format_value.getSelectedIndex(); + return AltosLib.MISSING; + } +} diff --git a/teststand/AltosConfigPyroUI.java b/teststand/AltosConfigPyroUI.java new file mode 100644 index 00000000..eff612e1 --- /dev/null +++ b/teststand/AltosConfigPyroUI.java @@ -0,0 +1,464 @@ +/* + * Copyright © 2012 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.text.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigPyroUI + extends AltosUIDialog + implements ItemListener, DocumentListener, AltosUnitsListener, ActionListener +{ + AltosConfigFCUI owner; + Container pane; + + static Insets il = new Insets(4,4,4,4); + static Insets ir = new Insets(4,4,4,4); + + static String[] state_names; + + static void make_state_names() { + if (state_names == null) { + + state_names = new String[AltosLib.ao_flight_landed - AltosLib.ao_flight_boost + 1]; + for (int state = AltosLib.ao_flight_boost; state <= AltosLib.ao_flight_landed; state++) + state_names[state - AltosLib.ao_flight_boost] = AltosLib.state_name_capital(state); + } + } + + class PyroItem implements ItemListener, DocumentListener, AltosUnitsListener + { + public int flag; + public JCheckBox enable; + public JTextField value; + public JComboBox combo; + AltosConfigPyroUI ui; + boolean setting; + + public void set_enable(boolean enable) { + if (value != null) + value.setEnabled(enable); + if (combo != null) + combo.setEnabled(enable); + } + + public void itemStateChanged(ItemEvent e) { + set_enable(enable.isSelected()); + if (!setting) + ui.set_dirty(); + } + + public void changedUpdate(DocumentEvent e) { + if (!setting) + ui.set_dirty(); + } + + public void insertUpdate(DocumentEvent e) { + if (!setting) + ui.set_dirty(); + } + + public void removeUpdate(DocumentEvent e) { + if (!setting) + ui.set_dirty(); + } + + public void units_changed(boolean imperial_units) { + AltosUnits units = AltosPyro.pyro_to_units(flag); + + if (units != null) { + try { + double v = units.parse_locale(value.getText(), !imperial_units); + set(enabled(), v); + } catch (ParseException pe) { + set(enabled(), 0.0); + } + } + } + + public void set(boolean new_enable, double new_value) { + setting = true; + enable.setSelected(new_enable); + set_enable(new_enable); + if (value != null) { + double scale = AltosPyro.pyro_to_scale(flag); + double unit_value = new_value; + AltosUnits units = AltosPyro.pyro_to_units(flag); + if (units != null) + unit_value = units.parse_value(new_value); + String format; + if (scale >= 100) + format = "%6.2f"; + else if (scale >= 10) + format = "%6.1f"; + else + format = "%6.0f"; + value.setText(String.format(format, unit_value)); + } + if (combo != null) + if (new_value >= AltosLib.ao_flight_boost && new_value <= AltosLib.ao_flight_landed) + combo.setSelectedIndex((int) new_value - AltosLib.ao_flight_boost); + setting = false; + } + + public boolean enabled() { + return enable.isSelected(); + } + + public double value() throws AltosConfigDataException { + if (value != null) { + AltosUnits units = AltosPyro.pyro_to_units(flag); + try { + if (units != null) + return units.parse_locale(value.getText()); + return AltosParse.parse_double_locale(value.getText()); + } catch (ParseException e) { + throw new AltosConfigDataException("\"%s\": %s\n", value.getText(), e.getMessage()); + } + } + if (combo != null) + return combo.getSelectedIndex() + AltosLib.ao_flight_boost; + return 0; + } + + public PyroItem(AltosConfigPyroUI in_ui, int in_flag, int x, int y) { + + ui = in_ui; + flag = in_flag; + + GridBagConstraints c; + c = new GridBagConstraints(); + c.gridx = x; c.gridy = y; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + enable = new JCheckBox(); + enable.addItemListener(this); + pane.add(enable, c); + + if ((flag & AltosPyro.pyro_no_value) == 0) { + c = new GridBagConstraints(); + c.gridx = x+1; c.gridy = y; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + if ((flag & AltosPyro.pyro_state_value) != 0) { + make_state_names(); + combo = new JComboBox(state_names); + combo.addItemListener(this); + pane.add(combo, c); + } else { + value = new JTextField(10); + value.getDocument().addDocumentListener(this); + pane.add(value, c); + } + } + } + } + + class PyroColumn implements AltosUnitsListener { + public PyroItem[] items; + public JLabel label; + int channel; + + public void set(AltosPyro pyro) { + int row = 0; + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) { + if ((AltosPyro.pyro_all & flag) != 0) { + items[row].set((pyro.flags & flag) != 0, + pyro.get_value(flag)); + row++; + } + } + } + + public AltosPyro get() throws AltosConfigDataException { + AltosPyro p = new AltosPyro(channel); + + int row = 0; + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) { + if ((AltosPyro.pyro_all & flag) != 0) { + if (items[row].enabled()) { + try { + p.flags |= flag; + p.set_value(flag, items[row].value()); + } catch (AltosConfigDataException ae) { + throw new AltosConfigDataException("%s, %s", + AltosPyro.pyro_to_name(flag), + ae.getMessage()); + } + } + row++; + } + } + return p; + } + + public void units_changed(boolean imperial_units) { + int row = 0; + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) { + if ((AltosPyro.pyro_all & flag) != 0) { + items[row].units_changed(imperial_units); + row++; + } + } + } + + public PyroColumn(AltosConfigPyroUI ui, int x, int y, int in_channel) { + + channel = in_channel; + + int nrow = 0; + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) + if ((flag & AltosPyro.pyro_all) != 0) + nrow++; + + items = new PyroItem[nrow]; + int row = 0; + + GridBagConstraints c; + c = new GridBagConstraints(); + c.gridx = x; c.gridy = y; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + label = new JLabel(String.format("Pyro Channel %c", 'A' + channel)); + pane.add(label, c); + y++; + + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) + if ((flag & AltosPyro.pyro_all) != 0) { + items[row] = new PyroItem(ui, flag, x, y + row); + row++; + } + } + } + + PyroColumn[] columns; + JLabel[] labels; + + public void set_pyros(AltosPyro[] pyros) { + for (int i = 0; i < pyros.length; i++) { + if (pyros[i].channel < columns.length) + columns[pyros[i].channel].set(pyros[i]); + } + } + + public AltosPyro[] get_pyros() throws AltosConfigDataException { + AltosPyro[] pyros = new AltosPyro[columns.length]; + for (int c = 0; c < columns.length; c++) { + try { + pyros[c] = columns[c].get(); + } catch (AltosConfigDataException ae) { + throw new AltosConfigDataException ("Channel %c, %s", c + 'A', ae.getMessage()); + } + } + return pyros; + } + + JLabel pyro_firing_time_label; + JComboBox pyro_firing_time_value; + + static String[] pyro_firing_time_values = { + "0.050", "0.100", "0.250", "0.500", "1.0", "2.0" + }; + + boolean initializing; + + public void set_pyro_firing_time(double new_pyro_firing_time) { + initializing = true; + pyro_firing_time_value.setSelectedItem(Double.toString(new_pyro_firing_time)); + pyro_firing_time_value.setEnabled(new_pyro_firing_time >= 0); + initializing = false; + } + + public double get_pyro_firing_time() throws AltosConfigDataException { + String v = pyro_firing_time_value.getSelectedItem().toString(); + + try { + return AltosParse.parse_double_locale(v); + } catch (ParseException e) { + throw new AltosConfigDataException("Invalid pyro firing time \"%s\"", v); + } + } + + public void set_dirty() { + if (!initializing) + owner.set_dirty(); + } + + public void itemStateChanged(ItemEvent e) { + if (!initializing) + owner.set_dirty(); + } + + public void changedUpdate(DocumentEvent e) { + if (!initializing) + owner.set_dirty(); + } + + public void insertUpdate(DocumentEvent e) { + if (!initializing) + owner.set_dirty(); + } + + public void removeUpdate(DocumentEvent e) { + if (!initializing) + owner.set_dirty(); + } + + public void units_changed(boolean imperial_units) { + for (int c = 0; c < columns.length; c++) + columns[c].units_changed(imperial_units); + int r = 0; + for (int flag = 1; flag <= AltosPyro.pyro_all; flag <<= 1) { + String n = AltosPyro.pyro_to_name(flag); + if (n != null) { + labels[r].setText(n); + r++; + } + } + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigPyroUI ui; + AltosConfigFCUI owner; + + public ConfigListener(AltosConfigPyroUI this_ui, AltosConfigFCUI this_owner) { + ui = this_ui; + owner = this_owner; + } + + public void windowClosing(WindowEvent e) { + ui.setVisible(false); + } + } + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("Close")) + setVisible(false); + } + + public AltosConfigPyroUI(AltosConfigFCUI in_owner, AltosPyro[] pyros, double pyro_firing_time) { + + super(in_owner, "Configure Pyro Channels", false); + + owner = in_owner; + + GridBagConstraints c; + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + int nrow = 0; + for (int flag = 1; flag < AltosPyro.pyro_all; flag <<= 1) + if ((flag & AltosPyro.pyro_all) != 0) + nrow++; + + labels = new JLabel[nrow]; + + int row = 1; + + for (int flag = 1; flag <= AltosPyro.pyro_all; flag <<= 1) { + String n; + + n = AltosPyro.pyro_to_name(flag); + if (n != null) { + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + JLabel label = new JLabel(n); + pane.add(label, c); + labels[row-1] = label; + row++; + } + } + + columns = new PyroColumn[pyros.length]; + + for (int i = 0; i < pyros.length; i++) { + columns[i] = new PyroColumn(this, i*2 + 1, 0, i); + columns[i].set(pyros[i]); + } + + /* Pyro firing time */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + pyro_firing_time_label = new JLabel("Pyro Firing Time(s):"); + pane.add(pyro_firing_time_label, c); + + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = row; + c.gridwidth = 7; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + pyro_firing_time_value = new JComboBox(pyro_firing_time_values); + pyro_firing_time_value.setEditable(true); + pyro_firing_time_value.addItemListener(this); + set_pyro_firing_time(pyro_firing_time); + pane.add(pyro_firing_time_value, c); + pyro_firing_time_value.setToolTipText("Length of extra pyro channel firing pulse"); + + c = new GridBagConstraints(); + c.gridx = pyros.length*2-1; + c.fill = GridBagConstraints.HORIZONTAL; + c.gridwidth = 2; + c.gridy = 1000; + JButton close = new JButton("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("Close"); + + addWindowListener(new ConfigListener(this, owner)); + AltosPreferences.register_units_listener(this); + } + + public void dispose() { + AltosPreferences.unregister_units_listener(this); + super.dispose(); + } + + public void make_visible() { + pack(); + setVisible(true); + } +} diff --git a/teststand/AltosConfigTD.java b/teststand/AltosConfigTD.java new file mode 100644 index 00000000..f047874c --- /dev/null +++ b/teststand/AltosConfigTD.java @@ -0,0 +1,380 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigTD implements 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; + int_ref serial; + int_ref radio_channel; + int_ref radio_calibration; + int_ref radio_setting; + int_ref radio_frequency; + int_ref telemetry_rate; + string_ref config_version; + string_ref version; + string_ref product; + AltosConfigTDUI config_ui; + boolean made_visible; + + 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; + } + } + + synchronized void update_ui() { + config_ui.set_serial(serial.get()); + config_ui.set_product(product.get()); + config_ui.set_version(version.get()); + config_ui.set_radio_frequency(frequency()); + config_ui.set_radio_calibration(radio_calibration.get()); + config_ui.set_telemetry_rate(telemetry_rate.get()); + config_ui.set_clean(); + if (!made_visible) { + made_visible = true; + config_ui.make_visible(); + } + } + + void finish_input(String line) { + if (line == null) { + abort(); + return; + } + if (line.equals("all finished")) { + if (serial_line != null) + update_ui(); + return; + } + } + + synchronized void process_line(String line) { + if (line == null || line.equals("all finished")) { + final String last_line = line; + Runnable r = new Runnable() { + public void run() { + finish_input(last_line); + } + }; + SwingUtilities.invokeLater(r); + } else { + get_string(line, "Config version", config_version); + get_int(line, "serial-number", serial); + get_int(line, "Radio channel:", radio_channel); + get_int(line, "Radio cal:", radio_calibration); + get_int(line, "Frequency:", radio_frequency); + get_int(line, "Radio setting:", radio_setting); + get_int(line, "Telemetry rate:", telemetry_rate); + get_string(line,"software-version", version); + get_string(line,"product", product); + } + } + + synchronized void reset_data() { + serial.set(0); + radio_channel.set(0); + radio_setting.set(0); + radio_frequency.set(0); + radio_calibration.set(1186611); + telemetry_rate.set(Altos.ao_telemetry_rate_38400); + config_version.set("0.0"); + version.set("unknown"); + product.set("unknown"); + } + + synchronized double frequency() { + return AltosConvert.radio_to_frequency(radio_frequency.get(), + radio_setting.get(), + radio_calibration.get(), + radio_channel.get()); + } + + synchronized void set_frequency(double freq) { + int frequency = radio_frequency.get(); + int setting = radio_setting.get(); + + if (frequency > 0) { + radio_frequency.set((int) Math.floor (freq * 1000 + 0.5)); + } else if (setting > 0) { + radio_setting.set(AltosConvert.radio_frequency_to_setting(freq, + radio_calibration.get())); + radio_channel.set(0); + } else { + radio_channel.set(AltosConvert.radio_frequency_to_channel(freq)); + } + } + + synchronized int telemetry_rate() { + return telemetry_rate.get(); + } + + synchronized void set_telemetry_rate(int new_telemetry_rate){ + int rate = telemetry_rate.get(); + + if (rate >= 0) + telemetry_rate.set(new_telemetry_rate); + } + + final static int serial_mode_read = 0; + final static int serial_mode_save = 1; + final static int serial_mode_reboot = 2; + + SerialData serial_data; + Thread serial_thread; + + class SerialData implements Runnable { + AltosConfigTD config; + int serial_mode; + + void get_data() { + try { + boolean been_there = false; + config.reset_data(); + + while (config.serial_line != null) { + config.serial_line.printf("c s\nf\nv\n"); + while (config.serial_line != null) { + try { + String line = config.serial_line.get_reply(5000); + config.process_line(line); + if (line != null && line.startsWith("software-version")) + break; + } catch (Exception e) { + break; + } + } + if (been_there) + break; + if (!config_version.get().equals("0.0")) + break; + been_there = true; + if (config != null && config.serial_line != null) { + config.serial_line.printf("C\n "); + config.serial_line.flush_input(); + } + } + } catch (InterruptedException ie) { + } + /* + * This makes sure the displayed frequency respects the limits that the + * available firmware version might place on the actual frequency + */ + config.set_frequency(AltosPreferences.frequency(serial.get())); + config.set_telemetry_rate(AltosPreferences.telemetry_rate(serial.get())); + config.process_line("all finished"); + } + + void save_data() { + double frequency = frequency(); + if (frequency != 0) + AltosPreferences.set_frequency(serial.get(), + frequency); + AltosPreferences.set_telemetry_rate(serial.get(), + telemetry_rate()); + } + + public void run () { + switch (serial_mode) { + case serial_mode_save: + save_data(); + /* fall through ... */ + case serial_mode_read: + get_data(); + serial_thread = null; + break; + } + } + + public SerialData(AltosConfigTD in_config, int in_serial_mode) { + config = in_config; + serial_mode = in_serial_mode; + } + } + + void run_serial_thread(int serial_mode) { + serial_data = new SerialData(this, serial_mode); + serial_thread = new Thread(serial_data); + serial_thread.start(); + } + + void abort_serial_thread() { + if (serial_thread != null) { + serial_thread.interrupt(); + serial_thread = null; + } + } + void init_ui () throws InterruptedException, TimeoutException { + config_ui = new AltosConfigTDUI(owner); + config_ui.addActionListener(this); + serial_line.set_frame(owner); + set_ui(); + } + + void abort() { + abort_serial_thread(); + if (serial_line != null) { + serial_line.close(); + serial_line = null; + } + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + config_ui.setVisible(false); + } + + void set_ui() throws InterruptedException, TimeoutException { + if (serial_line != null) + run_serial_thread(serial_mode_read); + else + update_ui(); + } + + void save_data() { + double freq = config_ui.radio_frequency(); + set_frequency(freq); + int telemetry_rate = config_ui.telemetry_rate(); + set_telemetry_rate(telemetry_rate); + run_serial_thread(serial_mode_save); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + try { + if (cmd.equals("Save")) { + save_data(); + } else if (cmd.equals("Reset")) { + set_ui(); + } else if (cmd.equals("Reboot")) { + if (serial_line != null) + run_serial_thread(serial_mode_reboot); + } else if (cmd.equals("Close")) { + if (serial_line != null) + serial_line.close(); + } + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public AltosConfigTD(JFrame given_owner) { + owner = given_owner; + + serial = new int_ref(0); + radio_channel = new int_ref(0); + radio_setting = new int_ref(0); + radio_frequency = new int_ref(0); + radio_calibration = new int_ref(1186611); + telemetry_rate = new int_ref(AltosLib.ao_telemetry_rate_38400); + config_version = new string_ref("0.0"); + version = new string_ref("unknown"); + product = new string_ref("unknown"); + + device = AltosDeviceUIDialog.show(owner, Altos.product_basestation); + if (device != null) { + try { + serial_line = new AltosSerial(device); + try { + init_ui(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/teststand/AltosConfigTDUI.java b/teststand/AltosConfigTDUI.java new file mode 100644 index 00000000..43da16f7 --- /dev/null +++ b/teststand/AltosConfigTDUI.java @@ -0,0 +1,378 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigTDUI + extends AltosUIDialog + implements ActionListener, ItemListener, DocumentListener +{ + + Container pane; + Box box; + JLabel product_label; + JLabel version_label; + JLabel serial_label; + JLabel frequency_label; + JLabel radio_calibration_label; + JLabel radio_frequency_label; + JLabel rate_label; + + public boolean dirty; + + JFrame owner; + JLabel product_value; + JLabel version_value; + JLabel serial_value; + AltosUIFreqList radio_frequency_value; + JLabel radio_calibration_value; + AltosUIRateList rate_value; + + JButton save; + JButton reset; + JButton reboot; + JButton close; + + ActionListener listener; + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigTDUI ui; + + public ConfigListener(AltosConfigTDUI 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 AltosConfigTDUI(JFrame in_owner) { + super (in_owner, "Configure TeleDongle", 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 = 4; + 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 = 4; c.gridy = 0; + c.gridwidth = 4; + 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 = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + version_label = new JLabel("Software version:"); + pane.add(version_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 1; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + version_value = new JLabel(""); + pane.add(version_value, c); + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + /* Frequency */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_frequency_label = new JLabel("Frequency:"); + pane.add(radio_frequency_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_frequency_value = new AltosUIFreqList(); + radio_frequency_value.addItemListener(this); + pane.add(radio_frequency_value, c); + radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency"); + + /* Radio Calibration */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("RF Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JLabel(String.format("%d", 1186611)); + pane.add(radio_calibration_value, c); + + /* Telemetry Rate */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 7; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + rate_label = new JLabel("Telemetry Rate:"); + pane.add(rate_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 7; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + rate_value = new AltosUIRateList(); + pane.add(rate_value, c); + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 12; + 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 = 12; + 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 = 6; c.gridy = 12; + 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(String operation) { + if (dirty) { + Object[] options = { String.format("%s anyway", operation), "Keep editing" }; + int i; + i = JOptionPane.showOptionDialog(this, + String.format("Configuration modified. %s anyway?", operation), + "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") || cmd.equals("Reboot")) + if (!check_dirty(cmd)) + return; + listener.actionPerformed(e); + if (cmd.equals("Close") || cmd.equals("Reboot")) { + 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) { + radio_frequency_value.set_product(product); + product_value.setText(product); + } + + public void set_version(String version) { + version_value.setText(version); + } + + public void set_serial(int serial) { + radio_frequency_value.set_serial(serial); + serial_value.setText(String.format("%d", serial)); + } + + public void set_radio_frequency(double new_radio_frequency) { + int i; + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (f.close(new_radio_frequency)) { + radio_frequency_value.setSelectedIndex(i); + return; + } + } + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (new_radio_frequency < f.frequency) + break; + } + String description = String.format("%s serial %s", + product_value.getText(), + serial_value.getText()); + AltosFrequency new_frequency = new AltosFrequency(new_radio_frequency, description); + AltosPreferences.add_common_frequency(new_frequency); + radio_frequency_value.insertItemAt(new_frequency, i); + radio_frequency_value.setSelectedIndex(i); + } + + public double radio_frequency() { + return radio_frequency_value.frequency(); + } + + public void set_radio_calibration(int calibration) { + radio_calibration_value.setText(String.format("%d", calibration)); + } + + public int telemetry_rate() { + return rate_value.getSelectedIndex(); + } + + public void set_telemetry_rate(int rate) { + rate_value.setSelectedIndex(rate); + } + + public void set_clean() { + dirty = false; + } +} diff --git a/teststand/AltosConfigureUI.java b/teststand/AltosConfigureUI.java new file mode 100644 index 00000000..30c0c3c9 --- /dev/null +++ b/teststand/AltosConfigureUI.java @@ -0,0 +1,157 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import javax.swing.*; +import javax.swing.event.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosConfigureUI + extends AltosUIConfigure + implements DocumentListener +{ + AltosVoice voice; + + public JTextField callsign_value; + public JComboBox position_value; + + /* DocumentListener interface methods */ + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public void changedUpdate(DocumentEvent e) { + if (callsign_value != null) + AltosUIPreferences.set_callsign(callsign_value.getText()); + } + + public void add_voice() { + + /* Voice settings */ + pane.add(new JLabel("Voice"), constraints(0, 1)); + + JRadioButton enable_voice = new JRadioButton("Enable", AltosUIPreferences.voice()); + enable_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JRadioButton item = (JRadioButton) e.getSource(); + boolean enabled = item.isSelected(); + AltosUIPreferences.set_voice(enabled); + if (enabled) + voice.speak_always("Enable voice."); + else + voice.speak_always("Disable voice."); + } + }); + pane.add(enable_voice, constraints(1, 1)); + enable_voice.setToolTipText("Enable/Disable all audio in-flight announcements"); + + JButton test_voice = new JButton("Test Voice"); + test_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + voice.speak("That's one small step for man; one giant leap for mankind."); + } + }); + pane.add(test_voice, constraints(2, 1)); + test_voice.setToolTipText("Play a stock audio clip to check volume"); + row++; + } + + public void add_callsign() { + /* Callsign setting */ + pane.add(new JLabel("Callsign"), constraints(0, 1)); + + callsign_value = new JTextField(AltosUIPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + callsign_value.setToolTipText("Callsign sent in packet mode"); + pane.add(callsign_value, constraints(1, 2, GridBagConstraints.BOTH)); + row++; + } + + boolean has_bluetooth; + + public void add_bluetooth() { + JButton manage_bluetooth = new JButton("Manage Bluetooth"); + manage_bluetooth.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosBTManage.show(owner, AltosBTKnown.bt_known()); + } + }); + pane.add(manage_bluetooth, constraints(0, 2)); + /* in the same row as add_frequencies, so don't bump row */ + has_bluetooth = true; + } + + public void add_frequencies() { + JButton manage_frequencies = new JButton("Manage Frequencies"); + manage_frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosConfigFreqUI.show(owner); + } + }); + manage_frequencies.setToolTipText("Configure which values are shown in frequency menus"); + if (has_bluetooth) + pane.add(manage_frequencies, constraints(2, 1)); + else + pane.add(manage_frequencies, constraints(0, 3)); + row++; + } + + final static String[] position_names = { + "Top left", + "Top", + "Top right", + "Left", + "Center", + "Right", + "Bottom left", + "Bottom", + "Bottom right", + }; + + public void add_position() { + pane.add(new JLabel ("Menu position"), constraints(0, 1)); + + position_value = new JComboBox(position_names); + position_value.setMaximumRowCount(position_names.length); + int position = AltosUIPreferences.position(); + position_value.setSelectedIndex(position); + position_value.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int position = position_value.getSelectedIndex(); + AltosUIPreferences.set_position(position); + } + }); + pane.add(position_value, constraints(1, 2, GridBagConstraints.BOTH)); + position_value.setToolTipText("Position of main AltosUI window"); + row++; + } + + public AltosConfigureUI(JFrame owner, AltosVoice voice) { + super(owner); + + this.voice = voice; + } +} diff --git a/teststand/AltosDescent.java b/teststand/AltosDescent.java new file mode 100644 index 00000000..5d8d8de8 --- /dev/null +++ b/teststand/AltosDescent.java @@ -0,0 +1,165 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosDescent extends AltosUIFlightTab { + + class Height extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { return state.height(); } + + public Height (Container container, int x, int y) { + super (container, x, y, AltosConvert.height, "Height"); + } + } + + class Speed extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { return state.speed(); } + + public Speed (Container container, int x, int y) { + super (container, x, y, AltosConvert.speed, "Speed"); + } + } + + class Lat extends AltosUIUnitsIndicator { + + public boolean hide (AltosState state, int i) { return state.gps == null || !state.gps.connected; } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lat; + } + + public Lat (Container container, int x, int y) { + super (container, x, y, AltosConvert.latitude, "Latitude"); + } + } + + class Lon extends AltosUIUnitsIndicator { + public boolean hide (AltosState state, int i) { return state.gps == null || !state.gps.connected; } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lon; + } + + public Lon (Container container, int x, int y) { + super (container, x, y, AltosConvert.longitude, "Longitude"); + } + } + + class Apogee extends AltosUIUnitsIndicator { + public boolean hide(double v) { return v == AltosLib.MISSING; } + public double value(AltosState state, int i) { return state.apogee_voltage; } + public double good() { return AltosLib.ao_igniter_good; } + + public Apogee (Container container, int y) { + super(container, 0, y, 3, AltosConvert.voltage, "Apogee Igniter Voltage", 1, true, 3); + } + } + + class Main extends AltosUIUnitsIndicator { + public boolean hide(double v) { return v == AltosLib.MISSING; } + public double value(AltosState state, int i) { return state.main_voltage; } + public double good() { return AltosLib.ao_igniter_good; } + + public Main (Container container, int y) { + super(container, 0, y, 3, AltosConvert.voltage, "Main Igniter Voltage", 1, true, 3); + } + } + + class Distance extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { + if (state.from_pad != null) + return state.from_pad.distance; + else + return AltosLib.MISSING; + } + + public Distance(Container container, int x, int y) { + super(container, x, y, AltosConvert.distance, "Ground Distance"); + } + } + + class Range extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { + return state.range; + } + public Range (Container container, int x, int y) { + super (container, x, y, AltosConvert.distance, "Range"); + } + } + + class Bearing extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + if (state.from_pad != null && state.from_pad.bearing != AltosLib.MISSING) { + show( String.format("%3.0f°", state.from_pad.bearing), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_LONG)); + } else { + show("Missing", "Missing"); + } + } + public Bearing (Container container, int x, int y) { + super (container, x, y, 1, "Bearing", 2, false, 1, 2); + } + } + + class Elevation extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + if (state.elevation == AltosLib.MISSING) + show("Missing"); + else + show("%3.0f°", state.elevation); + } + public Elevation (Container container, int x, int y) { + super (container, x, y, "Elevation", 1, false, 1); + } + } + + public String getName() { + return "Descent"; + } + + public AltosDescent() { + /* Elements in descent display */ + add(new Speed(this, 0, 0)); + add(new Height(this, 2, 0)); + add(new Elevation(this, 0, 1)); + add(new Range(this, 2, 1)); + add(new Bearing(this, 0, 2)); + add(new Distance(this, 0, 3)); + add(new Lat(this, 0, 4)); + add(new Lon(this, 2, 4)); + add(new Apogee(this, 5)); + add(new Main(this, 6)); + } +} diff --git a/teststand/AltosDevice.java b/teststand/AltosDevice.java new file mode 100644 index 00000000..a833dcc2 --- /dev/null +++ b/teststand/AltosDevice.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2011 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; +import libaltosJNI.*; + +public interface AltosDevice { + public abstract String toString(); + public abstract String toShortString(); + public abstract int getSerial(); + public abstract String getPath(); + public abstract boolean matchProduct(int product); + public abstract String getErrorString(); + public SWIGTYPE_p_altos_file open(); +} diff --git a/teststand/AltosFlightStatus.java b/teststand/AltosFlightStatus.java new file mode 100644 index 00000000..cbc3fa7c --- /dev/null +++ b/teststand/AltosFlightStatus.java @@ -0,0 +1,325 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosFlightStatus extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public abstract class FlightValue { + JLabel label; + JTextField value; + + void show() { + label.setVisible(true); + value.setVisible(true); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + } + + abstract void show(AltosState state, AltosListenerState listener_state); + + void reset() { + value.setText(""); + } + + void set_font() { + label.setFont(Altos.status_font); + value.setFont(Altos.status_font); + } + + void setVisible(boolean visible) { + label.setVisible(visible); + value.setVisible(visible); + } + + public FlightValue (GridBagLayout layout, int x, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(5, 5, 5, 5); + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.status_font); + label.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(""); + value.setEditable(false); + value.setFont(Altos.status_font); + value.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 1; + layout.setConstraints(value, c); + add(value); + } + } + + class Call extends FlightValue { + + String last_call = ""; + + boolean same_call(String call) { + if (last_call == null) + return call == null; + else + return last_call.equals(call); + } + + void show(AltosState state, AltosListenerState listener_state) { + if (!same_call(state.cal_data().callsign)) { + show(); + value.setText(state.cal_data().callsign); + if (state.cal_data().callsign == null) + setVisible(false); + else + setVisible(true); + last_call = state.cal_data().callsign; + } + } + + public void reset() { + super.reset(); + last_call = ""; + } + + public Call (GridBagLayout layout, int x) { + super (layout, x, "Callsign"); + } + } + + Call call; + + class Serial extends FlightValue { + + int last_serial = -1; + void show(AltosState state, AltosListenerState listener_state) { + AltosCalData cal_data = state.cal_data(); + if (cal_data.serial != last_serial) { + show(); + if (cal_data.serial == AltosLib.MISSING) + value.setText("none"); + else + value.setText(String.format("%d", cal_data.serial)); + last_serial = cal_data.serial; + } + } + + public void reset() { + super.reset(); + last_serial = -1; + } + + public Serial (GridBagLayout layout, int x) { + super (layout, x, "Serial"); + } + } + + Serial serial; + + class Flight extends FlightValue { + + int last_flight = -1; + + void show(AltosState state, AltosListenerState listener_state) { + AltosCalData cal_data = state.cal_data(); + if (cal_data.flight != last_flight) { + show(); + if (cal_data.flight == AltosLib.MISSING) + value.setText("none"); + else + value.setText(String.format("%d", cal_data.flight)); + last_flight = cal_data.flight; + } + } + + public void reset() { + super.reset(); + last_flight = -1; + } + + public Flight (GridBagLayout layout, int x) { + super (layout, x, "Flight"); + } + } + + Flight flight; + + class FlightState extends FlightValue { + + int last_state = -1; + + void show(AltosState state, AltosListenerState listener_state) { + if (state.state() != last_state) { + if (state.state() == AltosLib.ao_flight_stateless) + hide(); + else { + show(); + value.setText(state.state_name()); + } + last_state = state.state(); + } + } + + public void reset() { + super.reset(); + last_state = -1; + } + + public FlightState (GridBagLayout layout, int x) { + super (layout, x, "State"); + } + } + + FlightState flight_state; + + class RSSI extends FlightValue { + + int last_rssi = 10000; + + void show(AltosState state, AltosListenerState listener_state) { + if (state.rssi() != last_rssi) { + show(); + value.setText(String.format("%d", state.rssi())); + if (state.rssi == AltosLib.MISSING) + setVisible(false); + else + setVisible(true); + last_rssi = state.rssi(); + } + } + + public void reset() { + super.reset(); + last_rssi = 10000; + } + + public RSSI (GridBagLayout layout, int x) { + super (layout, x, "RSSI"); + } + } + + RSSI rssi; + + class LastPacket extends FlightValue { + + long last_secs = -1; + + void show(AltosState state, AltosListenerState listener_state) { + if (listener_state.running) { + long secs = (System.currentTimeMillis() - state.received_time + 500) / 1000; + if (secs != last_secs) { + value.setText(String.format("%d", secs)); + last_secs = secs; + } + } else { + value.setText("done"); + } + } + + public void reset() { + super.reset(); + last_secs = -1; + } + + public LastPacket(GridBagLayout layout, int x) { + super (layout, x, "Age"); + } + } + + LastPacket last_packet; + + public void reset () { + call.reset(); + serial.reset(); + flight.reset(); + flight_state.reset(); + rssi.reset(); + last_packet.reset(); + } + + public void font_size_changed(int font_size) { + call.set_font(); + serial.set_font(); + flight.set_font(); + flight_state.set_font(); + rssi.set_font(); + last_packet.set_font(); + } + + public void units_changed(boolean imperial_units) { + } + + public void show (AltosState state, AltosListenerState listener_state) { + call.show(state, listener_state); + serial.show(state, listener_state); + flight.show(state, listener_state); + flight_state.show(state, listener_state); + rssi.show(state, listener_state); + last_packet.show(state, listener_state); + if (!listener_state.running) + stop(); + } + + public int height() { + Dimension d = layout.preferredLayoutSize(this); + return d.height; + } + + public String getName() { return "Flight Status"; } + + AltosFlightStatusUpdate status_update; + javax.swing.Timer timer; + + public void start(AltosFlightStatusUpdate status_update) { + this.status_update = status_update; + timer = new javax.swing.Timer(100, status_update); + timer.start(); + } + + public void stop() { + if (timer != null) { + timer.stop(); + timer = null; + } + } + + public AltosFlightStatus() { + layout = new GridBagLayout(); + + setLayout(layout); + + call = new Call(layout, 0); + serial = new Serial(layout, 1); + flight = new Flight(layout, 2); + flight_state = new FlightState(layout, 3); + rssi = new RSSI(layout, 4); + last_packet = new LastPacket(layout, 5); + } +} diff --git a/teststand/AltosFlightStatusTableModel.java b/teststand/AltosFlightStatusTableModel.java new file mode 100644 index 00000000..63008adf --- /dev/null +++ b/teststand/AltosFlightStatusTableModel.java @@ -0,0 +1,69 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +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 org.altusmetrum.altoslib_12.*; + +public class AltosFlightStatusTableModel extends AbstractTableModel { + private String[] columnNames = { + String.format("Height (%s)", AltosConvert.show_distance_units()), + "State", + "RSSI (dBm)", + String.format("Speed (%s)", AltosConvert.show_speed_unit()) + }; + 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", AltosConvert.distance(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", AltosConvert.speed(speed)), 3); + } +} +*/ diff --git a/teststand/AltosFlightStatusUpdate.java b/teststand/AltosFlightStatusUpdate.java new file mode 100644 index 00000000..aa495c2e --- /dev/null +++ b/teststand/AltosFlightStatusUpdate.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2012 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.event.*; +import org.altusmetrum.altoslib_12.*; + +public class AltosFlightStatusUpdate implements ActionListener { + + public AltosState saved_state; + public AltosListenerState saved_listener_state; + AltosFlightStatus flightStatus; + + public void actionPerformed (ActionEvent e) { + if (saved_state != null) { + if (saved_listener_state == null) + saved_listener_state = new AltosListenerState(); + flightStatus.show(saved_state, saved_listener_state); + } + } + + public AltosFlightStatusUpdate (AltosFlightStatus in_flightStatus) { + flightStatus = in_flightStatus; + } +} + diff --git a/teststand/AltosFlightUI.java b/teststand/AltosFlightUI.java new file mode 100644 index 00000000..fa0814da --- /dev/null +++ b/teststand/AltosFlightUI.java @@ -0,0 +1,333 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay { + AltosVoice voice; + AltosFlightReader reader; + AltosDisplayThread thread; + + LinkedList displays; + + JTabbedPane pane; + + AltosPad pad; + AltosIgnitor igniter; + AltosAscent ascent; + AltosDescent descent; + AltosLanded landed; + AltosCompanionInfo companion; + AltosUIMap sitemap; + boolean has_map; + boolean has_companion; + boolean has_state; + boolean has_igniter; + + private AltosFlightStatus flightStatus; + private AltosInfoTable flightInfo; + + boolean exit_on_close = false; + + JComponent cur_tab = null; + JComponent which_tab(AltosState state) { + if (state.state() < Altos.ao_flight_boost) + return pad; + if (state.state() <= Altos.ao_flight_coast) + return ascent; + if (state.state() <= Altos.ao_flight_main) + return descent; + if (state.state() == AltosLib.ao_flight_stateless) + return descent; + return landed; + } + + void stop_display() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + } + + void disconnect() { + stop_display(); + } + + public void reset() { + for (AltosFlightDisplay d : displays) + d.reset(); + } + + public void font_size_changed(int font_size) { + for (AltosFlightDisplay d : displays) + d.font_size_changed(font_size); + } + + public void units_changed(boolean imperial_units) { + for (AltosFlightDisplay d : displays) + d.units_changed(imperial_units); + } + + AltosFlightStatusUpdate status_update; + + public void show(AltosState state, AltosListenerState listener_state) { + status_update.saved_state = state; + status_update.saved_listener_state = listener_state; + + if (state == null) + state = new AltosState(new AltosCalData()); + + if (state.state() != Altos.ao_flight_startup) { + if (!has_state) { + pane.setTitleAt(0, "Launch Pad"); + pane.add(ascent, 1); + pane.add(descent, 2); + pane.add(landed, 3); + has_state = true; + } + } + + JComponent tab = which_tab(state); + if (tab != cur_tab) { + if (cur_tab == pane.getSelectedComponent()) + pane.setSelectedComponent(tab); + cur_tab = tab; + } + + if (igniter.should_show(state)) { + if (!has_igniter) { + pane.add("Ignitor", igniter); + has_igniter = true; + } + } else { + if (has_igniter) { + pane.remove(igniter); + has_igniter = false; + } + } + + if (state.companion != null) { + if (!has_companion) { + pane.add("Companion", companion); + has_companion= true; + } + } else { + if (has_companion) { + pane.remove(companion); + has_companion = false; + } + } + + if (state.gps != null) { + if (!has_map) { + pane.add("Site Map", sitemap); + has_map = true; + } + } else { + if (has_map) { + pane.remove(sitemap); + has_map = false; + } + } + + for (AltosFlightDisplay d : displays) { + try { + d.show(state, listener_state); + } catch (Exception e) { + System.out.printf("Exception showing %s\n", d.getName()); + e.printStackTrace(); + } + } + } + + public void set_exit_on_close() { + exit_on_close = true; + } + + Container bag; + AltosUIFreqList frequencies; + AltosUIRateList rates; + AltosUITelemetryList telemetries; + JLabel telemetry; + + ActionListener show_timer; + + public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) { + AltosUIPreferences.set_component(this); + + displays = new LinkedList(); + + voice = in_voice; + reader = in_reader; + + bag = getContentPane(); + bag.setLayout(new GridBagLayout()); + + setTitle(String.format("AltOS %s", reader.name)); + + /* Stick channel selector at top of table for telemetry monitoring */ + if (serial >= 0) { + set_inset(3); + + // Frequency menu + frequencies = new AltosUIFreqList(AltosUIPreferences.frequency(serial)); + frequencies.set_product("Monitor"); + frequencies.set_serial(serial); + frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + double frequency = frequencies.frequency(); + try { + reader.set_frequency(frequency); + } catch (TimeoutException te) { + } catch (InterruptedException ie) { + } + reader.save_frequency(); + } + }); + bag.add (frequencies, constraints(0, 1)); + + // Telemetry rate list + rates = new AltosUIRateList(AltosUIPreferences.telemetry_rate(serial)); + rates.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int rate = rates.rate(); + try { + reader.set_telemetry_rate(rate); + } catch (TimeoutException te) { + } catch (InterruptedException ie) { + } + reader.save_telemetry_rate(); + } + }); + rates.setEnabled(reader.supports_telemetry_rate(AltosLib.ao_telemetry_rate_2400)); + bag.add (rates, constraints(1, 1)); + + // Telemetry format list + if (reader.supports_telemetry(Altos.ao_telemetry_standard)) { + telemetries = new AltosUITelemetryList(serial); + telemetries.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int telemetry = telemetries.get_selected(); + reader.set_telemetry(telemetry); + reader.save_telemetry(); + } + }); + bag.add (telemetries, constraints(2, 1)); + } else { + String version; + + if (reader.supports_telemetry(Altos.ao_telemetry_0_9)) + version = "Telemetry: 0.9"; + else if (reader.supports_telemetry(Altos.ao_telemetry_0_8)) + version = "Telemetry: 0.8"; + else + version = "Telemetry: None"; + + telemetry = new JLabel(version); + bag.add (telemetry, constraints(2, 1)); + } + next_row(); + } + set_inset(0); + + /* Flight status is always visible */ + flightStatus = new AltosFlightStatus(); + displays.add(flightStatus); + bag.add(flightStatus, constraints(0, 4, GridBagConstraints.HORIZONTAL)); + next_row(); + + /* The rest of the window uses a tabbed pane to + * show one of the alternate data views + */ + pane = new JTabbedPane(); + + pad = new AltosPad(); + displays.add(pad); + pane.add("Status", pad); + + igniter = new AltosIgnitor(); + displays.add(igniter); + ascent = new AltosAscent(); + displays.add(ascent); + descent = new AltosDescent(); + displays.add(descent); + landed = new AltosLanded(reader); + displays.add(landed); + + flightInfo = new AltosInfoTable(); + displays.add(flightInfo); + pane.add("Table", new JScrollPane(flightInfo)); + + companion = new AltosCompanionInfo(); + displays.add(companion); + has_companion = false; + has_state = false; + + sitemap = new AltosUIMap(); + displays.add(sitemap); + has_map = false; + + /* Make the tabbed pane use the rest of the window space */ + bag.add(pane, constraints(0, 4, GridBagConstraints.BOTH)); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + AltosUIPreferences.register_font_listener(this); + AltosPreferences.register_units_listener(this); + + status_update = new AltosFlightStatusUpdate(flightStatus); + + flightStatus.start(status_update); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + flightStatus.stop(); + disconnect(); + setVisible(false); + dispose(); + AltosUIPreferences.unregister_font_listener(AltosFlightUI.this); + AltosPreferences.unregister_units_listener(AltosFlightUI.this); + if (exit_on_close) + System.exit(0); + } + }); + + pack(); + setVisible(true); + + thread = new AltosDisplayThread(this, voice, this, reader); + + thread.start(); + } + + public AltosFlightUI (AltosVoice in_voice, AltosFlightReader in_reader) { + this(in_voice, in_reader, -1); + } +} diff --git a/teststand/AltosGraphUI.java b/teststand/AltosGraphUI.java new file mode 100644 index 00000000..6cad982c --- /dev/null +++ b/teststand/AltosGraphUI.java @@ -0,0 +1,150 @@ +/* + * Copyright © 2010 Anthony Towns + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.io.*; +import java.util.ArrayList; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.ui.RefineryUtilities; + +public class AltosGraphUI extends AltosUIFrame implements AltosFontListener, AltosUnitsListener, AltosFilterListener +{ + JTabbedPane pane; + AltosGraph graph; + AltosUIEnable enable; + AltosUIMap map; + AltosFlightStats stats; + AltosFlightStatsTable statsTable; + AltosGPS gps; + boolean has_gps; + + void fill_map(AltosFlightSeries flight_series) { + boolean any_gps = false; + AltosGPSTimeValue gtv_last = null; + + if (flight_series.gps_series != null) { + for (AltosGPSTimeValue gtv : flight_series.gps_series) { + AltosGPS gps = gtv.gps; + if (gps != null && + gps.locked && + gps.nsat >= 4) { + if (map == null) + map = new AltosUIMap(); + map.show(gps, (int) flight_series.value_before(AltosFlightSeries.state_name, gtv.time)); + this.gps = gps; + gtv_last = gtv; + has_gps = true; + } + } + } + if (gtv_last != null) { + int state = (int) flight_series.value_after(AltosFlightSeries.state_name, gtv_last.time); + if (state == AltosLib.ao_flight_landed) + map.show(gtv_last.gps, state); + } + } + + public void font_size_changed(int font_size) { + if (map != null) + map.font_size_changed(font_size); + if (statsTable != null) + statsTable.font_size_changed(font_size); + } + + public void units_changed(boolean imperial_units) { + if (map != null) + map.units_changed(imperial_units); + if (enable != null) + enable.units_changed(imperial_units); + } + + AltosUIFlightSeries flight_series; + + public void filter_changed(double speed_filter, double accel_filter) { + flight_series.set_filter(speed_filter, accel_filter); + graph.filter_changed(); + stats = new AltosFlightStats(flight_series); + statsTable.filter_changed(stats); + } + + public double speed_filter() { + return flight_series.speed_filter_width; + } + + public double accel_filter() { + return flight_series.accel_filter_width; + } + + AltosGraphUI(AltosRecordSet set, File file) throws InterruptedException, IOException { + super(file.getName()); + AltosCalData cal_data = set.cal_data(); + + + pane = new JTabbedPane(); + + flight_series = new AltosUIFlightSeries(cal_data); + + enable = new AltosUIEnable(this); + + set.capture_series(flight_series); + + flight_series.finish(); + + stats = new AltosFlightStats(flight_series); + + graph = new AltosGraph(enable, stats, flight_series); + + statsTable = new AltosFlightStatsTable(stats); + + pane.add("Flight Graph", graph.panel); + pane.add("Configure Graph", enable); + pane.add("Flight Statistics", statsTable); + + has_gps = false; + fill_map(flight_series); + if (has_gps) + pane.add("Map", map); + + setContentPane (pane); + + AltosUIPreferences.register_font_listener(this); + AltosPreferences.register_units_listener(this); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + AltosUIPreferences.unregister_font_listener(AltosGraphUI.this); + AltosPreferences.unregister_units_listener(AltosGraphUI.this); + } + }); + pack(); + + setVisible(true); + if (gps != null) + map.centre(gps); + } +} diff --git a/teststand/AltosIdleMonitorUI.java b/teststand/AltosIdleMonitorUI.java new file mode 100644 index 00000000..a6c5fd61 --- /dev/null +++ b/teststand/AltosIdleMonitorUI.java @@ -0,0 +1,306 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.Arrays; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosIdleMonitorUI extends AltosUIFrame implements AltosFlightDisplay, AltosIdleMonitorListener, DocumentListener { + AltosDevice device; + JTabbedPane pane; + AltosPad pad; + AltosInfoTable flightInfo; + AltosFlightStatus flightStatus; + AltosIgnitor igniter; + AltosIdleMonitor thread; + AltosUIMap sitemap; + int serial; + boolean remote; + boolean has_igniter; + boolean has_map; + + void stop_display() { + if (thread != null) { + try { + thread.abort(); + } catch (InterruptedException ie) { + } + } + thread = null; + } + + void disconnect() { + stop_display(); + } + + public void reset() { + pad.reset(); + flightInfo.clear(); + } + + public void font_size_changed(int font_size) { + pad.font_size_changed(font_size); + flightInfo.font_size_changed(font_size); + } + + public void units_changed(boolean imperial_units) { + pad.units_changed(imperial_units); + flightInfo.units_changed(imperial_units); + } + + AltosFlightStatusUpdate status_update; + + public void show(AltosState state, AltosListenerState listener_state) { + status_update.saved_state = state; + if (igniter.should_show(state)) { + if (!has_igniter) { + pane.add("Ignitor", igniter); + has_igniter = true; + } + } else { + if (has_igniter) { + pane.remove(igniter); + has_igniter = false; + } + } + if (state.gps != null && state.gps.connected) { + if (!has_map) { + pane.add("Site Map", sitemap); + has_map = true; + } + } else { + if (has_map) { + pane.remove(sitemap); + has_map = false; + } + } + +// try { + pad.show(state, listener_state); + flightStatus.show(state, listener_state); + flightInfo.show(state, listener_state); + if (has_igniter) + igniter.show(state, listener_state); + if (has_map) + sitemap.show(state, listener_state); +// } catch (Exception e) { +// System.out.print("Show exception " + e); +// } + } + + public void update(final AltosState state, final AltosListenerState listener_state) { + Runnable r = new Runnable() { + public void run() { + show(state, listener_state); + } + }; + SwingUtilities.invokeLater(r); + } + + public void failed() { + Runnable r = new Runnable() { + public void run() { + close(); + } + }; + SwingUtilities.invokeLater(r); + } + + public void error(final String reason) { + Runnable r = new Runnable() { + public void run() { + disconnect(); + JOptionPane.showMessageDialog(AltosIdleMonitorUI.this, + reason, + "Error fetching data", + JOptionPane.ERROR_MESSAGE); + } + }; + SwingUtilities.invokeLater(r); + } + + Container bag; + AltosUIFreqList frequencies; + JTextField callsign_value; + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + if (callsign_value != null) { + String callsign = callsign_value.getText(); + System.out.printf("callsign set to %s\n", callsign); + thread.set_callsign(callsign); + AltosUIPreferences.set_callsign(callsign); + } + } + + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + void idle_exception(JFrame owner, Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + } + + private void close() { + try { + disconnect(); + } catch (Exception ex) { + System.out.printf("Exception %s\n", ex.toString()); + for (StackTraceElement el : ex.getStackTrace()) + System.out.printf("%s\n", el.toString()); + } + setVisible(false); + dispose(); + AltosUIPreferences.unregister_font_listener(AltosIdleMonitorUI.this); + } + + public AltosIdleMonitorUI(JFrame in_owner) + throws FileNotFoundException, TimeoutException, InterruptedException { + + device = AltosDeviceUIDialog.show(in_owner, Altos.product_any); + remote = false; + if (!device.matchProduct(Altos.product_altimeter)) + remote = true; + + serial = device.getSerial(); + + AltosSerial link; + try { + link = new AltosSerial(device); + } catch (Exception ex) { + idle_exception(in_owner, ex); + return; + } + link.set_frame(this); + + /* We let the user set the freq/callsign, so don't bother with the cancel dialog */ + link.set_cancel_enable(false); + + bag = getContentPane(); + bag.setLayout(new GridBagLayout()); + + setTitle(String.format("AltOS %s", device.toShortString())); + + /* Stick frequency selector at top of table for telemetry monitoring */ + if (remote && serial >= 0) { + set_inset(3); + + // Frequency menu + frequencies = new AltosUIFreqList(AltosUIPreferences.frequency(serial)); + frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + double frequency = frequencies.frequency(); + thread.set_frequency(frequency); + AltosUIPreferences.set_frequency(device.getSerial(), + frequency); + } + }); + bag.add (frequencies, constraints(0, 1)); + bag.add (new JLabel("Callsign:"), constraints(1, 1)); + /* Add callsign configuration */ + callsign_value = new JTextField(AltosUIPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + callsign_value.setToolTipText("Callsign sent in packet mode"); + bag.add(callsign_value, constraints(2, 1, GridBagConstraints.HORIZONTAL)); + next_row(); + } + + set_inset(0); + + /* Flight status is always visible */ + flightStatus = new AltosFlightStatus(); + bag.add(flightStatus, constraints(0, 4, GridBagConstraints.HORIZONTAL)); + + next_row(); + + /* The rest of the window uses a tabbed pane to + * show one of the alternate data views + */ + pane = new JTabbedPane(); + + pad = new AltosPad(); + pane.add("Launch Pad", pad); + + flightInfo = new AltosInfoTable(); + pane.add("Table", new JScrollPane(flightInfo)); + + igniter = new AltosIgnitor(); + + sitemap = new AltosUIMap(); + + /* Make the tabbed pane use the rest of the window space */ + bag.add(pane, constraints(0, 4, GridBagConstraints.BOTH)); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + AltosUIPreferences.register_font_listener(this); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + close(); + } + }); + + pack(); + setVisible(true); + + thread = new AltosIdleMonitor(this, link, (boolean) remote); + + status_update = new AltosFlightStatusUpdate(flightStatus); + + new javax.swing.Timer(100, status_update).start(); + + thread.start(); + } +} diff --git a/teststand/AltosIgniteUI.java b/teststand/AltosIgniteUI.java new file mode 100644 index 00000000..b78f62bd --- /dev/null +++ b/teststand/AltosIgniteUI.java @@ -0,0 +1,477 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosIgniteUI + extends AltosUIDialog + implements ActionListener +{ + AltosDevice device; + JFrame owner; + JLabel label; + JToggleButton arm; + JButton fire; + javax.swing.Timer timer; + JButton close; + ButtonGroup group; + Boolean opened; + + int npyro; + + final static int timeout = 1 * 1000; + + int time_remaining; + boolean timer_running; + + int poll_remaining; + + LinkedBlockingQueue command_queue; + + class Igniter { + JRadioButton button; + JLabel status_label; + String name; + int status; + + void set_status (int status) { + this.status = status; + status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(status))); + } + + Igniter(AltosIgniteUI ui, String label, String name, int y) { + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + this.name = name; + this.status = AltosIgnite.Unknown; + + c.gridx = 0; + c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + button = new JRadioButton (label); + pane.add(button, c); + button.addActionListener(ui); + button.setActionCommand(name); + group.add(button); + + c.gridx = 1; + c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + status_label = new JLabel("plenty of text"); + pane.add(status_label, c); + + status = AltosIgnite.Unknown; + } + } + + Igniter igniters[]; + + void set_status(String _name, int _status) { + + final String name = _name; + final int status = _status; + Runnable r = new Runnable() { + public void run() { + for (int p = 0; p < igniters.length; p++) + if (name.equals(igniters[p].name)) + igniters[p].set_status(status); + } + }; + SwingUtilities.invokeLater(r); + } + + class IgniteHandler implements Runnable { + AltosIgnite ignite; + JFrame owner; + AltosLink link; + + void send_exception(Exception e) { + final Exception f_e = e; + Runnable r = new Runnable() { + public void run() { + ignite_exception(f_e); + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + try { + ignite = new AltosIgnite(link, + !device.matchProduct(Altos.product_altimeter)); + + } catch (Exception e) { + send_exception(e); + return; + } + + for (;;) { + Runnable r; + + try { + String command = command_queue.take(); + String reply = null; + + if (command.equals("get_status")) { + HashMap status_map = ignite.status(); + + for (int p = 0; p < igniters.length; p++) { + Integer i = status_map.get(igniters[p].name); + if (i != null) + set_status(igniters[p].name, i); + } + reply = "status"; + } else if (command.equals("get_npyro")) { + reply = String.format("npyro %d", ignite.npyro()); + } else if (command.equals("quit")) { + ignite.close(); + break; + } else { + ignite.fire(command); + reply = "fired"; + } + final String f_reply = reply; + r = new Runnable() { + public void run() { + ignite_reply(f_reply); + } + }; + SwingUtilities.invokeLater(r); + } catch (Exception e) { + send_exception(e); + } + } + } + + public IgniteHandler(JFrame in_owner, AltosLink in_link) { + owner = in_owner; + link = in_link; + } + } + + void ignite_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + void ignite_reply(String reply) { + if (reply.equals("status")) { + set_ignite_status(); + } else if (reply.equals("fired")) { + fired(); + } else if (reply.startsWith("npyro")) { + npyro = Integer.parseInt(reply.substring(6)); + if (npyro == AltosLib.MISSING) + npyro = 0; + make_ui(); + } + } + + void set_arm_text() { + if (arm.isSelected()) + arm.setText(String.format("%d", time_remaining)); + else + arm.setText("Arm"); + } + + void start_timer() { + time_remaining = 10; + set_arm_text(); + timer_running = true; + } + + void stop_timer() { + time_remaining = 0; + fire.setEnabled(false); + timer_running = false; + arm.setSelected(false); + arm.setEnabled(false); + set_arm_text(); + } + + void cancel () { + group.clearSelection(); + fire.setEnabled(false); + stop_timer(); + } + + void send_command(String command) { + try { + command_queue.put(command); + } catch (Exception ex) { + ignite_exception(ex); + } + } + + boolean getting_status = false; + + void set_ignite_status() { + getting_status = false; + poll_remaining = 2; + if (!isVisible()) + setVisible(true); + } + + void poll_ignite_status() { + if (poll_remaining > 0) { + --poll_remaining; + return; + } + if (!getting_status) { + getting_status = true; + send_command("get_status"); + } + } + + boolean firing = false; + + void start_fire(String which) { + if (!firing) { + firing = true; + send_command(which); + } + } + + void fired() { + firing = false; + cancel(); + } + + void close() { + if (opened) { + send_command("quit"); + } + if (timer != null) + timer.stop(); + setVisible(false); + dispose(); + } + + void tick_timer() { + if (timer_running) { + --time_remaining; + if (time_remaining <= 0) + cancel(); + else + set_arm_text(); + } + poll_ignite_status(); + } + + void fire() { + if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) { + String igniter = "none"; + + for (int p = 0; p < igniters.length; p++) + if (igniters[p].button.isSelected()) { + igniter = igniters[p].name; + break; + } + send_command(igniter); + cancel(); + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + for (int p = 0; p < igniters.length; p++) + if (cmd.equals(igniters[p].name)) { + stop_timer(); + arm.setEnabled(true); + break; + } + + if (cmd.equals("arm")) { + if (arm.isSelected()) { + fire.setEnabled(true); + start_timer(); + } else + cancel(); + } + if (cmd.equals("fire")) + fire(); + if (cmd.equals("tick")) + tick_timer(); + if (cmd.equals("close")) + close(); + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosIgniteUI ui; + + public ConfigListener(AltosIgniteUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + command_queue = new LinkedBlockingQueue(); + + opened = false; + device = AltosDeviceUIDialog.show(owner, Altos.product_any); + if (device != null) { + try { + AltosSerial serial = new AltosSerial(device); + serial.set_frame(owner); + IgniteHandler handler = new IgniteHandler(owner, serial); + Thread t = new Thread(handler); + t.start(); + opened = true; + return true; + } catch (Exception ex) { + ignite_exception(ex); + } + } + return false; + } + + private void make_ui() { + group = new ButtonGroup(); + + Container pane = getContentPane(); + + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + timer = new javax.swing.Timer(timeout, this); + timer.setActionCommand("tick"); + timer_running = false; + timer.restart(); + + pane.setLayout(new GridBagLayout()); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + int y = 0; + + c.gridx = 0; + c.gridy = y; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + label = new JLabel ("Fire Igniter"); + pane.add(label, c); + + y++; + + igniters = new Igniter[2 + npyro]; + + igniters[0] = new Igniter(this, "Apogee", AltosIgnite.Apogee, y++); + igniters[1] = new Igniter(this, "Main", AltosIgnite.Main, y++); + + for (int p = 0; p < npyro; p++) { + String name = String.format("%d", p); + String label = String.format("%c", 'A' + p); + igniters[2+p] = new Igniter(this, label, name, y++); + } + + c.gridx = 0; + c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + arm = new JToggleButton ("Arm"); + pane.add(arm, c); + arm.addActionListener(this); + arm.setActionCommand("arm"); + arm.setEnabled(false); + + c.gridx = 1; + c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + fire = new JButton ("Fire"); + fire.setEnabled(false); + pane.add(fire, c); + fire.addActionListener(this); + fire.setActionCommand("fire"); + + y++; + + c.gridx = 0; + c.gridy = y; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + close = new JButton ("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("close"); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + } + + public AltosIgniteUI(JFrame in_owner) { + + owner = in_owner; + + if (!open()) + return; + + send_command("get_npyro"); + } +} diff --git a/teststand/AltosIgnitor.java b/teststand/AltosIgnitor.java new file mode 100644 index 00000000..c7ba00d0 --- /dev/null +++ b/teststand/AltosIgnitor.java @@ -0,0 +1,92 @@ +/* + * Copyright © 2014 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosIgnitor extends AltosUIFlightTab { + + public class Ignitor extends AltosUIUnitsIndicator { + int igniter; + + public double value(AltosState state, int i) { + if (state.igniter_voltage == null || + state.igniter_voltage.length < igniter) + return AltosLib.MISSING; + return state.igniter_voltage[igniter]; + } + + public double good() { return AltosLib.ao_igniter_good; } + + public Ignitor (AltosUIFlightTab container, int y) { + super(container, y, AltosConvert.voltage, String.format ("%s Voltage", AltosLib.igniter_name(y)), 1, true, 1); + igniter = y; + } + } + + Ignitor[] igniters; + + public void show(AltosState state, AltosListenerState listener_state) { + if (isShowing()) + make_igniters(state); + super.show(state, listener_state); + } + + public boolean should_show(AltosState state) { + if (state == null) + return false; + if (state.igniter_voltage == null) + return false; + return state.igniter_voltage.length > 0; + } + + void make_igniters(AltosState state) { + int n = (state == null || state.igniter_voltage == null) ? 0 : state.igniter_voltage.length; + int old_n = igniters == null ? 0 : igniters.length; + + if (n != old_n) { + + if (igniters != null) { + for (int i = 0; i < igniters.length; i++) { + remove(igniters[i]); + igniters[i].remove(this); + igniters = null; + } + } + + if (n > 0) { + setVisible(true); + igniters = new Ignitor[n]; + for (int i = 0; i < n; i++) { + igniters[i] = new Ignitor(this, i); + add(igniters[i]); + } + } else + setVisible(false); + } + } + + public String getName() { + return "Ignitors"; + } +} diff --git a/teststand/AltosLanded.java b/teststand/AltosLanded.java new file mode 100644 index 00000000..c18c0e7b --- /dev/null +++ b/teststand/AltosLanded.java @@ -0,0 +1,193 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosLanded extends AltosUIFlightTab implements ActionListener { + + class Bearing extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + if (state.from_pad != null && state.from_pad.bearing != AltosLib.MISSING) { + show( String.format("%3.0f°", state.from_pad.bearing), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_LONG)); + } else { + show("Missing", "Missing"); + } + } + public Bearing (Container container, int y) { + super (container, y, "Bearing", 2); + } + } + + class Distance extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { + if (state.from_pad != null) + return state.from_pad.distance; + else + return AltosLib.MISSING; + } + + public Distance(Container container, int y) { + super(container, y, AltosConvert.distance, "Ground Distance", 2); + } + } + + class Lat extends AltosUIUnitsIndicator { + + public boolean hide (AltosState state, int i) { return state.gps == null || !state.gps.connected; } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lat; + } + + public Lat (Container container, int y) { + super (container, y, AltosConvert.latitude, "Latitude", 2); + } + } + + class Lon extends AltosUIUnitsIndicator { + public boolean hide (AltosState state, int i) { return state.gps == null || !state.gps.connected; } + + public double value(AltosState state, int i) { + if (state.gps == null) + return AltosLib.MISSING; + if (!state.gps.connected) + return AltosLib.MISSING; + return state.gps.lon; + } + + public Lon (Container container, int y) { + super (container, y, AltosConvert.longitude, "Longitude", 2); + } + } + + class MaxHeight extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { return state.max_height(); } + + public MaxHeight (Container container, int y) { + super (container, y, AltosConvert.height, "Maximum Height", 2); + } + } + + class MaxSpeed extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { return state.max_speed(); } + + public MaxSpeed (Container container, int y) { + super (container, y, AltosConvert.speed, "Maximum Speed", 2); + } + } + + class MaxAccel extends AltosUIUnitsIndicator { + public double value(AltosState state, int i) { return state.max_acceleration(); } + + public MaxAccel (Container container, int y) { + super (container, y, AltosConvert.speed, "Maximum acceleration", 2); + } + } + + JButton graph; + AltosFlightReader reader; + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("graph")) { + File file = reader.backing_file(); + if (file != null) { + String filename = file.getName(); + try { + AltosRecordSet record_set = null; + FileInputStream in = new FileInputStream(file); + if (filename.endsWith("eeprom")) { + record_set = new AltosEepromRecordSet(in); + } else if (filename.endsWith("telem")) { + record_set = new AltosTelemetryFile(in); + } else { + throw new FileNotFoundException(filename); + } + try { + new AltosGraphUI(record_set, file); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } catch (FileNotFoundException fe) { + JOptionPane.showMessageDialog(null, + fe.getMessage(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ie) { + JOptionPane.showMessageDialog(null, + ie.getMessage(), + "Error reading file file", + JOptionPane.ERROR_MESSAGE); + } + } + } + } + + public String getName() { + return "Landed"; + } + + public void show(AltosState state, AltosListenerState listener_state) { + super.show(state, listener_state); + if (reader.backing_file() != null) + graph.setEnabled(true); + } + + public AltosLanded(AltosFlightReader in_reader) { + reader = in_reader; + + /* Elements in descent display */ + add(new Bearing(this, 0)); + add(new Distance(this, 1)); + add(new Lat(this, 2)); + add(new Lon(this, 3)); + add(new MaxHeight(this, 4)); + add(new MaxSpeed(this, 5)); + add(new MaxAccel(this, 6)); + + graph = new JButton ("Graph Flight"); + graph.setActionCommand("graph"); + graph.addActionListener(this); + graph.setEnabled(false); + + GridBagConstraints c = new GridBagConstraints(); + + c.gridx = 1; c.gridy = 7; + c.insets = new Insets(10, 10, 10, 10); + c.anchor = GridBagConstraints.WEST; + c.weightx = 0; + c.weighty = 0; + c.fill = GridBagConstraints.VERTICAL; + add(graph, c); + addHierarchyListener(this); + } +} diff --git a/teststand/AltosLaunch.java b/teststand/AltosLaunch.java new file mode 100644 index 00000000..c77086d7 --- /dev/null +++ b/teststand/AltosLaunch.java @@ -0,0 +1,198 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.io.*; +import java.util.concurrent.*; +import java.awt.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosLaunch { + AltosDevice device; + AltosSerial serial; + boolean serial_started; + int launcher_serial; + int launcher_channel; + int rssi; + + final static int Unknown = -1; + final static int Good = 0; + final static int Bad = 1; + + int armed; + int igniter; + + private void start_serial() throws InterruptedException { + serial_started = true; + } + + private void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (serial == null) + return; + } + + class string_ref { + String value; + + public String get() { + return value; + } + public void set(String i) { + value = i; + } + public string_ref() { + value = null; + } + } + + private 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; + } + } + + public boolean status() throws InterruptedException, TimeoutException { + boolean ok = false; + if (serial == null) + return false; + string_ref status_name = new string_ref(); + start_serial(); + serial.printf("l %d %d\n", launcher_serial, launcher_channel); + for (;;) { + String line = serial.get_reply(20000); + if (line == null) + throw new TimeoutException(); + if (get_string(line, "Rssi: ", status_name)) { + try { + rssi = (int) Altos.fromdec(status_name.get()); + } catch (NumberFormatException ne) { + } + break; + } else if (get_string(line, "Armed: ", status_name)) { + armed = Good; + String status = status_name.get(); + if (status.startsWith("igniter good")) + igniter = Good; + else if (status.startsWith("igniter bad")) + igniter = Bad; + else + igniter = Unknown; + ok = true; + } else if (get_string(line, "Disarmed: ", status_name)) { + armed = Bad; + if (status_name.get().startsWith("igniter good")) + igniter = Good; + else if (status_name.get().startsWith("igniter bad")) + igniter = Bad; + else + igniter = Unknown; + ok = true; + } else if (get_string(line, "Error ", status_name)) { + armed = Unknown; + igniter = Unknown; + ok = false; + break; + } + } + stop_serial(); + if (!ok) { + armed = Unknown; + igniter = Unknown; + } + return ok; + } + + public static String status_string(int status) { + switch (status) { + case Good: + return "good"; + case Bad: + return "open"; + } + return "unknown"; + } + + public void arm() { + if (serial == null) + return; + try { + start_serial(); + serial.printf("a %d %d\n", launcher_serial, launcher_channel); + serial.flush_output(); + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void fire() { + if (serial == null) + return; + try { + start_serial(); + serial.printf("i %d %d\n", launcher_serial, launcher_channel); + serial.flush_output(); + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void close() { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + serial.close(); + serial = null; + } + + public void set_frame(Frame frame) { + serial.set_frame(frame); + } + + public void set_remote(int in_serial, int in_channel) { + launcher_serial = in_serial; + launcher_channel = in_channel; + } + + public AltosLaunch(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + + device = in_device; + serial = new AltosSerial(device); + } +} diff --git a/teststand/AltosLaunchUI.java b/teststand/AltosLaunchUI.java new file mode 100644 index 00000000..d0383409 --- /dev/null +++ b/teststand/AltosLaunchUI.java @@ -0,0 +1,513 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.text.*; +import java.util.concurrent.*; +import org.altusmetrum.altosuilib_12.*; + +class FireButton extends JButton { + protected void processMouseEvent(MouseEvent e) { + super.processMouseEvent(e); + switch (e.getID()) { + case MouseEvent.MOUSE_PRESSED: + if (actionListener != null) + actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_down")); + break; + case MouseEvent.MOUSE_RELEASED: + if (actionListener != null) + actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_up")); + break; + } + } + + public FireButton(String s) { + super(s); + } +} + +public class AltosLaunchUI + extends AltosUIDialog + implements ActionListener +{ + AltosDevice device; + JFrame owner; + JLabel label; + + int radio_channel; + JLabel radio_channel_label; + JTextField radio_channel_text; + + int launcher_serial; + JLabel launcher_serial_label; + JTextField launcher_serial_text; + + int launcher_channel; + JLabel launcher_channel_label; + JTextField launcher_channel_text; + + JLabel armed_label; + JLabel armed_status_label; + JLabel igniter; + JLabel igniter_status_label; + JToggleButton arm; + FireButton fire; + javax.swing.Timer arm_timer; + javax.swing.Timer fire_timer; + + boolean firing; + boolean armed; + int armed_status; + int igniter_status; + int rssi; + + final static int arm_timeout = 1 * 1000; + final static int fire_timeout = 250; + + int armed_count; + + LinkedBlockingQueue command_queue; + + class LaunchHandler implements Runnable { + AltosLaunch launch; + JFrame owner; + + void send_exception(Exception e) { + final Exception f_e = e; + Runnable r = new Runnable() { + public void run() { + launch_exception(f_e); + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + try { + launch = new AltosLaunch(device); + } catch (Exception e) { + send_exception(e); + return; + } + launch.set_frame(owner); + launch.set_remote(launcher_serial, launcher_channel); + + for (;;) { + Runnable r; + + try { + String command = command_queue.take(); + String reply = null; + + if (command.equals("get_status")) { + launch.status(); + reply = "status"; + armed_status = launch.armed; + igniter_status = launch.igniter; + rssi = launch.rssi; + } else if (command.equals("set_remote")) { + launch.set_remote(launcher_serial, launcher_channel); + reply = "remote set"; + } else if (command.equals("arm")) { + launch.arm(); + reply = "armed"; + } else if (command.equals("fire")) { + launch.fire(); + reply = "fired"; + } else if (command.equals("quit")) { + launch.close(); + break; + } else { + throw new ParseException(String.format("invalid command %s", command), 0); + } + final String f_reply = reply; + r = new Runnable() { + public void run() { + launch_reply(f_reply); + } + }; + SwingUtilities.invokeLater(r); + } catch (Exception e) { + send_exception(e); + } + } + } + + public LaunchHandler(JFrame in_owner) { + owner = in_owner; + } + } + + void launch_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + void launch_reply(String reply) { + if (reply == null) + return; + if (reply.equals("remote set")) + poll_launch_status(); + if (reply.equals("status")) { + set_launch_status(); + } + } + + void set_arm_text() { + if (arm.isSelected()) + arm.setText(String.format("%d", armed_count)); + else + arm.setText("Arm"); + } + + void start_arm_timer() { + armed_count = 30; + set_arm_text(); + } + + void stop_arm_timer() { + armed_count = 0; + armed = false; + arm.setSelected(false); + fire.setEnabled(false); + set_arm_text(); + } + + void cancel () { + fire.setEnabled(false); + firing = false; + stop_arm_timer(); + } + + void send_command(String command) { + try { + command_queue.put(command); + } catch (Exception ex) { + launch_exception(ex); + } + } + + boolean getting_status = false; + + void set_launch_status() { + getting_status = false; + armed_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(armed_status))); + igniter_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(igniter_status))); + } + + void poll_launch_status() { + if (!getting_status && !firing && !armed) { + getting_status = true; + send_command("get_status"); + } + } + + void fired() { + firing = false; + cancel(); + } + + void close() { + send_command("quit"); + arm_timer.stop(); + setVisible(false); + dispose(); + } + + void tick_arm_timer() { + if (armed_count > 0) { + --armed_count; + if (armed_count <= 0) { + armed_count = 0; + cancel(); + } else { + if (!firing) { + send_command("arm"); + set_arm_text(); + } + } + } + poll_launch_status(); + } + + void arm() { + if (arm.isSelected()) { + fire.setEnabled(true); + start_arm_timer(); + if (!firing) + send_command("arm"); + armed = true; + } else + cancel(); + } + + void fire_more() { + if (firing) + send_command("fire"); + } + + void fire_down() { + if (arm.isEnabled() && arm.isSelected() && armed_count > 0) { + firing = true; + fire_more(); + fire_timer.restart(); + } + } + + void fire_up() { + firing = false; + fire_timer.stop(); + } + + void set_radio() { + try { + radio_channel = Integer.parseInt(radio_channel_text.getText()); + } catch (NumberFormatException ne) { + radio_channel_text.setText(String.format("%d", radio_channel)); + } + } + + void set_serial() { + try { + launcher_serial = Integer.parseInt(launcher_serial_text.getText()); + AltosUIPreferences.set_launcher_serial(launcher_serial); + send_command("set_remote"); + } catch (NumberFormatException ne) { + launcher_serial_text.setText(String.format("%d", launcher_serial)); + } + } + + void set_channel() { + try { + launcher_channel = Integer.parseInt(launcher_channel_text.getText()); + AltosUIPreferences.set_launcher_serial(launcher_channel); + send_command("set_remote"); + } catch (NumberFormatException ne) { + launcher_channel_text.setText(String.format("%d", launcher_channel)); + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("armed") || cmd.equals("igniter")) { + stop_arm_timer(); + } + + if (cmd.equals("arm")) + arm(); + if (cmd.equals("tick_arm")) + tick_arm_timer(); + if (cmd.equals("close")) + close(); + if (cmd.equals("fire_down")) + fire_down(); + if (cmd.equals("fire_up")) + fire_up(); + if (cmd.equals("tick_fire")) + fire_more(); + if (cmd.equals("new_serial")) + set_serial(); + if (cmd.equals("new_channel")) + set_channel(); + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosLaunchUI ui; + + public ConfigListener(AltosLaunchUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + command_queue = new LinkedBlockingQueue(); + + device = AltosDeviceUIDialog.show(owner, Altos.product_any); + if (device != null) { + LaunchHandler handler = new LaunchHandler(owner); + Thread t = new Thread(handler); + t.start(); + return true; + } + return false; + } + + public AltosLaunchUI(JFrame in_owner) { + + launcher_channel = AltosUIPreferences.launcher_channel(); + launcher_serial = AltosUIPreferences.launcher_serial(); + owner = in_owner; + armed_status = AltosLaunch.Unknown; + igniter_status = AltosLaunch.Unknown; + + if (!open()) + return; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + arm_timer = new javax.swing.Timer(arm_timeout, this); + arm_timer.setActionCommand("tick_arm"); + arm_timer.restart(); + + fire_timer = new javax.swing.Timer(fire_timeout, this); + fire_timer.setActionCommand("tick_fire"); + + owner = in_owner; + + pane.setLayout(new GridBagLayout()); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + label = new JLabel ("Launch Controller"); + pane.add(label, c); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_serial_label = new JLabel("Launcher Serial"); + pane.add(launcher_serial_label, c); + + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_serial_text = new JTextField(7); + launcher_serial_text.setText(String.format("%d", launcher_serial)); + launcher_serial_text.setActionCommand("new_serial"); + launcher_serial_text.addActionListener(this); + pane.add(launcher_serial_text, c); + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_channel_label = new JLabel("Launcher Channel"); + pane.add(launcher_channel_label, c); + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_channel_text = new JTextField(7); + launcher_channel_text.setText(String.format("%d", launcher_channel)); + launcher_channel_text.setActionCommand("new_channel"); + launcher_channel_text.addActionListener(this); + pane.add(launcher_channel_text, c); + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + armed_label = new JLabel ("Armed"); + pane.add(armed_label, c); + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + armed_status_label = new JLabel(); + pane.add(armed_status_label, c); + + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + igniter = new JLabel ("Igniter"); + pane.add(igniter, c); + + c.gridx = 1; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + igniter_status_label = new JLabel(); + pane.add(igniter_status_label, c); + + c.gridx = 0; + c.gridy = 5; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + arm = new JToggleButton ("Arm"); + pane.add(arm, c); + arm.addActionListener(this); + arm.setActionCommand("arm"); + arm.setEnabled(true); + + c.gridx = 1; + c.gridy = 5; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + fire = new FireButton ("Fire"); + fire.setEnabled(false); + pane.add(fire, c); + fire.addActionListener(this); + fire.setActionCommand("fire"); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + + setVisible(true); + } +} diff --git a/teststand/AltosLib.jar b/teststand/AltosLib.jar new file mode 120000 index 00000000..f0a4676f --- /dev/null +++ b/teststand/AltosLib.jar @@ -0,0 +1 @@ +../altoslib/AltosLib.jar \ No newline at end of file diff --git a/teststand/AltosPad.java b/teststand/AltosPad.java new file mode 100644 index 00000000..cf27ad80 --- /dev/null +++ b/teststand/AltosPad.java @@ -0,0 +1,253 @@ +/* + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.util.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class AltosPad extends AltosUIFlightTab { + + class Battery extends AltosUIVoltageIndicator { + public double voltage(AltosState state) { return state.battery_voltage; } + public double good() { return AltosLib.ao_battery_good; } + public Battery (AltosUIFlightTab container, int y) { super(container, y, "Battery Voltage", 2); } + } + + class Apogee extends AltosUIVoltageIndicator { + public boolean hide(double v) { return v == AltosLib.MISSING; } + public double voltage(AltosState state) { return state.apogee_voltage; } + public double good() { return AltosLib.ao_igniter_good; } + public Apogee (AltosUIFlightTab container, int y) { super(container, y, "Apogee Igniter Voltage", 2); } + } + + class Main extends AltosUIVoltageIndicator { + public boolean hide(double v) { return v == AltosLib.MISSING; } + public double voltage(AltosState state) { return state.main_voltage; } + public double good() { return AltosLib.ao_igniter_good; } + public Main (AltosUIFlightTab container, int y) { super(container, y, "Main Igniter Voltage", 2); } + } + + class LoggingReady extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + AltosCalData cal_data = state.cal_data(); + if (state == null || cal_data.flight == AltosLib.MISSING) { + hide(); + } else { + if (cal_data.flight != 0) { + if (state.state() <= Altos.ao_flight_pad) + show("Ready to record"); + else if (state.state() < Altos.ao_flight_landed || + state.state() == AltosLib.ao_flight_stateless) + show("Recording data"); + else + show("Recorded data"); + } else + show("Storage full"); + set_lights(cal_data.flight != 0); + } + } + public LoggingReady (AltosUIFlightTab container, int y) { + super(container, y, "On-board Data Logging", 1, true, 2); + } + } + + class GPSLocked extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) + hide(); + else { + int sol = state.gps.nsat; + int sat = state.gps.cc_gps_sat == null ? 0 : state.gps.cc_gps_sat.length; + show("%d in solution", sol, "%d in view", sat); + set_lights(state.gps.locked && sol >= 4); + } + } + public GPSLocked (AltosUIFlightTab container, int y) { + super (container, y, "GPS Locked", 2, true, 1); + } + } + + class GPSReady extends AltosUIIndicator { + public void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) + hide(); + else { + if (state.gps_ready) + show("Ready"); + else + show("Waiting %d", state.gps_waiting); + set_lights(state.gps_ready); + } + } + public GPSReady (AltosUIFlightTab container, int y) { + super (container, y, "GPS Ready", 1, true, 2); + } + } + + class ReceiverBattery extends AltosUIVoltageIndicator { + + double last_voltage = AltosLib.MISSING; + + public double voltage(AltosState state) { + return last_voltage; + } + + public double good() { return AltosLib.ao_battery_good; } + + public boolean hide(AltosState state, AltosListenerState listener_state, int i) { + return value(state, listener_state, i) == AltosLib.MISSING; + } + + public double value(AltosState state, AltosListenerState listener_state, int i) { + if (listener_state == null) + last_voltage = AltosLib.MISSING; + else + last_voltage = listener_state.battery; + return last_voltage; + } + + public ReceiverBattery (AltosUIFlightTab container, int y) { + super(container, y, "Receiver Battery", 2); + } + } + + boolean report_pad(AltosState state) { + if (state.state() == AltosLib.ao_flight_stateless || + state.state() < AltosLib.ao_flight_pad) + { + return false; + } + return true; + } + + class PadLat extends AltosUIIndicator { + + double last_lat = AltosLib.MISSING - 1; + + public void show (AltosState state, AltosListenerState listener_state) { + double lat = AltosLib.MISSING; + String label = null; + + if (state != null) { + if (report_pad(state)) { + lat = state.pad_lat; + label = "Pad Latitude"; + } else if (state.gps != null) { + lat = state.gps.lat; + label = "Latitude"; + } + } + if (lat != last_lat) { + if (lat != AltosLib.MISSING) { + show(AltosConvert.latitude.show(10, lat)); + set_label(label); + } else + hide(); + last_lat = lat; + } + } + + public void reset() { + super.reset(); + last_lat = AltosLib.MISSING - 1; + } + + public PadLat (AltosUIFlightTab container, int y) { + super (container, y, "Pad Latitude", 1, false, 2); + } + } + + class PadLon extends AltosUIIndicator { + + double last_lon = AltosLib.MISSING - 1; + + public void show (AltosState state, AltosListenerState listener_state) { + double lon = AltosLib.MISSING; + String label = null; + + if (state != null) { + if (report_pad(state)) { + lon = state.pad_lon; + label = "Pad Longitude"; + } else if (state.gps != null) { + lon = state.gps.lon; + label = "Longitude"; + } + } + if (lon != last_lon) { + if (lon != AltosLib.MISSING) { + show(AltosConvert.longitude.show(10, lon)); + set_label(label); + } else + hide(); + last_lon = lon; + } + } + + public void reset() { + super.reset(); + last_lon = AltosLib.MISSING - 1; + } + + public PadLon (AltosUIFlightTab container, int y) { + super (container, y, "Pad Longitude", 1, false, 2); + } + } + + class PadAlt extends AltosUIUnitsIndicator { + + public double value(AltosState state, int i) { + if (report_pad(state)) + return state.pad_alt; + else if (state.gps != null) + return state.gps.alt; + else + return state.altitude(); + } + + public void show (AltosState state, AltosListenerState listener_state) { + String label = "Altitude"; + + if (state != null && report_pad(state)) + label = "Pad Altitude"; + set_label(label); + super.show(state, listener_state); + } + + public PadAlt (AltosUIFlightTab container, int y) { + super (container, y, AltosConvert.height, "Pad Altitude", 1, false, 2); + } + } + public String getName() { return "Pad"; } + + public AltosPad() { + int y = 0; + add(new Battery(this, y++)); + add(new ReceiverBattery(this, y++)); + add(new Apogee(this, y++)); + add(new Main(this, y++)); + add(new LoggingReady(this, y++)); + add(new GPSLocked(this, y++)); + add(new GPSReady(this, y++)); + add(new PadLat(this, y++)); + add(new PadLon(this, y++)); + add(new PadAlt(this, y++)); + } +} diff --git a/teststand/AltosVersion.java b/teststand/AltosVersion.java new file mode 100644 index 00000000..4ff16301 --- /dev/null +++ b/teststand/AltosVersion.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2011 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 teststand; + +public class AltosVersion { + public final static String version = "1.1.9.2"; +} diff --git a/teststand/Info.plist.in b/teststand/Info.plist.in new file mode 100644 index 00000000..8dc797d6 --- /dev/null +++ b/teststand/Info.plist.in @@ -0,0 +1,73 @@ + + + + + CFBundleName + AltosUI + CFBundleVersion + @VERSION@ + CFBundleAllowMixedLocalizations + true + CFBundleExecutable + JavaApplicationStub + CFBundleDevelopmentRegion + English + CFBundlePackageType + APPL + CFBundleIdentifier + org.altusmetrum.altosui + CFBundleSignature + Altu + CFBundleGetInfoString + AltOS UI version @VERSION@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleIconFile + altusmetrum-altosui.icns + CFBundleDocumentTypes + + + CFBundleTypeName + Telemetry + CFBundleTypeIconFile + application-vnd.altusmetrum.telemetry.icns + CFBundleTypeExtensions + + telem + + CFBundleTypeRole + Editor + + + CFBundleTypeName + Eeprom + CFBundleTypeIconFile + application-vnd.altusmetrum.eeprom.icns + CFBundleTypeExtensions + + eeprom + + CFBundleTypeRole + Editor + + + Java + + MainClass + altosui.AltosUI + JVMVersion + 1.5+ + ClassPath + + $JAVAROOT/altosui.jar + $JAVAROOT/freetts.jar + + VMOptions + + -Xms512M + -Xmx512M + -Dosgi.clean=true + + + + diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/Example.nsi b/teststand/Instdrv/NSIS/Contrib/InstDrv/Example.nsi new file mode 100644 index 00000000..3ed821eb --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/Example.nsi @@ -0,0 +1,84 @@ +# +# InstDrv Example, (c) 2003 Jan Kiszka (Jan Kiszka@web.de) +# + +Name "InstDrv.dll test" + +OutFile "InstDrv-Test.exe" + +ShowInstDetails show + +ComponentText "InstDrv Plugin Usage Example" + +Page components +Page instfiles + +Section "Install a Driver" InstDriver + InstDrv::InitDriverSetup /NOUNLOAD "{4d36e978-e325-11ce-bfc1-08002be10318}" "IrCOMM2k" + Pop $0 + DetailPrint "InitDriverSetup: $0" + + InstDrv::DeleteOemInfFiles /NOUNLOAD + Pop $0 + DetailPrint "DeleteOemInfFiles: $0" + StrCmp $0 "00000000" PrintInfNames ContInst1 + + PrintInfNames: + Pop $0 + DetailPrint "Deleted $0" + Pop $0 + DetailPrint "Deleted $0" + + ContInst1: + InstDrv::CreateDevice /NOUNLOAD + Pop $0 + DetailPrint "CreateDevice: $0" + + SetOutPath $TEMP + File "ircomm2k.inf" + File "ircomm2k.sys" + + InstDrv::InstallDriver /NOUNLOAD "$TEMP\ircomm2k.inf" + Pop $0 + DetailPrint "InstallDriver: $0" + StrCmp $0 "00000000" PrintReboot ContInst2 + + PrintReboot: + Pop $0 + DetailPrint "Reboot: $0" + + ContInst2: + InstDrv::CountDevices + Pop $0 + DetailPrint "CountDevices: $0" +SectionEnd + +Section "Uninstall the driver again" UninstDriver + InstDrv::InitDriverSetup /NOUNLOAD "{4d36e978-e325-11ce-bfc1-08002be10318}" "IrCOMM2k" + Pop $0 + DetailPrint "InitDriverSetup: $0" + + InstDrv::DeleteOemInfFiles /NOUNLOAD + Pop $0 + DetailPrint "DeleteOemInfFiles: $0" + StrCmp $0 "00000000" PrintInfNames ContUninst1 + + PrintInfNames: + Pop $0 + DetailPrint "Deleted $0" + Pop $0 + DetailPrint "Deleted $0" + + ContUninst1: + InstDrv::RemoveAllDevices + Pop $0 + DetailPrint "RemoveAllDevices: $0" + StrCmp $0 "00000000" PrintReboot ContUninst2 + + PrintReboot: + Pop $0 + DetailPrint "Reboot: $0" + + ContUninst2: + Delete "$SYSDIR\system32\ircomm2k.sys" +SectionEnd \ No newline at end of file diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe new file mode 100644 index 0000000000000000000000000000000000000000..615bae154c2ef39071a7c8fd9b6b7fbcb04b52cf GIT binary patch literal 51831 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

%PQw`G&P#NW(5t>@pu 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 literal 0 HcmV?d00001 diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c new file mode 100644 index 00000000..efe866e9 --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c @@ -0,0 +1,704 @@ +/* + +InstDrv.dll - Installs or Removes Device Drivers + +Copyright © 2003 Jan Kiszka (Jan.Kiszka@web.de) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; + you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the + product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, + and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any distribution. + +*/ + + +#include +#include +#include +#include "../exdll/exdll.h" + + +char paramBuf[1024]; +GUID devClass; +char hwIdBuf[1024]; +int initialized = 0; + + + +void* memset(void* dst, int val, unsigned int len) +{ + while (len-- > 0) + *((char *)dst)++ = val; + + return NULL; +} + + + +void* memcpy(void* dst, const void* src, unsigned int len) +{ + while (len-- > 0) + *((char *)dst)++ = *((char *)src)++; + + return NULL; +} + + + +int HexCharToInt(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + else if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + else + return -1; +} + + + +BOOLEAN HexStringToUInt(char* str, int width, void* valBuf) +{ + int i, val; + + + for (i = width - 4; i >= 0; i -= 4) + { + val = HexCharToInt(*str++); + if (val < 0) + return FALSE; + *(unsigned int *)valBuf += val << i; + } + + return TRUE; +} + + + +BOOLEAN StringToGUID(char* guidStr, GUID* pGuid) +{ + int i; + + + memset(pGuid, 0, sizeof(GUID)); + + if (*guidStr++ != '{') + return FALSE; + + if (!HexStringToUInt(guidStr, 32, &pGuid->Data1)) + return FALSE; + guidStr += 8; + + if (*guidStr++ != '-') + return FALSE; + + if (!HexStringToUInt(guidStr, 16, &pGuid->Data2)) + return FALSE; + guidStr += 4; + + if (*guidStr++ != '-') + return FALSE; + + if (!HexStringToUInt(guidStr, 16, &pGuid->Data3)) + return FALSE; + guidStr += 4; + + if (*guidStr++ != '-') + return FALSE; + + for (i = 0; i < 2; i++) + { + if (!HexStringToUInt(guidStr, 8, &pGuid->Data4[i])) + return FALSE; + guidStr += 2; + } + + if (*guidStr++ != '-') + return FALSE; + + for (i = 2; i < 8; i++) + { + if (!HexStringToUInt(guidStr, 8, &pGuid->Data4[i])) + return FALSE; + guidStr += 2; + } + + if (*guidStr++ != '}') + return FALSE; + + return TRUE; +} + + + +DWORD FindNextDevice(HDEVINFO devInfoSet, SP_DEVINFO_DATA* pDevInfoData, DWORD* pIndex) +{ + DWORD buffersize = 0; + LPTSTR buffer = NULL; + DWORD dataType; + DWORD result; + + + while (1) + { + if (!SetupDiEnumDeviceInfo(devInfoSet, (*pIndex)++, pDevInfoData)) + { + result = GetLastError(); + break; + } + + GetDeviceRegistryProperty: + if (!SetupDiGetDeviceRegistryProperty(devInfoSet, pDevInfoData, SPDRP_HARDWAREID, + &dataType, (PBYTE)buffer, buffersize, + &buffersize)) + { + result = GetLastError(); + + if (result == ERROR_INSUFFICIENT_BUFFER) + { + if (buffer != NULL) + LocalFree(buffer); + + buffer = (LPTSTR)LocalAlloc(LPTR, buffersize); + + if (buffer == NULL) + break; + + goto GetDeviceRegistryProperty; + } + else if (result == ERROR_INVALID_DATA) + continue; // ignore invalid entries + else + break; // break on other errors + } + + if (lstrcmpi(buffer, hwIdBuf) == 0) + { + result = 0; + break; + } + } + + if (buffer != NULL) + LocalFree(buffer); + + return result; +} + + + +DWORD FindFirstDevice(HWND hwndParent, const GUID* pDevClass, const LPTSTR hwId, + HDEVINFO* pDevInfoSet, SP_DEVINFO_DATA* pDevInfoData, + DWORD *pIndex, DWORD flags) +{ + DWORD result; + + + *pDevInfoSet = SetupDiGetClassDevs((GUID*)pDevClass, NULL, hwndParent, flags); + if (*pDevInfoSet == INVALID_HANDLE_VALUE) + return GetLastError(); + + pDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA); + *pIndex = 0; + + result = FindNextDevice(*pDevInfoSet, pDevInfoData, pIndex); + + if (result != 0) + SetupDiDestroyDeviceInfoList(*pDevInfoSet); + + return result; +} + + + +/* + * InstDrv::InitDriverSetup devClass drvHWID + * + * devClass - GUID of the driver's device setup class + * drvHWID - Hardware ID of the supported device + * + * Return: + * result - error message, empty on success + */ +void __declspec(dllexport) InitDriverSetup(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + EXDLL_INIT(); + + /* convert class GUID */ + popstring(paramBuf); + + if (!StringToGUID(paramBuf, &devClass)) + { + popstring(paramBuf); + pushstring("Invalid GUID!"); + return; + } + + /* get hardware ID */ + memset(hwIdBuf, 0, sizeof(hwIdBuf)); + popstring(hwIdBuf); + + initialized = 1; + pushstring(""); +} + + + +/* + * InstDrv::CountDevices + * + * Return: + * result - Number of installed devices the driver supports + */ +void __declspec(dllexport) CountDevices(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + HDEVINFO devInfoSet; + SP_DEVINFO_DATA devInfoData; + int count = 0; + char countBuf[16]; + DWORD index; + DWORD result; + + + EXDLL_INIT(); + + if (!initialized) + { + pushstring("Fatal error!"); + return; + } + + result = FindFirstDevice(hwndParent, &devClass, hwIdBuf, &devInfoSet, &devInfoData, + &index, DIGCF_PRESENT); + if (result != 0) + { + pushstring("0"); + return; + } + + do + { + count++; + } while (FindNextDevice(devInfoSet, &devInfoData, &index) == 0); + + SetupDiDestroyDeviceInfoList(devInfoSet); + + wsprintf(countBuf, "%d", count); + pushstring(countBuf); +} + + + +/* + * InstDrv::CreateDevice + * + * Return: + * result - Windows error code + */ +void __declspec(dllexport) CreateDevice(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + HDEVINFO devInfoSet; + SP_DEVINFO_DATA devInfoData; + DWORD result = 0; + char resultBuf[16]; + + + EXDLL_INIT(); + + if (!initialized) + { + pushstring("Fatal error!"); + return; + } + + devInfoSet = SetupDiCreateDeviceInfoList(&devClass, hwndParent); + if (devInfoSet == INVALID_HANDLE_VALUE) + { + wsprintf(resultBuf, "%08X", GetLastError()); + pushstring(resultBuf); + return; + } + + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + if (!SetupDiCreateDeviceInfo(devInfoSet, hwIdBuf, &devClass, NULL, + hwndParent, DICD_GENERATE_ID, &devInfoData)) + { + result = GetLastError(); + goto InstallCleanup; + } + + if (!SetupDiSetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_HARDWAREID, + hwIdBuf, (lstrlen(hwIdBuf)+2)*sizeof(TCHAR))) + { + result = GetLastError(); + goto InstallCleanup; + } + + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, devInfoSet, &devInfoData)) + result = GetLastError(); + + InstallCleanup: + SetupDiDestroyDeviceInfoList(devInfoSet); + + wsprintf(resultBuf, "%08X", result); + pushstring(resultBuf); +} + + + +/* + * InstDrv::InstallDriver infPath + * + * Return: + * result - Windows error code + * reboot - non-zero if reboot is required + */ +void __declspec(dllexport) InstallDriver(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + char resultBuf[16]; + BOOL reboot; + + + EXDLL_INIT(); + popstring(paramBuf); + + if (!initialized) + { + pushstring("Fatal error!"); + return; + } + + if (!UpdateDriverForPlugAndPlayDevices(hwndParent, hwIdBuf, paramBuf, + INSTALLFLAG_FORCE, &reboot)) + { + wsprintf(resultBuf, "%08X", GetLastError()); + pushstring(resultBuf); + } + else + { + wsprintf(resultBuf, "%d", reboot); + pushstring(resultBuf); + pushstring("00000000"); + } +} + + + +/* + * InstDrv::DeleteOemInfFiles + * + * Return: + * result - Windows error code + * oeminf - Path of the deleted devices setup file (oemXX.inf) + * oempnf - Path of the deleted devices setup file (oemXX.pnf) + */ +void __declspec(dllexport) DeleteOemInfFiles(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + HDEVINFO devInfo; + SP_DEVINFO_DATA devInfoData; + SP_DRVINFO_DATA drvInfoData; + SP_DRVINFO_DETAIL_DATA drvInfoDetail; + DWORD index; + DWORD result; + char resultBuf[16]; + + + if (!initialized) + { + pushstring("Fatal error!"); + return; + } + + result = FindFirstDevice(NULL, &devClass, hwIdBuf, &devInfo, &devInfoData, &index, 0); + if (result != 0) + goto Cleanup1; + + if (!SetupDiBuildDriverInfoList(devInfo, &devInfoData, SPDIT_COMPATDRIVER)) + { + result = GetLastError(); + goto Cleanup2; + } + + drvInfoData.cbSize = sizeof(SP_DRVINFO_DATA); + drvInfoDetail.cbSize = sizeof(SP_DRVINFO_DETAIL_DATA); + + if (!SetupDiEnumDriverInfo(devInfo, &devInfoData, SPDIT_COMPATDRIVER, 0, &drvInfoData)) + { + result = GetLastError(); + goto Cleanup3; + } + + if (!SetupDiGetDriverInfoDetail(devInfo, &devInfoData, &drvInfoData, + &drvInfoDetail, sizeof(drvInfoDetail), NULL)) + { + result = GetLastError(); + + if (result != ERROR_INSUFFICIENT_BUFFER) + goto Cleanup3; + + result = 0; + } + + pushstring(drvInfoDetail.InfFileName); + if (!DeleteFile(drvInfoDetail.InfFileName)) + result = GetLastError(); + else + { + index = lstrlen(drvInfoDetail.InfFileName); + if (index > 3) + { + lstrcpy(drvInfoDetail.InfFileName+index-3, "pnf"); + pushstring(drvInfoDetail.InfFileName); + if (!DeleteFile(drvInfoDetail.InfFileName)) + result = GetLastError(); + } + } + + Cleanup3: + SetupDiDestroyDriverInfoList(devInfo, &devInfoData, SPDIT_COMPATDRIVER); + + Cleanup2: + SetupDiDestroyDeviceInfoList(devInfo); + + Cleanup1: + wsprintf(resultBuf, "%08X", result); + pushstring(resultBuf); +} + + + +/* + * InstDrv::RemoveAllDevices + * + * Return: + * result - Windows error code + * reboot - non-zero if reboot is required + */ +void __declspec(dllexport) RemoveAllDevices(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + HDEVINFO devInfo; + SP_DEVINFO_DATA devInfoData; + DWORD index; + DWORD result; + char resultBuf[16]; + BOOL reboot = FALSE; + SP_DEVINSTALL_PARAMS instParams; + + + EXDLL_INIT(); + + if (!initialized) + { + pushstring("Fatal error!"); + return; + } + + result = FindFirstDevice(NULL, &devClass, hwIdBuf, &devInfo, &devInfoData, &index, 0); + if (result != 0) + goto Cleanup1; + + do + { + if (!SetupDiCallClassInstaller(DIF_REMOVE, devInfo, &devInfoData)) + { + result = GetLastError(); + break; + } + + instParams.cbSize = sizeof(instParams); + if (!reboot && + SetupDiGetDeviceInstallParams(devInfo, &devInfoData, &instParams) && + ((instParams.Flags & (DI_NEEDRESTART|DI_NEEDREBOOT)) != 0)) + { + reboot = TRUE; + } + + result = FindNextDevice(devInfo, &devInfoData, &index); + } while (result == 0); + + SetupDiDestroyDeviceInfoList(devInfo); + + Cleanup1: + if ((result == 0) || (result == ERROR_NO_MORE_ITEMS)) + { + wsprintf(resultBuf, "%d", reboot); + pushstring(resultBuf); + pushstring("00000000"); + } + else + { + wsprintf(resultBuf, "%08X", result); + pushstring(resultBuf); + } +} + + + +/* + * InstDrv::StartSystemService serviceName + * + * Return: + * result - Windows error code + */ +void __declspec(dllexport) StartSystemService(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + SC_HANDLE managerHndl; + SC_HANDLE svcHndl; + SERVICE_STATUS svcStatus; + DWORD oldCheckPoint; + DWORD result; + char resultBuf[16]; + + + EXDLL_INIT(); + popstring(paramBuf); + + managerHndl = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (managerHndl == NULL) + { + result = GetLastError(); + goto Cleanup1; + } + + svcHndl = OpenService(managerHndl, paramBuf, SERVICE_START | SERVICE_QUERY_STATUS); + if (svcHndl == NULL) + { + result = GetLastError(); + goto Cleanup2; + } + + if (!StartService(svcHndl, 0, NULL) || !QueryServiceStatus(svcHndl, &svcStatus)) + { + result = GetLastError(); + goto Cleanup3; + } + + while (svcStatus.dwCurrentState == SERVICE_START_PENDING) + { + oldCheckPoint = svcStatus.dwCheckPoint; + + Sleep(svcStatus.dwWaitHint); + + if (!QueryServiceStatus(svcHndl, &svcStatus)) + { + result = GetLastError(); + break; + } + + if (oldCheckPoint >= svcStatus.dwCheckPoint) + { + if ((svcStatus.dwCurrentState == SERVICE_STOPPED) && + (svcStatus.dwWin32ExitCode != 0)) + result = svcStatus.dwWin32ExitCode; + else + result = ERROR_SERVICE_REQUEST_TIMEOUT; + } + } + + if (svcStatus.dwCurrentState == SERVICE_RUNNING) + result = 0; + + Cleanup3: + CloseServiceHandle(svcHndl); + + Cleanup2: + CloseServiceHandle(managerHndl); + + Cleanup1: + wsprintf(resultBuf, "%08X", result); + pushstring(resultBuf); +} + + + +/* + * InstDrv::StopSystemService serviceName + * + * Return: + * result - Windows error code + */ +void __declspec(dllexport) StopSystemService(HWND hwndParent, int string_size, char *variables, stack_t **stacktop) +{ + SC_HANDLE managerHndl; + SC_HANDLE svcHndl; + SERVICE_STATUS svcStatus; + DWORD oldCheckPoint; + DWORD result; + char resultBuf[16]; + + + EXDLL_INIT(); + popstring(paramBuf); + + managerHndl = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (managerHndl == NULL) + { + result = GetLastError(); + goto Cleanup1; + } + + svcHndl = OpenService(managerHndl, paramBuf, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (svcHndl == NULL) + { + result = GetLastError(); + goto Cleanup2; + } + + if (!ControlService(svcHndl, SERVICE_CONTROL_STOP, &svcStatus)) + { + result = GetLastError(); + goto Cleanup3; + } + + while (svcStatus.dwCurrentState == SERVICE_STOP_PENDING) + { + oldCheckPoint = svcStatus.dwCheckPoint; + + Sleep(svcStatus.dwWaitHint); + + if (!QueryServiceStatus(svcHndl, &svcStatus)) + { + result = GetLastError(); + break; + } + + if (oldCheckPoint >= svcStatus.dwCheckPoint) + { + result = ERROR_SERVICE_REQUEST_TIMEOUT; + break; + } + } + + if (svcStatus.dwCurrentState == SERVICE_STOPPED) + result = 0; + + Cleanup3: + CloseServiceHandle(svcHndl); + + Cleanup2: + CloseServiceHandle(managerHndl); + + Cleanup1: + wsprintf(resultBuf, "%08X", result); + pushstring(resultBuf); +} + + + +BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + return TRUE; +} diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp new file mode 100644 index 00000000..874e66c7 --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="InstDrv" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** NICHT BEARBEITEN ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=InstDrv - Win32 Debug +!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE +!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl +!MESSAGE +!MESSAGE NMAKE /f "InstDrv.mak". +!MESSAGE +!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben +!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel: +!MESSAGE +!MESSAGE NMAKE /f "InstDrv.mak" CFG="InstDrv - Win32 Debug" +!MESSAGE +!MESSAGE Für die Konfiguration stehen zur Auswahl: +!MESSAGE +!MESSAGE "InstDrv - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE "InstDrv - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "InstDrv - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /I "C:\Programme\WINDDK\3790\inc\ddk\w2k" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x407 /d "NDEBUG" +# ADD RSC /l 0x407 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib shell32.lib setupapi.lib newdev.lib /nologo /entry:"_DllMainCRTStartup" /dll /machine:I386 /nodefaultlib /out:"../../Plugins/InstDrv.dll" /libpath:"C:\Programme\WINDDK\3790\lib\w2k\i386" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "InstDrv - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x407 /d "_DEBUG" +# ADD RSC /l 0x407 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /entry:"_DllMainCRTStartup" /dll /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "InstDrv - Win32 Release" +# Name "InstDrv - Win32 Debug" +# Begin Group "Quellcodedateien" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\InstDrv.c +# End Source File +# End Group +# Begin Group "Header-Dateien" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Ressourcendateien" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw new file mode 100644 index 00000000..b3d02f0e --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN! + +############################################################################### + +Project: "InstDrv"=.\InstDrv.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/Readme.txt b/teststand/Instdrv/NSIS/Contrib/InstDrv/Readme.txt new file mode 100644 index 00000000..e5877aa6 --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/Readme.txt @@ -0,0 +1,141 @@ +InstDrv.dll version 0.2 - Installs or Removes Device Drivers +------------------------------------------------------------ + + +The plugin helps you to create NSIS scripts for installing device drivers or +removing them again. It can count installed device instances, create new ones +or delete all supported device. InstDrv works on Windows 2000 or later. + + + +InstDrv::InitDriverSetup devClass drvHWID +Return: result + +To start processing a driver, first call this function. devClass is the GUID +of the device class the driver supports, drvHWID is the device hardware ID. If +you don't know what these terms mean, you may want to take a look at the +Windows DDK. This function returns an empty string on success, otherwise an +error message. + +InitDriverSetup has to be called every time after the plugin dll has been +(re-)loaded, or if you want to switch to a different driver. + + + +InstDrv::CountDevices +Return: number + +This call returns the number of installed and supported devices of the driver. + + + +InstDrv::CreateDevice +Return: result + +To create a new deviced node which the driver has to support, use this +function. You may even call it multiple times for more than one instance. The +return value is the Windows error code (in hex). Use CreateDevice before +installing or updating the driver itself. + + + +InstDrv::InstallDriver infPath +Return: result + reboot + +InstallDriver installs or updates a device driver as specified in the .inf +setup script. It returns a Windows error code (in hex) and, on success, a flag +signalling if a system reboot is required. + + + +InstDrv::DeleteOemInfFiles +Return: result + oeminf + oempnf + +DeleteOemInfFiles tries to clean up the Windows inf directory by deleting the +oemXX.inf and oemXX.pnf files associated with the drivers. It returns a +Windows error code (in hex) and, on success, the names of the deleted files. +This functions requires that at least one device instance is still present. +So, call it before you remove the devices itself. You should also call it +before updating a driver. This avoids that the inf directory gets slowly +messed up with useless old setup scripts (which does NOT really accelerate +Windows). The error code which comes up when no device is installed is +"00000103". + + + +InstDrv::RemoveAllDevices +Return: result + reboot + +This functions deletes all devices instances the driver supported. It returns +a Windows error code (in hex) and, on success, a flag signalling if the system +needs to be rebooted. You additionally have to remove the driver binaries from +the system paths. + + + +InstDrv::StartSystemService serviceName +Return: result + +Call this function to start the provided system service. The function blocks +until the service is started or the system reported a timeout. The return value +is the Windows error code (in hex). + + + +InstDrv::StopSystemService serviceName +Return: result + +This function tries to stop the provided system service. It blocks until the +service has been shut down or the system reported a timeout. The return value +is the Windows error code (in hex). + + + +Example.nsi + +The example script installs or removes the virtual COM port driver of IrCOMM2k +(2.0.0-alpha8, see www.ircomm2k.de/english). The driver and its setup script +are only included for demonstration purposes, they do not work without the +rest of IrCOMM2k (but they also do not cause any harm). + + + +Building the Source Code + +To build the plugin from the source code, some include files and libraries +which come with the Windows DDK are required. + + + +History + + 0.2 - fixed bug when calling InitDriverSetup the second time + - added StartSystemService and StopSystemService + + 0.1 - first release + + + +License + +Copyright © 2003 Jan Kiszka (Jan.Kiszka@web.de) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; + you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the + product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, + and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any distribution. diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf b/teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf new file mode 100644 index 00000000..ccda1d87 --- /dev/null +++ b/teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf @@ -0,0 +1,137 @@ +; IrCOMM2k.inf +; +; Installation file for the Virtual Infrared-COM-Port +; +; (c) Copyright 2001, 2002 Jan Kiszka +; + +[Version] +Signature="$Windows NT$" +Provider=%JK% +Class=Ports +ClassGUID={4d36e978-e325-11ce-bfc1-08002be10318} +;DriverVer=03/26/2002,1.2.1.0 + +[DestinationDirs] +IrCOMM2k.Copy2Drivers = 12 +IrCOMM2k.Copy2Winnt = 10 +IrCOMM2k.Copy2System32 = 11 +IrCOMM2k.Copy2Help = 18 + + +; +; Driver information +; + +[Manufacturer] +%JK% = JK.Mfg + +[JK.Mfg] +%JK.DeviceDescIrCOMM% = IrCOMM2k_inst,IrCOMM2k + + +; +; General installation section +; + +[IrCOMM2k_inst] +CopyFiles = IrCOMM2k.Copy2Drivers ;,IrCOMM2k.Copy2System32,IrCOMM2k.Copy2Help,IrCOMM2k.Copy2Winnt +;AddReg = IrCOMM2k_inst_AddReg + + +; +; File sections +; + +[IrCOMM2k.Copy2Drivers] +ircomm2k.sys,,,2 + +;[IrCOMM2k.Copy2System32] +;ircomm2k.exe,,,2 +;ircomm2k.dll,,,2 + +;[IrCOMM2k.Copy2Help] +;ircomm2k.hlp,,,2 + +;[IrCOMM2k.Copy2Winnt] +;IrCOMM2k-Setup.exe,Setup.exe,,2 + + +; +; Service Installation +; + +[IrCOMM2k_inst.Services] +AddService = IrCOMM2k,0x00000002,IrCOMM2k_DriverService_Inst,IrCOMM2k_DriverEventLog_Inst +;AddService = IrCOMM2kSvc,,IrCOMM2k_Service_Inst + +[IrCOMM2k_DriverService_Inst] +DisplayName = %IrCOMM2k.DrvName% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 0 ; SERVICE_ERROR_IGNORE +ServiceBinary = %12%\ircomm2k.sys + +;[IrCOMM2k_Service_Inst] +;DisplayName = %IrCOMM2k.SvcName% +;Description = %IrCOMM2k.SvcDesc% +;ServiceType = 0x00000120 ; SERVICE_WIN32_SHARE_PROCESS, SERVICE_INTERACTIVE_PROCESS +;StartType = 2 ; SERVICE_AUTO_START +;ErrorControl = 0 ; SERVICE_ERROR_IGNORE +;ServiceBinary = %11%\ircomm2k.exe +;Dependencies = IrCOMM2k +;AddReg = IrCOMM2kSvcAddReg + + +[IrCOMM2k_inst.nt.HW] +AddReg=IrCOMM2kHwAddReg + +[IrCOMM2kHwAddReg] +HKR,,PortSubClass,REG_BINARY,0x00000001 +;HKR,,TimeoutScaling,REG_DWORD,0x00000001 +;HKR,,StatusLines,REG_DWORD,0x00000000 + +;[IrCOMM2k_inst_AddReg] +;HKR,,EnumPropPages32,,"ircomm2k.dll,IrCOMM2kPropPageProvider" +;HKLM,%UNINSTALL_KEY%,DisplayIcon,0x00020000,"%windir%\IrCOMM2k-Setup.exe" +;HKLM,%UNINSTALL_KEY%,DisplayName,,"IrCOMM2k 1.2.1 " +;HKLM,%UNINSTALL_KEY%,DisplayVersion,,"1.2.1" +;HKLM,%UNINSTALL_KEY%,HelpLink,,"http://www.ircomm2k.de" +;HKLM,%UNINSTALL_KEY%,Publisher,,%JK% +;HKLM,%UNINSTALL_KEY%,UninstallString,0x00020000,"%windir%\IrCOMM2k-Setup.exe" + +;[IrCOMM2kSvcAddReg] +;HKR,Parameters,ActiveConnectOnly,REG_DWORD,0x00000000 + + +[IrCOMM2k_DriverEventLog_Inst] +AddReg = IrCOMM2k_DriverEventLog_AddReg + +[IrCOMM2k_DriverEventLog_AddReg] +HKR,,EventMessageFile,REG_EXPAND_SZ,"%SystemRoot%\System32\IoLogMsg.dll;%SystemRoot%\System32\drivers\ircomm2k.sys" +HKR,,TypesSupported,REG_DWORD,7 + + +[Strings] + +; +; Non-Localizable Strings +; + +REG_SZ = 0x00000000 +REG_MULTI_SZ = 0x00010000 +REG_EXPAND_SZ = 0x00020000 +REG_BINARY = 0x00000001 +REG_DWORD = 0x00010001 +SERVICEROOT = "System\CurrentControlSet\Services" +UNINSTALL_KEY = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\IrCOMM2k" + +; +; Localizable Strings +; + +JK = "Jan Kiszka" +JK.DeviceDescIrCOMM = "Virtueller Infrarot-Kommunikationsanschluss" +IrCOMM2k.DrvName = "Virtueller Infrarot-Kommunikationsanschluss" +;IrCOMM2k.SvcName = "Virtueller Infrarot-Kommunikationsanschluß, Dienstprogramm" +;IrCOMM2k.SvcDesc = "Bildet über Infarot einen Kommunikationsanschluß nach." diff --git a/teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys b/teststand/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys new file mode 100644 index 0000000000000000000000000000000000000000..7882583beb6f8ee8d4f9da38939415a50a536adf GIT binary patch literal 30464 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 literal 0 HcmV?d00001 diff --git a/teststand/Instdrv/NSIS/Includes/java.nsh b/teststand/Instdrv/NSIS/Includes/java.nsh new file mode 100644 index 00000000..8e0e208a --- /dev/null +++ b/teststand/Instdrv/NSIS/Includes/java.nsh @@ -0,0 +1,181 @@ +!include WordFunc.nsh + +; Definitions for Java Detection + +!define JAVA_VERSION "6.0" + +Function GetFileVersion + !define GetFileVersion `!insertmacro GetFileVersionCall` + + !macro GetFileVersionCall _FILE _RESULT + Push `${_FILE}` + Call GetFileVersion + Pop ${_RESULT} + !macroend + + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + ClearErrors + + GetDllVersion '$0' $1 $2 + IfErrors error + IntOp $3 $1 >> 16 + IntOp $3 $3 & 0x0000FFFF + IntOp $4 $1 & 0x0000FFFF + IntOp $5 $2 >> 16 + IntOp $5 $5 & 0x0000FFFF + IntOp $6 $2 & 0x0000FFFF + StrCpy $0 '$3.$4.$5.$6' + goto end + + error: + SetErrors + StrCpy $0 '' + + end: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Exch $0 +FunctionEnd + +Function openLinkNewWindow + Push $3 + Exch + Push $2 + Exch + Push $1 + Exch + Push $0 + Exch + + ReadRegStr $1 HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice" "Progid" + IfErrors iexplore + + Goto foundbrowser +iexplore: + StrCpy $1 "IE.AssocFile.HTM" + +foundbrowser: + + StrCpy $2 "\shell\open\command" + + StrCpy $3 $1$2 + + ReadRegStr $0 HKCR $3 "" + +# Get browser path + DetailPrint $0 + + StrCpy $2 '"' + StrCpy $1 $0 1 + StrCmp $1 $2 +2 # if path is not enclosed in " look for space as final char + StrCpy $2 ' ' + StrCpy $3 1 + loop: + StrCpy $1 $0 1 $3 + DetailPrint $1 + StrCmp $1 $2 found + StrCmp $1 "" found + IntOp $3 $3 + 1 + Goto loop + + found: + StrCpy $1 $0 $3 + StrCmp $2 " " +2 + StrCpy $1 '$1"' + + Pop $0 + Exec '$1 $0' + Pop $0 + Pop $1 + Pop $2 + Pop $3 +FunctionEnd + +!macro _OpenURL URL +Push "${URL}" +Call openLinkNewWindow +!macroend + +!define OpenURL '!insertmacro "_OpenURL"' + +Function DoDetectJRE + + DetailPrint "Desired Java version ${JAVA_VERSION}" + + SearchPath $0 javaw.exe + IfErrors no + + DetailPrint "Detected java in $0" + + ${GetFileVersion} "$0" $1 + IfErrors no + + DetailPrint "Java version $1" + + ${VersionCompare} $1 ${JAVA_VERSION} $2 + IntCmp $2 1 yes yes old + +yes: + StrCpy $0 2 + Goto done + +old: + StrCpy $0 1 + Goto done + +no: + StrCpy $0 0 + Goto done + +done: + +FunctionEnd + +var dialog +var hwnd +var null + +var install +var quit +var skip + +Function GetJRE + ${OpenURL} "java.com" + MessageBox MB_OK "Click OK to continue after completing the Java Install." +FunctionEnd + +Function DetectJRE + + Call DoDetectJRE + + IntCmp $0 1 ask_old ask_no yes + +ask_no: + StrCpy $0 "Cannot find Java. Download and install?" + Goto ask + +ask_old: + StrCpy $0 "Java version appears to be too old. Download and install?" + Goto ask + +ask: + MessageBox MB_YESNOCANCEL $0 IDYES do_java IDNO skip_java + +do_java: + Call GetJRE + + +skip_java: +yes: + +FunctionEnd diff --git a/teststand/Instdrv/NSIS/Includes/refresh-sh.nsh b/teststand/Instdrv/NSIS/Includes/refresh-sh.nsh new file mode 100644 index 00000000..23d8e5e4 --- /dev/null +++ b/teststand/Instdrv/NSIS/Includes/refresh-sh.nsh @@ -0,0 +1,14 @@ +!define SHCNE_ASSOCCHANGED 0x08000000 +!define SHCNF_IDLIST 0 + +Function RefreshShellIcons + ; By jerome tremblay - april 2003 + ${DisableX64FSRedirection} + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)' +FunctionEnd + +Function un.RefreshShellIcons + ; By jerome tremblay - april 2003 + ${DisableX64FSRedirection} + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)' +FunctionEnd diff --git a/teststand/Instdrv/NSIS/Plugins/InstDrv.dll b/teststand/Instdrv/NSIS/Plugins/InstDrv.dll new file mode 100644 index 0000000000000000000000000000000000000000..482e955e5a65643662f25b0c6e7a4acbfde9dee4 GIT binary patch literal 6656 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 literal 0 HcmV?d00001 diff --git a/teststand/Makefile-standalone b/teststand/Makefile-standalone new file mode 100644 index 00000000..e5f41639 --- /dev/null +++ b/teststand/Makefile-standalone @@ -0,0 +1,191 @@ +.SUFFIXES: .java .class + +CLASSPATH=classes:./*:/usr/share/java/* +CLASSFILES=\ + Altos.class \ + AltosChannelMenu.class \ + AltosConfigFC.class \ + AltosConfigFCUI.class \ + AltosConvert.class \ + AltosCRCException.class \ + AltosCSV.class \ + AltosCSVUI.class \ + AltosDebug.class \ + AltosEepromDownload.class \ + AltosEepromMonitor.class \ + AltosEepromReader.class \ + AltosEepromRecord.class \ + AltosFile.class \ + AltosFlash.class \ + AltosFlashUI.class \ + AltosFlightInfoTableModel.class \ + AltosFlightStatusTableModel.class \ + AltosGPS.class \ + AltosGreatCircle.class \ + AltosHexfile.class \ + AltosLine.class \ + AltosInfoTable.class \ + AltosLog.class \ + AltosLogfileChooser.class \ + AltosParse.class \ + AltosPreferences.class \ + AltosReader.class \ + AltosRecord.class \ + AltosSerialMonitor.class \ + AltosSerial.class \ + AltosState.class \ + AltosStatusTable.class \ + AltosTelemetry.class \ + AltosTelemetryReader.class \ + AltosUI.class \ + AltosDevice.class \ + AltosDeviceDialog.class \ + AltosRomconfig.class \ + AltosRomconfigUI.class \ + AltosVoice.class + +JAVA_ICONS=\ + ../icon/altus-metrum-16.png \ + ../icon/altus-metrum-32.png \ + ../icon/altus-metrum-48.png \ + ../icon/altus-metrum-64.png \ + ../icon/altus-metrum-128.png \ + ../icon/altus-metrum-256.png + +WINDOWS_ICON=../icon/altus-metrum.ico + +# where altosui.jar gets installed +ALTOSLIB=/usr/share/java + +# where freetts.jar is to be found +FREETTSLIB=/usr/share/java + +# all of the freetts files +FREETTSJAR= \ + $(FREETTSLIB)/cmudict04.jar \ + $(FREETTSLIB)/cmulex.jar \ + $(FREETTSLIB)/cmu_time_awb.jar \ + $(FREETTSLIB)/cmutimelex.jar \ + $(FREETTSLIB)/cmu_us_kal.jar \ + $(FREETTSLIB)/en_us.jar \ + $(FREETTSLIB)/freetts.jar + +# The current hex files +HEXLIB=../src +HEXFILES = \ + $(HEXLIB)/telemetrum-v1.0.ihx \ + $(HEXLIB)/teledongle-v0.2.ihx + +JAVAFLAGS=-Xlint:unchecked -Xlint:deprecation + +ALTOSUIJAR = altosui.jar +FATJAR = fat/altosui.jar + +OS:=$(shell uname) + +LINUX_APP=altosui + +DARWIN_ZIP=Altos-Mac.zip + +WINDOWS_EXE=Altos-Windows.exe + +LINUX_TGZ=Altos-Linux.tgz + +all: altosui.jar $(LINUX_APP) +fat: altosui.jar $(LINUX_APP) $(DARWIN_ZIP) $(WINDOWS_EXE) $(LINUX_TGZ) + +$(CLASSFILES): + +.java.class: + javac -encoding UTF8 -classpath "$(CLASSPATH)" $(JAVAFLAGS) $*.java + +altosui.jar: classes/images classes/altosui classes/libaltosJNI $(CLASSFILES) Manifest.txt + cd ./classes && jar cfm ../$@ altosui/Manifest.txt images/* altosui/*.class libaltosJNI/*.class + +Manifest.txt: Makefile $(CLASSFILES) + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: $(FREETTSLIB)/freetts.jar" >> $@ + +classes/altosui: + mkdir -p classes + ln -sf .. classes/altosui + +classes/libaltosJNI: + mkdir -p classes + ln -sf ../libaltos/libaltosJNI classes/libaltosJNI + +classes/images: + mkdir -p classes/images + ln -sf ../$(JAVA_ICONS) classes/images + +altosui: + echo "#!/bin/sh" > $@ + echo "exec java -Djava.library.path=/usr/lib/altos -jar /usr/share/java/altosui.jar" >> $@ + chmod +x ./altosui + +fat/altosui: + echo "#!/bin/sh" > $@ + echo 'ME=`which "$0"`' >> $@ + echo 'DIR=`dirname "$ME"`' >> $@ + echo 'exec java -Djava.library.path="$$DIR" -jar "$$DIR"/altosui.jar' >> $@ + chmod +x $@ + +fat/altosui.jar: $(CLASSFILES) $(JAVA_ICONS) fat/classes/Manifest.txt + mkdir -p fat/classes + test -L fat/classes/altosui || ln -sf ../.. fat/classes/altosui + mkdir -p fat/classes/images + cp $(JAVA_ICONS) fat/classes/images + test -L fat/classes/libaltosJNI || ln -sf ../../libaltos/libaltosJNI fat/classes/libaltosJNI + cd ./fat/classes && jar cfm ../../$@ Manifest.txt images/* altosui/*.class libaltosJNI/*.class + +fat/classes/Manifest.txt: $(CLASSFILES) Makefile + mkdir -p fat/classes + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: freetts.jar" >> $@ + +install: altosui.jar altosui + install -m 0644 altosui.jar $(DESTDIR)/usr/share/java/altosui.jar + install -m 0644 altosui.1 $(DESTDIR)/usr/share/man/man1/altosui.1 + install altosui $(DESTDIR)/usr/bin/altosui + +clean: + rm -f *.class altosui.jar + rm -f AltosUI.app/Contents/Resources/Java/* + rm -rf classes + rm -rf windows linux + +distclean: clean + rm -f $(DARWIN_ZIP) $(WINDOWS_EXE) $(LINUX_TGZ) + rm -rf darwin fat + +FAT_FILES=$(FATJAR) $(FREETTSJAR) $(HEXFILES) + +LINUX_FILES=$(FAT_FILES) libaltos/libaltos.so fat/altosui +$(LINUX_TGZ): $(LINUX_FILES) + rm -f $@ + mkdir -p linux/AltOS + rm -f linux/AltOS/* + cp $(LINUX_FILES) linux/AltOS + cd linux && tar czf ../$@ AltOS + +DARWIN_RESOURCES=$(FATJAR) $(FREETTSJAR) libaltos/libaltos.dylib +DARWIN_EXTRA=$(HEXFILES) +DARWIN_FILES=$(DARWIN_RESOURCES) $(DARWIN_EXTRA) + +$(DARWIN_ZIP): $(DARWIN_FILES) + rm -f $@ + cp -a AltosUI.app darwin/ + mkdir -p darwin/AltosUI.app/Contents/Resources/Java + cp $(DARWIN_RESOURCES) darwin/AltosUI.app/Contents/Resources/Java + mkdir -p darwin/AltOS + cp $(DARWIN_EXTRA) darwin/AltOS + cd darwin && zip -r ../$@ AltosUI.app AltOS + +WINDOWS_FILES = $(FAT_FILES) libaltos/altos.dll ../altusmetrum.inf $(WINDOWS_ICON) + +$(WINDOWS_EXE): $(WINDOWS_FILES) altos-windows.nsi + rm -f $@ + mkdir -p windows/AltOS + rm -f windows/AltOS/* + cp $(WINDOWS_FILES) windows/AltOS + makensis altos-windows.nsi diff --git a/teststand/Makefile.am b/teststand/Makefile.am new file mode 100644 index 00000000..5ba8fc3f --- /dev/null +++ b/teststand/Makefile.am @@ -0,0 +1,394 @@ + +JAVAROOT=classes +AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6 + +man_MANS=teststand.1 + +altoslibdir=$(libdir)/altos + +CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH="$(JAVAROOT):../altoslib/*:../altosuilib/*:../libaltos:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar:$(FREETTS)/freetts.jar" + +bin_SCRIPTS=teststand + +teststand_JAVA = \ + AltosAscent.java \ + AltosChannelMenu.java \ + AltosCompanionInfo.java \ + AltosConfigFC.java \ + AltosConfigFCUI.java \ + AltosConfigPyroUI.java \ + AltosConfigureUI.java \ + AltosConfigTD.java \ + AltosConfigTDUI.java \ + AltosDescent.java \ + AltosFlightStatus.java \ + AltosFlightStatusUpdate.java \ + AltosFlightUI.java \ + Altos.java \ + AltosIdleMonitorUI.java \ + AltosIgniteUI.java \ + AltosIgnitor.java \ + AltosLaunch.java \ + AltosLaunchUI.java \ + AltosLanded.java \ + AltosPad.java \ + TestStand.java \ + AltosGraphUI.java + +JFREECHART_CLASS= \ + jfreechart.jar + +JCOMMON_CLASS=\ + jcommon.jar + +FREETTS_CLASS= \ + cmudict04.jar \ + cmulex.jar \ + cmu_time_awb.jar \ + cmutimelex.jar \ + cmu_us_kal.jar \ + en_us.jar \ + freetts.jar + +ALTOSLIB_CLASS=\ + altoslib_$(ALTOSLIB_VERSION).jar + +ALTOSUILIB_CLASS=\ + altosuilib_$(ALTOSUILIB_VERSION).jar + +if MULTI_ARCH +LIBALTOS_LINUX=libaltos32.so libaltos64.so +else +LIBALTOS_LINUX=libaltos.so +endif + +LIBALTOS= \ + $(LIBALTOS_LINUX) \ + libaltos.dylib \ + altos.dll \ + altos64.dll + +desktopdir = $(datadir)/applications +desktop_file = altusmetrum-teststand.desktop +desktop_SCRIPTS = $(desktop_file) + +JAR=teststand.jar + +FATJAR=teststand-fat.jar + +# Icons +ICONDIR=../icon + +JAVA_ICONS=\ + $(ICONDIR)/altusmetrum-teststand-16.png \ + $(ICONDIR)/altusmetrum-teststand-32.png \ + $(ICONDIR)/altusmetrum-teststand-48.png \ + $(ICONDIR)/altusmetrum-teststand-64.png \ + $(ICONDIR)/altusmetrum-teststand-128.png\ + $(ICONDIR)/altusmetrum-teststand-256.png + +# icon base names for jar +ICONJAR= \ + -C $(ICONDIR) altusmetrum-teststand-16.png \ + -C $(ICONDIR) altusmetrum-teststand-32.png \ + -C $(ICONDIR) altusmetrum-teststand-48.png \ + -C $(ICONDIR) altusmetrum-teststand-64.png \ + -C $(ICONDIR) altusmetrum-teststand-128.png\ + -C $(ICONDIR) altusmetrum-teststand-256.png + +WINDOWS_ICONS =\ + $(ICONDIR)/altusmetrum-teststand.ico \ + $(ICONDIR)/altusmetrum-teststand.exe + $(ICONDIR)/application-vnd.altusmetrum.eeprom.ico \ + $(ICONDIR)/application-vnd.altusmetrum.eeprom.exe \ + $(ICONDIR)/application-vnd.altusmetrum.telemetry.ico \ + $(ICONDIR)/application-vnd.altusmetrum.telemetry.exe + +MACOSX_ICONS =\ + $(ICONDIR)/altusmetrum-teststand.icns \ + $(ICONDIR)/application-vnd.altusmetrum.eeprom.icns \ + $(ICONDIR)/application-vnd.altusmetrum.telemetry.icns + +LINUX_ICONS =\ + $(ICONDIR)/altusmetrum-teststand.svg \ + $(ICONDIR)/application-vnd.altusmetrum.eeprom.svg \ + $(ICONDIR)/application-vnd.altusmetrum.telemetry.svg + +LINUX_MIMETYPE =\ + $(ICONDIR)/org-altusmetrum-mimetypes.xml + +# Firmware +FIRMWARE_TD_0_2=$(top_srcdir)/src/teledongle-v0.2/teledongle-v0.2-$(VERSION).ihx +FIRMWARE_TD_3_0=$(top_srcdir)/src/teledongle-v3.0/teledongle-v3.0-$(VERSION).ihx +FIRMWARE_TD=$(FIRMWARE_TD_0_2) $(FIRMWARE_TD_3_0) + +FIRMWARE_TM_1_0=$(top_srcdir)/src/telemetrum-v1.0/telemetrum-v1.0-$(VERSION).ihx +FIRMWARE_TM_1_1=$(top_srcdir)/src/telemetrum-v1.1/telemetrum-v1.1-$(VERSION).ihx +FIRMWARE_TM_1_2=$(top_srcdir)/src/telemetrum-v1.2/telemetrum-v1.2-$(VERSION).ihx +FIRMWARE_TM_2_0=$(top_srcdir)/src/telemetrum-v2.0/telemetrum-v2.0-$(VERSION).ihx +#FIRMWARE_TM_3_0=$(top_srcdir)/src/telemetrum-v3.0/telemetrum-v3.0-$(VERSION).ihx +#FIRMWARE_TM=$(FIRMWARE_TM_1_0) $(FIRMWARE_TM_1_1) $(FIRMWARE_TM_1_2) $(FIRMWARE_TM_2_0) $(FIRMWARE_TM_3_0) +FIRMWARE_TM=$(FIRMWARE_TM_1_0) $(FIRMWARE_TM_1_1) $(FIRMWARE_TM_1_2) $(FIRMWARE_TM_2_0) + +FIRMWARE_TELEMINI_1_0=$(top_srcdir)/src/telemini-v1.0/telemini-v1.0-$(VERSION).ihx +FIRMWARE_TELEMINI_3_0=$(top_srcdir)/src/telemini-v3.0/telemini-v3.0-$(VERSION).ihx +FIRMWARE_TELEMINI=$(FIRMWARE_TELEMINI_1_0) $(FIRMWARE_TELEMINI_3_0) + +FIRMWARE_TBT_1_0=$(top_srcdir)/src/telebt-v1.0/telebt-v1.0-$(VERSION).ihx +FIRMWARE_TBT_3_0=$(top_srcdir)/src/telebt-v3.0/telebt-v3.0-$(VERSION).ihx +FIRMWARE_TBT_4_0=$(top_srcdir)/src/telebt-v4.0/telebt-v4.0-$(VERSION).ihx +FIRMWARE_TBT=$(FIRMWARE_TBT_1_0) $(FIRMWARE_TBT_3_0) $(FIRMWARE_TBT_4_0) + +FIRMWARE_TMEGA_1_0=$(top_srcdir)/src/telemega-v1.0/telemega-v1.0-$(VERSION).ihx +FIRMWARE_TMEGA_2_0=$(top_srcdir)/src/telemega-v2.0/telemega-v2.0-$(VERSION).ihx +FIRMWARE_TMEGA=$(FIRMWARE_TMEGA_1_0) $(FIRMWARE_TMEGA_2_0) + +FIRMWARE_EMINI_1_0=$(top_srcdir)/src/easymini-v1.0/easymini-v1.0-$(VERSION).ihx +FIRMWARE_EMINI=$(FIRMWARE_EMINI_1_0) + +FIRMWARE_EMEGA_1_0=$(top_srcdir)/src/easymega-v1.0/easymega-v1.0-$(VERSION).ihx +FIRMWARE_EMEGA=$(FIRMWARE_EMEGA_1_0) + +FIRMWARE_TGPS_1_0=$(top_srcdir)/src/telegps-v1.0/telegps-v1.0-$(VERSION).ihx +FIRMWARE_TGPS=$(FIRMWARE_TGPS_1_0) + +FIRMWARE=$(FIRMWARE_TM) $(FIRMWARE_TELEMINI) $(FIRMWARE_TD) $(FIRMWARE_TBT) $(FIRMWARE_TMEGA) $(FIRMWARE_EMINI) $(FIRMWARE_TGPS) $(FIRMWARE_EMEGA) + +ALTUSMETRUM_DOC=$(top_srcdir)/doc/altusmetrum.pdf +ALTOS_DOC=$(top_srcdir)/doc/altos.pdf +TELEMETRY_DOC=$(top_srcdir)/doc/telemetry.pdf +TEMPLATE_DOC=\ + $(top_srcdir)/doc/telemetrum-outline.pdf \ + $(top_srcdir)/doc/easymini-outline.pdf \ + $(top_srcdir)/doc/telemega-outline.pdf \ + $(top_srcdir)/doc/telemini-v1-outline.pdf \ + $(top_srcdir)/doc/telemini-v3-outline.pdf + +DOC=$(ALTUSMETRUM_DOC) $(ALTOS_DOC) $(TELEMETRY_DOC) $(TEMPLATE_DOC) + +# Distribution targets +LINUX_DIST=Altos-Linux-$(VERSION).tar.bz2 +LINUX_SH=Altos-Linux-$(VERSION).sh +MACOSX_DIST=Altos-Mac-$(VERSION).dmg +WINDOWS_DIST=Altos-Windows-$(VERSION_DASH).exe +MDWN=$(VERSION).mdwn +MDWNTMPL=mdwn.tmpl + +FAT_FILES=$(FATJAR) $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) + +LINUX_LIBS=$(LIBALTOS_LINUX) + +LINUX_FILES=$(FAT_FILES) $(LINUX_LIBS) $(FIRMWARE) $(DOC) $(desktop_file).in $(LINUX_ICONS) $(LINUX_MIMETYPE) +LINUX_EXTRA=teststand-fat + +MACOSX_INFO_PLIST=Info.plist +MACOSX_FILES=$(FAT_FILES) libaltos.dylib $(MACOSX_INFO_PLIST) $(DOC) ReadMe-Mac.rtf $(MACOSX_ICONS) +MACOSX_EXTRA=$(FIRMWARE) + +WINDOWS_FILES=$(FAT_FILES) $(FIRMWARE) altos.dll altos64.dll $(top_srcdir)/altusmetrum.inf $(top_srcdir)/altusmetrum.cat $(WINDOWS_ICONS) + +all-local: classes/teststand $(JAR) teststand teststand-test teststand-jdb $(MDWN) + +clean-local: + -rm -rf classes $(JAR) $(FATJAR) \ + Altos-Linux-*.tar.bz2 Altos-Linux-*.sh Altos-Mac-*.dmg Altos-Windows-*.exe \ + windows altoslib_*.jar altosuilib_*.jar $(FREETTS_CLASS) \ + $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) Manifest.txt Manifest-fat.txt altos-windows.log altos-windows.nsi \ + teststand teststand-test teststand-jdb macosx linux *.desktop *.mdwn + +EXTRA_DIST = $(desktop_file).in + +$(desktop_file): $(desktop_file).in + sed -e 's#%bindir%#@bindir@#' -e 's#%icondir%#$(datadir)/icons/hicolor/scalable/apps#' ${srcdir}/$(desktop_file).in > $@ + chmod +x $@ + +if FATINSTALL + +FATTARGET=$(FATDIR)/AltOS/releases/$(VERSION) + +LINUX_SH_TARGET=$(FATTARGET)/$(LINUX_SH) +MACOSX_DIST_TARGET=$(FATTARGET)/$(MACOSX_DIST) +WINDOWS_DIST_TARGET=$(FATTARGET)/$(WINDOWS_DIST) +MDWN_TARGET=$(FATDIR)/AltOS/releases/$(MDWN) +RELNOTES=release-notes-$(VERSION).html +RELNOTES_SRC=$(top_builddir)/doc/$(RELNOTES) +RELNOTES_TARGET=$(FATTARGET)/$(RELNOTES) + +fat-install: fat $(LINUX_SH_TARGET) $(MACOSX_DIST_TARGET) $(WINDOWS_DIST_TARGET) $(MDWN_TARGET) $(RELNOTES_TARGET) + +$(LINUX_SH_TARGET): $(LINUX_SH) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(MACOSX_DIST_TARGET): $(MACOSX_DIST) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(WINDOWS_DIST_TARGET): $(WINDOWS_DIST) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(MDWN_TARGET): $(MDWN) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(RELNOTES_TARGET): $(RELNOTES_SRC) + mkdir -p $(FATTARGET) + sh $(top_srcdir)/doc/install-html -d $(FATTARGET) $(RELNOTES_SRC) + +endif + +$(MDWN): $(MDWNTMPL) + sed -e 's/%version%/$(VERSION)/g' -e 's/%version_dash%/$(VERSION_DASH)/g' $(MDWNTMPL) > $@ + +fat: $(LINUX_DIST) $(LINUX_SH) $(MACOSX_DIST) $(WINDOWS_DIST) + +teststanddir=$(datadir)/java + +install-teststandJAVA: teststand.jar + @$(NORMAL_INSTALL) + test -z "$(teststanddir)" || $(MKDIR_P) "$(DESTDIR)$(teststanddir)" + echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(teststanddir)/teststand.jar'"; \ + $(INSTALL_DATA) "$<" "$(DESTDIR)$(teststanddir)" + +classes/teststand: + mkdir -p classes/teststand + +$(JAR): classteststand.stamp Manifest.txt $(JAVA_ICONS) $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) + jar cfm $@ Manifest.txt \ + $(ICONJAR) \ + -C classes teststand \ + -C ../libaltos libaltosJNI + +$(FATJAR): classteststand.stamp Manifest-fat.txt $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) $(JAVA_ICONS) + jar cfm $@ Manifest-fat.txt \ + $(ICONJAR) \ + -C classes teststand \ + -C ../libaltos libaltosJNI + +Manifest.txt: Makefile + echo 'Main-Class: teststand.TestStand' > $@ + echo "Class-Path: $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(FREETTS)/freetts.jar $(JCOMMON)/jcommon.jar $(JFREECHART)/jfreechart.jar" >> $@ + +Manifest-fat.txt: + echo 'Main-Class: teststand.TestStand' > $@ + echo "Class-Path: $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) freetts.jar jcommon.jar jfreechart.jar" >> $@ + +teststand: Makefile + echo "#!/bin/sh" > $@ + echo 'exec java -Djava.library.path="$(altoslibdir)" -jar "$(altosuidir)/teststand.jar" "$$@"' >> $@ + chmod +x $@ + +teststand-test: Makefile + echo '#!/bin/sh' > $@ + echo 'dir="$$(dirname $$0)"' >> $@ + echo 'cd "$$dir"' >> $@ + echo 'teststand="$$(pwd -P)"' >> $@ + echo 'altos="$$(dirname $$teststand)"' >> $@ + echo 'exec java -Djava.library.path="$$altos/libaltos/.libs" -jar "$$teststand/teststand.jar" "$$@"' >> $@ + chmod +x $@ + +teststand-jdb: Makefile + echo "#!/bin/sh" > $@ + echo 'exec jdb -classpath "classes:./*:../libaltos:$(FREETTS)/freetts.jar:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar" -Djava.library.path="../libaltos/.libs" teststand/TestStand "$$@"' >> $@ + chmod +x $@ + +libaltos.so: build-libaltos + -rm -f "$@" + $(LN_S) ../libaltos/.libs/"$@" . + +libaltos32.so: build-libaltos + -rm -f "$@" + $(LN_S) ../libaltos/.libs/"$@" . + +libaltos64.so: build-libaltos + -rm -f "$@" + $(LN_S) ../libaltos/.libs/"$@" . + +libaltos.dylib: + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +altos.dll: ../libaltos/altos.dll + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +altos64.dll: ../libaltos/altos64.dll + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +../libaltos/.libs/libaltos64.so: ../libaltos/.libs/libaltos32.so + +../libaltos/.libs/libaltos32.so: build-libaltos + +../libaltos/.libs/libaltos.so: build-libaltos + +../libaltos/altos.dll: build-altos-dll + +../libaltos/altos64.dll: build-altos64-dll + +build-libaltos: + +cd ../libaltos && make libaltos.la +build-altos-dll: + +cd ../libaltos && make altos.dll + +build-altos64-dll: + +cd ../libaltos && make altos64.dll + +$(ALTOSLIB_CLASS): + -rm -f "$@" + $(LN_S) ../altoslib/"$@" . + +$(ALTOSUILIB_CLASS): + -rm -f "$@" + $(LN_S) ../altosuilib/"$@" . + +$(FREETTS_CLASS): + -rm -f "$@" + $(LN_S) "$(FREETTS)"/"$@" . + +$(JFREECHART_CLASS): + -rm -f "$@" + $(LN_S) "$(JFREECHART)"/"$@" . + +$(JCOMMON_CLASS): + -rm -f "$@" + $(LN_S) "$(JCOMMON)"/"$@" . + +$(LINUX_DIST): $(LINUX_FILES) $(LINUX_EXTRA) + -rm -f $@ + -rm -rf linux + mkdir -p linux/AltOS + cp -p $(LINUX_FILES) linux/AltOS + cp -p teststand-fat linux/AltOS/teststand + chmod +x linux/AltOS/teststand + tar cjf $@ -C linux AltOS + +$(LINUX_SH): $(LINUX_DIST) linux-install.sh + cat linux-install.sh $(LINUX_DIST) > $@ + chmod +x $@ + +$(MACOSX_DIST): $(MACOSX_FILES) $(MACOSX_EXTRA) Makefile + -rm -f $@ + -rm -rf macosx + mkdir macosx + cp -a TestStand.app macosx/ + cp -a ReadMe-Mac.rtf macosx/ReadMe.rtf + mkdir -p macosx/Doc + cp -a $(DOC) macosx/Doc + cp -p Info.plist macosx/TestStand.app/Contents + mkdir -p macosx/AltOS-$(VERSION) macosx/TestStand.app/Contents/Resources/Java + cp -p $(MACOSX_ICONS) macosx/TestStand.app/Contents/Resources + cp -p $(FATJAR) macosx/TestStand.app/Contents/Resources/Java/teststand.jar + cp -p libaltos.dylib macosx/TestStand.app/Contents/Resources/Java + cp -p $(ALTOSLIB_CLASS) macosx/TestStand.app/Contents/Resources/Java + cp -p $(ALTOSUILIB_CLASS) macosx/TestStand.app/Contents/Resources/Java + cp -p $(FREETTS_CLASS) macosx/TestStand.app/Contents/Resources/Java + cp -p $(JFREECHART_CLASS) macosx/TestStand.app/Contents/Resources/Java + cp -p $(JCOMMON_CLASS) macosx/TestStand.app/Contents/Resources/Java + cp -p $(MACOSX_EXTRA) macosx/AltOS-$(VERSION) + genisoimage -D -V AltOS-$(VERSION) -no-pad -r -apple -o $@ macosx + +$(WINDOWS_DIST): $(WINDOWS_FILES) altos-windows.nsi Instdrv/NSIS/Includes/java.nsh + -rm -f $@ + makensis -Oaltos-windows.log "-XOutFile $@" "-DVERSION=$(VERSION)" altos-windows.nsi || (cat altos-windows.log && exit 1) diff --git a/teststand/ReadMe-Mac.rtf b/teststand/ReadMe-Mac.rtf new file mode 100644 index 00000000..8a95262c --- /dev/null +++ b/teststand/ReadMe-Mac.rtf @@ -0,0 +1,56 @@ +{\rtf1\ansi\deff3\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset128 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset128 Arial;}{\f5\fnil\fprq2\fcharset128 SimSun;}{\f6\fnil\fprq2\fcharset128 Raghindi;}{\f7\fnil\fprq0\fcharset128 Raghindi;}} +{\colortbl;\red0\green0\blue0;\red128\green128\blue128;} +{\stylesheet{\s0\snext0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033 Normal;} +{\*\cs15\snext15 Numbering Symbols;} +{\s16\sbasedon0\snext17\sb240\sa120\keepn\hich\af5\dbch\af6\afs28\loch\f4\fs28 Heading;} +{\s17\sbasedon0\snext17\sb0\sa120 Text body;} +{\s18\sbasedon17\snext18\sb0\sa120\dbch\af7 List;} +{\s19\sbasedon0\snext19\sb120\sa120\noline\i\dbch\af7\afs24\ai\fs24 Caption;} +{\s20\sbasedon0\snext20\noline\dbch\af7 Index;} +}{\*\listtable{\list\listtemplateid1 +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'00);}{\levelnumbers\'01;}\fi-360\li720} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-360\li1080} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-360\li1440} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-360\li1800} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-360\li2160} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-360\li2520} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-360\li2880} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-360\li3240} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-360\li3600}\listid1} +{\list\listtemplateid2 +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-432\li432} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-576\li576} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-720\li720} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-864\li864} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-1008\li1008} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-1152\li1152} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-1296\li1296} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-1440\li1440} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow0{\leveltext \'00;}{\levelnumbers;}\fi-1584\li1584}\listid2} +}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}}{\info{\creatim\yr2013\mo1\dy6\hr13\min7}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}{\comment LibreOffice}{\vern3500}}\deftab709 + +{\*\pgdsctbl +{\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default;}} +\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sectunlocked1\pgndec\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc +\pgndec\pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +Installing AltOS software for Mac OS X Computers} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +The AltOS distribution for Mac OS X consists of:} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\listtext\pard\plain 1)\tab}\ilvl0\ls1 \li720\ri0\lin720\rin0\fi-360{\rtlch \ltrch\loch +The AltosUI application} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\listtext\pard\plain 2)\tab}\ilvl0\ls1 \li720\ri0\lin720\rin0\fi-360{\rtlch \ltrch\loch +Current AltOS firmware for Altus Metrum products} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +Install the AltosUI application by dragging it to your Applications folder (or wherever else you want to install it).} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +} +\par \pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af5\langfe2052\dbch\af6\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch +The AltOS firmware can be used to update your Altus Metrum products to the latest firmware version, you can copy it to your disk if you like, or simply use it directly from the installation disk image.} +\par } \ No newline at end of file diff --git a/teststand/TestStand.app/Contents/MacOS/JavaApplicationStub b/teststand/TestStand.app/Contents/MacOS/JavaApplicationStub new file mode 100755 index 0000000000000000000000000000000000000000..c661d3e15cee1d93f2e7732df096700c8862fd41 GIT binary patch literal 61296 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 + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 teststand; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.concurrent.*; +import org.altusmetrum.altoslib_12.*; +import org.altusmetrum.altosuilib_12.*; + +public class TestStand extends AltosUIFrame implements AltosEepromGrapher { + public AltosVoice voice = new AltosVoice(); + + public static boolean load_library(Frame frame) { + if (!Altos.load_library()) { + JOptionPane.showMessageDialog(frame, + String.format("No AltOS library in \"%s\"", + System.getProperty("java.library.path","")), + "Cannot load device access library", + JOptionPane.ERROR_MESSAGE); + return false; + } + return true; + } + + void telemetry_window(AltosDevice device) { + try { + AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device)); + if (reader != null) + new AltosFlightUI(voice, reader, device.getSerial()); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(TestStand.this, + ee.getMessage(), + String.format ("Cannot open %s", device.toShortString()), + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(TestStand.this, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(TestStand.this, + String.format ("Unknown I/O error on %s", device.toShortString()), + "Unknown I/O error", + JOptionPane.ERROR_MESSAGE); + } catch (TimeoutException te) { + JOptionPane.showMessageDialog(this, + String.format ("Timeout on %s", device.toShortString()), + "Timeout error", + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + JOptionPane.showMessageDialog(this, + String.format("Interrupted %s", device.toShortString()), + "Interrupted exception", + JOptionPane.ERROR_MESSAGE); + } + } + + public void scan_device_selected(AltosDevice device) { + telemetry_window(device); + } + + Container pane; + GridBagLayout gridbag; + + JButton addButton(int x, int y, String label) { + GridBagConstraints c; + JButton b; + + c = new GridBagConstraints(); + c.gridx = x; c.gridy = y; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + b = new JButton(label); + + //Dimension ps = b.getPreferredSize(); + + gridbag.setConstraints(b, c); + add(b, c); + return b; + } + + /* OSXAdapter interfaces */ + public void macosx_file_handler(String path) { + process_graph(new File(path)); + } + + public void macosx_quit_handler() { + System.exit(0); + } + + public void macosx_preferences_handler() { + ConfigureTestStand(); + } + + public TestStand() { + + load_library(null); + + register_for_macosx_events(); + + AltosUIPreferences.set_component(this); + + pane = getContentPane(); + gridbag = new GridBagLayout(); + pane.setLayout(gridbag); + + JButton b; + + b = addButton(0, 0, "Monitor Flight"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConnectToDevice(); + } + }); + b.setToolTipText("Connect to TeleDongle and monitor telemetry"); + b = addButton(1, 0, "Save Flight Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SaveFlightData(); + } + }); + b.setToolTipText("Download and/or delete flight data from an altimeter"); + b = addButton(2, 0, "Replay Flight"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Replay(); + } + }); + b.setToolTipText("Watch an old flight in real-time"); + b = addButton(3, 0, "Graph Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + GraphData(); + } + }); + b.setToolTipText("Present flight data in a graph and table of statistics"); + b = addButton(4, 0, "Export Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ExportData(); + } + }); + b.setToolTipText("Convert flight data for a spreadsheet or GoogleEarth"); + b = addButton(0, 1, "Configure Altimeter"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTeleMetrum(); + } + }); + b.setToolTipText("Set flight, storage and communication parameters"); + b = addButton(1, 1, "Configure TestStand"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTestStand(); + } + }); + b.setToolTipText("Global TestStand settings"); + + b = addButton(2, 1, "Configure Ground Station"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTeleDongle(); + } + }); + + b = addButton(3, 1, "Flash Image"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FlashImage(); + } + }); + b.setToolTipText("Replace the firmware in any AltusMetrum product"); + + b = addButton(4, 1, "Fire Igniter"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FireIgniter(); + } + }); + b.setToolTipText("Remote control of igniters for deployment testing"); + b = addButton(0, 2, "Scan Channels"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ScanChannels(); + } + }); + b.setToolTipText("Find what channel an altimeter is sending telemetry on"); + b = addButton(1, 2, "Load Maps"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + LoadMaps(); + } + }); + b.setToolTipText("Download satellite images for off-line flight monitoring"); + b = addButton(2, 2, "Monitor Idle"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + IdleMonitor(); + } + }); + b.setToolTipText("Check flight readiness of altimeter in idle mode"); + +// b = addButton(3, 2, "Launch Controller"); +// b.addActionListener(new ActionListener() { +// public void actionPerformed(ActionEvent e) { +// LaunchController(); +// } +// }); + + b = addButton(4, 2, "Quit"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + b.setToolTipText("Close all active windows and terminate TestStand"); + + setTitle("AltOS"); + + pane.doLayout(); + pane.validate(); + + doLayout(); + validate(); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + + setLocationByPlatform(false); + + /* Insets aren't set before the window is visible */ + setVisible(true); + } + + private void ConnectToDevice() { + AltosDevice device = AltosDeviceUIDialog.show(TestStand.this, + Altos.product_basestation); + + if (device != null) + telemetry_window(device); + } + + void ConfigureCallsign() { + String result; + result = JOptionPane.showInputDialog(TestStand.this, + "Configure Callsign", + AltosUIPreferences.callsign()); + if (result != null) + AltosUIPreferences.set_callsign(result); + } + + void ConfigureTeleMetrum() { + new AltosConfigFC(TestStand.this); + } + + void ConfigureTeleDongle() { + new AltosConfigTD(TestStand.this); + } + + void FlashImage() { + AltosFlashUI.show(TestStand.this); + } + + void FireIgniter() { + new AltosIgniteUI(TestStand.this); + } + + void ScanChannels() { + new AltosScanUI(TestStand.this, true); + } + + void LoadMaps() { + new AltosUIMapPreload(TestStand.this); + } + + void LaunchController() { + new AltosLaunchUI(TestStand.this); + } + + /* + * Replay a flight from telemetry data + */ + private void Replay() { + AltosDataChooser chooser = new AltosDataChooser( + TestStand.this); + + AltosRecordSet set = chooser.runDialog(); + if (set != null) { + AltosReplayReader reader = new AltosReplayReader(set, chooser.file()); + new AltosFlightUI(voice, reader); + } + } + + /* Connect to TeleMetrum, either directly or through + * a TeleDongle over the packet link + */ + + public void graph_flights(AltosEepromList flights) { + for (AltosEepromLog flight : flights) { + if (flight.graph_selected && flight.file != null) { + process_graph(flight.file); + } + } + } + + private void SaveFlightData() { + new AltosEepromManage(this, this, AltosLib.product_any); + } + + private static AltosFlightSeries make_series(AltosRecordSet set) { + AltosFlightSeries series = new AltosFlightSeries(set.cal_data()); + set.capture_series(series); + series.finish(); + return series; + } + + /* Load a flight log file and write out a CSV file containing + * all of the data in standard units + */ + + private void ExportData() { + AltosDataChooser chooser; + chooser = new AltosDataChooser(this); + AltosRecordSet set = chooser.runDialog(); + if (set == null) + return; + AltosFlightSeries series = make_series(set); + new AltosCSVUI(TestStand.this, series, chooser.file()); + } + + /* Load a flight log CSV file and display a pretty graph. + */ + + private void GraphData() { + AltosDataChooser chooser; + chooser = new AltosDataChooser(this); + AltosRecordSet set = chooser.runDialog(); + if (set == null) + return; + try { + new AltosGraphUI(set, chooser.file()); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } + + private void ConfigureTestStand() { + new AltosConfigureUI(TestStand.this, voice); + } + + private void IdleMonitor() { + try { + new AltosIdleMonitorUI(this); + } catch (Exception e) { + } + } + + static AltosWriter open_csv(File file) { + try { + return new AltosCSV(file); + } catch (FileNotFoundException fe) { + System.out.printf("%s\n", fe.getMessage()); + return null; + } + } + + static AltosWriter open_kml(File file) { + try { + return new AltosKML(file); + } catch (FileNotFoundException fe) { + System.out.printf("%s\n", fe.getMessage()); + return null; + } + } + + static AltosRecordSet record_set(File input) { + try { + return AltosLib.record_set(input); + } catch (IOException ie) { + String message = ie.getMessage(); + if (message == null) + message = String.format("%s (I/O error)", input.toString()); + System.err.printf("%s: %s\n", input.toString(), message); + } + return null; + } + + static final int process_none = 0; + static final int process_csv = 1; + static final int process_kml = 2; + static final int process_graph = 3; + static final int process_replay = 4; + static final int process_summary = 5; + static final int process_oneline = 6; + + static boolean process_csv(File input) { + AltosRecordSet set = record_set(input); + if (set == null) + return false; + + File output = Altos.replace_extension(input,".csv"); + System.out.printf("Processing \"%s\" to \"%s\"\n", input, output); + if (input.equals(output)) { + System.out.printf("Not processing '%s'\n", input); + return false; + } else { + AltosWriter writer = open_csv(output); + if (writer == null) + return false; + AltosFlightSeries series = make_series(set); + writer.write(series); + writer.close(); + } + return true; + } + + static boolean process_kml(File input) { + AltosRecordSet set = record_set(input); + if (set == null) + return false; + + File output = Altos.replace_extension(input,".kml"); + System.out.printf("Processing \"%s\" to \"%s\"\n", input, output); + if (input.equals(output)) { + System.out.printf("Not processing '%s'\n", input); + return false; + } else { + AltosWriter writer = open_kml(output); + if (writer == null) + return false; + AltosFlightSeries series = make_series(set); + series.finish(); + writer.write(series); + writer.close(); + return true; + } + } + + static AltosReplayReader replay_file(File file) { + AltosRecordSet set = record_set(file); + if (set == null) + return null; + return new AltosReplayReader(set, file); + } + + static boolean process_replay(File file) { + AltosReplayReader reader = replay_file(file); + if (reader == null) + return false; + AltosFlightUI flight_ui = new AltosFlightUI(new AltosVoice(), reader); + return true; + } + + static boolean process_graph(File file) { + AltosRecordSet set = record_set(file); + if (set == null) + return false; + try { + new AltosGraphUI(set, file); + return true; + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + return false; + } + + static boolean process_summary(File file) { + AltosRecordSet set = record_set(file); + if (set == null) + return false; + System.out.printf("%s:\n", file.toString()); + AltosFlightSeries series = make_series(set); + AltosFlightStats stats = new AltosFlightStats(series); + if (stats.serial != AltosLib.MISSING) + System.out.printf("Serial: %5d\n", stats.serial); + if (stats.flight != AltosLib.MISSING) + System.out.printf("Flight: %5d\n", stats.flight); + if (stats.year != AltosLib.MISSING) + System.out.printf("Date: %04d-%02d-%02d\n", + stats.year, stats.month, stats.day); + if (stats.hour != AltosLib.MISSING) + System.out.printf("Time: %02d:%02d:%02d UTC\n", + stats.hour, stats.minute, stats.second); + if (stats.max_height != AltosLib.MISSING) + System.out.printf("Max height: %6.0f m %6.0f ft\n", + stats.max_height, + AltosConvert.meters_to_feet(stats.max_height)); + if (stats.max_speed != AltosLib.MISSING) + System.out.printf("Max speed: %6.0f m/s %6.0f ft/s %6.4f Mach\n", + stats.max_speed, + AltosConvert.meters_to_feet(stats.max_speed), + AltosConvert.meters_to_mach(stats.max_speed)); + if (stats.max_acceleration != AltosLib.MISSING) { + System.out.printf("Max accel: %6.0f m/s² %6.0f ft/s² %6.2f g\n", + stats.max_acceleration, + AltosConvert.meters_to_feet(stats.max_acceleration), + AltosConvert.meters_to_g(stats.max_acceleration)); + } + if (stats.state_speed[Altos.ao_flight_drogue] != AltosLib.MISSING) + System.out.printf("Drogue rate: %6.0f m/s %6.0f ft/s\n", + stats.state_speed[Altos.ao_flight_drogue], + AltosConvert.meters_to_feet(stats.state_speed[Altos.ao_flight_drogue])); + if (stats.state_speed[Altos.ao_flight_main] != AltosLib.MISSING) + System.out.printf("Main rate: %6.0f m/s %6.0f ft/s\n", + stats.state_speed[Altos.ao_flight_main], + AltosConvert.meters_to_feet(stats.state_speed[Altos.ao_flight_main])); + if (stats.landed_time != AltosLib.MISSING && + stats.boost_time != AltosLib.MISSING && + stats.landed_time > stats.boost_time) + System.out.printf("Flight time: %6.0f s\n", + stats.landed_time - + stats.boost_time); + System.out.printf("\n"); + return true; + } + + static boolean process_oneline(File file) { + AltosRecordSet set = record_set(file); + if (set == null) + return false; + System.out.printf("%s", file.toString()); + AltosFlightSeries series = make_series(set); + AltosFlightStats stats = new AltosFlightStats(series); + if (stats.max_height != AltosLib.MISSING) + System.out.printf(" height %6.0f m", stats.max_height); + if (stats.max_speed != AltosLib.MISSING) + System.out.printf(" speed %6.0f m/s", stats.max_speed); + if (stats.state_enter_speed[AltosLib.ao_flight_drogue] != AltosLib.MISSING) + System.out.printf(" drogue-deploy %6.0f m/s", stats.state_enter_speed[AltosLib.ao_flight_drogue]); + if (stats.max_acceleration != AltosLib.MISSING) + System.out.printf(" accel %6.0f m/s²", stats.max_acceleration); + System.out.printf("\n"); + return true; + } + + public static void help(int code) { + System.out.printf("Usage: teststand [OPTION]... [FILE]...\n"); + System.out.printf(" Options:\n"); + System.out.printf(" --replay \t\trelive the glory of past flights \n"); + System.out.printf(" --graph \t\tgraph a flight\n"); + System.out.printf(" --summary \t\tText summary of a flight\n"); + System.out.printf(" --oneline \t\tOne line summary of a flight\n"); + System.out.printf(" --csv\tgenerate comma separated output for spreadsheets, etc\n"); + System.out.printf(" --kml\tgenerate KML output for use with Google Earth\n"); + System.exit(code); + } + + public static void main(final String[] args) { + int errors = 0; + load_library(null); + try { + UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel()); + } catch (Exception e) { + } + TestStand teststand = null; + + /* Handle batch-mode */ + if (args.length == 0) { + teststand = new TestStand(); + java.util.List devices = AltosUSBDevice.list(Altos.product_basestation); + if (devices != null) + for (AltosDevice device : devices) + teststand.telemetry_window(device); + } else { + int process = process_none; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--help")) + help(0); + else if (args[i].equals("--replay")) + process = process_replay; + else if (args[i].equals("--kml")) + process = process_kml; + else if (args[i].equals("--csv")) + process = process_csv; + else if (args[i].equals("--graph")) + process = process_graph; + else if (args[i].equals("--summary")) + process = process_summary; + else if (args[i].equals("--oneline")) + process = process_oneline; + else if (args[i].startsWith("--")) + help(1); + else { + File file = new File(args[i]); + switch (process) { + case process_none: + if (teststand == null) + teststand = new TestStand(); + case process_graph: + if (!process_graph(file)) + ++errors; + break; + case process_replay: + if (!process_replay(file)) + ++errors; + break; + case process_kml: + if (!process_kml(file)) + ++errors; + break; + case process_csv: + if (!process_csv(file)) + ++errors; + break; + case process_summary: + if (!process_summary(file)) + ++errors; + break; + case process_oneline: + if (!process_oneline(file)) + ++errors; + break; + } + } + } + } + if (errors != 0) + System.exit(errors); + } +} diff --git a/teststand/altosui-fat b/teststand/altosui-fat new file mode 100755 index 00000000..95b1c051 --- /dev/null +++ b/teststand/altosui-fat @@ -0,0 +1,4 @@ +#!/bin/sh +me=`which "$0"` +dir=`dirname "$me"` +exec java -cp "$dir/*" -Djava.library.path="$dir" -jar "$dir"/altosui-fat.jar "$@" diff --git a/teststand/altosuilib.jar b/teststand/altosuilib.jar new file mode 120000 index 00000000..4bfbb6ce --- /dev/null +++ b/teststand/altosuilib.jar @@ -0,0 +1 @@ +../altosuilib/altosuilib.jar \ No newline at end of file diff --git a/teststand/altusmetrum-teststand.desktop.in b/teststand/altusmetrum-teststand.desktop.in new file mode 100644 index 00000000..1128d57d --- /dev/null +++ b/teststand/altusmetrum-teststand.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Test Stand UI +GenericName=Altus Metrum Test Stand +Comment=Operate and analayze data from Altus Metrum wireless test stand +Icon=%icondir%/altusmetrum-teststand.svg +Exec=%bindir%/teststand %F +Terminal=false +MimeType=application/vnd.altusmetrum.telemetry;application/vnd.altusmetrum.eeprom +Categories=Education;Electronics;Science; diff --git a/teststand/altusmetrum.jpg b/teststand/altusmetrum.jpg new file mode 100644 index 0000000000000000000000000000000000000000..04027921d44149edada1e36f05f1f4e5c01cc789 GIT binary patch literal 72868 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 literal 0 HcmV?d00001 diff --git a/teststand/linux-install.sh b/teststand/linux-install.sh new file mode 100644 index 00000000..2e44c2aa --- /dev/null +++ b/teststand/linux-install.sh @@ -0,0 +1,244 @@ +#!/bin/sh + +can_ask=y + +finish() +{ + if [ "$can_ask" = "y" ]; then + echo "" + echo -n "Press enter to continue..." + read foo + fi + exit $1 +} + +# +# Make sure we have a terminal to talk to +# + +if tty -s; then + : +else + case "$DISPLAY" in + "") + echo 'No user input available' + can_ask=n + ;; + *) + GUESS_XTERMS="x-terminal-emulator xterm rxvt roxterm gnome-terminal dtterm eterm Eterm kvt konsole aterm" + for a in $GUESS_XTERMS; do + if type $a >/dev/null 2>&1; then + XTERM=$a + break + fi + done + case "$XTERM" in + "") + echo 'No terminal emulator available' + can_ask=n + ;; + *) + exec "$XTERM" -e "sh '$0'" + ;; + esac + ;; + esac +fi + +# +# Make sure we can run java +# + +echo -n "Checking for java..." + +if java -version > /dev/null 2>&1; then + echo " found it." +else + echo " java isn't working." + echo "" + echo "You'll need to install a java runtime system" + echo "on this computer before AltOS will work properly." + finish 1 +fi + +# +# Pick an installation target +# + +if [ '(' -d /opt -a -w /opt ')' -o '(' -d /opt/AltOS -a -w /opt/AltOS ')' ]; then + target_default=/opt +else + target_default="$HOME" +fi + +case "$#" in +0) + echo -n "Installation location [default: $target_default] " + if [ "$can_ask" = "y" ]; then + read target + else + echo "" + target="" + fi + case "$target" in + "") + target="$target_default" + ;; + esac + ;; +*) + target="$1" + ;; +esac + +target_altos="$target"/AltOS + +echo -n "Installing to $target..." + +# +# Make sure the target exists +# +mkdir -p "$target_altos" + +if [ ! -d "$target_altos" ]; then + echo "$target_altos does not exist and cannot be created" + finish 1 +fi + +if [ ! -w "$target_altos" ]; then + echo "$target_altos cannot be written" + finish 1 +fi + +# +# Unpack the tar archive appended to the end of this script +# +archive_line=`awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0"` + +tail -n+$archive_line "$0" | tar xjf - -C "$target" + +case $? in +0) + echo " done." + ;; +*) + echo "Install failed." + finish 1 + ;; +esac + +# +# Create the .desktop file by editing the paths +# + +case "$target" in +/*) + target_abs="$target" + ;; +*) + target_abs=`pwd`/$target + ;; +esac + +BIN="$target_abs"/AltOS + +for infile in "$target"/AltOS/*.desktop.in; do + desktop="$target"/AltOS/`basename "$infile" .in` + rm -f "$desktop" + sed -e "s;%bindir%;$BIN;" -e "s;%icondir%;$BIN;" "$infile" > "$desktop" + chmod +x "$desktop" +done + +# +# Install the .desktop file +# + +for desktop in "$target"/AltOS/*.desktop; do + case `id -u` in + 0) + xdg-desktop-menu install --mode system "$desktop" + ;; + *) + xdg-desktop-menu install --mode user "$desktop" + ;; + esac +done + +# +# Install mime type file +# + +for mimetype in "$target"/AltOS/*-mimetypes.xml; do + case `id -u` in + 0) + xdg-mime install --mode system "$mimetype" + ;; + *) + xdg-mime install --mode user "$mimetype" + ;; + esac +done + +# +# Install icons +# + +for icon_dir in /usr/share/icons/hicolor/scalable/mimetypes "$HOME/.icons" "$HOME/.kde/share/icons"; do + if [ -w "$icon_dir" ]; then + cp "$target"/AltOS/*.svg "$icon_dir" + update-icon-caches "$icon_dir" + fi +done + + +# +# Install icon to desktop if desired +# + +if [ -d $HOME/Desktop ]; then + default_desktop=n + if [ "$can_ask" = "y" ]; then + : + else + default_desktop=y + fi + + answered=n + while [ "$answered" = "n" ]; do + echo -n "Install icons to desktop? [default: $default_desktop] " + if [ "$can_ask" = "y" ]; then + read do_desktop + else + echo + do_desktop="" + fi + + case "$do_desktop" in + "") + do_desktop=$default_desktop + ;; + esac + + case "$do_desktop" in + [yYnN]*) + answered=y + ;; + esac + done + + case "$do_desktop" in + [yY]*) + echo -n "Installing desktop icons..." + for d in "$target"/AltOS/*.desktop; do + base=`basename $d` + cp --remove-destination "$d" "$HOME/Desktop/" + done + ;; + esac + + echo " done." +fi + +finish 0 + +__ARCHIVE_BELOW__ diff --git a/teststand/mdwn.tmpl b/teststand/mdwn.tmpl new file mode 100644 index 00000000..b64825a6 --- /dev/null +++ b/teststand/mdwn.tmpl @@ -0,0 +1,7 @@ +[[!inline pages="./%version%/release-notes-%version%.html" rss="no" raw="yes" ]] + +- Available Files: + - [Windows Installer](/AltOS/releases/%version%/Altos-Windows-%version_dash%.exe) + - [Mac OS X Package](/AltOS/releases/%version%/Altos-Mac-%version%.dmg) + - [Linux](/AltOS/releases/%version%/Altos-Linux-%version%.sh) + - [Source Snapshot](http://git.gag.com/?p=fw/altos;a=snapshot;h=refs/tags/%version%;sf=tgz) diff --git a/teststand/teststand-windows.nsi.in b/teststand/teststand-windows.nsi.in new file mode 100644 index 00000000..a6840828 --- /dev/null +++ b/teststand/teststand-windows.nsi.in @@ -0,0 +1,235 @@ +!addplugindir Instdrv/NSIS/Plugins +!addincludedir Instdrv/NSIS/Includes +!include x64.nsh +!include java.nsh +!include refresh-sh.nsh + +!define REG_NAME "Altus Metrum" +!define PROG_ID_EEPROM "altusmetrum.teststand.eeprom.1" +!define FAT_NAME "teststand-fat.jar" +!define WIN_APP_ICON "altusmetrum-teststand.ico" +!define WIN_APP_EXE "altusmetrum-teststand.exe" +!define WIN_EEPROM_EXE "application-vnd.altusmetrum.eeprom.exe" + +Name "${REG_NAME} Installer" + +; Default install directory +InstallDir "$PROGRAMFILES\AltusMetrum" + +; Tell the installer where to re-install a new version +InstallDirRegKey HKLM "Software\${REG_NAME}" "Install_Dir" + +LicenseText "GNU General Public License Version 2" +LicenseData "../COPYING" + +; Need admin privs for Vista or Win7 +RequestExecutionLevel admin + +ShowInstDetails Show + +ComponentText "${REG_NAME} Software and Driver Installer" + +Function .onInit + DetailPrint "Checking host operating system" + ${If} ${RunningX64} + DetailPrint "Installer running on 64-bit host" + SetRegView 64 + StrCpy $INSTDIR "$PROGRAMFILES64\AltusMetrum" + ${DisableX64FSRedirection} + ${EndIf} +FunctionEnd + +Function un.onInit + DetailPrint "Checking host operating system" + ${If} ${RunningX64} + DetailPrint "Installer running on 64-bit host" + SetRegView 64 + StrCpy $INSTDIR "$PROGRAMFILES64\AltusMetrum" + ${DisableX64FSRedirection} + ${EndIf} +FunctionEnd + +; Pages to present + +Page license +Page components +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +; And the stuff to install + +Section "Install Driver" InstDriver + + InstDrv::InitDriverSetup /NOUNLOAD {4D36E96D-E325-11CE-BFC1-08002BE10318} AltusMetrumSerial + Pop $0 + DetailPrint "InitDriverSetup: $0" + InstDrv::DeleteOemInfFiles /NOUNLOAD + InstDrv::CreateDevice /NOUNLOAD + + SetOutPath $INSTDIR + File "../altusmetrum.inf" + File "../altusmetrum.cat" + + ${DisableX64FSRedirection} + IfFileExists $WINDIR\System32\PnPutil.exe 0 nopnp + ${DisableX64FSRedirection} + nsExec::ExecToLog '"$WINDIR\System32\PnPutil.exe" -i -a "$INSTDIR\altusmetrum.inf"' + Goto done +nopnp: + InstDrv::InstallDriver /NOUNLOAD "$INSTDIR\altusmetrum.inf" +done: + +SectionEnd + +Section "${REG_NAME} Application" + Call DetectJRE + + SetOutPath $INSTDIR + + File "${FAT_NAME}" + File "altoslib_@ALTOSLIB_VERSION@.jar" + File "altosuilib_@ALTOSUILIB_VERSION@.jar" + File "cmudict04.jar" + File "cmulex.jar" + File "cmu_time_awb.jar" + File "cmutimelex.jar" + File "cmu_us_kal.jar" + File "en_us.jar" + File "freetts.jar" + File "jfreechart.jar" + File "jcommon.jar" + File "../icon/${WIN_APP_EXE}" + + File "*.dll" + + File "../icon/${WIN_APP_ICON}" + + CreateShortCut "$SMPROGRAMS\${REG_NAME}.lnk" "$INSTDIR\${WIN_APP_EXE}" "" "$INSTDIR\${WIN_APP_ICON}" +SectionEnd + +Section "${REG_NAME} Desktop Shortcut" + CreateShortCut "$DESKTOP\${REG_NAME}.lnk" "$INSTDIR\${WIN_APP_EXE}" "" "$INSTDIR\${WIN_APP_ICON}" +SectionEnd + +Section "Firmware" + + SetOutPath $INSTDIR + + File "../src/telemetrum-v1.0/telemetrum-v1.0-${VERSION}.ihx" + File "../src/telemetrum-v1.1/telemetrum-v1.1-${VERSION}.ihx" + File "../src/telemetrum-v1.2/telemetrum-v1.2-${VERSION}.ihx" + File "../src/telemetrum-v2.0/telemetrum-v2.0-${VERSION}.ihx" +; File "../src/telemetrum-v3.0/telemetrum-v3.0-${VERSION}.ihx" + File "../src/telemini-v1.0/telemini-v1.0-${VERSION}.ihx" + File "../src/telemini-v3.0/telemini-v3.0-${VERSION}.ihx" + File "../src/telegps-v1.0/telegps-v1.0-${VERSION}.ihx" + File "../src/teledongle-v0.2/teledongle-v0.2-${VERSION}.ihx" + File "../src/teledongle-v3.0/teledongle-v3.0-${VERSION}.ihx" + File "../src/telebt-v1.0/telebt-v1.0-${VERSION}.ihx" + File "../src/telebt-v3.0/telebt-v3.0-${VERSION}.ihx" + File "../src/telebt-v4.0/telebt-v4.0-${VERSION}.ihx" + File "../src/telemega-v1.0/telemega-v1.0-${VERSION}.ihx" + File "../src/telemega-v2.0/telemega-v2.0-${VERSION}.ihx" + File "../src/easymini-v1.0/easymini-v1.0-${VERSION}.ihx" + File "../src/easymini-v2.0/easymini-v2.0-${VERSION}.ihx" + File "../src/easymega-v1.0/easymega-v1.0-${VERSION}.ihx" + +SectionEnd + +Section "Documentation" + + SetOutPath $INSTDIR + + File "../doc/altusmetrum.pdf" + File "../doc/altos.pdf" + File "../doc/telemetry.pdf" + File "../doc/telemetrum-outline.pdf" + File "../doc/telemega-outline.pdf" + File "../doc/easymini-outline.pdf" + File "../doc/telemini-v1-outline.pdf" + File "../doc/telemini-v3-outline.pdf" +SectionEnd + +Section "File Associations" + + ${DisableX64FSRedirection} + + SetOutPath $INSTDIR + + File "../icon/${WIN_EEPROM_EXE}" + + DeleteRegKey HKCR "${PROG_ID_EEPROM}" + + DeleteRegKey HKCR ".eeprom\${PROG_ID_EEPROM}" + DeleteRegValue HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}" + DeleteRegKey HKCR ".telem\${PROG_ID_EEPROM}" + DeleteRegValue HKCR ".telem\OpenWithProgids" "${PROG_ID_EEPROM}" + + ; .eeprom elements + + WriteRegStr HKCR "${PROG_ID_EEPROM}" "" "Altus Metrum Log File" + WriteRegStr HKCR "${PROG_ID_EEPROM}" "FriendlyTypeName" "Altus Metrum Log File" + WriteRegStr HKCR "${PROG_ID_EEPROM}\CurVer" "" "${PROG_ID_EEPROM}" + WriteRegStr HKCR "${PROG_ID_EEPROM}\DefaultIcon" "" '"$INSTDIR\${WIN_EEPROM_EXE}",-101' + WriteRegExpandStr HKCR "${PROG_ID_EEPROM}\shell\open\command" "" '"$INSTDIR\${WIN_APP_EXE}" "%1"' + + WriteRegStr HKCR ".eeprom" "" "${PROG_ID_EEPROM}" + WriteRegStr HKCR ".eeprom" "PerceivedType" "Altus Metrum Log File" + WriteRegStr HKCR ".eeprom" "Content Type" "application/vnd.altusmetrum.eeprom" + + WriteRegStr HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}" "" + WriteRegStr HKCR ".eeprom\${PROG_ID_EEPROM}" "" "${REG_NAME}" + + Call RefreshShellIcons +SectionEnd + +Section "Uninstaller" + + ; Deal with the uninstaller + + ${DisableX64FSRedirection} + SetOutPath $INSTDIR + + ; Write the install path to the registry + WriteRegStr HKLM "SOFTWARE\${REG_NAME}" "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for windows + WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "DisplayName" "${REG_NAME}" + WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "UninstallString" '"$INSTDIR\uninstall-${REG_NAME}.exe"' + WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "NoModify" "1" + WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "NoRepair" "1" + + WriteUninstaller "uninstall-${REG_NAME}.exe" +SectionEnd + +Section "Uninstall" + + ${DisableX64FSRedirection} + + DeleteRegKey HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" + DeleteRegKey HKLM "SOFTWARE\${REG_NAME}" + + DetailPrint "Delete uninstall reg entries" + + DeleteRegKey HKCR "${PROG_ID_EEPROM}" + + DeleteRegKey HKCR ".eeprom\${PROG_ID_EEPROM}" + DeleteRegValue HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}" + + DetailPrint "Delete file association reg entries" + + Delete "$INSTDIR\${FAT_NAME}" + Delete "$INSTDIR\uninstall-${REG_NAME}.exe" + + Delete "$INSTDIR\${WIN_APP_ICON}" + Delete "$INSTDIR\${WIN_APP_EXE}" + + ; Remove shortcuts, if any + Delete "$SMPROGRAMS\${REG_NAME}.lnk" + Delete "$DESKTOP\${REG_NAME}.lnk" + + Call un.RefreshShellIcons +SectionEnd diff --git a/teststand/teststand.1 b/teststand/teststand.1 new file mode 100644 index 00000000..0704d32c --- /dev/null +++ b/teststand/teststand.1 @@ -0,0 +1,39 @@ +.\" +.\" Copyright © 2018 Bdale Garbee +.\" +.\" 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; either version 3 of the License, or +.\" (at your option) any later version. +.\" +.\" 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. +.\" +.\" +.TH ALTOSUI 1 "teststand" "" +.SH NAME +teststand \- Wireless Test Stand control program +.SH SYNOPSIS +.B "teststand" +.SH DESCRIPTION +.I teststand +connects to a TeleDongle or TeleBT device through a USB serial device. +It provides a menu-oriented +user interface to initiate, monitor, record and review rocket motor +test stand data. +.SH USAGE +With a TeleDongle or TeleBT attached, teststand makes it possible to +control a remote Altus Metrum wireless test stand board. +.SH FILES +All downloaded test stand data files are recorded into a user-specified +directory (default ~/TeleMetrum). Files are named using the current date, +the serial number of the reported device, the test number recorded in +the data and '.eeprom' for eeprom data. +.SH AUTHOR +Bdale Garbee -- 2.30.2