summaryrefslogtreecommitdiff
path: root/altosui
diff options
context:
space:
mode:
Diffstat (limited to 'altosui')
-rw-r--r--altosui/.gitignore22
-rw-r--r--altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml1
-rw-r--r--altosui/AltOS Package Configuration.pmdoc/01altosui.xml1
-rw-r--r--altosui/AltOS Package Configuration.pmdoc/index.xml1
-rw-r--r--altosui/Altos.java97
-rw-r--r--altosui/AltosAscent.java406
-rw-r--r--altosui/AltosBTDevice.java123
-rw-r--r--altosui/AltosBTDeviceIterator.java63
-rw-r--r--altosui/AltosBTKnown.java103
-rw-r--r--altosui/AltosBTManage.java359
-rw-r--r--altosui/AltosCSV.java338
-rw-r--r--altosui/AltosCSVUI.java109
-rw-r--r--altosui/AltosChannelMenu.java45
-rw-r--r--altosui/AltosCompanionInfo.java113
-rw-r--r--altosui/AltosConfig.java521
-rw-r--r--altosui/AltosConfigFreqUI.java419
-rw-r--r--altosui/AltosConfigTD.java374
-rw-r--r--altosui/AltosConfigTDUI.java355
-rw-r--r--altosui/AltosConfigUI.java808
-rw-r--r--altosui/AltosConfigureUI.java416
-rw-r--r--altosui/AltosDataChooser.java83
-rw-r--r--altosui/AltosDataPoint.java30
-rw-r--r--altosui/AltosDataPointReader.java86
-rw-r--r--altosui/AltosDebug.java280
-rw-r--r--altosui/AltosDescent.java440
-rw-r--r--altosui/AltosDevice.java31
-rw-r--r--altosui/AltosDeviceDialog.java188
-rw-r--r--altosui/AltosDialog.java63
-rw-r--r--altosui/AltosDisplayThread.java279
-rw-r--r--altosui/AltosEepromDelete.java151
-rw-r--r--altosui/AltosEepromDownload.java495
-rw-r--r--altosui/AltosEepromList.java126
-rw-r--r--altosui/AltosEepromManage.java237
-rw-r--r--altosui/AltosEepromMonitor.java261
-rw-r--r--altosui/AltosEepromSelect.java189
-rw-r--r--altosui/AltosFlash.java380
-rw-r--r--altosui/AltosFlashUI.java308
-rw-r--r--altosui/AltosFlightDisplay.java28
-rw-r--r--altosui/AltosFlightInfoTableModel.java85
-rw-r--r--altosui/AltosFlightStats.java162
-rw-r--r--altosui/AltosFlightStatsTable.java115
-rw-r--r--altosui/AltosFlightStatus.java185
-rw-r--r--altosui/AltosFlightStatusTableModel.java67
-rw-r--r--altosui/AltosFlightStatusUpdate.java46
-rw-r--r--altosui/AltosFlightUI.java331
-rw-r--r--altosui/AltosFontListener.java22
-rw-r--r--altosui/AltosFrame.java57
-rw-r--r--altosui/AltosFreqList.java88
-rw-r--r--altosui/AltosGraph.java27
-rw-r--r--altosui/AltosGraphTime.java236
-rw-r--r--altosui/AltosGraphUI.java313
-rw-r--r--altosui/AltosHexfile.java253
-rw-r--r--altosui/AltosIdleMonitorUI.java190
-rw-r--r--altosui/AltosIgniteUI.java423
-rw-r--r--altosui/AltosInfoTable.java211
-rw-r--r--altosui/AltosKML.java176
-rw-r--r--altosui/AltosLanded.java302
-rw-r--r--altosui/AltosLaunch.java202
-rw-r--r--altosui/AltosLaunchUI.java517
-rw-r--r--altosui/AltosLed.java55
-rw-r--r--altosui/AltosLights.java74
-rw-r--r--altosui/AltosPad.java358
-rw-r--r--altosui/AltosRomconfig.java148
-rw-r--r--altosui/AltosRomconfigUI.java197
-rw-r--r--altosui/AltosScanUI.java553
-rw-r--r--altosui/AltosSerial.java209
-rw-r--r--altosui/AltosSerialInUseException.java26
-rw-r--r--altosui/AltosSiteMap.java447
-rw-r--r--altosui/AltosSiteMapCache.java104
-rw-r--r--altosui/AltosSiteMapPreload.java474
-rw-r--r--altosui/AltosSiteMapTile.java130
-rwxr-xr-xaltosui/AltosUI.app/Contents/MacOS/JavaApplicationStubbin0 -> 61296 bytes
-rw-r--r--altosui/AltosUI.app/Contents/PkgInfo1
-rw-r--r--altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icnsbin0 -> 129010 bytes
-rw-r--r--altosui/AltosUI.java603
-rw-r--r--altosui/AltosUIListener.java22
-rw-r--r--altosui/AltosUIPreferences.java176
-rw-r--r--altosui/AltosUSBDevice.java112
-rw-r--r--altosui/AltosVersion.java.in22
-rw-r--r--altosui/AltosVoice.java95
-rw-r--r--altosui/AltosWriter.java34
-rw-r--r--altosui/GrabNDrag.java55
-rw-r--r--altosui/Info.plist.in46
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi84
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exebin0 -> 51831 bytes
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c704
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp110
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw29
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt141
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf137
-rw-r--r--altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sysbin0 -> 30464 bytes
-rw-r--r--altosui/Instdrv/NSIS/Plugins/InstDrv.dllbin0 -> 6656 bytes
-rw-r--r--altosui/Makefile-standalone184
-rw-r--r--altosui/Makefile.am330
-rw-r--r--altosui/altos-windows.nsi168
-rwxr-xr-xaltosui/altosui-fat4
-rw-r--r--altosui/altosui.146
-rw-r--r--altosui/altusmetrum.jpgbin0 -> 72868 bytes
-rw-r--r--altosui/launch-sites.txt13
-rw-r--r--altosui/libaltos/.gitignore12
-rw-r--r--altosui/libaltos/Makefile-standalone126
-rw-r--r--altosui/libaltos/Makefile.am54
-rw-r--r--altosui/libaltos/cjnitest.c71
-rw-r--r--altosui/libaltos/libaltos.c1318
-rwxr-xr-xaltosui/libaltos/libaltos.dylibbin0 -> 41648 bytes
-rw-r--r--altosui/libaltos/libaltos.h119
-rw-r--r--altosui/libaltos/libaltos.i05
107 files changed, 19933 insertions, 0 deletions
diff --git a/altosui/.gitignore b/altosui/.gitignore
new file mode 100644
index 00000000..6d2d8c23
--- /dev/null
+++ b/altosui/.gitignore
@@ -0,0 +1,22 @@
+windows/
+linux/
+macosx/
+fat/
+Manifest.txt
+Manifest-fat.txt
+AltosVersion.java
+Info.plist
+libaltosJNI
+classes
+altosui
+altosui-test
+altosui-jdb
+classaltosui.stamp
+Altos-Linux-*.tar.bz2
+Altos-Mac-*.zip
+Altos-Windows-*.exe
+*.dll
+*.dylib
+*.so
+*.jar
+*.class
diff --git a/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml b/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml
new file mode 100644
index 00000000..18e00fe4
--- /dev/null
+++ b/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml
@@ -0,0 +1 @@
+<pkg-contents spec="1.12"><f n="AltosUI.app" o="keithp" g="keithp" p="16877" pt="/Users/keithp/altos/ao-tools/altosui/AltosUI.app" m="false" t="file"><f n="Contents" o="keithp" g="keithp" p="16877"><f n="Info.plist" o="keithp" g="keithp" p="33188"/><f n="MacOS" o="keithp" g="keithp" p="16877"><f n="JavaApplicationStub" o="keithp" g="keithp" p="33133"/></f><f n="PkgInfo" o="keithp" g="keithp" p="33188"/><f n="Resources" o="keithp" g="keithp" p="16877"><f n="AltosUIIcon.icns" o="keithp" g="keithp" p="33188"/><f n="Java" o="keithp" g="keithp" p="16877"/></f></f></f></pkg-contents> \ No newline at end of file
diff --git a/altosui/AltOS Package Configuration.pmdoc/01altosui.xml b/altosui/AltOS Package Configuration.pmdoc/01altosui.xml
new file mode 100644
index 00000000..6170931b
--- /dev/null
+++ b/altosui/AltOS Package Configuration.pmdoc/01altosui.xml
@@ -0,0 +1 @@
+<pkgref spec="1.12" uuid="C5762664-2F26-4536-94C4-56F0FBC08D1A"><config><identifier>org.altusmetrum.altosUi.AltosUI.pkg</identifier><version>0.7</version><description></description><post-install type="none"/><installFrom relative="true" mod="true">AltosUI.app</installFrom><installTo mod="true" relocatable="true">/Applications/AltosUI.app</installTo><flags><followSymbolicLinks/></flags><packageStore type="internal"></packageStore><mod>installTo.path</mod><mod>installFrom.isRelativeType</mod><mod>version</mod><mod>parent</mod><mod>requireAuthorization</mod><mod>installTo</mod></config><contents><file-list>01altosui-contents.xml</file-list><filter>/CVS$</filter><filter>/\.svn$</filter><filter>/\.cvsignore$</filter><filter>/\.cvspass$</filter><filter>/\.DS_Store$</filter></contents></pkgref> \ No newline at end of file
diff --git a/altosui/AltOS Package Configuration.pmdoc/index.xml b/altosui/AltOS Package Configuration.pmdoc/index.xml
new file mode 100644
index 00000000..fabe54a6
--- /dev/null
+++ b/altosui/AltOS Package Configuration.pmdoc/index.xml
@@ -0,0 +1 @@
+<pkmkdoc spec="1.12"><properties><title>AltOS UI</title><build>/Users/keithp/altos/ao-tools/altosui/AltosUI.pkg</build><organization>org.altusmetrum</organization><userSees ui="both"/><min-target os="3"/><domain system="true" user="true"/></properties><distribution><versions min-spec="1.000000"/><scripts></scripts></distribution><description>Install AltOS User Interface</description><contents><choice title="AltosUI" id="choice0" starts_selected="true" starts_enabled="true" starts_hidden="false"><pkgref id="org.altusmetrum.altosUi.AltosUI.pkg"/></choice></contents><resources bg-scale="tofit" bg-align="center"><locale lang="en"><resource relative="true" mod="true" type="background">altusmetrum.jpg</resource></locale></resources><flags/><item type="file">01altosui.xml</item><mod>properties.anywhereDomain</mod><mod>properties.title</mod><mod>properties.customizeOption</mod><mod>description</mod><mod>properties.userDomain</mod><mod>properties.systemDomain</mod></pkmkdoc> \ No newline at end of file
diff --git a/altosui/Altos.java b/altosui/Altos.java
new file mode 100644
index 00000000..cd17a93e
--- /dev/null
+++ b/altosui/Altos.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.util.*;
+import java.text.*;
+import java.nio.charset.Charset;
+
+import libaltosJNI.*;
+
+import org.altusmetrum.AltosLib.*;
+
+public class Altos extends AltosLib {
+
+ static final int tab_elt_pad = 5;
+
+ static Font label_font;
+ static Font value_font;
+ static Font status_font;
+ static Font table_label_font;
+ static Font table_value_font;
+
+ final static int font_size_small = 1;
+ final static int font_size_medium = 2;
+ final static int font_size_large = 3;
+
+ static void set_fonts(int size) {
+ int brief_size;
+ int table_size;
+ int status_size;
+
+ switch (size) {
+ case font_size_small:
+ brief_size = 16;
+ status_size = 18;
+ table_size = 11;
+ break;
+ default:
+ case font_size_medium:
+ brief_size = 22;
+ status_size = 24;
+ table_size = 14;
+ break;
+ case font_size_large:
+ brief_size = 26;
+ status_size = 30;
+ table_size = 17;
+ break;
+ }
+ label_font = new Font("Dialog", Font.PLAIN, brief_size);
+ value_font = new Font("Monospaced", Font.PLAIN, brief_size);
+ status_font = new Font("SansSerif", Font.BOLD, status_size);
+ table_label_font = new Font("SansSerif", Font.PLAIN, table_size);
+ table_value_font = new Font("Monospaced", Font.PLAIN, table_size);
+ }
+
+ static final int text_width = 20;
+
+ static public boolean initialized = false;
+ static public boolean loaded_library = false;
+
+ public static boolean load_library() {
+ if (!initialized) {
+ try {
+ System.loadLibrary("altos");
+ libaltos.altos_init();
+ loaded_library = true;
+ } catch (UnsatisfiedLinkError e) {
+ try {
+ System.loadLibrary("altos64");
+ libaltos.altos_init();
+ loaded_library = true;
+ } catch (UnsatisfiedLinkError e2) {
+ loaded_library = false;
+ }
+ }
+ initialized = true;
+ }
+ return loaded_library;
+ }
+}
diff --git a/altosui/AltosAscent.java b/altosui/AltosAscent.java
new file mode 100644
index 00000000..a158eb21
--- /dev/null
+++ b/altosui/AltosAscent.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosAscent extends JComponent implements AltosFlightDisplay {
+ GridBagLayout layout;
+ JLabel cur, max;
+
+ public class AscentStatus {
+ JLabel label;
+ JTextField value;
+ AltosLights lights;
+
+ void show() {
+ value.setVisible(true);
+ lights.setVisible(true);
+ label.setVisible(true);
+ }
+
+ void hide() {
+ value.setVisible(false);
+ lights.setVisible(false);
+ label.setVisible(false);
+ }
+
+ void show(AltosState state, int crc_errors) {}
+ void reset() {
+ value.setText("");
+ lights.set(false);
+ }
+
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ public AscentStatus (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ lights = new AltosLights();
+ c.gridx = 0; c.gridy = y;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(lights, c);
+ add(lights);
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 2; c.gridy = y;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+
+ }
+ }
+
+ public class AscentValue {
+ JLabel label;
+ JTextField value;
+ void show(AltosState state, int crc_errors) {}
+
+ void reset() {
+ value.setText("");
+ }
+
+ void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ }
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ public AscentValue (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 2; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.gridwidth = 2;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ public class AscentValueHold {
+ JLabel label;
+ JTextField value;
+ JTextField max_value;
+ double max;
+
+ void show(AltosState state, int crc_errors) {}
+
+ void reset() {
+ value.setText("");
+ max_value.setText("");
+ max = AltosRecord.MISSING;
+ }
+
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ max_value.setFont(Altos.value_font);
+ }
+
+ void show(AltosUnits units, double v) {
+ if (v == AltosRecord.MISSING) {
+ value.setText("Missing");
+ max_value.setText("Missing");
+ } else {
+ value.setText(units.show(8, v));
+ if (v > max || max == AltosRecord.MISSING) {
+ max_value.setText(units.show(8, v));
+ max = v;
+ }
+ }
+ }
+ public AscentValueHold (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 2; c.gridy = y;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+
+ max_value = new JTextField(Altos.text_width);
+ max_value.setFont(Altos.value_font);
+ max_value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 3; c.gridy = y;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(max_value, c);
+ add(max_value);
+ }
+ }
+
+
+ class Height extends AscentValueHold {
+ void show (AltosState state, int crc_errors) {
+ show(AltosConvert.height, state.height);
+ }
+ public Height (GridBagLayout layout, int y) {
+ super (layout, y, "Height");
+ }
+ }
+
+ Height height;
+
+ class Speed extends AscentValueHold {
+ void show (AltosState state, int crc_errors) {
+ double speed = state.speed;
+ if (!state.ascent)
+ speed = state.baro_speed;
+ show(AltosConvert.speed, speed);
+ }
+ public Speed (GridBagLayout layout, int y) {
+ super (layout, y, "Speed");
+ }
+ }
+
+ Speed speed;
+
+ class Accel extends AscentValueHold {
+ void show (AltosState state, int crc_errors) {
+ show(AltosConvert.accel, state.acceleration);
+ }
+ public Accel (GridBagLayout layout, int y) {
+ super (layout, y, "Acceleration");
+ }
+ }
+
+ Accel accel;
+
+ String pos(double p, String pos, String neg) {
+ String h = pos;
+ if (p < 0) {
+ h = neg;
+ p = -p;
+ }
+ int deg = (int) Math.floor(p);
+ double min = (p - Math.floor(p)) * 60.0;
+ return String.format("%s %4d° %9.6f", h, deg, min);
+ }
+
+ class Apogee extends AscentStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.drogue_sense));
+ lights.set(state.drogue_sense > 3.2);
+ }
+ public Apogee (GridBagLayout layout, int y) {
+ super(layout, y, "Apogee Igniter Voltage");
+ }
+ }
+
+ Apogee apogee;
+
+ class Main extends AscentStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.main_sense));
+ lights.set(state.main_sense > 3.2);
+ }
+ public Main (GridBagLayout layout, int y) {
+ super(layout, y, "Main Igniter Voltage");
+ }
+ }
+
+ Main main;
+
+ class Lat extends AscentValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.gps != null)
+ value.setText(pos(state.gps.lat,"N", "S"));
+ else
+ value.setText("???");
+ }
+ public Lat (GridBagLayout layout, int y) {
+ super (layout, y, "Latitude");
+ }
+ }
+
+ Lat lat;
+
+ class Lon extends AscentValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.gps != null)
+ value.setText(pos(state.gps.lon,"E", "W"));
+ else
+ value.setText("???");
+ }
+ public Lon (GridBagLayout layout, int y) {
+ super (layout, y, "Longitude");
+ }
+ }
+
+ Lon lon;
+
+ public void reset() {
+ lat.reset();
+ lon.reset();
+ main.reset();
+ apogee.reset();
+ height.reset();
+ speed.reset();
+ accel.reset();
+ }
+
+ public void set_font() {
+ cur.setFont(Altos.label_font);
+ max.setFont(Altos.label_font);
+ lat.set_font();
+ lon.set_font();
+ main.set_font();
+ apogee.set_font();
+ height.set_font();
+ speed.set_font();
+ accel.set_font();
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ if (state.gps != null && state.gps.connected) {
+ lat.show(state, crc_errors);
+ lon.show(state, crc_errors);
+ } else {
+ lat.hide();
+ lon.hide();
+ }
+ height.show(state, crc_errors);
+ if (state.main_sense != AltosRecord.MISSING)
+ main.show(state, crc_errors);
+ else
+ main.hide();
+ if (state.drogue_sense != AltosRecord.MISSING)
+ apogee.show(state, crc_errors);
+ else
+ apogee.hide();
+ speed.show(state, crc_errors);
+ accel.show(state, crc_errors);
+ }
+
+ public void labels(GridBagLayout layout, int y) {
+ GridBagConstraints c;
+
+ cur = new JLabel("Current");
+ cur.setFont(Altos.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(Altos.label_font);
+ c.gridx = 3; c.gridy = y;
+ layout.setConstraints(max, c);
+ add(max);
+ }
+
+ public AltosAscent() {
+ layout = new GridBagLayout();
+
+ setLayout(layout);
+
+ /* Elements in ascent display:
+ *
+ * lat
+ * lon
+ * height
+ */
+ labels(layout, 0);
+ height = new Height(layout, 1);
+ speed = new Speed(layout, 2);
+ accel = new Accel(layout, 3);
+ lat = new Lat(layout, 4);
+ lon = new Lon(layout, 5);
+ apogee = new Apogee(layout, 6);
+ main = new Main(layout, 7);
+ }
+}
diff --git a/altosui/AltosBTDevice.java b/altosui/AltosBTDevice.java
new file mode 100644
index 00000000..5e353fdd
--- /dev/null
+++ b/altosui/AltosBTDevice.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.lang.*;
+import java.util.*;
+import libaltosJNI.*;
+
+public class AltosBTDevice extends altos_bt_device implements AltosDevice {
+
+ public String getProductName() {
+ String name = getName();
+ if (name == null)
+ return "Altus Metrum";
+ int dash = name.lastIndexOf("-");
+ if (dash < 0)
+ return name;
+ return name.substring(0,dash);
+ }
+
+ public int getProduct() {
+ if (Altos.bt_product_telebt.equals(getProductName()))
+ return Altos.product_telebt;
+ return 0;
+ }
+
+ public String getPath() {
+ return getAddr();
+ }
+
+ public String getErrorString() {
+ altos_error error = new altos_error();
+
+ libaltos.altos_get_last_error(error);
+ return String.format("%s (%d)", error.getString(), error.getCode());
+ }
+
+ public int getSerial() {
+ String name = getName();
+ if (name == null)
+ return 0;
+ int dash = name.lastIndexOf("-");
+ if (dash < 0 || dash >= name.length())
+ return 0;
+ String sn = name.substring(dash + 1, name.length());
+ try {
+ return Integer.parseInt(sn);
+ } catch (NumberFormatException ne) {
+ return 0;
+ }
+ }
+
+ public String toString() {
+ return String.format("%-20.20s %4d %s",
+ getProductName(), getSerial(), getAddr());
+ }
+
+ public String toShortString() {
+ return String.format("%s %d %s",
+ getProductName(), getSerial(), getAddr());
+
+ }
+
+ public SWIGTYPE_p_altos_file open() {
+ return libaltos.altos_bt_open(this);
+ }
+
+ private boolean isAltusMetrum() {
+ if (getName().startsWith(Altos.bt_product_telebt))
+ return true;
+ return false;
+ }
+
+ public boolean matchProduct(int want_product) {
+
+// if (!isAltusMetrum())
+// return false;
+
+ if (want_product == Altos.product_any)
+ return true;
+
+ if (want_product == Altos.product_basestation)
+ return matchProduct(Altos.product_telebt);
+
+ if (want_product == getProduct())
+ return true;
+
+ return false;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof AltosBTDevice))
+ return false;
+ AltosBTDevice other = (AltosBTDevice) o;
+ return getName().equals(other.getName()) && getAddr().equals(other.getAddr());
+ }
+
+ public int hashCode() {
+ return getName().hashCode() ^ getAddr().hashCode();
+ }
+
+ public AltosBTDevice(String name, String addr) {
+ Altos.load_library();
+ libaltos.altos_bt_fill_in(name, addr,this);
+ }
+
+ public AltosBTDevice() {
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosBTDeviceIterator.java b/altosui/AltosBTDeviceIterator.java
new file mode 100644
index 00000000..58ed86d5
--- /dev/null
+++ b/altosui/AltosBTDeviceIterator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.lang.*;
+import java.util.*;
+import libaltosJNI.*;
+
+public class AltosBTDeviceIterator implements Iterator<AltosBTDevice> {
+ AltosBTDevice current;
+ boolean done;
+ SWIGTYPE_p_altos_bt_list list;
+
+ public boolean hasNext() {
+ if (list == null)
+ return false;
+ if (current != null)
+ return true;
+ if (done)
+ return false;
+ current = new AltosBTDevice();
+ while (libaltos.altos_bt_list_next(list, current) != 0) {
+// if (current.matchProduct(product))
+ return true;
+ }
+ current = null;
+ done = true;
+ return false;
+ }
+
+ public AltosBTDevice next() {
+ if (hasNext()) {
+ AltosBTDevice next = current;
+ current = null;
+ return next;
+ }
+ return null;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public AltosBTDeviceIterator(int inquiry_time) {
+ done = false;
+ current = null;
+ list = libaltos.altos_bt_list_start(inquiry_time);
+ }
+}
diff --git a/altosui/AltosBTKnown.java b/altosui/AltosBTKnown.java
new file mode 100644
index 00000000..6a8e53cb
--- /dev/null
+++ b/altosui/AltosBTKnown.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.lang.*;
+import java.util.*;
+import libaltosJNI.*;
+import java.util.prefs.*;
+
+public class AltosBTKnown implements Iterable<AltosBTDevice> {
+ LinkedList<AltosBTDevice> devices = new LinkedList<AltosBTDevice>();
+ Preferences bt_pref = AltosUIPreferences.bt_devices();
+
+ private String get_address(String name) {
+ return bt_pref.get(name, "");
+ }
+
+ private void set_address(String name, String addr) {
+ bt_pref.put(name, addr);
+ }
+
+ private void remove(String name) {
+ bt_pref.remove(name);
+ }
+
+ private void load() {
+ try {
+ String[] names = bt_pref.keys();
+ for (int i = 0; i < names.length; i++) {
+ String name = names[i];
+ String addr = get_address(name);
+ devices.add(new AltosBTDevice(name, addr));
+ }
+ } catch (BackingStoreException be) {
+ } catch (IllegalStateException ie) {
+ }
+ }
+
+ public Iterator<AltosBTDevice> iterator() {
+ return devices.iterator();
+ }
+
+ private void flush() {
+ AltosUIPreferences.flush_preferences();
+ }
+
+ public void set(Iterable<AltosBTDevice> new_devices) {
+ for (AltosBTDevice old : devices) {
+ boolean found = false;
+ for (AltosBTDevice new_device : new_devices) {
+ if (new_device.equals(old)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ remove(old.getName());
+ }
+ devices = new LinkedList<AltosBTDevice>();
+ for (AltosBTDevice new_device : new_devices) {
+ devices.add(new_device);
+ set_address(new_device.getName(), new_device.getAddr());
+ }
+ flush();
+ }
+
+ public List<AltosDevice> list(int product) {
+ LinkedList<AltosDevice> list = new LinkedList<AltosDevice>();
+ for (AltosBTDevice device : devices) {
+ if (device.matchProduct(product))
+ list.add(device);
+ }
+ return list;
+ }
+
+ public AltosBTKnown() {
+ devices = new LinkedList<AltosBTDevice>();
+ bt_pref = AltosUIPreferences.bt_devices();
+ load();
+ }
+
+ static AltosBTKnown known;
+
+ static public AltosBTKnown bt_known() {
+ if (known == null)
+ known = new AltosBTKnown();
+ return known;
+ }
+}
diff --git a/altosui/AltosBTManage.java b/altosui/AltosBTManage.java
new file mode 100644
index 00000000..aeb964bb
--- /dev/null
+++ b/altosui/AltosBTManage.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import javax.swing.plaf.basic.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosBTManage extends AltosDialog implements ActionListener, Iterable<AltosBTDevice> {
+ LinkedBlockingQueue<AltosBTDevice> found_devices;
+ Frame frame;
+ LinkedList<ActionListener> listeners;
+ AltosBTKnown bt_known;
+
+ class DeviceList extends JList implements Iterable<AltosBTDevice> {
+ LinkedList<AltosBTDevice> devices;
+ DefaultListModel list_model;
+
+ public void add (AltosBTDevice device) {
+ if (!devices.contains(device)) {
+ devices.add(device);
+ list_model.addElement(device);
+ }
+ }
+
+ public void remove (AltosBTDevice device) {
+ if (devices.contains(device)) {
+ devices.remove(device);
+ list_model.removeElement(device);
+ }
+ }
+
+ public boolean contains(AltosBTDevice device) {
+ return devices.contains(device);
+ }
+
+ //Subclass JList to workaround bug 4832765, which can cause the
+ //scroll pane to not let the user easily scroll up to the beginning
+ //of the list. An alternative would be to set the unitIncrement
+ //of the JScrollBar to a fixed value. You wouldn't get the nice
+ //aligned scrolling, but it should work.
+ public int getScrollableUnitIncrement(Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ int row;
+ if (orientation == SwingConstants.VERTICAL &&
+ direction < 0 && (row = getFirstVisibleIndex()) != -1) {
+ Rectangle r = getCellBounds(row, row);
+ if ((r.y == visibleRect.y) && (row != 0)) {
+ Point loc = r.getLocation();
+ loc.y--;
+ int prevIndex = locationToIndex(loc);
+ Rectangle prevR = getCellBounds(prevIndex, prevIndex);
+
+ if (prevR == null || prevR.y >= r.y) {
+ return 0;
+ }
+ return prevR.height;
+ }
+ }
+ return super.getScrollableUnitIncrement(
+ visibleRect, orientation, direction);
+ }
+
+ public Iterator<AltosBTDevice> iterator() {
+ return devices.iterator();
+ }
+
+ public java.util.List<AltosBTDevice> selected_list() {
+ java.util.LinkedList<AltosBTDevice> l = new java.util.LinkedList<AltosBTDevice>();
+ Object[] a = getSelectedValues();
+ for (int i = 0; i < a.length; i++)
+ l.add((AltosBTDevice)a[i]);
+ return l;
+ }
+
+ public DeviceList() {
+ devices = new LinkedList<AltosBTDevice>();
+ list_model = new DefaultListModel();
+ setModel(list_model);
+ setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ setLayoutOrientation(JList.HORIZONTAL_WRAP);
+ setVisibleRowCount(-1);
+ }
+ }
+
+ DeviceList visible_devices;
+
+ DeviceList known_devices;
+ Thread bt_thread;
+
+ public Iterator<AltosBTDevice> iterator() {
+ return known_devices.iterator();
+ }
+
+ public void commit() {
+ bt_known.set(this);
+ }
+
+ public void add_known() {
+ for (AltosBTDevice device : visible_devices.selected_list()) {
+ known_devices.add(device);
+ visible_devices.remove(device);
+ }
+ }
+
+ public void remove_known() {
+ for (AltosBTDevice device : known_devices.selected_list()) {
+ known_devices.remove(device);
+ visible_devices.add(device);
+ }
+ }
+
+ public void addActionListener(ActionListener l) {
+ listeners.add(l);
+ }
+
+ private void forwardAction(ActionEvent e) {
+ for (ActionListener l : listeners)
+ l.actionPerformed(e);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String command = e.getActionCommand();
+ if ("ok".equals(command)) {
+ bt_thread.interrupt();
+ commit();
+ setVisible(false);
+ forwardAction(e);
+ } else if ("cancel".equals(command)) {
+ bt_thread.interrupt();
+ setVisible(false);
+ forwardAction(e);
+ } else if ("select".equals(command)) {
+ add_known();
+ } else if ("deselect".equals(command)) {
+ remove_known();
+ }
+ }
+
+ public void got_visible_device() {
+ while (!found_devices.isEmpty()) {
+ AltosBTDevice device = found_devices.remove();
+ if (!known_devices.contains(device))
+ visible_devices.add(device);
+ }
+ }
+
+ class BTGetVisibleDevices implements Runnable {
+ public void run () {
+ for (;;)
+ for (int time = 1; time <= 8; time <<= 1) {
+ AltosBTDeviceIterator i = new AltosBTDeviceIterator(time);
+ AltosBTDevice device;
+
+ if (Thread.interrupted())
+ return;
+ try {
+ while ((device = i.next()) != null) {
+ Runnable r;
+
+ if (Thread.interrupted())
+ return;
+ found_devices.add(device);
+ r = new Runnable() {
+ public void run() {
+ got_visible_device();
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+ } catch (Exception e) {
+ System.out.printf("uh-oh, exception %s\n", e.toString());
+ }
+ }
+ }
+ }
+
+ public static void show(Component frameComp, AltosBTKnown known) {
+ Frame frame = JOptionPane.getFrameForComponent(frameComp);
+ AltosBTManage dialog;
+
+ dialog = new AltosBTManage(frame, known);
+ dialog.setVisible(true);
+ }
+
+ public AltosBTManage(Frame in_frame, AltosBTKnown in_known) {
+ super(in_frame, "Manage Bluetooth Devices", true);
+
+ frame = in_frame;
+ bt_known = in_known;
+ BTGetVisibleDevices get_visible_devices = new BTGetVisibleDevices();
+ bt_thread = new Thread(get_visible_devices);
+ bt_thread.start();
+
+ listeners = new LinkedList<ActionListener>();
+
+ found_devices = new LinkedBlockingQueue<AltosBTDevice>();
+
+ Container pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(4,4,4,4);
+
+ /*
+ * Known devices label and list
+ */
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(new JLabel("Known Devices"), c);
+
+ known_devices = new DeviceList();
+ for (AltosBTDevice device : bt_known)
+ known_devices.add(device);
+
+ JScrollPane known_list_scroller = new JScrollPane(known_devices);
+ known_list_scroller.setPreferredSize(new Dimension(400, 80));
+ known_list_scroller.setAlignmentX(LEFT_ALIGNMENT);
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.gridheight = 2;
+ c.weightx = 1;
+ c.weighty = 1;
+ pane.add(known_list_scroller, c);
+
+ /*
+ * Visible devices label and list
+ */
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 2;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+
+ pane.add(new JLabel("Visible Devices"), c);
+
+ visible_devices = new DeviceList();
+ JScrollPane visible_list_scroller = new JScrollPane(visible_devices);
+ visible_list_scroller.setPreferredSize(new Dimension(400, 80));
+ visible_list_scroller.setAlignmentX(LEFT_ALIGNMENT);
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 2;
+ c.gridy = 1;
+ c.gridheight = 2;
+ c.gridwidth = 1;
+ c.weightx = 1;
+ c.weighty = 1;
+ pane.add(visible_list_scroller, c);
+
+ /*
+ * Arrows between the two lists
+ */
+ BasicArrowButton select_arrow = new BasicArrowButton(SwingConstants.WEST);
+ select_arrow.setActionCommand("select");
+ select_arrow.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.SOUTH;
+ c.gridx = 1;
+ c.gridy = 1;
+ c.gridheight = 1;
+ c.gridwidth = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(select_arrow, c);
+
+ BasicArrowButton deselect_arrow = new BasicArrowButton(SwingConstants.EAST);
+ deselect_arrow.setActionCommand("deselect");
+ deselect_arrow.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.NORTH;
+ c.gridx = 1;
+ c.gridy = 2;
+ c.gridheight = 1;
+ c.gridwidth = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(deselect_arrow, c);
+
+ JButton cancel_button = new JButton("Cancel");
+ cancel_button.setActionCommand("cancel");
+ cancel_button.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridheight = 1;
+ c.gridwidth = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(cancel_button, c);
+
+ JButton ok_button = new JButton("OK");
+ ok_button.setActionCommand("ok");
+ ok_button.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 2;
+ c.gridy = 3;
+ c.gridheight = 1;
+ c.gridwidth = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(ok_button, c);
+
+ getRootPane().setDefaultButton(ok_button);
+
+ pack();
+ setLocationRelativeTo(frame);
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ bt_thread.interrupt();
+ setVisible(false);
+ }
+ });
+ }
+}
diff --git a/altosui/AltosCSV.java b/altosui/AltosCSV.java
new file mode 100644
index 00000000..c876d9ca
--- /dev/null
+++ b/altosui/AltosCSV.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosCSV implements AltosWriter {
+ File name;
+ PrintStream out;
+ boolean header_written;
+ boolean seen_boost;
+ int boost_tick;
+ LinkedList<AltosRecord> pad_records;
+ AltosState state;
+
+ static final int ALTOS_CSV_VERSION = 5;
+
+ /* Version 4 format:
+ *
+ * General info
+ * version number
+ * serial number
+ * flight number
+ * callsign
+ * time (seconds since boost)
+ * clock (tick count / 100)
+ * rssi
+ * link quality
+ *
+ * Flight status
+ * state
+ * state name
+ *
+ * Basic sensors
+ * acceleration (m/s²)
+ * pressure (mBar)
+ * altitude (m)
+ * height (m)
+ * accelerometer speed (m/s)
+ * barometer speed (m/s)
+ * temp (°C)
+ * battery (V)
+ * drogue (V)
+ * main (V)
+ *
+ * Advanced sensors (if available)
+ * accel_x (m/s²)
+ * accel_y (m/s²)
+ * accel_z (m/s²)
+ * gyro_x (d/s)
+ * gyro_y (d/s)
+ * gyro_z (d/s)
+ * mag_x (g)
+ * mag_y (g)
+ * mag_z (g)
+ *
+ * GPS data (if available)
+ * connected (1/0)
+ * locked (1/0)
+ * nsat (used for solution)
+ * latitude (°)
+ * longitude (°)
+ * altitude (m)
+ * year (e.g. 2010)
+ * month (1-12)
+ * day (1-31)
+ * hour (0-23)
+ * minute (0-59)
+ * second (0-59)
+ * from_pad_dist (m)
+ * from_pad_azimuth (deg true)
+ * from_pad_range (m)
+ * from_pad_elevation (deg from horizon)
+ * hdop
+ *
+ * GPS Sat data
+ * C/N0 data for all 32 valid SDIDs
+ *
+ * Companion data
+ * companion_id (1-255. 10 is TeleScience)
+ * time of last companion data (seconds since boost)
+ * update_period (0.1-2.55 minimum telemetry interval)
+ * channels (0-12)
+ * channel data for all 12 possible channels
+ */
+
+ void write_general_header() {
+ out.printf("version,serial,flight,call,time,clock,rssi,lqi");
+ }
+
+ void write_general(AltosRecord record) {
+ out.printf("%s, %d, %d, %s, %8.2f, %8.2f, %4d, %3d",
+ ALTOS_CSV_VERSION, record.serial, record.flight, record.callsign,
+ (double) record.time, (double) record.tick / 100.0,
+ record.rssi,
+ record.status & 0x7f);
+ }
+
+ void write_flight_header() {
+ out.printf("state,state_name");
+ }
+
+ void write_flight(AltosRecord record) {
+ out.printf("%d,%8s", record.state, record.state());
+ }
+
+ void write_basic_header() {
+ out.printf("acceleration,pressure,altitude,height,accel_speed,baro_speed,temperature,battery_voltage,drogue_voltage,main_voltage");
+ }
+
+ void write_basic(AltosRecord record) {
+ out.printf("%8.2f,%10.2f,%8.2f,%8.2f,%8.2f,%8.2f,%5.1f,%5.2f,%5.2f,%5.2f",
+ record.acceleration(),
+ record.raw_pressure(),
+ record.raw_altitude(),
+ record.raw_height(),
+ record.accel_speed(),
+ state.baro_speed,
+ record.temperature(),
+ record.battery_voltage(),
+ record.drogue_voltage(),
+ record.main_voltage());
+ }
+
+ void write_advanced_header() {
+ out.printf("accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z");
+ }
+
+ void write_advanced(AltosRecord record) {
+ AltosIMU imu = record.imu();
+ AltosMag mag = record.mag();
+
+ if (imu == null)
+ imu = new AltosIMU();
+ if (mag == null)
+ mag = new AltosMag();
+ out.printf("%6d,%6d,%6d,%6d,%6d,%6d,%6d,%6d,%6d",
+ imu.accel_x, imu.accel_y, imu.accel_z,
+ imu.gyro_x, imu.gyro_y, imu.gyro_z,
+ mag.x, mag.y, mag.z);
+ }
+
+ void write_gps_header() {
+ out.printf("connected,locked,nsat,latitude,longitude,altitude,year,month,day,hour,minute,second,pad_dist,pad_range,pad_az,pad_el,hdop");
+ }
+
+ void write_gps(AltosRecord record) {
+ AltosGPS gps = record.gps;
+ if (gps == null)
+ gps = new AltosGPS();
+
+ AltosGreatCircle from_pad = state.from_pad;
+ if (from_pad == null)
+ from_pad = new AltosGreatCircle();
+
+ out.printf("%2d,%2d,%3d,%12.7f,%12.7f,%6d,%5d,%3d,%3d,%3d,%3d,%3d,%9.0f,%9.0f,%4.0f,%4.0f,%6.1f",
+ gps.connected?1:0,
+ gps.locked?1:0,
+ gps.nsat,
+ gps.lat,
+ gps.lon,
+ gps.alt,
+ gps.year,
+ gps.month,
+ gps.day,
+ gps.hour,
+ gps.minute,
+ gps.second,
+ from_pad.distance,
+ state.range,
+ from_pad.bearing,
+ state.elevation,
+ gps.hdop);
+ }
+
+ void write_gps_sat_header() {
+ for(int i = 1; i <= 32; i++) {
+ out.printf("sat%02d", i);
+ if (i != 32)
+ out.printf(",");
+ }
+ }
+
+ void write_gps_sat(AltosRecord record) {
+ AltosGPS gps = record.gps;
+ for(int i = 1; i <= 32; i++) {
+ int c_n0 = 0;
+ if (gps != null && gps.cc_gps_sat != null) {
+ for(int j = 0; j < gps.cc_gps_sat.length; j++)
+ if (gps.cc_gps_sat[j].svid == i) {
+ c_n0 = gps.cc_gps_sat[j].c_n0;
+ break;
+ }
+ }
+ out.printf ("%3d", c_n0);
+ if (i != 32)
+ out.printf(",");
+ }
+ }
+
+ void write_companion_header() {
+ out.printf("companion_id,companion_time,companion_update,companion_channels");
+ for (int i = 0; i < 12; i++)
+ out.printf(",companion_%02d", i);
+ }
+
+ void write_companion(AltosRecord record) {
+ AltosRecordCompanion companion = record.companion;
+
+ int channels_written = 0;
+ if (companion == null) {
+ out.printf("0,0,0,0");
+ } else {
+ out.printf("%3d,%5.2f,%5.2f,%2d",
+ companion.board_id,
+ (companion.tick - boost_tick) / 100.0,
+ companion.update_period / 100.0,
+ companion.channels);
+ for (; channels_written < companion.channels; channels_written++)
+ out.printf(",%5d", companion.companion_data[channels_written]);
+ }
+ for (; channels_written < 12; channels_written++)
+ out.printf(",0");
+ }
+
+ void write_header(boolean advanced, boolean gps, boolean companion) {
+ out.printf("#"); write_general_header();
+ out.printf(","); write_flight_header();
+ out.printf(","); write_basic_header();
+ if (advanced)
+ out.printf(","); write_advanced_header();
+ if (gps) {
+ out.printf(","); write_gps_header();
+ out.printf(","); write_gps_sat_header();
+ }
+ if (companion) {
+ out.printf(","); write_companion_header();
+ }
+ out.printf ("\n");
+ }
+
+ void write_one(AltosRecord record) {
+ state = new AltosState(record, state);
+ write_general(record); out.printf(",");
+ write_flight(record); out.printf(",");
+ write_basic(record); out.printf(",");
+ if (record.imu() != null || record.mag() != null)
+ write_advanced(record);
+ if (record.gps != null) {
+ out.printf(",");
+ write_gps(record); out.printf(",");
+ write_gps_sat(record);
+ }
+ if (record.companion != null) {
+ out.printf(",");
+ write_companion(record);
+ }
+ out.printf ("\n");
+ }
+
+ void flush_pad() {
+ while (!pad_records.isEmpty()) {
+ write_one (pad_records.remove());
+ }
+ }
+
+ public void write(AltosRecord record) {
+ if (record.state == Altos.ao_flight_startup)
+ return;
+ if (!header_written) {
+ write_header(record.imu() != null || record.mag() != null,
+ record.gps != null, record.companion != null);
+ header_written = true;
+ }
+ if (!seen_boost) {
+ if (record.state >= Altos.ao_flight_boost) {
+ seen_boost = true;
+ boost_tick = record.tick;
+ flush_pad();
+ }
+ }
+ if (seen_boost)
+ write_one(record);
+ else
+ pad_records.add(record);
+ }
+
+ public PrintStream out() {
+ return out;
+ }
+
+ public void close() {
+ if (!pad_records.isEmpty()) {
+ boost_tick = pad_records.element().tick;
+ flush_pad();
+ }
+ out.close();
+ }
+
+ public void write(AltosRecordIterable iterable) {
+ iterable.write_comments(out());
+ for (AltosRecord r : iterable)
+ write(r);
+ }
+
+ public AltosCSV(PrintStream in_out, File in_name) {
+ name = in_name;
+ out = in_out;
+ pad_records = new LinkedList<AltosRecord>();
+ }
+
+ public AltosCSV(File in_name) throws FileNotFoundException {
+ this(new PrintStream(in_name), in_name);
+ }
+
+ public AltosCSV(String in_string) throws FileNotFoundException {
+ this(new File(in_string));
+ }
+}
diff --git a/altosui/AltosCSVUI.java b/altosui/AltosCSVUI.java
new file mode 100644
index 00000000..2702668b
--- /dev/null
+++ b/altosui/AltosCSVUI.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosCSVUI
+ extends AltosDialog
+ implements ActionListener
+{
+ JFileChooser csv_chooser;
+ JPanel accessory;
+ JComboBox combo_box;
+ AltosRecordIterable iterable;
+ AltosWriter writer;
+
+ static String[] combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" };
+
+ void set_default_file() {
+ File current = csv_chooser.getSelectedFile();
+ String current_name = current.getName();
+ String new_name = null;
+ String selected = (String) combo_box.getSelectedItem();
+
+ if (selected.contains("CSV"))
+ new_name = Altos.replace_extension(current_name, ".csv");
+ else if (selected.contains("KML"))
+ new_name = Altos.replace_extension(current_name, ".kml");
+ if (new_name != null)
+ csv_chooser.setSelectedFile(new File(new_name));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getActionCommand().equals("comboBoxChanged"))
+ set_default_file();
+ }
+
+ public AltosCSVUI(JFrame frame, AltosRecordIterable in_iterable, File source_file) {
+ iterable = in_iterable;
+ csv_chooser = new JFileChooser(source_file);
+
+ accessory = new JPanel();
+ accessory.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 1;
+ c.weighty = 0;
+ c.insets = new Insets (4, 4, 4, 4);
+
+ JLabel accessory_label = new JLabel("Export File Type");
+ c.gridx = 0;
+ c.gridy = 0;
+ accessory.add(accessory_label, c);
+
+ combo_box = new JComboBox(combo_box_items);
+ combo_box.addActionListener(this);
+ c.gridx = 0;
+ c.gridy = 1;
+ accessory.add(combo_box, c);
+
+ csv_chooser.setAccessory(accessory);
+ csv_chooser.setSelectedFile(source_file);
+ set_default_file();
+ int ret = csv_chooser.showSaveDialog(frame);
+ if (ret == JFileChooser.APPROVE_OPTION) {
+ File file = csv_chooser.getSelectedFile();
+ String type = (String) combo_box.getSelectedItem();
+ try {
+ if (type.contains("CSV"))
+ writer = new AltosCSV(file);
+ else
+ writer = new AltosKML(file);
+ writer.write(iterable);
+ writer.close();
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(frame,
+ ee.getMessage(),
+ "Cannot open file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+}
diff --git a/altosui/AltosChannelMenu.java b/altosui/AltosChannelMenu.java
new file mode 100644
index 00000000..0249a0bd
--- /dev/null
+++ b/altosui/AltosChannelMenu.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+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/altosui/AltosCompanionInfo.java b/altosui/AltosCompanionInfo.java
new file mode 100644
index 00000000..4ba8fe98
--- /dev/null
+++ b/altosui/AltosCompanionInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosCompanionInfo extends JTable {
+ 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 set_font() {
+ setFont(Altos.table_value_font);
+ setRowHeight(desired_row_height());
+ doLayout();
+ }
+
+ public AltosCompanionInfo() {
+ super(new AltosFlightInfoTableModel(info_rows, info_columns));
+ model = (AltosFlightInfoTableModel) getModel();
+ setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
+ setShowGrid(true);
+ set_font();
+ }
+
+ public Dimension getPreferredScrollableViewportSize() {
+ return getPreferredSize();
+ }
+
+ void info_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();
+ }
+
+ AltosRecordCompanion companion;
+
+ public String board_name() {
+ if (companion == null)
+ return "None";
+ switch (companion.board_id) {
+ case AltosRecordCompanion.board_id_telescience:
+ return "TeleScience";
+ default:
+ return String.format("%02x\n", companion.board_id);
+ }
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ if (state == null)
+ return;
+ if (state.data.companion != null)
+ companion = state.data.companion;
+ info_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/altosui/AltosConfig.java b/altosui/AltosConfig.java
new file mode 100644
index 00000000..cae41858
--- /dev/null
+++ b/altosui/AltosConfig.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosConfig 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 remote_config_data;
+ double remote_frequency;
+ int_ref serial;
+ int_ref log_format;
+ int_ref main_deploy;
+ int_ref apogee_delay;
+ int_ref apogee_lockout;
+ int_ref radio_channel;
+ int_ref radio_calibration;
+ int_ref flight_log_max;
+ int_ref ignite_mode;
+ int_ref pad_orientation;
+ int_ref radio_setting;
+ int_ref radio_frequency;
+ int_ref storage_size;
+ int_ref storage_erase_unit;
+ int_ref stored_flight;
+ int_ref radio_enable;
+ string_ref version;
+ string_ref product;
+ string_ref callsign;
+ AltosConfigUI config_ui;
+ boolean serial_started;
+ 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;
+ }
+ }
+
+ 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();
+ }
+
+ int log_limit() {
+ if (storage_size.get() > 0 && storage_erase_unit.get() > 0) {
+ int log_limit = storage_size.get() - storage_erase_unit.get();
+ if (log_limit > 0)
+ return log_limit / 1024;
+ }
+ return 1024;
+ }
+
+ void update_ui() {
+ config_ui.set_serial(serial.get());
+ config_ui.set_product(product.get());
+ config_ui.set_version(version.get());
+ config_ui.set_main_deploy(main_deploy.get());
+ config_ui.set_apogee_delay(apogee_delay.get());
+ config_ui.set_apogee_lockout(apogee_lockout.get());
+ config_ui.set_radio_calibration(radio_calibration.get());
+ config_ui.set_radio_frequency(frequency());
+ boolean max_enabled = true;
+ switch (log_format.get()) {
+ case Altos.AO_LOG_FORMAT_TINY:
+ max_enabled = false;
+ break;
+ default:
+ if (stored_flight.get() >= 0)
+ max_enabled = false;
+ break;
+ }
+ config_ui.set_flight_log_max_enabled(max_enabled);
+ config_ui.set_radio_enable(radio_enable.get());
+ config_ui.set_flight_log_max_limit(log_limit());
+ config_ui.set_flight_log_max(flight_log_max.get());
+ config_ui.set_ignite_mode(ignite_mode.get());
+ config_ui.set_pad_orientation(pad_orientation.get());
+ config_ui.set_callsign(callsign.get());
+ config_ui.set_clean();
+ if (!made_visible) {
+ made_visible = true;
+ config_ui.make_visible();
+ }
+ }
+
+ void process_line(String line) {
+ if (line == null) {
+ abort();
+ return;
+ }
+ if (line.equals("all finished")) {
+ if (serial_line != null)
+ update_ui();
+ return;
+ }
+ get_int(line, "serial-number", serial);
+ get_int(line, "log-format", log_format);
+ get_int(line, "Main deploy:", main_deploy);
+ get_int(line, "Apogee delay:", apogee_delay);
+ get_int(line, "Apogee lockout:", apogee_lockout);
+ get_int(line, "Radio channel:", radio_channel);
+ get_int(line, "Radio cal:", radio_calibration);
+ get_int(line, "Max flight log:", flight_log_max);
+ get_int(line, "Ignite mode:", ignite_mode);
+ get_int(line, "Pad orientation:", pad_orientation);
+ get_int(line, "Radio setting:", radio_setting);
+ if (get_int(line, "Frequency:", radio_frequency))
+ if (radio_frequency.get() < 0)
+ radio_frequency.set(434550);
+ get_int(line, "Radio enable:", radio_enable);
+ get_int(line, "Storage size:", storage_size);
+ get_int(line, "Storage erase unit:", storage_erase_unit);
+ get_int(line, "flight", stored_flight);
+ get_string(line, "Callsign:", callsign);
+ get_string(line,"software-version", version);
+ get_string(line,"product", product);
+ }
+
+ 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 {
+ AltosConfig config;
+ int serial_mode;
+
+ void process_line(String line) {
+ config.process_line(line);
+ }
+ void callback(String in_line) {
+ final String line = in_line;
+ Runnable r = new Runnable() {
+ public void run() {
+ process_line(line);
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ void reset_data() {
+ serial.set(0);
+ log_format.set(Altos.AO_LOG_FORMAT_UNKNOWN);
+ main_deploy.set(250);
+ apogee_delay.set(0);
+ apogee_lockout.set(0);
+ radio_channel.set(0);
+ radio_setting.set(0);
+ radio_frequency.set(0);
+ radio_calibration.set(1186611);
+ radio_enable.set(-1);
+ flight_log_max.set(0);
+ ignite_mode.set(-1);
+ pad_orientation.set(-1);
+ storage_size.set(-1);
+ storage_erase_unit.set(-1);
+ stored_flight.set(-1);
+ callsign.set("N0CALL");
+ version.set("unknown");
+ product.set("unknown");
+ }
+
+ void get_data() {
+ try {
+ config.start_serial();
+ reset_data();
+
+ config.serial_line.printf("c s\nf\nl\nv\n");
+ for (;;) {
+ try {
+ String line = config.serial_line.get_reply(5000);
+ if (line == null)
+ stop_serial();
+ callback(line);
+ if (line.startsWith("software-version"))
+ break;
+ } catch (Exception e) {
+ break;
+ }
+ }
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ } finally {
+ try {
+ stop_serial();
+ } catch (InterruptedException ie) {
+ }
+ }
+ callback("all finished");
+ }
+
+ void save_data() {
+ try {
+ double frequency = frequency();
+ boolean has_frequency = radio_frequency.get() > 0;
+ boolean has_setting = radio_setting.get() > 0;
+ start_serial();
+ serial_line.printf("c m %d\n", main_deploy.get());
+ serial_line.printf("c d %d\n", apogee_delay.get());
+ serial_line.printf("c L %d\n", apogee_lockout.get());
+ if (!remote)
+ serial_line.printf("c f %d\n", radio_calibration.get());
+ serial_line.set_radio_frequency(frequency,
+ has_frequency,
+ has_setting,
+ radio_calibration.get());
+ if (remote) {
+ serial_line.stop_remote();
+ serial_line.set_radio_frequency(frequency);
+ AltosUIPreferences.set_frequency(device.getSerial(), frequency);
+ serial_line.start_remote();
+ }
+ serial_line.printf("c c %s\n", callsign.get());
+ if (flight_log_max.get() != 0)
+ serial_line.printf("c l %d\n", flight_log_max.get());
+ if (radio_enable.get() >= 0)
+ serial_line.printf("c e %d\n", radio_enable.get());
+ if (ignite_mode.get() >= 0)
+ serial_line.printf("c i %d\n", ignite_mode.get());
+ if (pad_orientation.get() >= 0)
+ serial_line.printf("c o %d\n", pad_orientation.get());
+ serial_line.printf("c w\n");
+ } 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();
+ } catch (InterruptedException ie) {
+ }
+ serial_line.close();
+ }
+ }
+
+ 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(AltosConfig 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 AltosConfigUI(owner, remote);
+ config_ui.addActionListener(this);
+ serial_line.set_frame(owner);
+ set_ui();
+ }
+
+ void abort() {
+ 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(radio_frequency.get(),
+ radio_setting.get(),
+ radio_calibration.get(),
+ radio_channel.get());
+ }
+
+ 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));
+ radio_channel.set(0);
+ } 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));
+ }
+ }
+
+ void save_data() {
+
+ /* bounds check stuff */
+ if (config_ui.flight_log_max() > log_limit()) {
+ JOptionPane.showMessageDialog(owner,
+ String.format("Requested flight log, %dk, is larger than the available space, %dk.\n",
+ config_ui.flight_log_max(),
+ log_limit()),
+ "Maximum Flight Log Too Large",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ main_deploy.set(config_ui.main_deploy());
+ apogee_delay.set(config_ui.apogee_delay());
+ apogee_lockout.set(config_ui.apogee_lockout());
+ radio_calibration.set(config_ui.radio_calibration());
+ set_frequency(config_ui.radio_frequency());
+ flight_log_max.set(config_ui.flight_log_max());
+ if (radio_enable.get() >= 0)
+ radio_enable.set(config_ui.radio_enable());
+ if (ignite_mode.get() >= 0)
+ ignite_mode.set(config_ui.ignite_mode());
+ if (pad_orientation.get() >= 0)
+ pad_orientation.set(config_ui.pad_orientation());
+ callsign.set(config_ui.callsign());
+ 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 AltosConfig(JFrame given_owner) {
+ owner = given_owner;
+
+ serial = new int_ref(0);
+ log_format = new int_ref(Altos.AO_LOG_FORMAT_UNKNOWN);
+ main_deploy = new int_ref(250);
+ apogee_delay = new int_ref(0);
+ apogee_lockout = 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);
+ radio_enable = new int_ref(-1);
+ flight_log_max = new int_ref(0);
+ ignite_mode = new int_ref(-1);
+ pad_orientation = new int_ref(-1);
+ storage_size = new int_ref(-1);
+ storage_erase_unit = new int_ref(-1);
+ stored_flight = new int_ref(-1);
+ callsign = new string_ref("N0CALL");
+ version = new string_ref("unknown");
+ product = new string_ref("unknown");
+
+ device = AltosDeviceDialog.show(owner, Altos.product_any);
+ if (device != null) {
+ try {
+ serial_line = new AltosSerial(device);
+ try {
+ if (!device.matchProduct(Altos.product_altimeter))
+ 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);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(owner,
+ device.toShortString(),
+ ee.getLocalizedMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosConfigFreqUI.java b/altosui/AltosConfigFreqUI.java
new file mode 100644
index 00000000..7958a21c
--- /dev/null
+++ b/altosui/AltosConfigFreqUI.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import javax.swing.plaf.basic.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+class AltosEditFreqUI extends AltosDialog implements ActionListener {
+ Frame frame;
+ JTextField frequency;
+ JTextField description;
+ JButton ok_button, cancel_button;
+ boolean got_ok;
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if ("ok".equals(cmd)) {
+ got_ok = true;
+ setVisible(false);
+ }
+ if ("cancel".equals(cmd)) {
+ got_ok = false;
+ setVisible(false);
+ }
+ }
+
+ public AltosFrequency get() {
+ if (!got_ok)
+ return null;
+
+ String f_s = frequency.getText();
+ String d_s = description.getText();
+
+ try {
+ double f_d = Double.parseDouble(f_s);
+
+ return new AltosFrequency(f_d, d_s);
+ } catch (NumberFormatException ne) {
+ }
+ return null;
+ }
+
+ public AltosEditFreqUI(Frame in_frame, AltosFrequency existing) {
+ super(in_frame, true);
+
+ got_ok = false;
+ frame = in_frame;
+
+ Container pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets (4,4,4,4);
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(new JLabel("Frequency"), c);
+
+ frequency = new JTextField(12);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(frequency, c);
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(new JLabel("Description"), c);
+
+ description = new JTextField(12);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(description, c);
+
+ ok_button = new JButton("OK");
+ ok_button.setActionCommand("ok");
+ ok_button.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 2;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(ok_button, c);
+
+ cancel_button = new JButton("Cancel");
+ cancel_button.setActionCommand("cancel");
+ cancel_button.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 1;
+ c.gridy = 2;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(cancel_button, c);
+
+ if (existing == null)
+ setTitle("Add New Frequency");
+ else {
+ setTitle("Edit Existing Frequency");
+ frequency.setText(String.format("%7.3f", existing.frequency));
+ description.setText(existing.description);
+ }
+ getRootPane().setDefaultButton(ok_button);
+
+ pack();
+ setLocationRelativeTo(frame);
+
+ }
+
+ public AltosEditFreqUI(Frame in_frame) {
+ this(in_frame, (AltosFrequency) null);
+ }
+}
+
+public class AltosConfigFreqUI extends AltosDialog implements ActionListener {
+
+ Frame frame;
+ LinkedList<ActionListener> listeners;
+
+ class FrequencyList extends JList {
+ DefaultListModel list_model;
+
+ public void add(AltosFrequency frequency) {
+ int i;
+ for (i = 0; i < list_model.size(); i++) {
+ AltosFrequency f = (AltosFrequency) list_model.get(i);
+ if (frequency.frequency == f.frequency)
+ return;
+ if (frequency.frequency < f.frequency)
+ break;
+ }
+ list_model.insertElementAt(frequency, i);
+ }
+
+ public void remove(AltosFrequency frequency) {
+ list_model.removeElement(frequency);
+ }
+
+ //Subclass JList to workaround bug 4832765, which can cause the
+ //scroll pane to not let the user easily scroll up to the beginning
+ //of the list. An alternative would be to set the unitIncrement
+ //of the JScrollBar to a fixed value. You wouldn't get the nice
+ //aligned scrolling, but it should work.
+ public int getScrollableUnitIncrement(Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ int row;
+ if (orientation == SwingConstants.VERTICAL &&
+ direction < 0 && (row = getFirstVisibleIndex()) != -1) {
+ Rectangle r = getCellBounds(row, row);
+ if ((r.y == visibleRect.y) && (row != 0)) {
+ Point loc = r.getLocation();
+ loc.y--;
+ int prevIndex = locationToIndex(loc);
+ Rectangle prevR = getCellBounds(prevIndex, prevIndex);
+
+ if (prevR == null || prevR.y >= r.y) {
+ return 0;
+ }
+ return prevR.height;
+ }
+ }
+ return super.getScrollableUnitIncrement(
+ visibleRect, orientation, direction);
+ }
+
+ public AltosFrequency selected() {
+ AltosFrequency f = (AltosFrequency) getSelectedValue();
+ return f;
+ }
+
+ public AltosFrequency[] frequencies() {
+ AltosFrequency[] ret;
+
+ ret = new AltosFrequency[list_model.size()];
+ for (int i = 0; i < list_model.size(); i++)
+ ret[i] = (AltosFrequency) list_model.get(i);
+ return ret;
+ }
+
+ public FrequencyList(AltosFrequency[] in_frequencies) {
+ list_model = new DefaultListModel();
+ setModel(list_model);
+ setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ setLayoutOrientation(JList.HORIZONTAL_WRAP);
+ for (int i = 0; i < in_frequencies.length; i++) {
+ add(in_frequencies[i]);
+ }
+ setVisibleRowCount(in_frequencies.length);
+ }
+ }
+
+ FrequencyList frequencies;
+
+ void save_frequencies() {
+ AltosUIPreferences.set_common_frequencies(frequencies.frequencies());
+ }
+
+ JButton add, edit, remove;
+
+ JButton cancel, ok;
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if ("ok".equals(cmd)) {
+ save_frequencies();
+ setVisible(false);
+ } else if ("cancel".equals(cmd)) {
+ setVisible(false);
+ } else if ("add".equals(cmd)) {
+ AltosEditFreqUI ui = new AltosEditFreqUI(frame);
+ ui.setVisible(true);
+ AltosFrequency f = ui.get();
+ if (f != null)
+ frequencies.add(f);
+ } else if ("edit".equals(cmd)) {
+ AltosFrequency old_f = frequencies.selected();
+ if (old_f == null)
+ return;
+ AltosEditFreqUI ui = new AltosEditFreqUI(frame, old_f);
+ ui.setVisible(true);
+ AltosFrequency new_f = ui.get();
+ if (new_f != null) {
+ if (old_f != null)
+ frequencies.remove(old_f);
+ frequencies.add(new_f);
+ }
+ } else if ("remove".equals(cmd)) {
+ AltosFrequency old_f = frequencies.selected();
+ if (old_f == null)
+ return;
+ int ret = JOptionPane.showConfirmDialog(this,
+ String.format("Remove frequency \"%s\"?",
+ old_f.toShortString()),
+ "Remove Frequency",
+ JOptionPane.YES_NO_OPTION);
+ if (ret == JOptionPane.YES_OPTION)
+ frequencies.remove(old_f);
+ }
+ }
+
+ public AltosFrequency[] frequencies() {
+ return frequencies.frequencies();
+ }
+
+ public AltosConfigFreqUI(Frame in_frame,
+ AltosFrequency[] in_frequencies) {
+ super(in_frame, "Manage Frequencies", true);
+
+ frame = in_frame;
+
+ listeners = new LinkedList<ActionListener>();
+
+ frequencies = new FrequencyList(in_frequencies);
+
+ Container pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(4,4,4,4);
+
+ /*
+ * Frequencies label and list
+ */
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(new JLabel("Frequencies"), c);
+
+ JScrollPane list_scroller = new JScrollPane(frequencies);
+ list_scroller.setAlignmentX(LEFT_ALIGNMENT);
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 6;
+ c.gridheight = 2;
+ c.weightx = 1;
+ c.weighty = 1;
+ pane.add(list_scroller, c);
+
+ add = new JButton("Add");
+ add.setActionCommand("add");
+ add.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridwidth = 2;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(add, c);
+
+ edit = new JButton("Edit");
+ edit.setActionCommand("edit");
+ edit.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 2;
+ c.gridy = 3;
+ c.gridwidth = 2;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(edit, c);
+
+ remove = new JButton("Remove");
+ remove.setActionCommand("remove");
+ remove.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 4;
+ c.gridy = 3;
+ c.gridwidth = 2;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(remove, c);
+
+ ok = new JButton("OK");
+ ok.setActionCommand("ok");
+ ok.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0;
+ c.gridy = 4;
+ c.gridwidth = 3;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(ok, c);
+
+ cancel = new JButton("Cancel");
+ cancel.setActionCommand("cancel");
+ cancel.addActionListener(this);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 3;
+ c.gridy = 4;
+ c.gridwidth = 3;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(cancel, c);
+
+ pack();
+ setLocationRelativeTo(frame);
+ }
+
+ public static void show(Component frameComp) {
+ Frame frame = JOptionPane.getFrameForComponent(frameComp);
+ AltosConfigFreqUI dialog;
+
+ dialog = new AltosConfigFreqUI(frame, AltosUIPreferences.common_frequencies());
+ dialog.setVisible(true);
+ }
+
+}
diff --git a/altosui/AltosConfigTD.java b/altosui/AltosConfigTD.java
new file mode 100644
index 00000000..324a5988
--- /dev/null
+++ b/altosui/AltosConfigTD.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+
+import libaltosJNI.*;
+
+import org.altusmetrum.AltosLib.*;
+
+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;
+ string_ref config_version;
+ string_ref version;
+ string_ref product;
+ AltosConfigTDUI config_ui;
+ boolean serial_started;
+ 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;
+ }
+ }
+
+ void start_serial() throws InterruptedException, TimeoutException {
+ serial_started = true;
+ }
+
+ void stop_serial() throws InterruptedException {
+ if (!serial_started)
+ return;
+ serial_started = false;
+ }
+
+ 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_clean();
+ if (!made_visible) {
+ made_visible = true;
+ config_ui.make_visible();
+ }
+ }
+
+ void process_line(String line) {
+ if (line == null) {
+ abort();
+ return;
+ }
+ if (line.equals("all finished")) {
+ if (serial_line != null)
+ update_ui();
+ return;
+ }
+ 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_string(line,"software-version", version);
+ get_string(line,"product", product);
+ }
+
+ 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 {
+ AltosConfigTD config;
+ int serial_mode;
+
+ void process_line(String line) {
+ config.process_line(line);
+ }
+ void callback(String in_line) {
+ final String line = in_line;
+ Runnable r = new Runnable() {
+ public void run() {
+ process_line(line);
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ void reset_data() {
+ serial.set(0);
+ radio_channel.set(0);
+ radio_setting.set(0);
+ radio_frequency.set(0);
+ radio_calibration.set(1186611);
+ config_version.set("0.0");
+ version.set("unknown");
+ product.set("unknown");
+ }
+
+ void get_data() {
+ try {
+ boolean been_there = false;
+ config.start_serial();
+ reset_data();
+
+ for (;;) {
+ config.serial_line.printf("c s\nf\nl\nv\n");
+ for (;;) {
+ try {
+ String line = config.serial_line.get_reply(5000);
+ if (line == null)
+ stop_serial();
+ callback(line);
+ if (line.startsWith("software-version"))
+ break;
+ } catch (Exception e) {
+ break;
+ }
+ }
+ if (been_there)
+ break;
+ if (!config_version.get().equals("0.0"))
+ break;
+ been_there = true;
+ config.serial_line.printf("C\n ");
+ config.serial_line.flush_input();
+ }
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ } finally {
+ try {
+ stop_serial();
+ } catch (InterruptedException ie) {
+ }
+ }
+ double pref_frequency = AltosPreferences.frequency(serial.get());
+ if (pref_frequency != 0)
+ radio_frequency.set((int) Math.floor (pref_frequency * 1000 + 0.5));
+ callback("all finished");
+ }
+
+ void save_data() {
+ double frequency = frequency();
+ if (frequency != 0)
+ AltosPreferences.set_frequency(serial.get(),
+ frequency);
+ }
+
+ public void run () {
+ switch (serial_mode) {
+ case serial_mode_save:
+ save_data();
+ /* fall through ... */
+ case serial_mode_read:
+ get_data();
+ 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) {
+ SerialData sd = new SerialData(this, serial_mode);
+ Thread st = new Thread(sd);
+ st.start();
+ }
+
+ void init_ui () throws InterruptedException, TimeoutException {
+ config_ui = new AltosConfigTDUI(owner);
+ config_ui.addActionListener(this);
+ serial_line.set_frame(owner);
+ set_ui();
+ }
+
+ void abort() {
+ 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(radio_frequency.get(),
+ radio_setting.get(),
+ radio_calibration.get(),
+ radio_channel.get());
+ }
+
+ 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));
+ }
+ }
+
+ void save_data() {
+
+ set_frequency(config_ui.radio_frequency());
+ 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);
+ config_version = new string_ref("0.0");
+ version = new string_ref("unknown");
+ product = new string_ref("unknown");
+
+ device = AltosDeviceDialog.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);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(owner,
+ device.toShortString(),
+ ee.getLocalizedMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosConfigTDUI.java b/altosui/AltosConfigTDUI.java
new file mode 100644
index 00000000..f2058f69
--- /dev/null
+++ b/altosui/AltosConfigTDUI.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import libaltosJNI.*;
+
+import org.altusmetrum.AltosLib.*;
+
+public class AltosConfigTDUI
+ extends AltosDialog
+ 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;
+
+ public boolean dirty;
+
+ JFrame owner;
+ JLabel product_value;
+ JLabel version_value;
+ JLabel serial_value;
+ AltosFreqList radio_frequency_value;
+ JLabel radio_calibration_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 AltosFreqList();
+ 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);
+
+ /* 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 void set_clean() {
+ dirty = false;
+ }
+}
diff --git a/altosui/AltosConfigUI.java b/altosui/AltosConfigUI.java
new file mode 100644
index 00000000..62394fa6
--- /dev/null
+++ b/altosui/AltosConfigUI.java
@@ -0,0 +1,808 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosConfigUI
+ extends AltosDialog
+ implements ActionListener, ItemListener, DocumentListener
+{
+
+ Container pane;
+ Box box;
+ JLabel product_label;
+ JLabel version_label;
+ JLabel serial_label;
+ JLabel main_deploy_label;
+ JLabel apogee_delay_label;
+ JLabel apogee_lockout_label;
+ JLabel frequency_label;
+ JLabel radio_calibration_label;
+ JLabel radio_frequency_label;
+ JLabel radio_enable_label;
+ JLabel flight_log_max_label;
+ JLabel ignite_mode_label;
+ JLabel pad_orientation_label;
+ JLabel callsign_label;
+
+ public boolean dirty;
+
+ JFrame owner;
+ JLabel product_value;
+ JLabel version_value;
+ JLabel serial_value;
+ JComboBox main_deploy_value;
+ JComboBox apogee_delay_value;
+ JComboBox apogee_lockout_value;
+ AltosFreqList radio_frequency_value;
+ JTextField radio_calibration_value;
+ JRadioButton radio_enable_value;
+ JComboBox flight_log_max_value;
+ JComboBox ignite_mode_value;
+ JComboBox pad_orientation_value;
+ JTextField callsign_value;
+
+ JButton save;
+ JButton reset;
+ JButton reboot;
+ JButton close;
+
+ ActionListener listener;
+
+ static String[] main_deploy_values = {
+ "100", "150", "200", "250", "300", "350",
+ "400", "450", "500"
+ };
+
+ static String[] apogee_delay_values = {
+ "0", "1", "2", "3", "4", "5"
+ };
+
+ static String[] apogee_lockout_values = {
+ "0", "5", "10", "15", "20"
+ };
+
+ static String[] flight_log_max_values = {
+ "64", "128", "192", "256", "320",
+ "384", "448", "512", "576", "640",
+ "704", "768", "832", "896", "960",
+ };
+
+ static String[] ignite_mode_values = {
+ "Dual Deploy",
+ "Redundant Apogee",
+ "Redundant Main",
+ };
+
+ static String[] pad_orientation_values = {
+ "Antenna Up",
+ "Antenna Down",
+ };
+
+ /* A window listener to catch closing events and tell the config code */
+ class ConfigListener extends WindowAdapter {
+ AltosConfigUI ui;
+
+ public ConfigListener(AltosConfigUI this_ui) {
+ ui = this_ui;
+ }
+
+ public void windowClosing(WindowEvent e) {
+ ui.actionPerformed(new ActionEvent(e.getSource(),
+ ActionEvent.ACTION_PERFORMED,
+ "Close"));
+ }
+ }
+
+ boolean is_telemini() {
+ String product = product_value.getText();
+ return product != null && product.startsWith("TeleMini");
+ }
+
+ boolean is_telemetrum() {
+ String product = product_value.getText();
+ return product != null && product.startsWith("TeleMetrum");
+ }
+
+ void set_radio_calibration_tool_tip() {
+ if (radio_calibration_value.isEnabled())
+ radio_calibration_value.setToolTipText("Tune radio output to match desired frequency");
+ else
+ radio_calibration_value.setToolTipText("Cannot tune radio while connected over packet mode");
+ }
+
+ void set_radio_enable_tool_tip() {
+ if (radio_enable_value.isEnabled())
+ radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions");
+ else
+ radio_enable_value.setToolTipText("Firmware version does not support disabling radio");
+ }
+
+ void set_flight_log_max_tool_tip() {
+ if (flight_log_max_value.isEnabled())
+ flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)");
+ else {
+ if (is_telemetrum())
+ flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory");
+ else if (is_telemini())
+ flight_log_max_value.setToolTipText("TeleMini stores only one flight");
+ else
+ flight_log_max_value.setToolTipText("Cannot set max flight log value");
+ }
+ }
+
+ void set_ignite_mode_tool_tip() {
+ if (ignite_mode_value.isEnabled())
+ 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.isEnabled())
+ pad_orientation_value.setToolTipText("How will TeleMetrum be mounted in the airframe");
+ else {
+ if (is_telemetrum())
+ pad_orientation_value.setToolTipText("Older TeleMetrum firmware must fly antenna forward");
+ else if (is_telemini())
+ pad_orientation_value.setToolTipText("TeleMini doesn't care how it is mounted");
+ else
+ pad_orientation_value.setToolTipText("Can't select orientation");
+ }
+ }
+
+ /* Build the UI using a grid bag */
+ public AltosConfigUI(JFrame in_owner, boolean remote) {
+ super (in_owner, "Configure TeleMetrum", 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("Main Deploy Altitude(m):");
+ 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 AltosFreqList();
+ 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 JTextField(String.format("%d", 1186611));
+ radio_calibration_value.getDocument().addDocumentListener(this);
+ if (remote)
+ radio_calibration_value.setEnabled(false);
+ pane.add(radio_calibration_value, c);
+ set_radio_calibration_tool_tip();
+ 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 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++;
+
+ /* 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:");
+ 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_values);
+ 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++;
+
+ /* 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));
+ }
+
+ /* 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);
+ set_pad_orientation_tool_tip();
+ 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_main_deploy(int new_main_deploy) {
+ main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy));
+ }
+
+ public int main_deploy() {
+ return Integer.parseInt(main_deploy_value.getSelectedItem().toString());
+ }
+
+ public void set_apogee_delay(int new_apogee_delay) {
+ apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay));
+ }
+
+ public int apogee_delay() {
+ return Integer.parseInt(apogee_delay_value.getSelectedItem().toString());
+ }
+
+ public void set_apogee_lockout(int new_apogee_lockout) {
+ apogee_lockout_value.setSelectedItem(Integer.toString(new_apogee_lockout));
+ }
+
+ public int apogee_lockout() {
+ return Integer.parseInt(apogee_lockout_value.getSelectedItem().toString());
+ }
+
+ 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);
+ AltosUIPreferences.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 new_radio_calibration) {
+ radio_calibration_value.setText(String.format("%d", new_radio_calibration));
+ }
+
+ public int radio_calibration() {
+ return Integer.parseInt(radio_calibration_value.getText());
+ }
+
+ public void set_radio_enable(int new_radio_enable) {
+ if (new_radio_enable >= 0) {
+ radio_enable_value.setSelected(new_radio_enable > 0);
+ radio_enable_value.setEnabled(true);
+ } else {
+ radio_enable_value.setSelected(true);
+ radio_enable_value.setEnabled(false);
+ }
+ set_radio_enable_tool_tip();
+ }
+
+ public int radio_enable() {
+ if (radio_enable_value.isEnabled())
+ return radio_enable_value.isSelected() ? 1 : 0;
+ else
+ return -1;
+ }
+
+ public void set_callsign(String new_callsign) {
+ callsign_value.setText(new_callsign);
+ }
+
+ public String callsign() {
+ return callsign_value.getText();
+ }
+
+ public void set_flight_log_max(int new_flight_log_max) {
+ if (new_flight_log_max == 0)
+ flight_log_max_value.setEnabled(false);
+ flight_log_max_value.setSelectedItem(Integer.toString(new_flight_log_max));
+ 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() {
+ return Integer.parseInt(flight_log_max_value.getSelectedItem().toString());
+ }
+
+ public void set_flight_log_max_limit(int flight_log_max_limit) {
+ boolean any_added = false;
+ flight_log_max_value.removeAllItems();
+ for (int i = 0; i < flight_log_max_values.length; i++) {
+ if (Integer.parseInt(flight_log_max_values[i]) < flight_log_max_limit){
+ flight_log_max_value.addItem(flight_log_max_values[i]);
+ any_added = true;
+ }
+ }
+ flight_log_max_value.addItem(String.format("%d", flight_log_max_limit));
+ }
+
+ public void set_ignite_mode(int new_ignite_mode) {
+ 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);
+ set_ignite_mode_tool_tip();
+ }
+
+ public int ignite_mode() {
+ if (ignite_mode_value.isEnabled())
+ return ignite_mode_value.getSelectedIndex();
+ else
+ return -1;
+ }
+
+
+ public void set_pad_orientation(int new_pad_orientation) {
+ if (new_pad_orientation >= pad_orientation_values.length)
+ new_pad_orientation = 0;
+ if (new_pad_orientation < 0) {
+ pad_orientation_value.setEnabled(false);
+ new_pad_orientation = 0;
+ } else {
+ pad_orientation_value.setEnabled(true);
+ }
+ pad_orientation_value.setSelectedIndex(new_pad_orientation);
+ set_pad_orientation_tool_tip();
+ }
+
+ public int pad_orientation() {
+ if (pad_orientation_value.isEnabled())
+ return pad_orientation_value.getSelectedIndex();
+ else
+ return -1;
+ }
+
+ public void set_clean() {
+ dirty = false;
+ }
+}
diff --git a/altosui/AltosConfigureUI.java b/altosui/AltosConfigureUI.java
new file mode 100644
index 00000000..da82e8e0
--- /dev/null
+++ b/altosui/AltosConfigureUI.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import javax.swing.plaf.basic.*;
+import org.altusmetrum.AltosLib.*;
+
+class DelegatingRenderer implements ListCellRenderer {
+
+ // ...
+ public static void install(JComboBox comboBox) {
+ DelegatingRenderer renderer = new DelegatingRenderer(comboBox);
+ renderer.initialise();
+ comboBox.setRenderer(renderer);
+ }
+
+ // ...
+ private final JComboBox comboBox;
+
+ // ...
+ private ListCellRenderer delegate;
+
+ // ...
+ private DelegatingRenderer(JComboBox comboBox) {
+ this.comboBox = comboBox;
+ }
+
+ // ...
+ private void initialise() {
+ delegate = new JComboBox().getRenderer();
+ comboBox.addPropertyChangeListener("UI", new PropertyChangeListener() {
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ delegate = new JComboBox().getRenderer();
+ }
+ });
+ }
+
+ // ...
+ public Component getListCellRendererComponent(JList list,
+ Object value, int index, boolean isSelected, boolean cellHasFocus) {
+
+ return delegate.getListCellRendererComponent(list,
+ ((UIManager.LookAndFeelInfo) value).getName(),
+ index, isSelected, cellHasFocus);
+ }
+}
+
+public class AltosConfigureUI
+ extends AltosDialog
+ implements DocumentListener
+{
+ JFrame owner;
+ AltosVoice voice;
+ Container pane;
+
+ JRadioButton enable_voice;
+ JButton test_voice;
+ JButton close;
+
+ JButton configure_log;
+ JTextField log_directory;
+
+ JLabel callsign_label;
+ JTextField callsign_value;
+
+ JRadioButton imperial_units;
+
+ JLabel font_size_label;
+ JComboBox font_size_value;
+
+ JLabel look_and_feel_label;
+ JComboBox look_and_feel_value;
+
+ JRadioButton serial_debug;
+
+ JButton manage_bluetooth;
+ JButton manage_frequencies;
+
+ final static String[] font_size_names = { "Small", "Medium", "Large" };
+
+ /* DocumentListener interface methods */
+ public void changedUpdate(DocumentEvent e) {
+ AltosUIPreferences.set_callsign(callsign_value.getText());
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ changedUpdate(e);
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ changedUpdate(e);
+ }
+
+ public AltosConfigureUI(JFrame in_owner, AltosVoice in_voice) {
+ super(in_owner, "Configure AltosUI", false);
+
+ GridBagConstraints c;
+
+ Insets insets = new Insets(4, 4, 4, 4);
+
+ int row = 0;
+
+ owner = in_owner;
+ voice = in_voice;
+ pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ c = new GridBagConstraints();
+ c.insets = insets;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+
+ /* Nice label at the top */
+ c.gridx = 0;
+ c.gridy = row++;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ pane.add(new JLabel ("Configure AltOS UI"), c);
+
+ c.gridx = 0;
+ c.gridy = row++;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ pane.add(new JLabel (String.format("AltOS version %s", AltosVersion.version)), c);
+
+ /* Voice settings */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Voice"), c);
+
+ 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.");
+ }
+ });
+ c.gridx = 1;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.weightx = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(enable_voice, c);
+ enable_voice.setToolTipText("Enable/Disable all audio in-flight announcements");
+
+ c.gridx = 2;
+ c.gridy = row++;
+ c.gridwidth = 1;
+ c.weightx = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.EAST;
+ 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, c);
+ test_voice.setToolTipText("Play a stock audio clip to check volume");
+
+ /* Log directory settings */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Log Directory"), c);
+
+ configure_log = new JButton(AltosUIPreferences.logdir().getPath());
+ configure_log.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ AltosUIPreferences.ConfigureLog();
+ configure_log.setText(AltosUIPreferences.logdir().getPath());
+ }
+ });
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(configure_log, c);
+ configure_log.setToolTipText("Which directory flight logs are stored in");
+
+ /* Callsign setting */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Callsign"), c);
+
+ callsign_value = new JTextField(AltosUIPreferences.callsign());
+ callsign_value.getDocument().addDocumentListener(this);
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(callsign_value, c);
+ callsign_value.setToolTipText("Callsign sent in packet mode");
+
+ /* Imperial units setting */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Imperial Units"), c);
+
+ imperial_units = new JRadioButton("Enable", AltosUIPreferences.serial_debug());
+ imperial_units.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JRadioButton item = (JRadioButton) e.getSource();
+ boolean enabled = item.isSelected();
+ AltosUIPreferences.set_imperial_units(enabled);
+ }
+ });
+ imperial_units.setToolTipText("Use Imperial units instead of metric");
+
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(imperial_units, c);
+
+ /* Font size setting */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Font size"), c);
+
+ font_size_value = new JComboBox(font_size_names);
+ int font_size = AltosUIPreferences.font_size();
+ font_size_value.setSelectedIndex(font_size - Altos.font_size_small);
+ font_size_value.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int size = font_size_value.getSelectedIndex() + Altos.font_size_small;
+
+ AltosUIPreferences.set_font_size(size);
+ }
+ });
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(font_size_value, c);
+ font_size_value.setToolTipText("Font size used in telemetry window");
+
+ /* Look & Feel setting */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Look & feel"), c);
+
+ class LookAndFeelRenderer extends BasicComboBoxRenderer implements ListCellRenderer {
+
+ public LookAndFeelRenderer() {
+ super();
+ }
+
+ public Component getListCellRendererComponent(
+ JList list,
+ Object value,
+ int index,
+ boolean isSelected,
+ boolean cellHasFocus)
+ {
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ setText(((UIManager.LookAndFeelInfo) value).getName());
+ return this;
+ }
+ }
+
+ final UIManager.LookAndFeelInfo[] look_and_feels = UIManager.getInstalledLookAndFeels();
+
+ look_and_feel_value = new JComboBox(look_and_feels);
+
+ DelegatingRenderer.install(look_and_feel_value);
+
+ String look_and_feel = AltosUIPreferences.look_and_feel();
+ for (int i = 0; i < look_and_feels.length; i++)
+ if (look_and_feel.equals(look_and_feels[i].getClassName()))
+ look_and_feel_value.setSelectedIndex(i);
+
+ look_and_feel_value.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int id = look_and_feel_value.getSelectedIndex();
+
+ AltosUIPreferences.set_look_and_feel(look_and_feels[id].getClassName());
+ }
+ });
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(look_and_feel_value, c);
+ look_and_feel_value.setToolTipText("Look&feel used for new windows");
+
+ /* Serial debug setting */
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 1;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(new JLabel("Serial Debug"), c);
+
+ serial_debug = new JRadioButton("Enable", AltosUIPreferences.serial_debug());
+ serial_debug.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JRadioButton item = (JRadioButton) e.getSource();
+ boolean enabled = item.isSelected();
+ AltosUIPreferences.set_serial_debug(enabled);
+ }
+ });
+ serial_debug.setToolTipText("Enable/Disable USB I/O getting sent to the console");
+
+ c.gridx = 1;
+ c.gridy = row++;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(serial_debug, c);
+
+ manage_bluetooth = new JButton("Manage Bluetooth");
+ manage_bluetooth.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ AltosBTManage.show(owner, AltosBTKnown.bt_known());
+ }
+ });
+ c.gridx = 0;
+ c.gridy = row;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(manage_bluetooth, c);
+
+ 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");
+ c.gridx = 2;
+ c.gridx = 2;
+ c.gridy = row++;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ pane.add(manage_frequencies, c);
+
+ /* And a close button at the bottom */
+ close = new JButton("Close");
+ close.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+ });
+ c.gridx = 0;
+ c.gridy = row++;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ pane.add(close, c);
+
+ pack();
+ setLocationRelativeTo(owner);
+ setVisible(true);
+ }
+}
diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java
new file mode 100644
index 00000000..4bd51c39
--- /dev/null
+++ b/altosui/AltosDataChooser.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosDataChooser extends JFileChooser {
+ JFrame frame;
+ String filename;
+ File file;
+
+ public String filename() {
+ return filename;
+ }
+
+ public File file() {
+ return file;
+ }
+
+ public AltosRecordIterable runDialog() {
+ int ret;
+
+ ret = showOpenDialog(frame);
+ if (ret == APPROVE_OPTION) {
+ file = getSelectedFile();
+ if (file == null)
+ return null;
+ filename = file.getName();
+ try {
+ if (filename.endsWith("eeprom")) {
+ FileInputStream in = new FileInputStream(file);
+ return new AltosEepromIterable(in);
+ } else if (filename.endsWith("telem")) {
+ FileInputStream in = new FileInputStream(file);
+ return new AltosTelemetryIterable(in);
+ } else if (filename.endsWith("mega")) {
+ FileInputStream in = new FileInputStream(file);
+ return new AltosEepromMegaIterable(in);
+ } else {
+ throw new FileNotFoundException();
+ }
+ } catch (FileNotFoundException fe) {
+ JOptionPane.showMessageDialog(frame,
+ fe.getMessage(),
+ "Cannot open file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ return null;
+ }
+
+ public AltosDataChooser(JFrame in_frame) {
+ frame = in_frame;
+ setDialogTitle("Select Flight Record File");
+ setFileFilter(new FileNameExtensionFilter("Flight data file",
+ "telem", "eeprom", "mega"));
+ setCurrentDirectory(AltosUIPreferences.logdir());
+ }
+}
diff --git a/altosui/AltosDataPoint.java b/altosui/AltosDataPoint.java
new file mode 100644
index 00000000..5e077320
--- /dev/null
+++ b/altosui/AltosDataPoint.java
@@ -0,0 +1,30 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+interface AltosDataPoint {
+ int version();
+ int serial();
+ int flight();
+ String callsign();
+ double time();
+ double rssi();
+
+ int state();
+ String state_name();
+
+ double acceleration();
+ double pressure();
+ double altitude();
+ double height();
+ double accel_speed();
+ double baro_speed();
+ double temperature();
+ double battery_voltage();
+ double drogue_voltage();
+ double main_voltage();
+ boolean has_accel();
+}
+
diff --git a/altosui/AltosDataPointReader.java b/altosui/AltosDataPointReader.java
new file mode 100644
index 00000000..821b0771
--- /dev/null
+++ b/altosui/AltosDataPointReader.java
@@ -0,0 +1,86 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.lang.UnsupportedOperationException;
+import java.util.NoSuchElementException;
+import java.util.Iterator;
+import org.altusmetrum.AltosLib.*;
+
+class AltosDataPointReader implements Iterable<AltosDataPoint> {
+ Iterator<AltosRecord> iter;
+ AltosState state;
+ AltosRecord record;
+ boolean has_gps;
+ boolean has_accel;
+ boolean has_ignite;
+
+ final static int MISSING = AltosRecord.MISSING;
+
+ public AltosDataPointReader(AltosRecordIterable reader) {
+ this.iter = reader.iterator();
+ this.state = null;
+ has_accel = reader.has_accel();
+ has_gps = reader.has_gps();
+ has_ignite = reader.has_ignite();
+ }
+
+ private void read_next_record()
+ throws NoSuchElementException
+ {
+ record = iter.next();
+ state = new AltosState(record, state);
+ }
+
+ private AltosDataPoint current_dp() {
+ assert this.record != null;
+
+ return new AltosDataPoint() {
+ public int version() { return record.version; }
+ public int serial() { return record.serial; }
+ public int flight() { return record.flight; }
+ public String callsign() { return record.callsign; }
+ public double time() { return record.time; }
+ public double rssi() { return record.rssi; }
+
+ public int state() { return record.state; }
+ public String state_name() { return record.state(); }
+
+ public double acceleration() { return record.acceleration(); }
+ public double pressure() { return record.raw_pressure(); }
+ public double altitude() { return record.raw_altitude(); }
+ public double height() { return record.raw_height(); }
+ public double accel_speed() { return record.accel_speed(); }
+ public double baro_speed() { return state.baro_speed; }
+ public double temperature() { return record.temperature(); }
+ public double battery_voltage() { return record.battery_voltage(); }
+ public double drogue_voltage() { return record.drogue_voltage(); }
+ public double main_voltage() { return record.main_voltage(); }
+ public boolean has_accel() { return has_accel; }
+ };
+ }
+
+ public Iterator<AltosDataPoint> iterator() {
+ return new Iterator<AltosDataPoint>() {
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ public boolean hasNext() {
+ if (record != null && record.state == Altos.ao_flight_landed)
+ return false;
+ return iter.hasNext();
+ }
+ public AltosDataPoint next() {
+ do {
+ read_next_record();
+ } while (record.time < -1.0 && hasNext());
+ return current_dp();
+ }
+ };
+ }
+}
+
diff --git a/altosui/AltosDebug.java b/altosui/AltosDebug.java
new file mode 100644
index 00000000..23e38bc0
--- /dev/null
+++ b/altosui/AltosDebug.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.util.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosDebug extends AltosSerial {
+
+ public static final byte WR_CONFIG = 0x1d;
+ public static final byte RD_CONFIG = 0x24;
+ public static final byte CONFIG_TIMERS_OFF = (1 << 3);
+ public static final byte CONFIG_DMA_PAUSE = (1 << 2);
+ public static final byte CONFIG_TIMER_SUSPEND = (1 << 1);
+ public static final byte SET_FLASH_INFO_PAGE = (1 << 0);
+
+ public static final byte GET_PC = 0x28;
+ public static final byte READ_STATUS = 0x34;
+ public static final byte STATUS_CHIP_ERASE_DONE = (byte) (1 << 7);
+ public static final byte STATUS_PCON_IDLE = (1 << 6);
+ public static final byte STATUS_CPU_HALTED = (1 << 5);
+ public static final byte STATUS_POWER_MODE_0 = (1 << 4);
+ public static final byte STATUS_HALT_STATUS = (1 << 3);
+ public static final byte STATUS_DEBUG_LOCKED = (1 << 2);
+ public static final byte STATUS_OSCILLATOR_STABLE = (1 << 1);
+ public static final byte STATUS_STACK_OVERFLOW = (1 << 0);
+
+ public static final byte SET_HW_BRKPNT = 0x3b;
+ public static byte HW_BRKPNT_N(byte n) { return (byte) ((n) << 3); }
+ public static final byte HW_BRKPNT_N_MASK = (0x3 << 3);
+ public static final byte HW_BRKPNT_ENABLE = (1 << 2);
+
+ public static final byte HALT = 0x44;
+ public static final byte RESUME = 0x4c;
+ public static byte DEBUG_INSTR(byte n) { return (byte) (0x54|(n)); }
+ public static final byte STEP_INSTR = 0x5c;
+ public static byte STEP_REPLACE(byte n) { return (byte) (0x64|(n)); }
+ public static final byte GET_CHIP_ID = 0x68;
+
+
+ boolean debug_mode;
+
+ void ensure_debug_mode() {
+ if (!debug_mode) {
+ printf("D\n");
+ try {
+ flush_input();
+ } catch (InterruptedException ie) {
+ }
+ debug_mode = true;
+ }
+ }
+
+ void dump_memory(String header, int address, byte[] bytes, int start, int len) {
+ System.out.printf("%s\n", header);
+ for (int j = 0; j < len; j++) {
+ if ((j & 15) == 0) {
+ if (j != 0)
+ System.out.printf("\n");
+ System.out.printf ("%04x:", address + j);
+ }
+ System.out.printf(" %02x", bytes[start + j]);
+ }
+ System.out.printf("\n");
+ }
+
+ /*
+ * Write target memory
+ */
+ public void write_memory(int address, byte[] bytes, int start, int len) {
+ ensure_debug_mode();
+// dump_memory("write_memory", address, bytes, start, len);
+ printf("O %x %x\n", len, address);
+ for (int i = 0; i < len; i++)
+ printf("%02x", bytes[start + i]);
+ }
+
+ public void write_memory(int address, byte[] bytes) {
+ write_memory(address, bytes, 0, bytes.length);
+ }
+
+ /*
+ * Read target memory
+ */
+ public byte[] read_memory(int address, int length)
+ throws IOException, InterruptedException {
+ byte[] data = new byte[length];
+
+ flush_input();
+ ensure_debug_mode();
+ printf("I %x %x\n", length, address);
+ int i = 0;
+ int start = 0;
+ while (i < length) {
+ String line = get_reply().trim();
+ if (!Altos.ishex(line) || line.length() % 2 != 0)
+ throw new IOException(
+ String.format
+ ("Invalid reply \"%s\"", line));
+ int this_time = line.length() / 2;
+ for (int j = 0; j < this_time; j++)
+ data[start + j] = (byte) ((Altos.fromhex(line.charAt(j*2)) << 4) +
+ Altos.fromhex(line.charAt(j*2+1)));
+ start += this_time;
+ i += this_time;
+ }
+// dump_memory("read_memory", address, data, 0, length);
+
+ return data;
+ }
+
+ /*
+ * Write raw bytes to the debug link using the 'P' command
+ */
+ public void write_bytes(byte[] bytes) throws IOException {
+ int i = 0;
+ ensure_debug_mode();
+ while (i < bytes.length) {
+ int this_time = bytes.length - i;
+ if (this_time > 8)
+ this_time = 0;
+ printf("P");
+ for (int j = 0; j < this_time; j++)
+ printf(" %02x", bytes[i+j]);
+ printf("\n");
+ i += this_time;
+ }
+ }
+
+ public void write_byte(byte b) throws IOException {
+ byte[] bytes = { b };
+ write_bytes(bytes);
+ }
+
+ /*
+ * Read raw bytes from the debug link using the 'G' command
+ */
+ public byte[] read_bytes(int length)
+ throws IOException, InterruptedException {
+
+ flush_input();
+ ensure_debug_mode();
+ printf("G %x\n", length);
+ int i = 0;
+ byte[] data = new byte[length];
+ while (i < length) {
+ String line = get_reply();
+
+ if (line == null)
+ throw new IOException("Timeout in read_bytes");
+ line = line.trim();
+ String tokens[] = line.split("\\s+");
+ for (int j = 0; j < tokens.length; j++) {
+ if (!Altos.ishex(tokens[j]) ||
+ tokens[j].length() != 2)
+ throw new IOException(
+ String.format
+ ("Invalid read_bytes reply \"%s\"", line));
+ try {
+ if (i + j >= length)
+ throw new IOException(
+ String.format
+ ("Invalid read_bytes reply \"%s\"", line));
+ else
+ data[i + j] = (byte) Integer.parseInt(tokens[j], 16);
+ } catch (NumberFormatException ne) {
+ throw new IOException(
+ String.format
+ ("Invalid read_bytes reply \"%s\"", line));
+ }
+ }
+ i += tokens.length;
+ }
+ return data;
+ }
+
+ public byte read_byte() throws IOException, InterruptedException {
+ return read_bytes(1)[0];
+ }
+
+ public byte debug_instr(byte[] instruction) throws IOException, InterruptedException {
+ byte[] command = new byte[1 + instruction.length];
+ command[0] = DEBUG_INSTR((byte) instruction.length);
+ for (int i = 0; i < instruction.length; i++)
+ command[i+1] = instruction[i];
+ write_bytes(command);
+ return read_byte();
+ }
+
+ public byte resume() throws IOException, InterruptedException {
+ write_byte(RESUME);
+ return read_byte();
+ }
+
+ public int read_uint16() throws IOException, InterruptedException {
+ byte[] d = read_bytes(2);
+ return ((int) (d[0] & 0xff) << 8) | (d[1] & 0xff);
+ }
+
+ public int read_uint8() throws IOException, InterruptedException {
+ byte[] d = read_bytes(1);
+ return (int) (d[0] & 0xff);
+ }
+
+ public int get_chip_id() throws IOException, InterruptedException {
+ write_byte(GET_CHIP_ID);
+ return read_uint16();
+ }
+
+ public int get_pc() throws IOException, InterruptedException {
+ write_byte(GET_PC);
+ return read_uint16();
+ }
+
+ public byte read_status() throws IOException, InterruptedException {
+ write_byte(READ_STATUS);
+ return read_byte();
+ }
+
+ static final byte LJMP = 0x02;
+
+ public void set_pc(int pc) throws IOException, InterruptedException {
+ byte high = (byte) (pc >> 8);
+ byte low = (byte) pc;
+ byte[] jump_mem = { LJMP, high, low };
+ debug_instr(jump_mem);
+ }
+
+ public boolean check_connection() throws IOException, InterruptedException {
+ byte reply = read_status();
+ if ((reply & STATUS_CHIP_ERASE_DONE) == 0)
+ return false;
+ if ((reply & STATUS_PCON_IDLE) != 0)
+ return false;
+ if ((reply & STATUS_POWER_MODE_0) == 0)
+ return false;
+ return true;
+ }
+
+ public AltosRomconfig romconfig() {
+ try {
+ byte[] bytes = read_memory(0xa0, 10);
+ return new AltosRomconfig(bytes, 0);
+ } catch (IOException ie) {
+ } catch (InterruptedException ie) {
+ }
+ return new AltosRomconfig();
+ }
+
+ /*
+ * Reset target
+ */
+ public void reset() {
+ printf ("R\n");
+ }
+
+ public AltosDebug (AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
+ super(in_device);
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosDescent.java b/altosui/AltosDescent.java
new file mode 100644
index 00000000..62258814
--- /dev/null
+++ b/altosui/AltosDescent.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosDescent extends JComponent implements AltosFlightDisplay {
+ GridBagLayout layout;
+
+ public abstract class DescentStatus {
+ JLabel label;
+ JTextField value;
+ AltosLights lights;
+
+ abstract void show(AltosState state, int crc_errors);
+
+ void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ lights.setVisible(true);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ lights.setVisible(false);
+ }
+
+ void reset() {
+ value.setText("");
+ lights.set(false);
+ }
+
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ public DescentStatus (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ lights = new AltosLights();
+ c.gridx = 0; c.gridy = y;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(lights, c);
+ add(lights);
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.gridwidth = 3;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 4; c.gridy = y;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+
+ }
+ }
+
+ public abstract class DescentValue {
+ JLabel label;
+ JTextField value;
+
+ void reset() {
+ value.setText("");
+ }
+
+ abstract void show(AltosState state, int crc_errors);
+
+ void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ }
+
+ void show(AltosUnits units, double v) {
+ value.setText(units.show(8, v));
+ }
+
+ void show(String format, double v) {
+ value.setText(String.format(format, v));
+ }
+
+ void show(String v) {
+ value.setText(v);
+ }
+
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ public DescentValue (GridBagLayout layout, int x, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = x + 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ add(label, c);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = x + 2; c.gridy = y;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ add(value, c);
+ }
+ }
+
+ public abstract class DescentDualValue {
+ JLabel label;
+ JTextField value1;
+ JTextField value2;
+
+ void reset() {
+ value1.setText("");
+ value2.setText("");
+ }
+
+ void show() {
+ label.setVisible(true);
+ value1.setVisible(true);
+ value2.setVisible(true);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value1.setVisible(false);
+ value2.setVisible(false);
+ }
+
+ void set_font() {
+ label.setFont(Altos.label_font);
+ value1.setFont(Altos.value_font);
+ value2.setFont(Altos.value_font);
+ }
+
+ abstract void show(AltosState state, int crc_errors);
+
+ void show(String v1, String v2) {
+ show();
+ value1.setText(v1);
+ value2.setText(v2);
+ }
+ void show(String f1, double v1, String f2, double v2) {
+ show();
+ value1.setText(String.format(f1, v1));
+ value2.setText(String.format(f2, v2));
+ }
+
+ public DescentDualValue (GridBagLayout layout, int x, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = x + 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value1 = new JTextField(Altos.text_width);
+ value1.setFont(Altos.value_font);
+ value1.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = x + 2; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value1, c);
+ add(value1);
+
+ value2 = new JTextField(Altos.text_width);
+ value2.setFont(Altos.value_font);
+ value2.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = x + 4; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ c.gridwidth = 1;
+ layout.setConstraints(value2, c);
+ add(value2);
+ }
+ }
+
+ class Height extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ show(AltosConvert.height, state.height);
+ }
+ public Height (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Height");
+ }
+ }
+
+ Height height;
+
+ class Speed extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ double speed = state.speed;
+ if (!state.ascent)
+ speed = state.baro_speed;
+ show(AltosConvert.speed, speed);
+ }
+ public Speed (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Speed");
+ }
+ }
+
+ Speed speed;
+
+ String pos(double p, String pos, String neg) {
+ String h = pos;
+ if (p < 0) {
+ h = neg;
+ p = -p;
+ }
+ int deg = (int) Math.floor(p);
+ double min = (p - Math.floor(p)) * 60.0;
+ return String.format("%s %d° %9.6f", h, deg, min);
+ }
+
+ class Lat extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ if (state.gps != null && state.gps.connected)
+ show(pos(state.gps.lat,"N", "S"));
+ else
+ show("???");
+ }
+ public Lat (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Latitude");
+ }
+ }
+
+ Lat lat;
+
+ class Lon extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ if (state.gps != null && state.gps.connected)
+ show(pos(state.gps.lon,"W", "E"));
+ else
+ show("???");
+ }
+ public Lon (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Longitude");
+ }
+ }
+
+ Lon lon;
+
+ class Apogee extends DescentStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.drogue_sense));
+ lights.set(state.drogue_sense > 3.2);
+ }
+ public Apogee (GridBagLayout layout, int y) {
+ super(layout, y, "Apogee Igniter Voltage");
+ }
+ }
+
+ Apogee apogee;
+
+ class Main extends DescentStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.main_sense));
+ lights.set(state.main_sense > 3.2);
+ }
+ public Main (GridBagLayout layout, int y) {
+ super(layout, y, "Main Igniter Voltage");
+ }
+ }
+
+ Main main;
+
+ class Bearing extends DescentDualValue {
+ void show (AltosState state, int crc_errors) {
+ if (state.from_pad != null) {
+ show( String.format("%3.0f°", state.from_pad.bearing),
+ state.from_pad.bearing_words(
+ AltosGreatCircle.BEARING_LONG));
+ } else {
+ show("???", "???");
+ }
+ }
+ public Bearing (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Bearing");
+ }
+ }
+
+ Bearing bearing;
+
+ class Range extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ show(AltosConvert.distance, state.range);
+ }
+ public Range (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Range");
+ }
+ }
+
+ Range range;
+
+ class Elevation extends DescentValue {
+ void show (AltosState state, int crc_errors) {
+ show("%3.0f°", state.elevation);
+ }
+ public Elevation (GridBagLayout layout, int x, int y) {
+ super (layout, x, y, "Elevation");
+ }
+ }
+
+ Elevation elevation;
+
+ public void reset() {
+ lat.reset();
+ lon.reset();
+ height.reset();
+ speed.reset();
+ bearing.reset();
+ range.reset();
+ elevation.reset();
+ main.reset();
+ apogee.reset();
+ }
+
+ public void set_font() {
+ lat.set_font();
+ lon.set_font();
+ height.set_font();
+ speed.set_font();
+ bearing.set_font();
+ range.set_font();
+ elevation.set_font();
+ main.set_font();
+ apogee.set_font();
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ height.show(state, crc_errors);
+ speed.show(state, crc_errors);
+ if (state.gps != null && state.gps.connected) {
+ bearing.show(state, crc_errors);
+ range.show(state, crc_errors);
+ elevation.show(state, crc_errors);
+ lat.show(state, crc_errors);
+ lon.show(state, crc_errors);
+ } else {
+ bearing.hide();
+ range.hide();
+ elevation.hide();
+ lat.hide();
+ lon.hide();
+ }
+ if (state.main_sense != AltosRecord.MISSING)
+ main.show(state, crc_errors);
+ else
+ main.hide();
+ if (state.drogue_sense != AltosRecord.MISSING)
+ apogee.show(state, crc_errors);
+ else
+ apogee.hide();
+ }
+
+ public AltosDescent() {
+ layout = new GridBagLayout();
+
+ setLayout(layout);
+
+ /* Elements in descent display */
+ speed = new Speed(layout, 0, 0);
+ height = new Height(layout, 2, 0);
+ elevation = new Elevation(layout, 0, 1);
+ range = new Range(layout, 2, 1);
+ bearing = new Bearing(layout, 0, 2);
+ lat = new Lat(layout, 0, 3);
+ lon = new Lon(layout, 2, 3);
+
+ apogee = new Apogee(layout, 4);
+ main = new Main(layout, 5);
+ }
+}
diff --git a/altosui/AltosDevice.java b/altosui/AltosDevice.java
new file mode 100644
index 00000000..1b5c1a91
--- /dev/null
+++ b/altosui/AltosDevice.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.lang.*;
+import java.util.*;
+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/altosui/AltosDeviceDialog.java b/altosui/AltosDeviceDialog.java
new file mode 100644
index 00000000..fa9d0013
--- /dev/null
+++ b/altosui/AltosDeviceDialog.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.util.*;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import libaltosJNI.*;
+
+public class AltosDeviceDialog extends AltosDialog implements ActionListener {
+
+ private AltosDevice value;
+ private JList list;
+ private JButton cancel_button;
+ private JButton select_button;
+ private JButton manage_bluetooth_button;
+ private Frame frame;
+ private int product;
+
+ private AltosDevice getValue() {
+ return value;
+ }
+
+ private AltosDevice[] devices() {
+ java.util.List<AltosDevice> usb_devices = AltosUSBDevice.list(product);
+ int num_devices = usb_devices.size();
+ java.util.List<AltosDevice> bt_devices = AltosBTKnown.bt_known().list(product);
+ num_devices += bt_devices.size();
+ AltosDevice[] devices = new AltosDevice[num_devices];
+
+ for (int i = 0; i < usb_devices.size(); i++)
+ devices[i] = usb_devices.get(i);
+ int off = usb_devices.size();
+ for (int j = 0; j < bt_devices.size(); j++)
+ devices[off + j] = bt_devices.get(j);
+ return devices;
+ }
+
+ private void update_devices() {
+ AltosDevice[] devices = devices();
+ list.setListData(devices);
+ select_button.setEnabled(devices.length > 0);
+ }
+
+ private AltosDeviceDialog (Frame in_frame, Component location, int in_product) {
+ super(in_frame, "Device Selection", true);
+
+ product = in_product;
+ frame = in_frame;
+ value = null;
+
+ AltosDevice[] devices = devices();
+
+ cancel_button = new JButton("Cancel");
+ cancel_button.setActionCommand("cancel");
+ cancel_button.addActionListener(this);
+
+ manage_bluetooth_button = new JButton("Manage Bluetooth");
+ manage_bluetooth_button.setActionCommand("manage");
+ manage_bluetooth_button.addActionListener(this);
+
+ select_button = new JButton("Select");
+ select_button.setActionCommand("select");
+ select_button.addActionListener(this);
+ if (devices.length == 0)
+ select_button.setEnabled(false);
+ getRootPane().setDefaultButton(select_button);
+
+ list = new JList(devices) {
+ //Subclass JList to workaround bug 4832765, which can cause the
+ //scroll pane to not let the user easily scroll up to the beginning
+ //of the list. An alternative would be to set the unitIncrement
+ //of the JScrollBar to a fixed value. You wouldn't get the nice
+ //aligned scrolling, but it should work.
+ public int getScrollableUnitIncrement(Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ int row;
+ if (orientation == SwingConstants.VERTICAL &&
+ direction < 0 && (row = getFirstVisibleIndex()) != -1) {
+ Rectangle r = getCellBounds(row, row);
+ if ((r.y == visibleRect.y) && (row != 0)) {
+ Point loc = r.getLocation();
+ loc.y--;
+ int prevIndex = locationToIndex(loc);
+ Rectangle prevR = getCellBounds(prevIndex, prevIndex);
+
+ if (prevR == null || prevR.y >= r.y) {
+ return 0;
+ }
+ return prevR.height;
+ }
+ }
+ return super.getScrollableUnitIncrement(
+ visibleRect, orientation, direction);
+ }
+ };
+
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
+ list.setVisibleRowCount(-1);
+ list.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ select_button.doClick(); //emulate button click
+ }
+ }
+ });
+ JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setPreferredSize(new Dimension(400, 80));
+ listScroller.setAlignmentX(LEFT_ALIGNMENT);
+
+ //Create a container so that we can add a title around
+ //the scroll pane. Can't add a title directly to the
+ //scroll pane because its background would be white.
+ //Lay out the label and scroll pane from top to bottom.
+ JPanel listPane = new JPanel();
+ listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
+
+ JLabel label = new JLabel("Select Device");
+ label.setLabelFor(list);
+ listPane.add(label);
+ listPane.add(Box.createRigidArea(new Dimension(0,5)));
+ listPane.add(listScroller);
+ listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+ //Lay out the buttons from left to right.
+ JPanel buttonPane = new JPanel();
+ buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
+ buttonPane.add(Box.createHorizontalGlue());
+ buttonPane.add(cancel_button);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(manage_bluetooth_button);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(select_button);
+
+ //Put everything together, using the content pane's BorderLayout.
+ Container contentPane = getContentPane();
+ contentPane.add(listPane, BorderLayout.CENTER);
+ contentPane.add(buttonPane, BorderLayout.PAGE_END);
+
+ //Initialize values.
+ if (devices != null && devices.length != 0)
+ list.setSelectedValue(devices[0], true);
+ pack();
+ setLocationRelativeTo(location);
+ }
+
+ //Handle clicks on the Set and Cancel buttons.
+ public void actionPerformed(ActionEvent e) {
+ if ("select".equals(e.getActionCommand()))
+ value = (AltosDevice)(list.getSelectedValue());
+ if ("manage".equals(e.getActionCommand())) {
+ AltosBTManage.show(frame, AltosBTKnown.bt_known());
+ update_devices();
+ return;
+ }
+ setVisible(false);
+ }
+
+ public static AltosDevice show (Component frameComp, int product) {
+
+ Frame frame = JOptionPane.getFrameForComponent(frameComp);
+ AltosDeviceDialog dialog;
+
+ dialog = new AltosDeviceDialog(frame, frameComp, product);
+ dialog.setVisible(true);
+ return dialog.getValue();
+ }
+}
diff --git a/altosui/AltosDialog.java b/altosui/AltosDialog.java
new file mode 100644
index 00000000..ff38c3e4
--- /dev/null
+++ b/altosui/AltosDialog.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+class AltosDialogListener extends WindowAdapter {
+ public void windowClosing (WindowEvent e) {
+ AltosUIPreferences.unregister_ui_listener((AltosDialog) e.getWindow());
+ }
+}
+
+public class AltosDialog extends JDialog implements AltosUIListener {
+
+ public void ui_changed(String look_and_feel) {
+ SwingUtilities.updateComponentTreeUI(this);
+ this.pack();
+ }
+
+ public AltosDialog() {
+ AltosUIPreferences.register_ui_listener(this);
+ addWindowListener(new AltosDialogListener());
+ }
+
+ public AltosDialog(Frame frame, String label, boolean modal) {
+ super(frame, label, modal);
+ AltosUIPreferences.register_ui_listener(this);
+ addWindowListener(new AltosDialogListener());
+ }
+
+ public AltosDialog(Frame frame, boolean modal) {
+ super(frame, modal);
+ AltosUIPreferences.register_ui_listener(this);
+ addWindowListener(new AltosDialogListener());
+ }
+}
diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java
new file mode 100644
index 00000000..cf69c414
--- /dev/null
+++ b/altosui/AltosDisplayThread.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosDisplayThread extends Thread {
+
+ Frame parent;
+ IdleThread idle_thread;
+ AltosVoice voice;
+ AltosFlightReader reader;
+ int crc_errors;
+ AltosFlightDisplay display;
+
+ void show_internal(AltosState state, int crc_errors) {
+ if (state != null)
+ display.show(state, crc_errors);
+ }
+
+ void show_safely(AltosState in_state, int in_crc_errors) {
+ final AltosState state = in_state;
+ final int crc_errors = in_crc_errors;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ show_internal(state, crc_errors);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ void reading_error_internal() {
+ JOptionPane.showMessageDialog(parent,
+ String.format("Error reading from \"%s\"", reader.name),
+ "Telemetry Read Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ void reading_error_safely() {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ reading_error_internal();
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ class IdleThread extends Thread {
+
+ boolean started;
+ private AltosState state;
+ int reported_landing;
+ int report_interval;
+ long report_time;
+
+ public synchronized void report(boolean last) {
+ if (state == null)
+ return;
+
+ /* reset the landing count once we hear about a new flight */
+ if (state.state < Altos.ao_flight_drogue)
+ reported_landing = 0;
+
+ /* Shut up once the rocket is on the ground */
+ if (reported_landing > 2) {
+ return;
+ }
+
+ /* If the rocket isn't on the pad, then report height */
+ if (Altos.ao_flight_drogue <= state.state &&
+ state.state < Altos.ao_flight_landed &&
+ state.range >= 0)
+ {
+ voice.speak("Height %s, bearing %s %d, elevation %d, range %s.\n",
+ AltosConvert.height.say(state.height),
+ state.from_pad.bearing_words(
+ AltosGreatCircle.BEARING_VOICE),
+ (int) (state.from_pad.bearing + 0.5),
+ (int) (state.elevation + 0.5),
+ AltosConvert.distance.say(state.range));
+ } else if (state.state > Altos.ao_flight_pad) {
+ voice.speak(AltosConvert.height.say_units(state.height));
+ } else {
+ reported_landing = 0;
+ }
+
+ /* If the rocket is coming down, check to see if it has landed;
+ * either we've got a landed report or we haven't heard from it in
+ * a long time
+ */
+ if (state.state >= Altos.ao_flight_drogue &&
+ (last ||
+ System.currentTimeMillis() - state.report_time >= 15000 ||
+ state.state == Altos.ao_flight_landed))
+ {
+ if (Math.abs(state.baro_speed) < 20 && state.height < 100)
+ voice.speak("rocket landed safely");
+ else
+ voice.speak("rocket may have crashed");
+ if (state.from_pad != null)
+ voice.speak("Bearing %d degrees, range %s.",
+ (int) (state.from_pad.bearing + 0.5),
+ AltosConvert.distance.say_units(state.from_pad.distance));
+ ++reported_landing;
+ if (state.state != Altos.ao_flight_landed) {
+ state.state = Altos.ao_flight_landed;
+ show_safely(state, 0);
+ }
+ }
+ }
+
+ long now () {
+ return System.currentTimeMillis();
+ }
+
+ void set_report_time() {
+ report_time = now() + report_interval;
+ }
+
+ public void run () {
+ try {
+ for (;;) {
+ set_report_time();
+ for (;;) {
+ voice.drain();
+ synchronized (this) {
+ long sleep_time = report_time - now();
+ if (sleep_time <= 0)
+ break;
+ wait(sleep_time);
+ }
+ }
+ report(false);
+ }
+ } catch (InterruptedException ie) {
+ try {
+ voice.drain();
+ } catch (InterruptedException iie) { }
+ }
+ }
+
+ public synchronized void notice(AltosState new_state, boolean spoken) {
+ AltosState old_state = state;
+ state = new_state;
+ if (!started && state.state > Altos.ao_flight_pad) {
+ started = true;
+ start();
+ }
+
+ if (state.state < Altos.ao_flight_drogue)
+ report_interval = 10000;
+ else
+ report_interval = 20000;
+ if (old_state != null && old_state.state != state.state) {
+ report_time = now();
+ this.notify();
+ } else if (spoken)
+ set_report_time();
+ }
+
+ public IdleThread() {
+ state = null;
+ reported_landing = 0;
+ report_interval = 10000;
+ }
+ }
+
+ boolean tell(AltosState state, AltosState old_state) {
+ boolean ret = false;
+ if (old_state == null || old_state.state != state.state) {
+ voice.speak(state.data.state());
+ if ((old_state == null || old_state.state <= Altos.ao_flight_boost) &&
+ state.state > Altos.ao_flight_boost) {
+ voice.speak("max speed: %s.",
+ AltosConvert.speed.say_units(state.max_speed + 0.5));
+ ret = true;
+ } else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) &&
+ state.state >= Altos.ao_flight_drogue) {
+ voice.speak("max height: %s.",
+ AltosConvert.height.say_units(state.max_height + 0.5));
+ ret = true;
+ }
+ }
+ if (old_state == null || old_state.gps_ready != state.gps_ready) {
+ if (state.gps_ready) {
+ voice.speak("GPS ready");
+ ret = true;
+ }
+ else if (old_state != null) {
+ voice.speak("GPS lost");
+ ret = true;
+ }
+ }
+ old_state = state;
+ return ret;
+ }
+
+ public void run() {
+ boolean interrupted = false;
+ String line;
+ AltosState state = null;
+ AltosState old_state = null;
+ boolean told;
+
+ idle_thread = new IdleThread();
+
+ try {
+ for (;;) {
+ try {
+ AltosRecord record = reader.read();
+ if (record == null)
+ break;
+ old_state = state;
+ state = new AltosState(record, state);
+ reader.update(state);
+ show_safely(state, crc_errors);
+ told = tell(state, old_state);
+ idle_thread.notice(state, told);
+ } catch (ParseException pp) {
+ System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage());
+ } catch (AltosCRCException ce) {
+ ++crc_errors;
+ show_safely(state, crc_errors);
+ }
+ }
+ } catch (InterruptedException ee) {
+ interrupted = true;
+ } catch (IOException ie) {
+ reading_error_safely();
+ } finally {
+ if (!interrupted)
+ idle_thread.report(true);
+ reader.close(interrupted);
+ idle_thread.interrupt();
+ try {
+ idle_thread.join();
+ } catch (InterruptedException ie) {}
+ }
+ }
+
+ public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) {
+ parent = in_parent;
+ voice = in_voice;
+ display = in_display;
+ reader = in_reader;
+ display.reset();
+ }
+}
diff --git a/altosui/AltosEepromDelete.java b/altosui/AltosEepromDelete.java
new file mode 100644
index 00000000..73f3a00f
--- /dev/null
+++ b/altosui/AltosEepromDelete.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosEepromDelete implements Runnable {
+ AltosEepromList flights;
+ Thread eeprom_thread;
+ AltosSerial serial_line;
+ boolean remote;
+ JFrame frame;
+ ActionListener listener;
+ boolean success;
+
+ private void DeleteLog (AltosEepromLog log)
+ throws IOException, InterruptedException, TimeoutException {
+
+ if (flights.config_data.flight_log_max != 0 || flights.config_data.log_format != 0) {
+
+ /* Devices with newer firmware can erase the
+ * flash blocks containing each flight
+ */
+ serial_line.flush_input();
+ serial_line.printf("d %d\n", log.flight);
+ for (;;) {
+ /* It can take a while to erase the flash... */
+ String line = serial_line.get_reply(20000);
+ if (line == null)
+ throw new TimeoutException();
+ if (line.equals("Erased"))
+ break;
+ if (line.startsWith("No such"))
+ throw new IOException(line);
+ }
+ }
+ }
+
+ private void show_error_internal(String message, String title) {
+ JOptionPane.showMessageDialog(frame,
+ message,
+ title,
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ private void show_error(String in_message, String in_title) {
+ final String message = in_message;
+ final String title = in_title;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ show_error_internal(message, title);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ public void run () {
+ success = false;
+ try {
+ if (remote)
+ serial_line.start_remote();
+
+ for (AltosEepromLog log : flights) {
+ if (log.selected) {
+ DeleteLog(log);
+ }
+ }
+ success = true;
+ } catch (IOException ee) {
+ show_error (ee.getLocalizedMessage(),
+ serial_line.device.toShortString());
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ show_error (String.format("Connection to \"%s\" failed",
+ serial_line.device.toShortString()),
+ "Connection Failed");
+ } finally {
+ try {
+ if (remote)
+ serial_line.stop_remote();
+ } catch (InterruptedException ie) {
+ } finally {
+ serial_line.flush_output();
+ serial_line.close();
+ }
+ }
+ if (listener != null) {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ listener.actionPerformed(new ActionEvent(this,
+ success ? 1 : 0,
+ "delete"));
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+ }
+
+ public void start() {
+ eeprom_thread = new Thread(this);
+ eeprom_thread.start();
+ }
+
+ public void addActionListener(ActionListener l) {
+ listener = l;
+ }
+
+ public AltosEepromDelete(JFrame given_frame,
+ AltosSerial given_serial_line,
+ boolean given_remote,
+ AltosEepromList given_flights) {
+ frame = given_frame;
+ serial_line = given_serial_line;
+ remote = given_remote;
+ flights = given_flights;
+ success = false;
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosEepromDownload.java b/altosui/AltosEepromDownload.java
new file mode 100644
index 00000000..b04890cd
--- /dev/null
+++ b/altosui/AltosEepromDownload.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosEepromDownload implements Runnable {
+
+ JFrame frame;
+ AltosSerial serial_line;
+ boolean remote;
+ Thread eeprom_thread;
+ AltosEepromMonitor monitor;
+
+ int flight;
+ int serial;
+ int year, month, day;
+ boolean want_file;
+ FileWriter eeprom_file;
+ LinkedList<String> eeprom_pending;
+
+ AltosEepromList flights;
+ ActionListener listener;
+ boolean success;
+ ParseException parse_exception;
+ String extension;
+
+ private void FlushPending() throws IOException {
+ for (String s : flights.config_data) {
+ eeprom_file.write(s);
+ eeprom_file.write('\n');
+ }
+
+ for (String s : eeprom_pending)
+ eeprom_file.write(s);
+ }
+
+ private void CheckFile(boolean force) throws IOException {
+ if (eeprom_file != null)
+ return;
+ if (force || (flight != 0 && want_file)) {
+ AltosFile eeprom_name;
+
+ if (extension == null)
+ extension = "data";
+ if (year != 0 && month != 0 && day != 0)
+ eeprom_name = new AltosFile(year, month, day, serial, flight, extension);
+ else
+ eeprom_name = new AltosFile(serial, flight, extension);
+
+ eeprom_file = new FileWriter(eeprom_name);
+ if (eeprom_file != null) {
+ monitor.set_file(eeprom_name.getName());
+ FlushPending();
+ eeprom_pending = null;
+ }
+ }
+ }
+
+ void Log(AltosEepromRecord r) throws IOException {
+ if (r.cmd != Altos.AO_LOG_INVALID) {
+ String log_line = String.format("%c %4x %4x %4x\n",
+ r.cmd, r.tick, r.a, r.b);
+ if (eeprom_file != null)
+ eeprom_file.write(log_line);
+ else
+ eeprom_pending.add(log_line);
+ }
+ }
+
+ void set_serial(int in_serial) {
+ serial = in_serial;
+ monitor.set_serial(serial);
+ }
+
+ void set_flight(int in_flight) {
+ flight = in_flight;
+ monitor.set_flight(flight);
+ }
+
+ boolean done;
+ int state;
+
+ void CaptureFull(AltosEepromChunk eechunk) throws IOException {
+ boolean any_valid = false;
+
+ extension = "eeprom";
+ set_serial(flights.config_data.serial);
+ for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromRecord.record_length) {
+ try {
+ AltosEepromRecord r = new AltosEepromRecord(eechunk, i);
+ if (r.cmd == Altos.AO_LOG_FLIGHT)
+ set_flight(r.b);
+
+ /* Monitor state transitions to update display */
+ if (r.cmd == Altos.AO_LOG_STATE && r.a <= Altos.ao_flight_landed) {
+ state = r.a;
+ if (state > Altos.ao_flight_pad)
+ want_file = true;
+ }
+
+ if (r.cmd == Altos.AO_LOG_GPS_DATE) {
+ year = 2000 + (r.a & 0xff);
+ month = (r.a >> 8) & 0xff;
+ day = (r.b & 0xff);
+ want_file = true;
+ }
+ if (r.cmd == Altos.AO_LOG_STATE && r.a == Altos.ao_flight_landed)
+ done = true;
+ any_valid = true;
+ Log(r);
+ } catch (ParseException pe) {
+ if (parse_exception == null)
+ parse_exception = pe;
+ }
+ }
+
+ if (!any_valid)
+ done = true;
+
+ CheckFile(false);
+ }
+
+ boolean start;
+ int tiny_tick;
+
+ void CaptureTiny (AltosEepromChunk eechunk) throws IOException {
+ boolean any_valid = false;
+
+ extension = "eeprom";
+ set_serial(flights.config_data.serial);
+ for (int i = 0; i < eechunk.data.length && !done; i += 2) {
+ int v = eechunk.data16(i);
+ AltosEepromRecord r;
+
+ if (i == 0 && start) {
+ tiny_tick = 0;
+ start = false;
+ set_flight(v);
+ r = new AltosEepromRecord(Altos.AO_LOG_FLIGHT, tiny_tick, 0, v);
+ any_valid = true;
+ } else {
+ int s = v ^ 0x8000;
+
+ if (Altos.ao_flight_startup <= s && s <= Altos.ao_flight_invalid) {
+ state = s;
+ r = new AltosEepromRecord(Altos.AO_LOG_STATE, tiny_tick, state, 0);
+ if (state == Altos.ao_flight_landed)
+ done = true;
+ state = s;
+ any_valid = true;
+ } else {
+ if (v != 0xffff)
+ any_valid = true;
+
+ r = new AltosEepromRecord(Altos.AO_LOG_PRESSURE, tiny_tick, 0, v);
+
+ /*
+ * The flight software records ascent data every 100ms, and descent
+ * data every 1s.
+ */
+ if (state < Altos.ao_flight_drogue)
+ tiny_tick += 10;
+ else
+ tiny_tick += 100;
+ }
+ }
+ Log(r);
+ }
+ CheckFile(false);
+ if (!any_valid)
+ done = true;
+ }
+
+ void LogTeleScience(AltosEepromTeleScience r) throws IOException {
+ if (r.type != Altos.AO_LOG_INVALID) {
+ String log_line = String.format("%c %4x %4x %d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d\n",
+ r.type, r.tick, r.tm_tick, r.tm_state,
+ r.data[0], r.data[1], r.data[2], r.data[3],
+ r.data[4], r.data[5], r.data[6], r.data[7],
+ r.data[8], r.data[9], r.data[10], r.data[11]);
+ if (eeprom_file != null)
+ eeprom_file.write(log_line);
+ else
+ eeprom_pending.add(log_line);
+ }
+ }
+
+ boolean telescience_start;
+
+ void CaptureTeleScience (AltosEepromChunk eechunk) throws IOException {
+ boolean any_valid = false;
+
+ extension = "science";
+ for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromTeleScience.record_length) {
+ try {
+ AltosEepromTeleScience r = new AltosEepromTeleScience(eechunk, i);
+ if (r.type == AltosEepromTeleScience.AO_LOG_TELESCIENCE_START) {
+ if (telescience_start) {
+ done = true;
+ break;
+ }
+ set_serial(r.data[0]);
+ set_flight(r.data[1]);
+ telescience_start = true;
+ } else {
+ if (!telescience_start)
+ break;
+ }
+ state = r.tm_state;
+ want_file =true;
+ any_valid = true;
+ LogTeleScience(r);
+ } catch (ParseException pe) {
+ if (parse_exception == null)
+ parse_exception = pe;
+ }
+ }
+
+ CheckFile(false);
+ if (!any_valid)
+ done = true;
+ }
+
+ void LogMega(AltosEepromMega r) throws IOException {
+ if (r.cmd != Altos.AO_LOG_INVALID) {
+ String log_line = String.format("%c %4x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x\n",
+ r.cmd, r.tick,
+ r.data8[0], r.data8[1], r.data8[2], r.data8[3],
+ r.data8[4], r.data8[5], r.data8[6], r.data8[7],
+ r.data8[8], r.data8[9], r.data8[10], r.data8[11],
+ r.data8[12], r.data8[13], r.data8[14], r.data8[15],
+ r.data8[16], r.data8[17], r.data8[18], r.data8[19],
+ r.data8[20], r.data8[21], r.data8[22], r.data8[23],
+ r.data8[24], r.data8[25], r.data8[26], r.data8[27]);
+ if (eeprom_file != null)
+ eeprom_file.write(log_line);
+ else
+ eeprom_pending.add(log_line);
+ }
+ }
+
+ void CaptureMega(AltosEepromChunk eechunk) throws IOException {
+ boolean any_valid = false;
+
+ extension = "mega";
+ set_serial(flights.config_data.serial);
+ for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromMega.record_length) {
+ try {
+ AltosEepromMega r = new AltosEepromMega(eechunk, i);
+ if (r.cmd == Altos.AO_LOG_FLIGHT)
+ set_flight(r.data16(0));
+
+ /* Monitor state transitions to update display */
+ if (r.cmd == Altos.AO_LOG_STATE && r.data16(0) <= Altos.ao_flight_landed) {
+ state = r.data16(0);
+ if (state > Altos.ao_flight_pad)
+ want_file = true;
+ }
+
+ if (r.cmd == Altos.AO_LOG_GPS_TIME) {
+ year = 2000 + r.data8(14);
+ month = r.data8(15);
+ day = r.data8(14);
+ want_file = true;
+ }
+
+ if (r.cmd == Altos.AO_LOG_STATE && r.data16(0) == Altos.ao_flight_landed)
+ done = true;
+ any_valid = true;
+ LogMega(r);
+ } catch (ParseException pe) {
+ if (parse_exception == null)
+ parse_exception = pe;
+ }
+ }
+ if (!any_valid)
+ done = true;
+
+ CheckFile(false);
+ }
+
+ void CaptureTelemetry(AltosEepromChunk eechunk) throws IOException {
+
+ }
+
+ void CaptureLog(AltosEepromLog log) throws IOException, InterruptedException, TimeoutException {
+ int block, state_block = 0;
+ int log_format = flights.config_data.log_format;
+
+ state = 0;
+ done = false;
+ start = true;
+
+ if (flights.config_data.serial == 0)
+ throw new IOException("no serial number found");
+
+ /* Reset per-capture variables */
+ flight = 0;
+ year = 0;
+ month = 0;
+ day = 0;
+ want_file = false;
+ eeprom_file = null;
+ eeprom_pending = new LinkedList<String>();
+
+ /* Set serial number in the monitor dialog window */
+ /* Now scan the eeprom, reading blocks of data and converting to .eeprom file form */
+
+ state = 0; state_block = log.start_block;
+ for (block = log.start_block; !done && block < log.end_block; block++) {
+ monitor.set_value(AltosLib.state_name(state), state, block - state_block);
+
+ AltosEepromChunk eechunk = new AltosEepromChunk(serial_line, block, block == log.start_block);
+
+ /*
+ * Guess what kind of data is there if the device
+ * didn't tell us
+ */
+
+ if (log_format == Altos.AO_LOG_FORMAT_UNKNOWN) {
+ if (block == log.start_block) {
+ if (eechunk.data(0) == Altos.AO_LOG_FLIGHT)
+ log_format = Altos.AO_LOG_FORMAT_FULL;
+ else
+ log_format = Altos.AO_LOG_FORMAT_TINY;
+ }
+ }
+
+ switch (log_format) {
+ case AltosLib.AO_LOG_FORMAT_FULL:
+ extension = "eeprom";
+ CaptureFull(eechunk);
+ break;
+ case AltosLib.AO_LOG_FORMAT_TINY:
+ extension = "eeprom";
+ CaptureTiny(eechunk);
+ break;
+ case AltosLib.AO_LOG_FORMAT_TELEMETRY:
+ extension = "telem";
+ CaptureTelemetry(eechunk);
+ break;
+ case AltosLib.AO_LOG_FORMAT_TELESCIENCE:
+ extension = "science";
+ CaptureTeleScience(eechunk);
+ break;
+ case AltosLib.AO_LOG_FORMAT_MEGAMETRUM:
+ extension = "mega";
+ CaptureMega(eechunk);
+ }
+ }
+ CheckFile(true);
+ if (eeprom_file != null) {
+ eeprom_file.flush();
+ eeprom_file.close();
+ }
+ }
+
+ private void show_message_internal(String message, String title, int message_type) {
+ JOptionPane.showMessageDialog(frame,
+ message,
+ title,
+ message_type);
+ }
+
+ private void show_message(String in_message, String in_title, int in_message_type) {
+ final String message = in_message;
+ final String title = in_title;
+ final int message_type = in_message_type;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ show_message_internal(message, title, message_type);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ public void run () {
+ try {
+ boolean failed = false;
+ if (remote)
+ serial_line.start_remote();
+
+ for (AltosEepromLog log : flights) {
+ parse_exception = null;
+ if (log.selected) {
+ monitor.reset();
+ CaptureLog(log);
+ }
+ if (parse_exception != null) {
+ failed = true;
+ show_message(String.format("Flight %d download error\n%s\nValid log data saved",
+ log.flight,
+ parse_exception.getMessage()),
+ serial_line.device.toShortString(),
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ success = !failed;
+ } catch (IOException ee) {
+ show_message(ee.getLocalizedMessage(),
+ serial_line.device.toShortString(),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (InterruptedException ie) {
+ System.out.printf("download interrupted\n");
+ } catch (TimeoutException te) {
+ show_message(String.format("Connection to \"%s\" failed",
+ serial_line.device.toShortString()),
+ "Connection Failed",
+ JOptionPane.ERROR_MESSAGE);
+ } finally {
+ if (remote) {
+ try {
+ serial_line.stop_remote();
+ } catch (InterruptedException ie) {
+ }
+ }
+ serial_line.flush_output();
+ }
+ monitor.done();
+ if (listener != null) {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ listener.actionPerformed(new ActionEvent(this,
+ success ? 1 : 0,
+ "download"));
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+ }
+
+ public void start() {
+ eeprom_thread = new Thread(this);
+ eeprom_thread.start();
+ }
+
+ public void addActionListener(ActionListener l) {
+ listener = l;
+ }
+
+ public AltosEepromDownload(JFrame given_frame,
+ AltosSerial given_serial_line,
+ boolean given_remote,
+ AltosEepromList given_flights) {
+
+ frame = given_frame;
+ serial_line = given_serial_line;
+ serial_line.set_frame(frame);
+ remote = given_remote;
+ flights = given_flights;
+ success = false;
+
+ monitor = new AltosEepromMonitor(frame, Altos.ao_flight_boost, Altos.ao_flight_landed);
+ monitor.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (eeprom_thread != null)
+ eeprom_thread.interrupt();
+ }
+ });
+ }
+}
diff --git a/altosui/AltosEepromList.java b/altosui/AltosEepromList.java
new file mode 100644
index 00000000..6a656215
--- /dev/null
+++ b/altosui/AltosEepromList.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+/*
+ * Temporary structure to hold the list of stored flights;
+ * each of these will be queried in turn to generate more
+ * complete information
+ */
+
+class AltosEepromFlight {
+ int flight;
+ int start;
+ int end;
+
+ public AltosEepromFlight(int in_flight, int in_start, int in_end) {
+ flight = in_flight;
+ start = in_start;
+ end = in_end;
+ }
+}
+
+/*
+ * Construct a list of flights available in a connected device
+ */
+
+public class AltosEepromList extends ArrayList<AltosEepromLog> {
+ AltosConfigData config_data;
+
+ public AltosEepromList (AltosSerial serial_line, boolean remote)
+ throws IOException, InterruptedException, TimeoutException
+ {
+ try {
+ if (remote)
+ serial_line.start_remote();
+ config_data = new AltosConfigData (serial_line);
+// if (config_data.serial == 0)
+// throw new IOException("no serial number found");
+
+ ArrayList<AltosEepromFlight> flights = new ArrayList<AltosEepromFlight>();
+
+ if (config_data.flight_log_max != 0 || config_data.log_format != 0) {
+
+ /* Devices with newer firmware will support the 'l'
+ * command which will list the region of storage
+ * occupied by each available flight
+ */
+ serial_line.printf("l\n");
+ for (;;) {
+ String line = serial_line.get_reply(5000);
+ if (line == null)
+ throw new TimeoutException();
+ if (line.contains("done"))
+ break;
+ if (line.contains("Syntax"))
+ continue;
+ String[] tokens = line.split("\\s+");
+ if (tokens.length < 6)
+ break;
+
+ int flight = -1, start = -1, end = -1;
+ try {
+ if (tokens[0].equals("flight"))
+ flight = AltosParse.parse_int(tokens[1]);
+ if (tokens[2].equals("start"))
+ start = AltosParse.parse_hex(tokens[3]);
+ if (tokens[4].equals("end"))
+ end = AltosParse.parse_hex(tokens[5]);
+ if (flight > 0 && start >= 0 && end > 0)
+ flights.add(new AltosEepromFlight(flight, start, end));
+ } catch (ParseException pe) { System.out.printf("Parse error %s\n", pe.toString()); }
+ }
+ } else {
+
+ /* Older devices will hold only a single
+ * flight. This also assumes that any older
+ * device will have a 1MB flash device
+ */
+ flights.add(new AltosEepromFlight(0, 0, 0xfff));
+ }
+
+ /* With the list of flights collected, collect more complete
+ * information on them by reading the first block or two of
+ * data. This will add GPS coordinates and a date. For older
+ * firmware, this will also extract the flight number.
+ */
+ for (AltosEepromFlight flight : flights) {
+ add(new AltosEepromLog(config_data, serial_line,
+ flight.flight, flight.start, flight.end));
+ }
+ } finally {
+ if (remote)
+ serial_line.stop_remote();
+ serial_line.flush_output();
+ }
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosEepromManage.java b/altosui/AltosEepromManage.java
new file mode 100644
index 00000000..563c90b3
--- /dev/null
+++ b/altosui/AltosEepromManage.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosEepromManage implements ActionListener {
+
+ JFrame frame;
+ boolean remote;
+ AltosDevice device;
+ AltosSerial serial_line;
+ AltosEepromList flights;
+ AltosEepromDownload download;
+ AltosEepromDelete delete;
+
+ public void finish() {
+ if (serial_line != null) {
+ try {
+ serial_line.flush_input();
+ } catch (InterruptedException ie) {
+ }
+ serial_line.close();
+ serial_line = null;
+ }
+ }
+
+ private String showDeletedFlights() {
+ String result = "";
+
+ for (AltosEepromLog flight : flights) {
+ if (flight.selected) {
+ if (result.equals(""))
+ result = String.format("%d", flight.flight);
+ else
+ result = String.format("%s, %d", result, flight.flight);
+ }
+ }
+ return result;
+ }
+
+ public boolean download_done() {
+ AltosEepromSelect select = new AltosEepromSelect(frame, flights, "Delete");
+
+ if (select.run()) {
+ boolean any_selected = false;
+ for (AltosEepromLog flight : flights)
+ any_selected = any_selected || flight.selected;
+ if (any_selected) {
+ delete = new AltosEepromDelete(frame,
+ serial_line,
+ remote,
+ flights);
+ delete.addActionListener(this);
+ /*
+ * Start flight log delete
+ */
+
+ delete.start();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ boolean success = e.getID() != 0;
+ boolean running = false;
+
+ if (cmd.equals("download")) {
+ if (success)
+ running = download_done();
+ } else if (cmd.equals("delete")) {
+ if (success) {
+ JOptionPane.showMessageDialog(frame,
+ String.format("Flights erased: %s",
+ showDeletedFlights()),
+ serial_line.device.toShortString(),
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+ if (!running)
+ finish();
+ }
+
+ public void got_flights(AltosEepromList in_flights) {
+ boolean running = false;;
+
+ flights = in_flights;
+ try {
+ if (flights.size() == 0) {
+ JOptionPane.showMessageDialog(frame,
+ String.format("No flights available on %d",
+ device.getSerial()),
+ serial_line.device.toShortString(),
+ JOptionPane.INFORMATION_MESSAGE);
+ } else {
+ AltosEepromSelect select = new AltosEepromSelect(frame, flights, "Download");
+
+ if (select.run()) {
+ boolean any_selected = false;
+ for (AltosEepromLog flight : flights)
+ any_selected = any_selected || flight.selected;
+ if (any_selected) {
+ download = new AltosEepromDownload(frame,
+ serial_line,
+ remote,
+ flights);
+ download.addActionListener(this);
+ /*
+ * Start flight log download
+ */
+
+ download.start();
+ running = true;
+ } else {
+ running = download_done();
+ }
+ }
+ }
+ if (!running)
+ finish();
+ } catch (Exception e) {
+ got_exception(e);
+ }
+ }
+
+ public void got_exception(Exception e) {
+ if (e instanceof IOException) {
+ IOException ee = (IOException) e;
+ JOptionPane.showMessageDialog(frame,
+ device.toShortString(),
+ ee.getLocalizedMessage(),
+ JOptionPane.ERROR_MESSAGE);
+ } else if (e instanceof TimeoutException) {
+ TimeoutException te = (TimeoutException) e;
+ JOptionPane.showMessageDialog(frame,
+ String.format("Communications failed with \"%s\"",
+ device.toShortString()),
+ "Cannot open target device",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ finish();
+ }
+
+ class EepromGetList implements Runnable {
+
+ AltosEepromManage manage;
+
+ public void run () {
+ Runnable r;
+ try {
+ flights = new AltosEepromList(serial_line, remote);
+ r = new Runnable() {
+ public void run() {
+ got_flights(flights);
+ }
+ };
+ } catch (Exception e) {
+ final Exception f_e = e;
+ r = new Runnable() {
+ public void run() {
+ got_exception(f_e);
+ }
+ };
+ }
+ SwingUtilities.invokeLater(r);
+ }
+
+ public EepromGetList(AltosEepromManage in_manage) {
+ manage = in_manage;
+ }
+ }
+
+ public AltosEepromManage(JFrame given_frame) {
+
+ boolean running = false;
+
+ frame = given_frame;
+ device = AltosDeviceDialog.show(frame, Altos.product_any);
+
+ remote = false;
+
+ if (device != null) {
+ try {
+ serial_line = new AltosSerial(device);
+ if (device.matchProduct(Altos.product_basestation))
+ remote = true;
+
+ serial_line.set_frame(frame);
+
+ EepromGetList get_list = new EepromGetList(this);
+ Thread t = new Thread(get_list);
+ t.start();
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(frame,
+ ee.getMessage(),
+ "Cannot open target device",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (AltosSerialInUseException si) {
+ JOptionPane.showMessageDialog(frame,
+ String.format("Device \"%s\" already in use",
+ device.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+}
diff --git a/altosui/AltosEepromMonitor.java b/altosui/AltosEepromMonitor.java
new file mode 100644
index 00000000..75643442
--- /dev/null
+++ b/altosui/AltosEepromMonitor.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosEepromMonitor extends AltosDialog {
+
+ Container pane;
+ Box box;
+ JLabel serial_label;
+ JLabel flight_label;
+ JLabel file_label;
+ JLabel serial_value;
+ JLabel flight_value;
+ JLabel file_value;
+ JButton cancel;
+ JProgressBar pbar;
+ int min_state, max_state;
+
+ public AltosEepromMonitor(JFrame owner, int in_min_state, int in_max_state) {
+ super (owner, "Download Flight Data", false);
+
+ GridBagConstraints c;
+ Insets il = new Insets(4,4,4,4);
+ Insets ir = new Insets(4,4,4,4);
+
+ pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ serial_label = new JLabel("Serial:");
+ pane.add(serial_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 1; c.gridy = 0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ serial_value = new JLabel("");
+ pane.add(serial_value, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.gridx = 0; c.gridy = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ flight_label = new JLabel("Flight:");
+ pane.add(flight_label, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridx = 1; c.gridy = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ flight_value = new JLabel("");
+ pane.add(flight_value, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.gridx = 0; c.gridy = 2;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ file_label = new JLabel("File:");
+ pane.add(file_label, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridx = 1; c.gridy = 2;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ file_value = new JLabel("");
+ pane.add(file_value, c);
+
+ min_state = in_min_state;
+ max_state = in_max_state;
+ pbar = new JProgressBar();
+ pbar.setMinimum(0);
+ pbar.setMaximum((max_state - min_state) * 100);
+ pbar.setValue(0);
+ pbar.setString("startup");
+ pbar.setStringPainted(true);
+ pbar.setPreferredSize(new Dimension(600, 20));
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0; c.gridy = 3;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ Insets ib = new Insets(4,4,4,4);
+ c.insets = ib;
+ pane.add(pbar, c);
+
+
+ cancel = new JButton("Cancel");
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0; c.gridy = 4;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ Insets ic = new Insets(4,4,4,4);
+ c.insets = ic;
+ pane.add(cancel, c);
+
+ pack();
+ setLocationRelativeTo(owner);
+ setVisible(true);
+ }
+
+ public void addActionListener (ActionListener l) {
+ cancel.addActionListener(l);
+ }
+
+ private void set_value_internal(String state_name, int in_state, int in_block) {
+ int block = in_block;
+ int state = in_state;
+
+ if (block > 100)
+ block = 100;
+ if (state < min_state) state = min_state;
+ if (state >= max_state) state = max_state - 1;
+ state -= min_state;
+
+ int pos = state * 100 + block;
+
+ pbar.setString(state_name);
+ pbar.setValue(pos);
+ }
+
+ public void set_value(String in_state_name, int in_state, int in_block) {
+ final String state_name = in_state_name;
+ final int state = in_state;
+ final int block = in_block;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ set_value_internal(state_name, state, block);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private void set_serial_internal(int serial) {
+ serial_value.setText(String.format("%d", serial));
+ }
+
+ public void set_serial(int in_serial) {
+ final int serial = in_serial;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ set_serial_internal(serial);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private void set_flight_internal(int flight) {
+ flight_value.setText(String.format("%d", flight));
+ }
+
+ public void set_flight(int in_flight) {
+ final int flight = in_flight;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ set_flight_internal(flight);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private void set_file_internal(String file) {
+ file_value.setText(String.format("%s", file));
+ }
+
+ public void set_file(String in_file) {
+ final String file = in_file;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ set_file_internal(file);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private void done_internal() {
+ setVisible(false);
+ dispose();
+ }
+
+ public void done() {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ done_internal();
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ private void reset_internal() {
+ set_value_internal("startup",min_state,0);
+ set_flight_internal(0);
+ set_file_internal("");
+ }
+
+ public void reset() {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ reset_internal();
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+}
diff --git a/altosui/AltosEepromSelect.java b/altosui/AltosEepromSelect.java
new file mode 100644
index 00000000..4ad78896
--- /dev/null
+++ b/altosui/AltosEepromSelect.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+import libaltosJNI.libaltos;
+import libaltosJNI.altos_device;
+import libaltosJNI.SWIGTYPE_p_altos_file;
+import libaltosJNI.SWIGTYPE_p_altos_list;
+import org.altusmetrum.AltosLib.*;
+
+class AltosEepromItem implements ActionListener {
+ AltosEepromLog log;
+ JLabel label;
+ JCheckBox action;
+ JCheckBox delete;
+
+ public void actionPerformed(ActionEvent e) {
+ log.selected = action.isSelected();
+ }
+
+ public AltosEepromItem(AltosEepromLog in_log) {
+ log = in_log;
+
+ String text;
+ if (log.year != 0)
+ text = String.format("Flight #%02d - %04d-%02d-%02d",
+ log.flight, log.year, log.month, log.day);
+ else
+ text = String.format("Flight #%02d", log.flight);
+
+ label = new JLabel(text);
+
+ action = new JCheckBox("", log.selected);
+ action.addActionListener(this);
+ }
+}
+
+public class AltosEepromSelect extends AltosDialog implements ActionListener {
+ private JList list;
+ private JFrame frame;
+ JButton ok;
+ JButton cancel;
+ boolean success;
+
+ /* Listen for events from our buttons */
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if (cmd.equals("ok"))
+ success = true;
+ setVisible(false);
+ }
+
+ public boolean run() {
+ success = false;
+ setLocationRelativeTo(frame);
+ setVisible(true);
+ return success;
+ }
+
+ public AltosEepromSelect (JFrame in_frame,
+ AltosEepromList flights,
+ String action) {
+
+ super(in_frame, String.format("Flight list for serial %d", flights.config_data.serial), true);
+ frame = in_frame;
+
+ /* Create the container for the dialog */
+ Container contentPane = getContentPane();
+
+ /* First, we create a pane containing the dialog's header/title */
+ JLabel selectLabel = new JLabel(String.format ("Select flights to %s", action), SwingConstants.CENTER);
+
+ JPanel labelPane = new JPanel();
+ labelPane.setLayout(new BoxLayout(labelPane, BoxLayout.X_AXIS));
+ labelPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
+ labelPane.add(Box.createHorizontalGlue());
+ labelPane.add(selectLabel);
+ labelPane.add(Box.createHorizontalGlue());
+
+ /* Add the header to the container. */
+ contentPane.add(labelPane, BorderLayout.PAGE_START);
+
+
+ /* Now we create the evilness that is a GridBag for the flight details */
+ GridBagConstraints c;
+ Insets i = new Insets(4,4,4,4);
+ JPanel flightPane = new JPanel();
+ flightPane.setLayout(new GridBagLayout());
+ flightPane.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
+
+ /* Flight Header */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ JLabel flightHeaderLabel = new JLabel("Flight");
+ flightPane.add(flightHeaderLabel, c);
+
+ /* Download Header */
+ c = new GridBagConstraints();
+ c.gridx = 1; c.gridy = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ JLabel downloadHeaderLabel = new JLabel(action);
+ flightPane.add(downloadHeaderLabel, c);
+
+ /* Add the flights to the GridBag */
+ AltosEepromItem item;
+ int itemNumber = 1;
+ for (AltosEepromLog flight : flights) {
+ /* Create a flight object with handlers and
+ * appropriate UI items
+ */
+ item = new AltosEepromItem(flight);
+
+ /* Add a decriptive label for the flight */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = itemNumber;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ flightPane.add(item.label, c);
+
+ /* Add action checkbox for the flight */
+ c = new GridBagConstraints();
+ c.gridx = 1; c.gridy = itemNumber;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0.5;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ flightPane.add(item.action, c);
+
+ itemNumber++;
+ }
+
+ /* Add the GridBag to the container */
+ contentPane.add(flightPane, BorderLayout.CENTER);
+
+ /* Create the dialog buttons */
+ ok = new JButton("OK");
+ ok.addActionListener(this);
+ ok.setActionCommand("ok");
+
+ cancel = new JButton("Cancel");
+ cancel.addActionListener(this);
+ cancel.setActionCommand("cancel");
+
+ JPanel buttonPane = new JPanel();
+ buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ buttonPane.add(Box.createHorizontalGlue());
+ buttonPane.add(cancel);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(ok);
+
+ /* Add the buttons to the container */
+ contentPane.add(buttonPane, BorderLayout.PAGE_END);
+
+ /* Pack the window! */
+ pack();
+ }
+}
diff --git a/altosui/AltosFlash.java b/altosui/AltosFlash.java
new file mode 100644
index 00000000..bd0c8a50
--- /dev/null
+++ b/altosui/AltosFlash.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlash {
+ File file;
+ FileInputStream input;
+ AltosHexfile image;
+ JFrame frame;
+ AltosDevice debug_dongle;
+ AltosDebug debug;
+ AltosRomconfig rom_config;
+ ActionListener listener;
+ boolean aborted;
+
+ static final byte MOV_direct_data = (byte) 0x75;
+ static final byte MOV_DPTR_data16 = (byte) 0x90;
+ static final byte MOV_A_data = (byte) 0x74;
+ static final byte MOVX_atDPTR_A = (byte) 0xf0;
+ static final byte MOVX_A_atDPTR = (byte) 0xe0;
+ static final byte INC_DPTR = (byte) 0xa3;
+ static final byte TRAP = (byte) 0xa5;
+
+ static final byte JB = (byte) 0x20;
+
+ static final byte MOV_A_direct = (byte) 0xe5;
+ static final byte MOV_direct1_direct2 = (byte) 0x85;
+ static final byte MOV_direct_A = (byte) 0xf5;
+ static final byte MOV_R0_data = (byte) (0x78 | 0);
+ static final byte MOV_R1_data = (byte) (0x78 | 1);
+ static final byte MOV_R2_data = (byte) (0x78 | 2);
+ static final byte MOV_R3_data = (byte) (0x78 | 3);
+ static final byte MOV_R4_data = (byte) (0x78 | 4);
+ static final byte MOV_R5_data = (byte) (0x78 | 5);
+ static final byte MOV_R6_data = (byte) (0x78 | 6);
+ static final byte MOV_R7_data = (byte) (0x78 | 7);
+ static final byte DJNZ_R0_rel = (byte) (0xd8 | 0);
+ static final byte DJNZ_R1_rel = (byte) (0xd8 | 1);
+ static final byte DJNZ_R2_rel = (byte) (0xd8 | 2);
+ static final byte DJNZ_R3_rel = (byte) (0xd8 | 3);
+ static final byte DJNZ_R4_rel = (byte) (0xd8 | 4);
+ static final byte DJNZ_R5_rel = (byte) (0xd8 | 5);
+ static final byte DJNZ_R6_rel = (byte) (0xd8 | 6);
+ static final byte DJNZ_R7_rel = (byte) (0xd8 | 7);
+
+ static final byte P1DIR = (byte) 0xFE;
+ static final byte P1 = (byte) 0x90;
+
+ /* flash controller */
+ static final byte FWT = (byte) 0xAB;
+ static final byte FADDRL = (byte) 0xAC;
+ static final byte FADDRH = (byte) 0xAD;
+ static final byte FCTL = (byte) 0xAE;
+ static final byte FCTL_BUSY = (byte) 0x80;
+ static final byte FCTL_BUSY_BIT = (byte) 7;
+ static final byte FCTL_SWBSY = (byte) 0x40;
+ static final byte FCTL_SWBSY_BIT = (byte) 6;
+ static final byte FCTL_CONTRD = (byte) 0x10;
+ static final byte FCTL_WRITE = (byte) 0x02;
+ static final byte FCTL_ERASE = (byte) 0x01;
+ static final byte FWDATA = (byte) 0xAF;
+
+ static final byte ACC = (byte) 0xE0;
+
+ /* offsets within the flash_page program */
+ static final int FLASH_ADDR_HIGH = 8;
+ static final int FLASH_ADDR_LOW = 11;
+ static final int RAM_ADDR_HIGH = 13;
+ static final int RAM_ADDR_LOW = 14;
+ static final int FLASH_WORDS_HIGH = 16;
+ static final int FLASH_WORDS_LOW = 18;
+ static final int FLASH_TIMING = 21;
+
+ /* sleep mode control */
+ static final int SLEEP = (byte) 0xbe;
+ static final int SLEEP_USB_EN = (byte) 0x80;
+ static final int SLEEP_XOSC_STB = (byte) 0x40;
+ static final int SLEEP_HFRC_STB = (byte) 0x20;
+ static final int SLEEP_RST_MASK = (byte) 0x18;
+ static final int SLEEP_RST_POWERON = (byte) 0x00;
+ static final int SLEEP_RST_EXTERNAL = (byte) 0x10;
+ static final int SLEEP_RST_WATCHDOG = (byte) 0x08;
+ static final int SLEEP_OSC_PD = (byte) 0x04;
+ static final int SLEEP_MODE_MASK = (byte) 0x03;
+ static final int SLEEP_MODE_PM0 = (byte) 0x00;
+ static final int SLEEP_MODE_PM1 = (byte) 0x01;
+ static final int SLEEP_MODE_PM2 = (byte) 0x02;
+ static final int SLEEP_MODE_PM3 = (byte) 0x03;
+
+ /* clock controller */
+ static final byte CLKCON = (byte) 0xC6;
+ static final byte CLKCON_OSC32K = (byte) 0x80;
+ static final byte CLKCON_OSC = (byte) 0x40;
+ static final byte CLKCON_TICKSPD = (byte) 0x38;
+ static final byte CLKCON_CLKSPD = (byte) 0x07;
+
+ static final byte[] flash_page_proto = {
+
+ MOV_direct_data, P1DIR, (byte) 0x02,
+ MOV_direct_data, P1, (byte) 0xFF,
+
+ MOV_direct_data, FADDRH, 0, /* FLASH_ADDR_HIGH */
+
+ MOV_direct_data, FADDRL, 0, /* FLASH_ADDR_LOW */
+
+ MOV_DPTR_data16, 0, 0, /* RAM_ADDR_HIGH, RAM_ADDR_LOW */
+
+ MOV_R7_data, 0, /* FLASH_WORDS_HIGH */
+
+ MOV_R6_data, 0, /* FLASH_WORDS_LOW */
+
+
+ MOV_direct_data, FWT, 0x20, /* FLASH_TIMING */
+
+ MOV_direct_data, FCTL, FCTL_ERASE,
+/* eraseWaitLoop: */
+ MOV_A_direct, FCTL,
+ JB, ACC|FCTL_BUSY_BIT, (byte) 0xfb,
+
+ MOV_direct_data, P1, (byte) 0xfd,
+
+ MOV_direct_data, FCTL, FCTL_WRITE,
+/* writeLoop: */
+ MOV_R5_data, 2,
+/* writeWordLoop: */
+ MOVX_A_atDPTR,
+ INC_DPTR,
+ MOV_direct_A, FWDATA,
+ DJNZ_R5_rel, (byte) 0xfa, /* writeWordLoop */
+/* writeWaitLoop: */
+ MOV_A_direct, FCTL,
+ JB, ACC|FCTL_SWBSY_BIT, (byte) 0xfb, /* writeWaitLoop */
+ DJNZ_R6_rel, (byte) 0xf1, /* writeLoop */
+ DJNZ_R7_rel, (byte) 0xef, /* writeLoop */
+
+ MOV_direct_data, P1DIR, (byte) 0x00,
+ MOV_direct_data, P1, (byte) 0xFF,
+ TRAP,
+ };
+
+ public byte[] make_flash_page(int flash_addr, int ram_addr, int byte_count) {
+ int flash_word_addr = flash_addr >> 1;
+ int flash_word_count = ((byte_count + 1) >> 1);
+
+ byte[] flash_page = new byte[flash_page_proto.length];
+ for (int i = 0; i < flash_page.length; i++)
+ flash_page[i] = flash_page_proto[i];
+
+ flash_page[FLASH_ADDR_HIGH] = (byte) (flash_word_addr >> 8);
+ flash_page[FLASH_ADDR_LOW] = (byte) (flash_word_addr);
+ flash_page[RAM_ADDR_HIGH] = (byte) (ram_addr >> 8);
+ flash_page[RAM_ADDR_LOW] = (byte) (ram_addr);
+
+ byte flash_words_low = (byte) (flash_word_count);
+ byte flash_words_high = (byte) (flash_word_count >> 8);
+ /* the flashing code has a minor 'bug' */
+ if (flash_words_low != 0)
+ flash_words_high++;
+
+ flash_page[FLASH_WORDS_HIGH] = (byte) flash_words_high;
+ flash_page[FLASH_WORDS_LOW] = (byte) flash_words_low;
+ return flash_page;
+ }
+
+ static byte[] set_clkcon_fast = {
+ MOV_direct_data, CLKCON, 0x00
+ };
+
+ static byte[] get_sleep = {
+ MOV_A_direct, SLEEP
+ };
+
+ public void clock_init() throws IOException, InterruptedException {
+ if (debug != null) {
+ debug.debug_instr(set_clkcon_fast);
+
+ byte status;
+ for (int times = 0; times < 20; times++) {
+ Thread.sleep(1);
+ status = debug.debug_instr(get_sleep);
+ if ((status & SLEEP_XOSC_STB) != 0)
+ return;
+ }
+ throw new IOException("Failed to initialize target clock");
+ }
+ }
+
+ void action(String in_s, int in_percent) {
+ final String s = in_s;
+ final int percent = in_percent;
+ if (listener != null && !aborted) {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ listener.actionPerformed(new ActionEvent(this,
+ percent,
+ s));
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+ }
+
+ void action(int part, int total) {
+ int percent = 100 * part / total;
+ action(String.format("%d/%d (%d%%)",
+ part, total, percent),
+ percent);
+ }
+
+ void altos_run(int pc) throws IOException, InterruptedException {
+ debug.set_pc(pc);
+ int set_pc = debug.get_pc();
+ if (pc != set_pc)
+ throw new IOException("Failed to set target program counter");
+ debug.resume();
+
+ for (int times = 0; times < 20; times++) {
+ byte status = debug.read_status();
+ if ((status & AltosDebug.STATUS_CPU_HALTED) != 0)
+ return;
+ }
+
+ throw new IOException("Failed to execute program on target");
+ }
+
+ public void flash() {
+ try {
+ if (!check_rom_config())
+ throw new IOException("Invalid rom config settings");
+ if (image.address + image.data.length > 0x8000)
+ throw new IOException(String.format("Flash image too long %d",
+ image.address +
+ image.data.length));
+ if ((image.address & 0x3ff) != 0)
+ throw new IOException(String.format("Flash image must start on page boundary (is 0x%x)",
+ image.address));
+ int ram_address = 0xf000;
+ int flash_prog = 0xf400;
+
+ /*
+ * Store desired config values into image
+ */
+ rom_config.write(image);
+ /*
+ * Bring up the clock
+ */
+ clock_init();
+
+ int remain = image.data.length;
+ int flash_addr = image.address;
+ int image_start = 0;
+
+ action("start", 0);
+ action(0, image.data.length);
+ while (remain > 0 && !aborted) {
+ int this_time = remain;
+ if (this_time > 0x400)
+ this_time = 0x400;
+
+ if (debug != null) {
+ /* write the data */
+ debug.write_memory(ram_address, image.data,
+ image_start, this_time);
+
+ /* write the flash program */
+ byte[] flash_page = make_flash_page(flash_addr,
+ ram_address,
+ this_time);
+ debug.write_memory(flash_prog, flash_page);
+
+ altos_run(flash_prog);
+ byte[] check = debug.read_memory(flash_addr, this_time);
+ for (int i = 0; i < this_time; i++)
+ if (check[i] != image.data[image_start + i])
+ throw new IOException(String.format("Flash write failed at 0x%x (%02x != %02x)",
+ image.address + image_start + i,
+ check[i], image.data[image_start + i]));
+ } else {
+ Thread.sleep(100);
+ }
+
+ remain -= this_time;
+ flash_addr += this_time;
+ image_start += this_time;
+
+ action(image.data.length - remain, image.data.length);
+ }
+ if (!aborted) {
+ action("done", 100);
+ if (debug != null) {
+ debug.set_pc(image.address);
+ debug.resume();
+ }
+ }
+ if (debug != null)
+ debug.close();
+ } catch (IOException ie) {
+ action(ie.getMessage(), -1);
+ abort();
+ } catch (InterruptedException ie) {
+ abort();
+ }
+ }
+
+ public void close() {
+ if (debug != null)
+ debug.close();
+ }
+
+ synchronized public void abort() {
+ aborted = true;
+ close();
+ }
+
+ public void addActionListener(ActionListener l) {
+ listener = l;
+ }
+
+ public boolean check_rom_config() {
+ if (debug == null)
+ return true;
+ if (rom_config == null)
+ rom_config = debug.romconfig();
+ return rom_config != null && rom_config.valid();
+ }
+
+ public void set_romconfig (AltosRomconfig romconfig) {
+ rom_config = romconfig;
+ }
+
+ public AltosRomconfig romconfig() {
+ if (!check_rom_config())
+ return null;
+ return rom_config;
+ }
+
+ public AltosFlash(File in_file, AltosDevice in_debug_dongle)
+ throws IOException, FileNotFoundException, AltosSerialInUseException, InterruptedException {
+ file = in_file;
+ debug_dongle = in_debug_dongle;
+ if (debug_dongle != null)
+ debug = new AltosDebug(in_debug_dongle);
+ input = new FileInputStream(file);
+ image = new AltosHexfile(input);
+ if (debug != null && !debug.check_connection()) {
+ debug.close();
+ throw new IOException("Debug port not connected");
+ }
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosFlashUI.java b/altosui/AltosFlashUI.java
new file mode 100644
index 00000000..66991d10
--- /dev/null
+++ b/altosui/AltosFlashUI.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlashUI
+ extends AltosDialog
+ implements ActionListener
+{
+ Container pane;
+ Box box;
+ JLabel serial_label;
+ JLabel serial_value;
+ JLabel file_label;
+ JLabel file_value;
+ JProgressBar pbar;
+ JButton cancel;
+
+ JFrame frame;
+
+ // Hex file with rom image
+ File file;
+
+ // Debug connection
+ AltosDevice debug_dongle;
+
+ // Desired Rom configuration
+ AltosRomconfig rom_config;
+
+ // Flash controller
+ AltosFlash flash;
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == cancel) {
+ if (flash != null)
+ flash.abort();
+ setVisible(false);
+ dispose();
+ } else {
+ String cmd = e.getActionCommand();
+ if (e.getID() == -1) {
+ JOptionPane.showMessageDialog(frame,
+ e.getActionCommand(),
+ file.toString(),
+ JOptionPane.ERROR_MESSAGE);
+ setVisible(false);
+ dispose();
+ } else if (cmd.equals("done")) {
+ setVisible(false);
+ dispose();
+ } else if (cmd.equals("start")) {
+ setVisible(true);
+ } else {
+ pbar.setValue(e.getID());
+ pbar.setString(cmd);
+ }
+ }
+ }
+
+ public void build_dialog() {
+ GridBagConstraints c;
+ Insets il = new Insets(4,4,4,4);
+ Insets ir = new Insets(4,4,4,4);
+
+ pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ serial_label = new JLabel("Serial:");
+ pane.add(serial_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 1; c.gridy = 0;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ serial_value = new JLabel("");
+ pane.add(serial_value, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.gridx = 0; c.gridy = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ file_label = new JLabel("File:");
+ pane.add(file_label, c);
+
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridx = 1; c.gridy = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ file_value = new JLabel(file.toString());
+ pane.add(file_value, c);
+
+ pbar = new JProgressBar();
+ pbar.setMinimum(0);
+ pbar.setMaximum(100);
+ pbar.setValue(0);
+ pbar.setString("");
+ pbar.setStringPainted(true);
+ pbar.setPreferredSize(new Dimension(600, 20));
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0; c.gridy = 2;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ Insets ib = new Insets(4,4,4,4);
+ c.insets = ib;
+ pane.add(pbar, c);
+
+ cancel = new JButton("Cancel");
+ c = new GridBagConstraints();
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.gridx = 0; c.gridy = 3;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ Insets ic = new Insets(4,4,4,4);
+ c.insets = ic;
+ pane.add(cancel, c);
+ cancel.addActionListener(this);
+ pack();
+ setLocationRelativeTo(frame);
+ }
+
+ void set_serial(int serial_number) {
+ serial_value.setText(String.format("%d", serial_number));
+ }
+
+ boolean select_source_file() {
+ JFileChooser hexfile_chooser = new JFileChooser();
+
+ File firmwaredir = AltosUIPreferences.firmwaredir();
+ if (firmwaredir != null)
+ hexfile_chooser.setCurrentDirectory(firmwaredir);
+
+ hexfile_chooser.setDialogTitle("Select Flash Image");
+ hexfile_chooser.setFileFilter(new FileNameExtensionFilter("Flash Image", "ihx"));
+ int returnVal = hexfile_chooser.showOpenDialog(frame);
+
+ if (returnVal != JFileChooser.APPROVE_OPTION)
+ return false;
+ file = hexfile_chooser.getSelectedFile();
+ if (file == null)
+ return false;
+ AltosUIPreferences.set_firmwaredir(file.getParentFile());
+ return true;
+ }
+
+ boolean select_debug_dongle() {
+ debug_dongle = AltosDeviceDialog.show(frame, Altos.product_any);
+
+ if (debug_dongle == null)
+ return false;
+ return true;
+ }
+
+ boolean update_rom_config_info(AltosRomconfig existing_config) {
+ AltosRomconfig new_config;
+ new_config = AltosRomconfigUI.show(frame, existing_config);
+ if (new_config == null)
+ return false;
+ rom_config = new_config;
+ set_serial(rom_config.serial_number);
+ setVisible(true);
+ return true;
+ }
+
+ void exception (Exception e) {
+ if (e instanceof FileNotFoundException) {
+ JOptionPane.showMessageDialog(frame,
+ ((FileNotFoundException) e).getMessage(),
+ "Cannot open file",
+ JOptionPane.ERROR_MESSAGE);
+ } else if (e instanceof AltosSerialInUseException) {
+ JOptionPane.showMessageDialog(frame,
+ String.format("Device \"%s\" already in use",
+ debug_dongle.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ } else if (e instanceof IOException) {
+ JOptionPane.showMessageDialog(frame,
+ e.getMessage(),
+ file.toString(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ class flash_task implements Runnable {
+ AltosFlashUI ui;
+ Thread t;
+ AltosFlash flash;
+
+ public void run () {
+ try {
+ flash = new AltosFlash(ui.file, ui.debug_dongle);
+ flash.addActionListener(ui);
+
+ final AltosRomconfig current_config = flash.romconfig();
+
+ final Semaphore await_rom_config = new Semaphore(0);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ ui.flash = flash;
+ ui.update_rom_config_info(current_config);
+ await_rom_config.release();
+ }
+ });
+ await_rom_config.acquire();
+
+ if (ui.rom_config != null) {
+ flash.set_romconfig(ui.rom_config);
+ flash.flash();
+ }
+ } catch (InterruptedException ee) {
+ final Exception e = ee;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ ui.exception(e);
+ }
+ });
+ } catch (IOException ee) {
+ final Exception e = ee;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ ui.exception(e);
+ }
+ });
+ } catch (AltosSerialInUseException ee) {
+ final Exception e = ee;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ ui.exception(e);
+ }
+ });
+ } finally {
+ if (flash != null)
+ flash.close();
+ }
+ }
+
+ public flash_task(AltosFlashUI in_ui) {
+ ui = in_ui;
+ t = new Thread(this);
+ t.start();
+ }
+ }
+
+ flash_task flasher;
+
+ /*
+ * Execute the steps for flashing
+ * a device. Note that this returns immediately;
+ * this dialog is not modal
+ */
+ void showDialog() {
+ if (!select_debug_dongle())
+ return;
+ if (!select_source_file())
+ return;
+ build_dialog();
+ flash_task f = new flash_task(this);
+ }
+
+ static void show(JFrame frame) {
+ AltosFlashUI ui = new AltosFlashUI(frame);
+
+ ui.showDialog();
+ }
+
+ public AltosFlashUI(JFrame in_frame) {
+ super(in_frame, "Program Altusmetrum Device", false);
+
+ frame = in_frame;
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java
new file mode 100644
index 00000000..826f9522
--- /dev/null
+++ b/altosui/AltosFlightDisplay.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import org.altusmetrum.AltosLib.*;
+
+public interface AltosFlightDisplay {
+ void reset();
+
+ void show(AltosState state, int crc_errors);
+
+ void set_font();
+}
diff --git a/altosui/AltosFlightInfoTableModel.java b/altosui/AltosFlightInfoTableModel.java
new file mode 100644
index 00000000..77969a89
--- /dev/null
+++ b/altosui/AltosFlightInfoTableModel.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightInfoTableModel extends AbstractTableModel {
+ final static private String[] columnNames = {"Field", "Value"};
+
+ int rows;
+ int cols;
+ private String[][] data;
+
+ public int getColumnCount() { return cols; }
+ public int getRowCount() { return rows; }
+ public String getColumnName(int col) { return columnNames[col & 1]; }
+
+ public Object getValueAt(int row, int col) {
+ if (row >= rows || col >= cols)
+ return "";
+ return data[row][col];
+ }
+
+ int[] current_row;
+
+ public void reset() {
+ for (int i = 0; i < cols / 2; i++)
+ current_row[i] = 0;
+ }
+
+ public void clear() {
+ reset();
+ for (int c = 0; c < cols; c++)
+ for (int r = 0; r < rows; r++)
+ data[r][c] = "";
+ fireTableDataChanged();
+ }
+
+ public void addRow(int col, String name, String value) {
+ if (current_row[col] < rows) {
+ data[current_row[col]][col * 2] = name;
+ data[current_row[col]][col * 2 + 1] = value;
+ }
+ current_row[col]++;
+ }
+
+ public void finish() {
+ for (int c = 0; c < cols / 2; c++)
+ while (current_row[c] < rows)
+ addRow(c, "", "");
+ fireTableDataChanged();
+ }
+
+ public AltosFlightInfoTableModel (int in_rows, int in_cols) {
+ rows = in_rows;
+ cols = in_cols * 2;
+ data = new String[rows][cols];
+ current_row = new int[in_cols];
+ }
+}
diff --git a/altosui/AltosFlightStats.java b/altosui/AltosFlightStats.java
new file mode 100644
index 00000000..ab094c80
--- /dev/null
+++ b/altosui/AltosFlightStats.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightStats {
+ double max_height;
+ double max_speed;
+ double max_acceleration;
+ double[] state_speed = new double[Altos.ao_flight_invalid + 1];
+ double[] state_baro_speed = new double[Altos.ao_flight_invalid + 1];
+ double[] state_accel = new double[Altos.ao_flight_invalid + 1];
+ int[] state_count = new int[Altos.ao_flight_invalid + 1];
+ double[] state_start = new double[Altos.ao_flight_invalid + 1];
+ double[] state_end = new double[Altos.ao_flight_invalid + 1];
+ int serial;
+ int flight;
+ int year, month, day;
+ int hour, minute, second;
+
+ double landed_time(AltosRecordIterable iterable) {
+ AltosState state = null;
+ for (AltosRecord record : iterable) {
+ state = new AltosState(record, state);
+
+ if (state.state == Altos.ao_flight_landed)
+ break;
+ }
+
+ double landed_height = state.height;
+
+ state = null;
+
+ boolean above = true;
+
+ double landed_time = -1000;
+
+ for (AltosRecord record : iterable) {
+ state = new AltosState(record, state);
+
+ if (state.height > landed_height + 10) {
+ above = true;
+ } else {
+ if (above && state.height < landed_height + 2) {
+ above = false;
+ landed_time = state.time;
+ }
+ }
+ }
+ if (landed_time == -1000)
+ landed_time = state.time;
+ return landed_time;
+ }
+
+ double boost_time(AltosRecordIterable iterable) {
+ double boost_time = -1000;
+
+ AltosState state = null;
+
+ for (AltosRecord record : iterable) {
+ state = new AltosState(record, state);
+
+ if (state.acceleration < 1)
+ boost_time = state.time;
+ if (state.state >= Altos.ao_flight_boost)
+ break;
+ }
+ if (boost_time == -1000)
+ boost_time = state.time;
+ return boost_time;
+ }
+
+
+ public AltosFlightStats(AltosRecordIterable iterable) throws InterruptedException, IOException {
+ AltosState state = null;
+ AltosState new_state = null;
+ double boost_time = boost_time(iterable);
+ double end_time = 0;
+ double landed_time = landed_time(iterable);
+
+ year = month = day = -1;
+ hour = minute = second = -1;
+ serial = flight = -1;
+ for (AltosRecord record : iterable) {
+ if (serial < 0)
+ serial = record.serial;
+ if ((record.seen & AltosRecord.seen_flight) != 0 && flight < 0)
+ flight = record.flight;
+ new_state = new AltosState(record, state);
+ end_time = new_state.time;
+ state = new_state;
+ if (state.time >= boost_time && state.state < Altos.ao_flight_boost)
+ state.state = Altos.ao_flight_boost;
+ if (state.time >= landed_time && state.state < Altos.ao_flight_landed)
+ state.state = Altos.ao_flight_landed;
+ if (0 <= state.state && state.state < Altos.ao_flight_invalid) {
+ if (state.state >= Altos.ao_flight_boost) {
+ if (state.gps != null && state.gps.locked &&
+ year < 0) {
+ year = state.gps.year;
+ month = state.gps.month;
+ day = state.gps.day;
+ hour = state.gps.hour;
+ minute = state.gps.minute;
+ second = state.gps.second;
+ }
+ }
+ state_accel[state.state] += state.acceleration;
+ state_speed[state.state] += state.speed;
+ state_baro_speed[state.state] += state.baro_speed;
+ state_count[state.state]++;
+ if (state_start[state.state] == 0.0)
+ state_start[state.state] = state.time;
+ if (state_end[state.state] < state.time)
+ state_end[state.state] = state.time;
+ max_height = state.max_height;
+ if (state.max_speed != 0)
+ max_speed = state.max_speed;
+ else
+ max_speed = state.max_baro_speed;
+ max_acceleration = state.max_acceleration;
+ }
+ }
+ for (int s = Altos.ao_flight_startup; s <= Altos.ao_flight_landed; s++) {
+ if (state_count[s] > 0) {
+ state_speed[s] /= state_count[s];
+ state_baro_speed[s] /= state_count[s];
+ state_accel[s] /= state_count[s];
+ }
+ if (state_start[s] == 0)
+ state_start[s] = end_time;
+ if (state_end[s] == 0)
+ state_end[s] = end_time;
+ }
+ }
+}
diff --git a/altosui/AltosFlightStatsTable.java b/altosui/AltosFlightStatsTable.java
new file mode 100644
index 00000000..87ba6aa8
--- /dev/null
+++ b/altosui/AltosFlightStatsTable.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightStatsTable extends JComponent {
+ GridBagLayout layout;
+
+ class FlightStat {
+ JLabel label;
+ JTextField value;
+
+ public FlightStat(GridBagLayout layout, int y, String label_text, String ... values) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.weighty = 1;
+
+ label = new JLabel(label_text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 0; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ for (int j = 0; j < values.length; j++) {
+ value = new JTextField(values[j]);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = j+1; c.gridy = y;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ }
+
+ public AltosFlightStatsTable(AltosFlightStats stats) {
+ layout = new GridBagLayout();
+
+ setLayout(layout);
+ int y = 0;
+ new FlightStat(layout, y++, "Serial", String.format("%d", stats.serial));
+ new FlightStat(layout, y++, "Flight", String.format("%d", stats.flight));
+ if (stats.year > 0)
+ new FlightStat(layout, y++, "Date",
+ String.format("%04d-%02d-%02d", stats.year, stats.month, stats.day));
+ if (stats.hour > 0)
+ new FlightStat(layout, y++, "Time",
+ String.format("%02d:%02d:%02d UTC", stats.hour, stats.minute, stats.second));
+ new FlightStat(layout, y++, "Maximum height",
+ String.format("%5.0f m", stats.max_height),
+ String.format("%5.0f ft", AltosConvert.meters_to_feet(stats.max_height)));
+ new FlightStat(layout, y++, "Maximum speed",
+ String.format("%5.0f m/s", stats.max_speed),
+ String.format("%5.0f mph", AltosConvert.meters_to_mph(stats.max_speed)),
+ String.format("Mach %4.1f", AltosConvert.meters_to_mach(stats.max_speed)));
+ if (stats.max_acceleration != AltosRecord.MISSING) {
+ new FlightStat(layout, y++, "Maximum boost acceleration",
+ String.format("%5.0f m/s²", stats.max_acceleration),
+ String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.max_acceleration)),
+ String.format("%5.0f G", AltosConvert.meters_to_g(stats.max_acceleration)));
+ new FlightStat(layout, y++, "Average boost acceleration",
+ String.format("%5.0f m/s²", stats.state_accel[Altos.ao_flight_boost]),
+ String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.state_accel[Altos.ao_flight_boost])),
+ String.format("%5.0f G", AltosConvert.meters_to_g(stats.state_accel[Altos.ao_flight_boost])));
+ }
+ new FlightStat(layout, y++, "Drogue descent rate",
+ String.format("%5.0f m/s", stats.state_baro_speed[Altos.ao_flight_drogue]),
+ String.format("%5.0f ft/s", AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_drogue])));
+ new FlightStat(layout, y++, "Main descent rate",
+ String.format("%5.0f m/s", stats.state_baro_speed[Altos.ao_flight_main]),
+ String.format("%5.0f ft/s", AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_main])));
+ for (int s = Altos.ao_flight_boost; s <= Altos.ao_flight_main; s++) {
+ new FlightStat(layout, y++, String.format("%s time", AltosLib.state_name_capital(s)),
+ String.format("%6.0f s", stats.state_end[s] - stats.state_start[s]));
+ }
+ new FlightStat(layout, y++, "Flight Time",
+ String.format("%6.0f s", stats.state_end[Altos.ao_flight_main] -
+ stats.state_start[Altos.ao_flight_boost]));
+
+ }
+
+} \ No newline at end of file
diff --git a/altosui/AltosFlightStatus.java b/altosui/AltosFlightStatus.java
new file mode 100644
index 00000000..6a351004
--- /dev/null
+++ b/altosui/AltosFlightStatus.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightStatus extends JComponent implements AltosFlightDisplay {
+ GridBagLayout layout;
+
+ public class FlightValue {
+ JLabel label;
+ JTextField value;
+
+ void show(AltosState state, int crc_errors) {}
+
+ void reset() {
+ value.setText("");
+ }
+
+ void set_font() {
+ label.setFont(Altos.status_font);
+ value.setFont(Altos.status_font);
+ }
+
+ 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.setFont(Altos.status_font);
+ value.setHorizontalAlignment(SwingConstants.CENTER);
+ c.gridx = x; c.gridy = 1;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ class Call extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ value.setText(state.data.callsign);
+ }
+ public Call (GridBagLayout layout, int x) {
+ super (layout, x, "Callsign");
+ }
+ }
+
+ Call call;
+
+ class Serial extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ value.setText(String.format("%d", state.data.serial));
+ }
+ public Serial (GridBagLayout layout, int x) {
+ super (layout, x, "Serial");
+ }
+ }
+
+ Serial serial;
+
+ class Flight extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ value.setText(String.format("%d", state.data.flight));
+ }
+ public Flight (GridBagLayout layout, int x) {
+ super (layout, x, "Flight");
+ }
+ }
+
+ Flight flight;
+
+ class FlightState extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ value.setText(state.data.state());
+ }
+ public FlightState (GridBagLayout layout, int x) {
+ super (layout, x, "State");
+ }
+ }
+
+ FlightState flight_state;
+
+ class RSSI extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ value.setText(String.format("%d", state.data.rssi));
+ }
+ public RSSI (GridBagLayout layout, int x) {
+ super (layout, x, "RSSI");
+ }
+ }
+
+ RSSI rssi;
+
+ class LastPacket extends FlightValue {
+ void show(AltosState state, int crc_errors) {
+ long secs = (System.currentTimeMillis() - state.report_time + 500) / 1000;
+ value.setText(String.format("%d", secs));
+ }
+ 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 set_font () {
+ call.set_font();
+ serial.set_font();
+ flight.set_font();
+ flight_state.set_font();
+ rssi.set_font();
+ last_packet.set_font();
+ }
+
+ public void show (AltosState state, int crc_errors) {
+ call.show(state, crc_errors);
+ serial.show(state, crc_errors);
+ flight.show(state, crc_errors);
+ flight_state.show(state, crc_errors);
+ rssi.show(state, crc_errors);
+ last_packet.show(state, crc_errors);
+ }
+
+ public int height() {
+ Dimension d = layout.preferredLayoutSize(this);
+ return d.height;
+ }
+
+ 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/altosui/AltosFlightStatusTableModel.java b/altosui/AltosFlightStatusTableModel.java
new file mode 100644
index 00000000..c2cf8cd1
--- /dev/null
+++ b/altosui/AltosFlightStatusTableModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+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/altosui/AltosFlightStatusUpdate.java b/altosui/AltosFlightStatusUpdate.java
new file mode 100644
index 00000000..d70fc7f8
--- /dev/null
+++ b/altosui/AltosFlightStatusUpdate.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightStatusUpdate implements ActionListener {
+
+ public AltosState saved_state;
+ AltosFlightStatus flightStatus;
+
+ public void actionPerformed (ActionEvent e) {
+ if (saved_state != null)
+ flightStatus.show(saved_state, 0);
+ }
+
+ public AltosFlightStatusUpdate (AltosFlightStatus in_flightStatus) {
+ flightStatus = in_flightStatus;
+ }
+}
+
diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java
new file mode 100644
index 00000000..ddc54cbd
--- /dev/null
+++ b/altosui/AltosFlightUI.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFlightUI extends AltosFrame implements AltosFlightDisplay, AltosFontListener {
+ AltosVoice voice;
+ AltosFlightReader reader;
+ AltosDisplayThread thread;
+
+ JTabbedPane pane;
+
+ AltosPad pad;
+ AltosAscent ascent;
+ AltosDescent descent;
+ AltosLanded landed;
+ AltosCompanionInfo companion;
+ AltosSiteMap sitemap;
+ boolean has_map;
+ boolean has_companion;
+
+ 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;
+ 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() {
+ pad.reset();
+ ascent.reset();
+ descent.reset();
+ landed.reset();
+ flightInfo.clear();
+ sitemap.reset();
+ }
+
+ public void set_font() {
+ pad.set_font();
+ ascent.set_font();
+ descent.set_font();
+ landed.set_font();
+ flightStatus.set_font();
+ flightInfo.set_font();
+ sitemap.set_font();
+ companion.set_font();
+ }
+
+ public void font_size_changed(int font_size) {
+ set_font();
+ }
+
+
+ AltosFlightStatusUpdate status_update;
+
+ public void show(AltosState state, int crc_errors) {
+ status_update.saved_state = state;
+ JComponent tab = which_tab(state);
+ try {
+ pad.show(state, crc_errors);
+ ascent.show(state, crc_errors);
+ descent.show(state, crc_errors);
+ landed.show(state, crc_errors);
+ if (tab != cur_tab) {
+ if (cur_tab == pane.getSelectedComponent()) {
+ pane.setSelectedComponent(tab);
+ }
+ cur_tab = tab;
+ }
+ flightStatus.show(state, crc_errors);
+ flightInfo.show(state, crc_errors);
+
+ if (state.data.companion != null) {
+ if (!has_companion) {
+ pane.add("Companion", companion);
+ has_companion= true;
+ }
+ companion.show(state, crc_errors);
+ } else {
+ if (has_companion) {
+ pane.remove(companion);
+ has_companion = false;
+ }
+ }
+ if (state.gps != null && state.gps.connected) {
+ if (!has_map) {
+ pane.add("Site Map", sitemap);
+ has_map = true;
+ }
+ sitemap.show(state, crc_errors);
+ } else {
+ if (has_map) {
+ pane.remove(sitemap);
+ has_map = false;
+ }
+ }
+ } catch (Exception e) {
+ System.out.print("Show exception" + e);
+ }
+ }
+
+ public void set_exit_on_close() {
+ exit_on_close = true;
+ }
+
+ Container bag;
+ AltosFreqList frequencies;
+ JComboBox telemetries;
+ JLabel telemetry;
+
+ ActionListener show_timer;
+
+ public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) {
+ AltosUIPreferences.set_component(this);
+
+ voice = in_voice;
+ reader = in_reader;
+
+ bag = getContentPane();
+ bag.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
+ if (imgURL != null)
+ setIconImage(new ImageIcon(imgURL).getImage());
+
+ setTitle(String.format("AltOS %s", reader.name));
+
+ /* Stick channel selector at top of table for telemetry monitoring */
+ if (serial >= 0) {
+ // Channel menu
+ frequencies = new AltosFreqList(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();
+ }
+ });
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.insets = new Insets(3, 3, 3, 3);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ bag.add (frequencies, c);
+
+ // Telemetry format menu
+ if (reader.supports_telemetry(Altos.ao_telemetry_standard)) {
+ telemetries = new JComboBox();
+ for (int i = 1; i <= Altos.ao_telemetry_max; i++)
+ telemetries.addItem(Altos.telemetry_name(i));
+ int telemetry = AltosPreferences.telemetry(serial);
+ if (telemetry <= Altos.ao_telemetry_off ||
+ telemetry > Altos.ao_telemetry_max)
+ telemetry = Altos.ao_telemetry_standard;
+ telemetries.setSelectedIndex(telemetry - 1);
+ telemetries.setMaximumRowCount(Altos.ao_telemetry_max);
+ telemetries.setPreferredSize(null);
+ telemetries.revalidate();
+ telemetries.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int telemetry = telemetries.getSelectedIndex() + 1;
+ reader.set_telemetry(telemetry);
+ reader.save_telemetry();
+ }
+ });
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ bag.add (telemetries, c);
+ c.insets = new Insets(0, 0, 0, 0);
+ } 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);
+ c.gridx = 1;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.WEST;
+ bag.add (telemetry, c);
+ c.insets = new Insets(0, 0, 0, 0);
+ }
+ }
+
+ /* Flight status is always visible */
+ flightStatus = new AltosFlightStatus();
+ c.gridx = 0;
+ c.gridy = 1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridwidth = 2;
+ bag.add(flightStatus, c);
+ c.gridwidth = 1;
+
+ /* 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);
+
+ ascent = new AltosAscent();
+ pane.add("Ascent", ascent);
+
+ descent = new AltosDescent();
+ pane.add("Descent", descent);
+
+ landed = new AltosLanded(reader);
+ pane.add("Landed", landed);
+
+ flightInfo = new AltosInfoTable();
+ pane.add("Table", new JScrollPane(flightInfo));
+
+ companion = new AltosCompanionInfo();
+ has_companion = false;
+
+ sitemap = new AltosSiteMap();
+ has_map = false;
+
+ /* Make the tabbed pane use the rest of the window space */
+ c.gridx = 0;
+ c.gridy = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ c.weighty = 1;
+ c.gridwidth = 2;
+ bag.add(pane, c);
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ AltosUIPreferences.register_font_listener(this);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ disconnect();
+ setVisible(false);
+ dispose();
+ AltosUIPreferences.unregister_font_listener(AltosFlightUI.this);
+ if (exit_on_close)
+ System.exit(0);
+ }
+ });
+
+ pack();
+ setVisible(true);
+
+ thread = new AltosDisplayThread(this, voice, this, reader);
+
+ status_update = new AltosFlightStatusUpdate(flightStatus);
+
+ new javax.swing.Timer(100, status_update).start();
+
+ thread.start();
+ }
+
+ public AltosFlightUI (AltosVoice in_voice, AltosFlightReader in_reader) {
+ this(in_voice, in_reader, -1);
+ }
+}
diff --git a/altosui/AltosFontListener.java b/altosui/AltosFontListener.java
new file mode 100644
index 00000000..0dda0f29
--- /dev/null
+++ b/altosui/AltosFontListener.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+public interface AltosFontListener {
+ void font_size_changed(int font_size);
+}
diff --git a/altosui/AltosFrame.java b/altosui/AltosFrame.java
new file mode 100644
index 00000000..70598634
--- /dev/null
+++ b/altosui/AltosFrame.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+class AltosFrameListener extends WindowAdapter {
+ public void windowClosing (WindowEvent e) {
+ AltosUIPreferences.unregister_ui_listener((AltosFrame) e.getWindow());
+ }
+}
+
+public class AltosFrame extends JFrame implements AltosUIListener {
+
+ public void ui_changed(String look_and_feel) {
+ SwingUtilities.updateComponentTreeUI(this);
+ this.pack();
+ }
+
+ public AltosFrame() {
+ AltosUIPreferences.register_ui_listener(this);
+ addWindowListener(new AltosFrameListener());
+ }
+
+ public AltosFrame(String name) {
+ super(name);
+ AltosUIPreferences.register_ui_listener(this);
+ addWindowListener(new AltosFrameListener());
+ }
+}
diff --git a/altosui/AltosFreqList.java b/altosui/AltosFreqList.java
new file mode 100644
index 00000000..1bbc97c6
--- /dev/null
+++ b/altosui/AltosFreqList.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosFreqList extends JComboBox {
+
+ String product;
+ int serial;
+ int calibrate;
+
+ public void set_frequency(double new_frequency) {
+ int i;
+ for (i = 0; i < getItemCount(); i++) {
+ AltosFrequency f = (AltosFrequency) getItemAt(i);
+
+ if (f.close(new_frequency)) {
+ setSelectedIndex(i);
+ return;
+ }
+ }
+ for (i = 0; i < getItemCount(); i++) {
+ AltosFrequency f = (AltosFrequency) getItemAt(i);
+
+ if (new_frequency < f.frequency)
+ break;
+ }
+ String description = String.format("%s serial %d", product, serial);
+ AltosFrequency frequency = new AltosFrequency(new_frequency, description);
+ AltosUIPreferences.add_common_frequency(frequency);
+ insertItemAt(frequency, i);
+ setMaximumRowCount(getItemCount());
+ }
+
+ public void set_product(String new_product) {
+ product = new_product;
+ }
+
+ public void set_serial(int new_serial) {
+ serial = new_serial;
+ }
+
+ public double frequency() {
+ AltosFrequency f = (AltosFrequency) getSelectedItem();
+ if (f != null)
+ return f.frequency;
+ return 434.550;
+ }
+
+ public AltosFreqList () {
+ super(AltosUIPreferences.common_frequencies());
+ setMaximumRowCount(getItemCount());
+ setEditable(false);
+ product = "Unknown";
+ serial = 0;
+ }
+
+ public AltosFreqList(double in_frequency) {
+ this();
+ set_frequency(in_frequency);
+ }
+}
diff --git a/altosui/AltosGraph.java b/altosui/AltosGraph.java
new file mode 100644
index 00000000..54d2bb0b
--- /dev/null
+++ b/altosui/AltosGraph.java
@@ -0,0 +1,27 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+import java.io.*;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.ChartUtilities;
+import org.altusmetrum.AltosLib.*;
+
+abstract class AltosGraph {
+ public String filename;
+ public abstract void addData(AltosDataPoint d);
+ public abstract JFreeChart createChart();
+ public String title;
+ public void toPNG() throws java.io.IOException { toPNG(300, 500); }
+ public void toPNG(int width, int height)
+ throws java.io.IOException
+ {
+ File pngout = new File(filename);
+ JFreeChart chart = createChart();
+ ChartUtilities.saveChartAsPNG(pngout, chart, width, height);
+ System.out.println("Created " + filename);
+ }
+}
diff --git a/altosui/AltosGraphTime.java b/altosui/AltosGraphTime.java
new file mode 100644
index 00000000..0955f6e6
--- /dev/null
+++ b/altosui/AltosGraphTime.java
@@ -0,0 +1,236 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.util.*;
+import java.text.*;
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.altusmetrum.AltosLib.*;
+
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.labels.StandardXYToolTipGenerator;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.plot.ValueMarker;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleAnchor;
+import org.jfree.ui.TextAnchor;
+
+class AltosGraphTime extends AltosGraph {
+ static interface Element {
+ void attachGraph(AltosGraphTime g);
+ void gotTimeData(double time, AltosDataPoint d);
+ void addToPlot(AltosGraphTime g, XYPlot plot);
+ }
+
+ static class TimeAxis implements Element {
+ private int axis;
+ private Color color;
+ private String label;
+ private AxisLocation locn;
+ private double min_y = Double.NaN;
+
+ public TimeAxis(int axis, String label, Color color, AxisLocation locn)
+ {
+ this.axis = axis;
+ this.color = color;
+ this.label = label;
+ this.locn = locn;
+ }
+
+ public void setLowerBound(double min_y) {
+ this.min_y = min_y;
+ }
+
+ public void attachGraph(AltosGraphTime g) { return; }
+ public void gotTimeData(double time, AltosDataPoint d) { return; }
+
+ public void addToPlot(AltosGraphTime g, XYPlot plot) {
+ NumberAxis numAxis = new NumberAxis(label);
+ if (!Double.isNaN(min_y))
+ numAxis.setLowerBound(min_y);
+ plot.setRangeAxis(axis, numAxis);
+ plot.setRangeAxisLocation(axis, locn);
+ numAxis.setLabelPaint(color);
+ numAxis.setTickLabelPaint(color);
+ numAxis.setAutoRangeIncludesZero(false);
+ }
+ }
+
+ abstract static class TimeSeries implements Element {
+ protected XYSeries series;
+ private String axisName;
+ private Color color;
+
+ public TimeSeries(String axisName, String label, Color color) {
+ this.series = new XYSeries(label);
+ this.axisName = axisName;
+ this.color = color;
+ }
+
+ public void attachGraph(AltosGraphTime g) {
+ g.setAxis(this, axisName, color);
+ }
+ abstract public void gotTimeData(double time, AltosDataPoint d);
+
+ public void addToPlot(AltosGraphTime g, XYPlot plot) {
+ XYSeriesCollection dataset = new XYSeriesCollection();
+ dataset.addSeries(this.series);
+
+ XYItemRenderer renderer = new StandardXYItemRenderer();
+ renderer.setSeriesPaint(0, color);
+
+ int dataNum = g.getDataNum(this);
+ int axisNum = g.getAxisNum(this);
+
+ plot.setDataset(dataNum, dataset);
+ plot.mapDatasetToRangeAxis(dataNum, axisNum);
+ plot.setRenderer(dataNum, renderer);
+ }
+ }
+
+ static class StateMarker implements Element {
+ private LinkedList<Double> times = new LinkedList<Double>();
+ private String name;
+ private int state;
+ private int prev_state = Altos.ao_flight_startup;
+
+ StateMarker(int state, String name) {
+ this.state = state;
+ this.name = name;
+ }
+
+ public void attachGraph(AltosGraphTime g) { return; }
+ public void gotTimeData(double time, AltosDataPoint d) {
+ if (prev_state != state && d.state() == state)
+ times.add(time);
+ prev_state = d.state();
+ }
+
+ public void addToPlot(AltosGraphTime g, XYPlot plot) {
+ for (double time : times) {
+ ValueMarker m = new ValueMarker(time);
+ m.setLabel(name);
+ m.setLabelAnchor(RectangleAnchor.TOP_RIGHT);
+ m.setLabelTextAnchor(TextAnchor.TOP_LEFT);
+ plot.addDomainMarker(m);
+ }
+ }
+ }
+
+ private String callsign = null;
+ private Integer serial = null;
+ private Integer flight = null;
+
+ private ArrayList<Element> elements;
+ private HashMap<String,Integer> axes;
+ private HashMap<Element,Integer> datasets;
+ private ArrayList<Integer> datasetAxis;
+
+ public AltosGraphTime(String title) {
+ this.filename = title.toLowerCase().replaceAll("[^a-z0-9]","_")+".png";
+ this.title = title;
+ this.elements = new ArrayList<Element>();
+ this.axes = new HashMap<String,Integer>();
+ this.datasets = new HashMap<Element,Integer>();
+ this.datasetAxis = new ArrayList<Integer>();
+ }
+
+ public AltosGraphTime addElement(Element e) {
+ e.attachGraph(this);
+ elements.add(e);
+ return this;
+ }
+
+ public void setAxis(Element ds, String axisName, Color color) {
+ Integer axisNum = axes.get(axisName);
+ int dsNum = datasetAxis.size();
+ if (axisNum == null) {
+ axisNum = newAxis(axisName, color);
+ }
+ datasets.put(ds, dsNum);
+ datasetAxis.add(axisNum);
+ }
+
+ public int getAxisNum(Element ds) {
+ return datasetAxis.get( datasets.get(ds) ).intValue();
+ }
+ public int getDataNum(Element ds) {
+ return datasets.get(ds).intValue();
+ }
+
+ private Integer newAxis(String name, Color color) {
+ int cnt = axes.size();
+ AxisLocation locn = AxisLocation.BOTTOM_OR_LEFT;
+ if (cnt > 0) {
+ locn = AxisLocation.TOP_OR_RIGHT;
+ }
+ Integer res = new Integer(cnt);
+ axes.put(name, res);
+ this.addElement(new TimeAxis(cnt, name, color, locn));
+ return res;
+ }
+
+ public void addData(AltosDataPoint d) {
+ double time = d.time();
+ for (Element e : elements) {
+ e.gotTimeData(time, d);
+ }
+ if (callsign == null) callsign = d.callsign();
+ if (serial == null) serial = new Integer(d.serial());
+ if (flight == null) flight = new Integer(d.flight());
+ }
+
+ public JFreeChart createChart() {
+ NumberAxis xAxis = new NumberAxis("Time (s)");
+ xAxis.setAutoRangeIncludesZero(false);
+ XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
+ XYPlot plot = new XYPlot();
+ plot.setDomainAxis(xAxis);
+ plot.setRenderer(renderer);
+ plot.setOrientation(PlotOrientation.VERTICAL);
+
+ if (serial != null && flight != null) {
+ title = serial + "/" + flight + ": " + title;
+ }
+ if (callsign != null) {
+ title = callsign + " - " + title;
+ }
+
+ renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
+ JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT,
+ plot, true);
+ ChartUtilities.applyCurrentTheme(chart);
+
+ plot.setDomainPannable(true);
+ plot.setRangePannable(true);
+
+ for (Element e : elements) {
+ e.addToPlot(this, plot);
+ }
+
+ return chart;
+ }
+
+ public void toPNG() throws java.io.IOException {
+ if (axes.size() > 1) {
+ toPNG(800, 500);
+ } else {
+ toPNG(300, 500);
+ }
+ }
+}
diff --git a/altosui/AltosGraphUI.java b/altosui/AltosGraphUI.java
new file mode 100644
index 00000000..edde1307
--- /dev/null
+++ b/altosui/AltosGraphUI.java
@@ -0,0 +1,313 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import org.altusmetrum.AltosLib.*;
+
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.ui.ApplicationFrame;
+import org.jfree.ui.RefineryUtilities;
+
+public class AltosGraphUI extends AltosFrame
+{
+ JTabbedPane pane;
+
+ static final private Color red = new Color(194,31,31);
+ static final private Color green = new Color(31,194,31);
+ static final private Color blue = new Color(31,31,194);
+ static final private Color black = new Color(31,31,31);
+ static final private Color yellow = new Color(194,194,31);
+ static final private Color cyan = new Color(31,194,194);
+ static final private Color magenta = new Color(194,31,194);
+
+ static private class OverallGraphs {
+ AltosGraphTime.Element height =
+ new AltosGraphTime.TimeSeries(String.format("Height (%s)", AltosConvert.height.show_units()), "Height (AGL)", red) {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double height = d.height();
+ if (height != AltosRecord.MISSING)
+ series.add(time, AltosConvert.height.value(height));
+ }
+ };
+
+ AltosGraphTime.Element speed =
+ new AltosGraphTime.TimeSeries(String.format("Speed (%s)", AltosConvert.speed.show_units()), "Vertical Speed", green) {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double speed;
+ if (d.state() < Altos.ao_flight_drogue && d.has_accel()) {
+ speed = d.accel_speed();
+ } else {
+ speed = d.baro_speed();
+ }
+ if (speed != AltosRecord.MISSING)
+ series.add(time, AltosConvert.speed.value(speed));
+ }
+ };
+
+ AltosGraphTime.Element acceleration =
+ new AltosGraphTime.TimeSeries(String.format("Acceleration (%s)",
+ AltosConvert.accel.show_units()),
+ "Axial Acceleration", blue)
+ {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double acceleration = d.acceleration();
+ if (acceleration != AltosRecord.MISSING)
+ series.add(time, AltosConvert.accel.value(acceleration));
+ }
+ };
+
+ AltosGraphTime.Element temperature =
+ new AltosGraphTime.TimeSeries("Temperature (\u00B0C)",
+ "Board temperature", red)
+ {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double temp = d.temperature();
+ if (temp != AltosRecord.MISSING)
+ series.add(time, d.temperature());
+ }
+ };
+
+ AltosGraphTime.Element drogue_voltage =
+ new AltosGraphTime.TimeSeries("Voltage (V)", "Drogue Continuity", yellow)
+ {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double v = d.drogue_voltage();
+ if (v != AltosRecord.MISSING)
+ series.add(time, v);
+ }
+ };
+
+ AltosGraphTime.Element main_voltage =
+ new AltosGraphTime.TimeSeries("Voltage (V)", "Main Continuity", magenta)
+ {
+ public void gotTimeData(double time, AltosDataPoint d) {
+ double v = d.main_voltage();
+ if (v != AltosRecord.MISSING)
+ series.add(time, v);
+ }
+ };
+
+ AltosGraphTime.Element e_pad = new AltosGraphTime.StateMarker(Altos.ao_flight_pad, "Pad");
+ AltosGraphTime.Element e_boost = new AltosGraphTime.StateMarker(Altos.ao_flight_boost, "Boost");
+ AltosGraphTime.Element e_fast = new AltosGraphTime.StateMarker(Altos.ao_flight_fast, "Fast");
+ AltosGraphTime.Element e_coast = new AltosGraphTime.StateMarker(Altos.ao_flight_coast, "Coast");
+ AltosGraphTime.Element e_drogue = new AltosGraphTime.StateMarker(Altos.ao_flight_drogue, "Drogue");
+ AltosGraphTime.Element e_main = new AltosGraphTime.StateMarker(Altos.ao_flight_main, "Main");
+ AltosGraphTime.Element e_landed = new AltosGraphTime.StateMarker(Altos.ao_flight_landed, "Landed");
+
+ protected AltosGraphTime myAltosGraphTime(String suffix) {
+ return (new AltosGraphTime("Overall " + suffix))
+ .addElement(e_boost)
+ .addElement(e_fast)
+ .addElement(e_coast)
+ .addElement(e_drogue)
+ .addElement(e_main)
+ .addElement(e_landed);
+ }
+
+ public ArrayList<AltosGraph> graphs() {
+ ArrayList<AltosGraph> graphs = new ArrayList<AltosGraph>();
+
+ graphs.add( myAltosGraphTime("Summary")
+ .addElement(height)
+ .addElement(speed)
+ .addElement(acceleration) );
+
+ graphs.add( myAltosGraphTime("Summary")
+ .addElement(height)
+ .addElement(speed));
+
+ graphs.add( myAltosGraphTime("Altitude")
+ .addElement(height) );
+
+ graphs.add( myAltosGraphTime("Speed")
+ .addElement(speed) );
+
+ graphs.add( myAltosGraphTime("Acceleration")
+ .addElement(acceleration) );
+
+ graphs.add( myAltosGraphTime("Temperature")
+ .addElement(temperature) );
+
+ graphs.add( myAltosGraphTime("Continuity")
+ .addElement(drogue_voltage)
+ .addElement(main_voltage) );
+
+ return graphs;
+ }
+ }
+
+ static private class AscentGraphs extends OverallGraphs {
+ protected AltosGraphTime myAltosGraphTime(String suffix) {
+ return (new AltosGraphTime("Ascent " + suffix) {
+ public void addData(AltosDataPoint d) {
+ int state = d.state();
+ if (Altos.ao_flight_boost <= state && state <= Altos.ao_flight_coast) {
+ super.addData(d);
+ }
+ }
+ }).addElement(e_boost)
+ .addElement(e_fast)
+ .addElement(e_coast);
+ }
+ }
+
+ static private class DescentGraphs extends OverallGraphs {
+ protected AltosGraphTime myAltosGraphTime(String suffix) {
+ return (new AltosGraphTime("Descent " + suffix) {
+ public void addData(AltosDataPoint d) {
+ int state = d.state();
+ if (Altos.ao_flight_drogue <= state && state <= Altos.ao_flight_main) {
+ super.addData(d);
+ }
+ }
+ }).addElement(e_drogue)
+ .addElement(e_main);
+ // ((XYGraph)graph[8]).ymin = new Double(-50);
+ }
+ }
+
+ public AltosGraphUI(AltosRecordIterable records, String name) throws InterruptedException, IOException {
+ super(String.format("Altos Graph %s", name));
+
+ AltosDataPointReader reader = new AltosDataPointReader (records);
+ if (reader == null)
+ return;
+
+ if (reader.has_accel)
+ init(reader, records, 0);
+ else
+ init(reader, records, 1);
+ }
+
+// public AltosGraphUI(AltosDataPointReader data, int which)
+ // {
+// super("Altos Graph");
+// init(data, which);
+// }
+
+ private void init(AltosDataPointReader data, AltosRecordIterable records, int which) throws InterruptedException, IOException {
+ pane = new JTabbedPane();
+
+ AltosGraph graph = createGraph(data, which);
+
+ JFreeChart chart = graph.createChart();
+ ChartPanel chartPanel = new ChartPanel(chart);
+ chartPanel.setMouseWheelEnabled(true);
+ chartPanel.setPreferredSize(new java.awt.Dimension(800, 500));
+ pane.add(graph.title, chartPanel);
+
+ AltosFlightStatsTable stats = new AltosFlightStatsTable(new AltosFlightStats(records));
+ pane.add("Flight Statistics", stats);
+
+ setContentPane (pane);
+
+ pack();
+
+ RefineryUtilities.centerFrameOnScreen(this);
+
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ setVisible(true);
+ }
+
+ private static AltosGraph createGraph(Iterable<AltosDataPoint> data,
+ int which)
+ {
+ return createGraphsWhich(data, which).get(0);
+ }
+
+ private static ArrayList<AltosGraph> createGraphs(
+ Iterable<AltosDataPoint> data)
+ {
+ return createGraphsWhich(data, -1);
+ }
+
+ private static ArrayList<AltosGraph> createGraphsWhich(
+ Iterable<AltosDataPoint> data, int which)
+ {
+ ArrayList<AltosGraph> graph = new ArrayList<AltosGraph>();
+ graph.addAll((new OverallGraphs()).graphs());
+// graph.addAll((new AscentGraphs()).graphs());
+// graph.addAll((new DescentGraphs()).graphs());
+
+ if (which > 0) {
+ if (which >= graph.size()) {
+ which = 0;
+ }
+ AltosGraph g = graph.get(which);
+ graph = new ArrayList<AltosGraph>();
+ graph.add(g);
+ }
+
+ for (AltosDataPoint dp : data) {
+ for (AltosGraph g : graph) {
+ g.addData(dp);
+ }
+ }
+
+ return graph;
+ }
+}
+
+/* gnuplot bits...
+ *
+300x400
+
+--------------------------------------------------------
+TOO HARD! :)
+
+"ascent-gps-accuracy.png" "Vertical error margin to apogee - GPS v Baro (m)"
+ 5:($7 < 6 ? $24-$11 : 1/0)
+"descent-gps-accuracy.png" "Vertical error margin during descent - GPS v Baro (m)"
+ 5:($7 < 6 ? 1/0 : $24-$11)
+
+set output "overall-gps-accuracy.png"
+set ylabel "distance above sea level (m)"
+plot "telemetry.csv" using 5:11 with lines ti "baro altitude" axis x1y1, \
+ "telemetry.csv" using 5:24 with lines ti "gps altitude" axis x1y1
+
+set term png tiny size 700,700 enhanced
+set xlabel "m"
+set ylabel "m"
+set polar
+set grid polar
+set rrange[*:*]
+set angles degrees
+
+set output "overall-gps-path.png"
+#:30 with yerrorlines
+plot "telemetry.csv" using (90-$33):($7 == 2 ? $31 : 1/0) with lines ti "pad", \
+ "telemetry.csv" using (90-$33):($7 == 3 ? $31 : 1/0) with lines ti "boost", \
+ "telemetry.csv" using (90-$33):($7 == 4 ? $31 : 1/0) with lines ti "fast", \
+ "telemetry.csv" using (90-$33):($7 == 5 ? $31 : 1/0) with lines ti "coast", \
+ "telemetry.csv" using (90-$33):($7 == 6 ? $31 : 1/0) with lines ti "drogue", \
+ "telemetry.csv" using (90-$33):($7 == 7 ? $31 : 1/0) with lines ti "main", \
+ "telemetry.csv" using (90-$33):($7 == 8 ? $31 : 1/0) with lines ti "landed"
+
+set output "ascent-gps-path.png"
+plot "telemetry.csv" using (90-$33):($7 == 2 ? $31 : 1/0):30 with lines ti "pad", \
+ "telemetry.csv" using (90-$33):($7 == 3 ? $31 : 1/0):20 with lines ti "boost", \
+ "telemetry.csv" using (90-$33):($7 == 4 ? $31 : 1/0):10 with lines ti "fast", \
+ "telemetry.csv" using (90-$33):($7 == 5 ? $31 : 1/0):5 with lines ti "coast"
+
+set output "descent-gps-path.png"
+plot "telemetry.csv" using (90-$33):($7 == 6 ? $31 : 1/0) with lines ti "drogue", \
+ "telemetry.csv" using (90-$33):($7 == 7 ? $31 : 1/0) with lines ti "main", \
+ "telemetry.csv" using (90-$33):($7 == 8 ? $31 : 1/0) with lines ti "landed"
+
+ */
+
+
diff --git a/altosui/AltosHexfile.java b/altosui/AltosHexfile.java
new file mode 100644
index 00000000..d52b46c3
--- /dev/null
+++ b/altosui/AltosHexfile.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.Arrays;
+import org.altusmetrum.AltosLib.*;
+
+class HexFileInputStream extends PushbackInputStream {
+ public int line;
+
+ public HexFileInputStream(FileInputStream o) {
+ super(new BufferedInputStream(o));
+ line = 1;
+ }
+
+ public int read() throws IOException {
+ int c = super.read();
+ if (c == '\n')
+ line++;
+ return c;
+ }
+
+ public void unread(int c) throws IOException {
+ if (c == '\n')
+ line--;
+ if (c != -1)
+ super.unread(c);
+ }
+}
+
+class HexRecord implements Comparable {
+ public int address;
+ public int type;
+ public byte checksum;
+ public byte[] data;
+
+ static final int NORMAL = 0;
+ static final int EOF = 1;
+ static final int EXTENDED_ADDRESS = 2;
+
+ enum read_state {
+ marker,
+ length,
+ address,
+ type,
+ data,
+ checksum,
+ newline,
+ white,
+ done,
+ }
+
+ boolean ishex(int c) {
+ if ('0' <= c && c <= '9')
+ return true;
+ if ('a' <= c && c <= 'f')
+ return true;
+ if ('A' <= c && c <= 'F')
+ return true;
+ return false;
+ }
+
+ boolean isspace(int c) {
+ switch (c) {
+ case ' ':
+ case '\t':
+ return true;
+ }
+ return false;
+ }
+
+ int fromhex(int c) {
+ if ('0' <= c && c <= '9')
+ return c - '0';
+ if ('a' <= c && c <= 'f')
+ return c - 'a' + 10;
+ if ('A' <= c && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+ }
+
+ public byte checksum() {
+ byte got = 0;
+
+ got += data.length;
+ got += (address >> 8) & 0xff;
+ got += (address ) & 0xff;
+ got += type;
+ for (int i = 0; i < data.length; i++)
+ got += data[i];
+ return (byte) (-got);
+ }
+
+ public int compareTo(Object other) {
+ HexRecord o = (HexRecord) other;
+ return address - o.address;
+ }
+
+ public String toString() {
+ return String.format("%04x: %02x (%d)", address, type, data.length);
+ }
+
+ public HexRecord(HexFileInputStream input) throws IOException {
+ read_state state = read_state.marker;
+ int nhexbytes = 0;
+ int hex = 0;
+ int ndata = 0;
+ byte got_checksum;
+
+ while (state != read_state.done) {
+ int c = input.read();
+ if (c < 0 && state != read_state.white)
+ throw new IOException(String.format("%d: Unexpected EOF", input.line));
+ if (c == ' ')
+ continue;
+ switch (state) {
+ case marker:
+ if (c != ':')
+ throw new IOException("Missing ':'");
+ state = read_state.length;
+ nhexbytes = 2;
+ hex = 0;
+ break;
+ case length:
+ case address:
+ case type:
+ case data:
+ case checksum:
+ if(!ishex(c))
+ throw new IOException(String.format("Non-hex char '%c'", c));
+ hex = hex << 4 | fromhex(c);
+ --nhexbytes;
+ if (nhexbytes != 0)
+ break;
+
+ switch (state) {
+ case length:
+ data = new byte[hex];
+ state = read_state.address;
+ nhexbytes = 4;
+ break;
+ case address:
+ address = hex;
+ state = read_state.type;
+ nhexbytes = 2;
+ break;
+ case type:
+ type = hex;
+ if (data.length > 0)
+ state = read_state.data;
+ else
+ state = read_state.checksum;
+ nhexbytes = 2;
+ ndata = 0;
+ break;
+ case data:
+ data[ndata] = (byte) hex;
+ ndata++;
+ nhexbytes = 2;
+ if (ndata == data.length)
+ state = read_state.checksum;
+ break;
+ case checksum:
+ checksum = (byte) hex;
+ state = read_state.newline;
+ break;
+ default:
+ break;
+ }
+ hex = 0;
+ break;
+ case newline:
+ if (c != '\n' && c != '\r')
+ throw new IOException("Missing newline");
+ state = read_state.white;
+ break;
+ case white:
+ if (!isspace(c)) {
+ input.unread(c);
+ state = read_state.done;
+ }
+ break;
+ case done:
+ break;
+ }
+ }
+ got_checksum = checksum();
+ if (got_checksum != checksum)
+ throw new IOException(String.format("Invalid checksum (read 0x%02x computed 0x%02x)\n",
+ checksum, got_checksum));
+ }
+}
+
+public class AltosHexfile {
+ public int address;
+ public byte[] data;
+
+ public byte get_byte(int a) {
+ return data[a - address];
+ }
+
+ public AltosHexfile(FileInputStream file) throws IOException {
+ HexFileInputStream input = new HexFileInputStream(file);
+ LinkedList<HexRecord> record_list = new LinkedList<HexRecord>();
+ boolean done = false;
+
+ while (!done) {
+ HexRecord record = new HexRecord(input);
+
+ if (record.type == HexRecord.EOF)
+ done = true;
+ else
+ record_list.add(record);
+ }
+ HexRecord[] records = record_list.toArray(new HexRecord[0]);
+ Arrays.sort(records);
+ if (records.length > 0) {
+ int base = records[0].address;
+ int bound = records[records.length-1].address +
+ records[records.length-1].data.length;
+
+ data = new byte[bound - base];
+ address = base;
+ Arrays.fill(data, (byte) 0xff);
+
+ /* Paint the records into the new array */
+ for (int i = 0; i < records.length; i++) {
+ for (int j = 0; j < records[i].data.length; j++)
+ data[records[i].address - base + j] = records[i].data[j];
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosIdleMonitorUI.java b/altosui/AltosIdleMonitorUI.java
new file mode 100644
index 00000000..46ca3e5d
--- /dev/null
+++ b/altosui/AltosIdleMonitorUI.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosIdleMonitorUI extends AltosFrame implements AltosFlightDisplay, AltosFontListener, AltosIdleMonitorListener {
+ AltosDevice device;
+ JTabbedPane pane;
+ AltosPad pad;
+ AltosInfoTable flightInfo;
+ AltosFlightStatus flightStatus;
+ AltosIdleMonitor thread;
+ int serial;
+ boolean remote;
+
+ 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() {
+ pad.reset();
+ flightInfo.clear();
+ }
+
+ public void set_font() {
+ pad.set_font();
+ flightInfo.set_font();
+ }
+
+ public void font_size_changed(int font_size) {
+ set_font();
+ }
+
+ AltosFlightStatusUpdate status_update;
+
+ public void show(AltosState state, int crc_errors) {
+ status_update.saved_state = state;
+ try {
+ pad.show(state, crc_errors);
+ flightStatus.show(state, crc_errors);
+ flightInfo.show(state, crc_errors);
+ } catch (Exception e) {
+ System.out.print("Show exception" + e);
+ }
+ }
+
+ public void update(final AltosState state) {
+ Runnable r = new Runnable() {
+ public void run() {
+ show(state, 0);
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ Container bag;
+ AltosFreqList frequencies;
+
+ public AltosIdleMonitorUI(JFrame in_owner)
+ throws FileNotFoundException, AltosSerialInUseException, TimeoutException, InterruptedException {
+
+ device = AltosDeviceDialog.show(in_owner, Altos.product_any);
+ remote = false;
+ if (!device.matchProduct(Altos.product_altimeter))
+ remote = true;
+
+ serial = device.getSerial();
+ bag = getContentPane();
+ bag.setLayout(new GridBagLayout());
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
+ if (imgURL != null)
+ setIconImage(new ImageIcon(imgURL).getImage());
+
+ setTitle(String.format("AltOS %s", device.toShortString()));
+
+ /* Stick frequency selector at top of table for telemetry monitoring */
+ if (remote && serial >= 0) {
+ // Frequency menu
+ frequencies = new AltosFreqList(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);
+ }
+ });
+ c.gridx = 0;
+ c.gridy = 0;
+ c.insets = new Insets(3, 3, 3, 3);
+ c.anchor = GridBagConstraints.WEST;
+ bag.add (frequencies, c);
+ }
+
+
+ /* Flight status is always visible */
+ flightStatus = new AltosFlightStatus();
+ c.gridx = 0;
+ c.gridy = 1;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.gridwidth = 2;
+ bag.add(flightStatus, c);
+ c.gridwidth = 1;
+
+ /* 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));
+
+ /* Make the tabbed pane use the rest of the window space */
+ c.gridx = 0;
+ c.gridy = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ c.weighty = 1;
+ c.gridwidth = 2;
+ bag.add(pane, c);
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ AltosUIPreferences.register_font_listener(this);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ disconnect();
+ setVisible(false);
+ dispose();
+ AltosUIPreferences.unregister_font_listener(AltosIdleMonitorUI.this);
+ }
+ });
+
+ pack();
+ setVisible(true);
+
+ thread = new AltosIdleMonitor((AltosIdleMonitorListener) this, (AltosLink) new AltosSerial (device), (boolean) remote);
+
+ status_update = new AltosFlightStatusUpdate(flightStatus);
+
+ new javax.swing.Timer(100, status_update).start();
+
+ thread.start();
+ }
+}
diff --git a/altosui/AltosIgniteUI.java b/altosui/AltosIgniteUI.java
new file mode 100644
index 00000000..78eba8e6
--- /dev/null
+++ b/altosui/AltosIgniteUI.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosIgniteUI
+ extends AltosDialog
+ implements ActionListener
+{
+ AltosDevice device;
+ JFrame owner;
+ JLabel label;
+ JRadioButton apogee;
+ JLabel apogee_status_label;
+ JRadioButton main;
+ JLabel main_status_label;
+ JToggleButton arm;
+ JButton fire;
+ javax.swing.Timer timer;
+ JButton close;
+
+ int apogee_status;
+ int main_status;
+
+ final static int timeout = 1 * 1000;
+
+ int time_remaining;
+ boolean timer_running;
+
+ LinkedBlockingQueue<String> command_queue;
+
+ class IgniteHandler implements Runnable {
+ AltosIgnite ignite;
+ JFrame owner;
+
+ 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 {
+ AltosSerial serial = new AltosSerial(device);
+ serial.set_frame(owner);
+ ignite = new AltosIgnite(serial,
+ !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")) {
+ apogee_status = ignite.status(AltosIgnite.Apogee);
+ main_status = ignite.status(AltosIgnite.Main);
+ reply = "status";
+ } else if (command.equals("main")) {
+ ignite.fire(AltosIgnite.Main);
+ reply = "fired";
+ } else if (command.equals("apogee")) {
+ ignite.fire(AltosIgnite.Apogee);
+ reply = "fired";
+ } else if (command.equals("quit")) {
+ ignite.close();
+ break;
+ } else {
+ throw new ParseException(String.format("invalid command %s", command), 0);
+ }
+ 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) {
+ owner = in_owner;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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;
+ arm.setSelected(false);
+ arm.setEnabled(false);
+ fire.setEnabled(false);
+ timer_running = false;
+ set_arm_text();
+ }
+
+ void cancel () {
+ apogee.setSelected(false);
+ main.setSelected(false);
+ 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;
+
+ boolean visible = false;
+ void set_ignite_status() {
+ getting_status = false;
+ apogee_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(apogee_status)));
+ main_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(main_status)));
+ if (!visible) {
+ visible = true;
+ setVisible(true);
+ }
+ }
+
+ void poll_ignite_status() {
+ 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() {
+ send_command("quit");
+ 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";
+ if (apogee.isSelected() && !main.isSelected())
+ igniter = "apogee";
+ else if (main.isSelected() && !apogee.isSelected())
+ igniter = "main";
+ send_command(igniter);
+ cancel();
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ if (cmd.equals("apogee") || cmd.equals("main")) {
+ stop_timer();
+ }
+
+ if (cmd.equals("apogee") && apogee.isSelected()) {
+ main.setSelected(false);
+ arm.setEnabled(true);
+ }
+ if (cmd.equals("main") && main.isSelected()) {
+ apogee.setSelected(false);
+ arm.setEnabled(true);
+ }
+
+ 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<String>();
+
+ device = AltosDeviceDialog.show(owner, Altos.product_any);
+ if (device != null) {
+ IgniteHandler handler = new IgniteHandler(owner);
+ Thread t = new Thread(handler);
+ t.start();
+ return true;
+ }
+ return false;
+ }
+
+ public AltosIgniteUI(JFrame in_owner) {
+
+ owner = in_owner;
+ apogee_status = AltosIgnite.Unknown;
+ main_status = AltosIgnite.Unknown;
+
+ if (!open())
+ return;
+
+ 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();
+
+ owner = in_owner;
+
+ pane.setLayout(new GridBagLayout());
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 0;
+ c.weighty = 0;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.CENTER;
+ label = new JLabel ("Fire Igniter");
+ pane.add(label, c);
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ apogee = new JRadioButton ("Apogee");
+ pane.add(apogee, c);
+ apogee.addActionListener(this);
+ apogee.setActionCommand("apogee");
+
+ c.gridx = 1;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ apogee_status_label = new JLabel();
+ pane.add(apogee_status_label, c);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ main = new JRadioButton ("Main");
+ pane.add(main, c);
+ main.addActionListener(this);
+ main.setActionCommand("main");
+
+ c.gridx = 1;
+ c.gridy = 2;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.WEST;
+ main_status_label = new JLabel();
+ pane.add(main_status_label, c);
+
+ c.gridx = 0;
+ c.gridy = 3;
+ 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 = 3;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+ fire = new JButton ("Fire");
+ fire.setEnabled(false);
+ pane.add(fire, c);
+ fire.addActionListener(this);
+ fire.setActionCommand("fire");
+
+ c.gridx = 0;
+ c.gridy = 4;
+ 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));
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosInfoTable.java b/altosui/AltosInfoTable.java
new file mode 100644
index 00000000..c1400976
--- /dev/null
+++ b/altosui/AltosInfoTable.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosInfoTable extends JTable {
+ private AltosFlightInfoTableModel model;
+
+ static final int info_columns = 3;
+ static final int info_rows = 17;
+
+ int desired_row_height() {
+ FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font);
+ return (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 18 / 10;
+ }
+
+ int text_width(String t) {
+ FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font);
+
+ return infoValueMetrics.stringWidth(t);
+ }
+
+ void set_layout() {
+ setRowHeight(desired_row_height());
+ for (int i = 0; i < info_columns * 2; i++)
+ {
+ TableColumn column = getColumnModel().getColumn(i);
+
+ if ((i & 1) == 0)
+ column.setPreferredWidth(text_width(" Satellites Visible "));
+ else
+ column.setPreferredWidth(text_width(" 179°59.99999' "));
+ }
+ }
+
+ public AltosInfoTable() {
+ super(new AltosFlightInfoTableModel(info_rows, info_columns));
+ model = (AltosFlightInfoTableModel) getModel();
+ setFont(Altos.table_value_font);
+ setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
+ setShowGrid(true);
+ set_layout();
+ doLayout();
+ }
+
+ public void set_font() {
+ setFont(Altos.table_value_font);
+ set_layout();
+ doLayout();
+ }
+
+ public Dimension getPreferredScrollableViewportSize() {
+ return getPreferredSize();
+ }
+
+ void info_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_add_deg(int col, String name, double v, int pos, int neg) {
+ int c = pos;
+ if (v < 0) {
+ c = neg;
+ v = -v;
+ }
+ double deg = Math.floor(v);
+ double min = (v - deg) * 60;
+
+ info_add_row(col, name, String.format("%3.0f°%08.5f'", deg, min));
+ }
+
+ void info_finish() {
+ model.finish();
+ }
+
+ public void clear() {
+ model.clear();
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ if (state == null)
+ return;
+ info_reset();
+ info_add_row(0, "Altitude", "%6.0f m", state.altitude);
+ info_add_row(0, "Pad altitude", "%6.0f m", state.ground_altitude);
+ info_add_row(0, "Height", "%6.0f m", state.height);
+ info_add_row(0, "Max height", "%6.0f m", state.max_height);
+ info_add_row(0, "Acceleration", "%8.1f m/s²", state.acceleration);
+ info_add_row(0, "Max acceleration", "%8.1f m/s²", state.max_acceleration);
+ info_add_row(0, "Speed", "%8.1f m/s", state.ascent ? state.speed : state.baro_speed);
+ info_add_row(0, "Max Speed", "%8.1f m/s", state.max_speed);
+ info_add_row(0, "Temperature", "%9.2f °C", state.temperature);
+ info_add_row(0, "Battery", "%9.2f V", state.battery);
+ if (state.drogue_sense != AltosRecord.MISSING)
+ info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense);
+ if (state.main_sense != AltosRecord.MISSING)
+ info_add_row(0, "Main", "%9.2f V", state.main_sense);
+ info_add_row(0, "CRC Errors", "%6d", crc_errors);
+
+ if (state.gps == null || !state.gps.connected) {
+ info_add_row(1, "GPS", "not available");
+ } else {
+ if (state.gps_ready)
+ info_add_row(1, "GPS state", "%s", "ready");
+ else
+ info_add_row(1, "GPS state", "wait (%d)",
+ state.gps_waiting);
+ if (state.data.gps.locked)
+ info_add_row(1, "GPS", " locked");
+ else if (state.data.gps.connected)
+ info_add_row(1, "GPS", " unlocked");
+ else
+ info_add_row(1, "GPS", " missing");
+ info_add_row(1, "Satellites", "%6d", state.data.gps.nsat);
+ info_add_deg(1, "Latitude", state.gps.lat, 'N', 'S');
+ info_add_deg(1, "Longitude", state.gps.lon, 'E', 'W');
+ info_add_row(1, "GPS altitude", "%6d", state.gps.alt);
+ info_add_row(1, "GPS height", "%6.0f", state.gps_height);
+
+ /* The SkyTraq GPS doesn't report these values */
+ if (false) {
+ info_add_row(1, "GPS ground speed", "%8.1f m/s %3d°",
+ state.gps.ground_speed,
+ state.gps.course);
+ info_add_row(1, "GPS climb rate", "%8.1f m/s",
+ state.gps.climb_rate);
+ info_add_row(1, "GPS error", "%6d m(h)%3d m(v)",
+ state.gps.h_error, state.gps.v_error);
+ }
+ info_add_row(1, "GPS hdop", "%8.1f", state.gps.hdop);
+
+ if (state.npad > 0) {
+ if (state.from_pad != null) {
+ info_add_row(1, "Distance from pad", "%6d m",
+ (int) (state.from_pad.distance + 0.5));
+ info_add_row(1, "Direction from pad", "%6d°",
+ (int) (state.from_pad.bearing + 0.5));
+ info_add_row(1, "Elevation from pad", "%6d°",
+ (int) (state.elevation + 0.5));
+ info_add_row(1, "Range from pad", "%6d m",
+ (int) (state.range + 0.5));
+ } else {
+ info_add_row(1, "Distance from pad", "unknown");
+ info_add_row(1, "Direction from pad", "unknown");
+ info_add_row(1, "Elevation from pad", "unknown");
+ info_add_row(1, "Range from pad", "unknown");
+ }
+ info_add_deg(1, "Pad latitude", state.pad_lat, 'N', 'S');
+ info_add_deg(1, "Pad longitude", state.pad_lon, 'E', 'W');
+ info_add_row(1, "Pad GPS alt", "%6.0f m", state.pad_alt);
+ }
+ info_add_row(1, "GPS date", "%04d-%02d-%02d",
+ state.gps.year,
+ state.gps.month,
+ state.gps.day);
+ info_add_row(1, "GPS time", " %02d:%02d:%02d",
+ state.gps.hour,
+ state.gps.minute,
+ state.gps.second);
+ int nsat_vis = 0;
+ int c;
+
+ if (state.gps.cc_gps_sat == null)
+ info_add_row(2, "Satellites Visible", "%4d", 0);
+ else {
+ info_add_row(2, "Satellites Visible", "%4d", state.gps.cc_gps_sat.length);
+ for (c = 0; c < state.gps.cc_gps_sat.length; c++) {
+ info_add_row(2, "Satellite id,C/N0",
+ "%4d, %4d",
+ state.gps.cc_gps_sat[c].svid,
+ state.gps.cc_gps_sat[c].c_n0);
+ }
+ }
+ }
+ info_finish();
+ }
+}
diff --git a/altosui/AltosKML.java b/altosui/AltosKML.java
new file mode 100644
index 00000000..ff0734b8
--- /dev/null
+++ b/altosui/AltosKML.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosKML implements AltosWriter {
+
+ File name;
+ PrintStream out;
+ int state = -1;
+ AltosRecord prev = null;
+
+ static final String[] kml_state_colors = {
+ "FF000000",
+ "FF000000",
+ "FF000000",
+ "FF0000FF",
+ "FF4080FF",
+ "FF00FFFF",
+ "FFFF0000",
+ "FF00FF00",
+ "FF000000",
+ "FFFFFFFF"
+ };
+
+ static final String kml_header_start =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n" +
+ "<Document>\n" +
+ " <name>AO Flight#%d S/N: %03d</name>\n" +
+ " <description>\n";
+ static final String kml_header_end =
+ " </description>\n" +
+ " <open>0</open>\n";
+
+ static final String kml_style_start =
+ " <Style id=\"ao-flightstate-%s\">\n" +
+ " <LineStyle><color>%s</color><width>4</width></LineStyle>\n" +
+ " <BalloonStyle>\n" +
+ " <text>\n";
+
+ static final String kml_style_end =
+ " </text>\n" +
+ " </BalloonStyle>\n" +
+ " </Style>\n";
+
+ static final String kml_placemark_start =
+ " <Placemark>\n" +
+ " <name>%s</name>\n" +
+ " <styleUrl>#ao-flightstate-%s</styleUrl>\n" +
+ " <LineString>\n" +
+ " <tessellate>1</tessellate>\n" +
+ " <altitudeMode>absolute</altitudeMode>\n" +
+ " <coordinates>\n";
+
+ static final String kml_coord_fmt =
+ " %.7f,%.7f,%.7f <!-- alt %12.7f time %12.7f sats %d -->\n";
+
+ static final String kml_placemark_end =
+ " </coordinates>\n" +
+ " </LineString>\n" +
+ " </Placemark>\n";
+
+ static final String kml_footer =
+ "</Document>\n" +
+ "</kml>\n";
+
+ void start (AltosRecord record) {
+ out.printf(kml_header_start, record.flight, record.serial);
+ out.printf("Date: %04d-%02d-%02d\n",
+ record.gps.year, record.gps.month, record.gps.day);
+ out.printf("Time: %2d:%02d:%02d\n",
+ record.gps.hour, record.gps.minute, record.gps.second);
+ out.printf("%s", kml_header_end);
+ }
+
+ boolean started = false;
+
+ void state_start(AltosRecord record) {
+ String state_name = Altos.state_name(record.state);
+ out.printf(kml_style_start, state_name, kml_state_colors[record.state]);
+ out.printf("\tState: %s\n", state_name);
+ out.printf("%s", kml_style_end);
+ out.printf(kml_placemark_start, state_name, state_name);
+ }
+
+ void state_end(AltosRecord record) {
+ out.printf("%s", kml_placemark_end);
+ }
+
+ void coord(AltosRecord record) {
+ AltosGPS gps = record.gps;
+ out.printf(kml_coord_fmt,
+ gps.lon, gps.lat,
+ record.filtered_altitude(), (double) gps.alt,
+ record.time, gps.nsat);
+ }
+
+ void end() {
+ out.printf("%s", kml_footer);
+ }
+
+ public void close() {
+ if (prev != null) {
+ state_end(prev);
+ end();
+ prev = null;
+ }
+ }
+
+ public void write(AltosRecord record) {
+ AltosGPS gps = record.gps;
+
+ if (gps == null)
+ return;
+ if ((record.seen & (AltosRecord.seen_flight)) == 0)
+ return;
+ if ((record.seen & (AltosRecord.seen_gps_lat)) == 0)
+ return;
+ if ((record.seen & (AltosRecord.seen_gps_lon)) == 0)
+ return;
+ if (!started) {
+ start(record);
+ started = true;
+ }
+ if (prev != null &&
+ prev.gps.second == record.gps.second &&
+ prev.gps.minute == record.gps.minute &&
+ prev.gps.hour == record.gps.hour)
+ return;
+ if (record.state != state) {
+ state = record.state;
+ if (prev != null) {
+ coord(record);
+ state_end(prev);
+ }
+ state_start(record);
+ }
+ coord(record);
+ prev = record;
+ }
+
+ public void write(AltosRecordIterable iterable) {
+ for (AltosRecord record : iterable)
+ write(record);
+ }
+
+ public AltosKML(File in_name) throws FileNotFoundException {
+ name = in_name;
+ out = new PrintStream(name);
+ }
+
+ public AltosKML(String in_string) throws FileNotFoundException {
+ this(new File(in_string));
+ }
+}
diff --git a/altosui/AltosLanded.java b/altosui/AltosLanded.java
new file mode 100644
index 00000000..a47e1cbd
--- /dev/null
+++ b/altosui/AltosLanded.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosLanded extends JComponent implements AltosFlightDisplay, ActionListener {
+ GridBagLayout layout;
+
+ public class LandedValue {
+ JLabel label;
+ JTextField value;
+ void show(AltosState state, int crc_errors) {}
+
+ void reset() {
+ value.setText("");
+ }
+
+ void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ }
+
+ public void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ }
+
+ void show(String format, double v) {
+ show();
+ value.setText(String.format(format, v));
+ }
+
+
+ public LandedValue (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 0; c.gridy = y;
+ c.insets = new Insets(10, 10, 10, 10);
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 0;
+ c.fill = GridBagConstraints.VERTICAL;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 1; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.weightx = 1;
+ c.fill = GridBagConstraints.BOTH;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ String pos(double p, String pos, String neg) {
+ String h = pos;
+ if (p < 0) {
+ h = neg;
+ p = -p;
+ }
+ int deg = (int) Math.floor(p);
+ double min = (p - Math.floor(p)) * 60.0;
+ return String.format("%s %4d° %9.6f", h, deg, min);
+ }
+
+ class Lat extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.gps != null && state.gps.connected)
+ value.setText(pos(state.gps.lat,"N", "S"));
+ else
+ value.setText("???");
+ }
+ public Lat (GridBagLayout layout, int y) {
+ super (layout, y, "Latitude");
+ }
+ }
+
+ Lat lat;
+
+ class Lon extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.gps != null && state.gps.connected)
+ value.setText(pos(state.gps.lon,"E", "W"));
+ else
+ value.setText("???");
+ }
+ public Lon (GridBagLayout layout, int y) {
+ super (layout, y, "Longitude");
+ }
+ }
+
+ Lon lon;
+
+ class Bearing extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.from_pad != null)
+ show("%3.0f°", state.from_pad.bearing);
+ else
+ value.setText("???");
+ }
+ public Bearing (GridBagLayout layout, int y) {
+ super (layout, y, "Bearing");
+ }
+ }
+
+ Bearing bearing;
+
+ class Distance extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.from_pad != null)
+ show("%6.0f m", state.from_pad.distance);
+ else
+ value.setText("???");
+ }
+ public Distance (GridBagLayout layout, int y) {
+ super (layout, y, "Distance");
+ }
+ }
+
+ Distance distance;
+
+ class Height extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show("%6.0f m", state.max_height);
+ }
+ public Height (GridBagLayout layout, int y) {
+ super (layout, y, "Maximum Height");
+ }
+ }
+
+ Height height;
+
+ class Speed extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show("%6.0f m/s", state.max_speed);
+ }
+ public Speed (GridBagLayout layout, int y) {
+ super (layout, y, "Maximum Speed");
+ }
+ }
+
+ Speed speed;
+
+ class Accel extends LandedValue {
+ void show (AltosState state, int crc_errors) {
+ show("%6.0f m/s²", state.max_acceleration);
+ }
+ public Accel (GridBagLayout layout, int y) {
+ super (layout, y, "Maximum Acceleration");
+ }
+ }
+
+ Accel accel;
+
+ public void reset() {
+ lat.reset();
+ lon.reset();
+ bearing.reset();
+ distance.reset();
+ height.reset();
+ speed.reset();
+ accel.reset();
+ }
+
+ public void set_font() {
+ lat.set_font();
+ lon.set_font();
+ bearing.set_font();
+ distance.set_font();
+ height.set_font();
+ speed.set_font();
+ accel.set_font();
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ if (state.gps != null && state.gps.connected) {
+ bearing.show(state, crc_errors);
+ distance.show(state, crc_errors);
+ lat.show(state, crc_errors);
+ lon.show(state, crc_errors);
+ } else {
+ bearing.hide();
+ distance.hide();
+ lat.hide();
+ lon.hide();
+ }
+ height.show(state, crc_errors);
+ speed.show(state, crc_errors);
+ accel.show(state, crc_errors);
+ if (reader.backing_file() != null)
+ graph.setEnabled(true);
+ }
+
+ 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 {
+ AltosRecordIterable records = null;
+ if (filename.endsWith("eeprom")) {
+ FileInputStream in = new FileInputStream(file);
+ records = new AltosEepromIterable(in);
+ } else if (filename.endsWith("telem")) {
+ FileInputStream in = new FileInputStream(file);
+ records = new AltosTelemetryIterable(in);
+ } else {
+ throw new FileNotFoundException(filename);
+ }
+ try {
+ new AltosGraphUI(records, filename);
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ } catch (FileNotFoundException fe) {
+ JOptionPane.showMessageDialog(null,
+ fe.getMessage(),
+ "Cannot open file",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ }
+
+ public AltosLanded(AltosFlightReader in_reader) {
+ layout = new GridBagLayout();
+
+ reader = in_reader;
+
+ setLayout(layout);
+
+ /* Elements in descent display */
+ bearing = new Bearing(layout, 0);
+ distance = new Distance(layout, 1);
+ lat = new Lat(layout, 2);
+ lon = new Lon(layout, 3);
+ height = new Height(layout, 4);
+ speed = new Speed(layout, 5);
+ accel = new Accel(layout, 6);
+
+ graph = new JButton ("Graph Flight");
+ graph.setActionCommand("graph");
+ graph.addActionListener(this);
+ graph.setEnabled(false);
+
+ GridBagConstraints c = new GridBagConstraints();
+
+ c.gridx = 0; 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);
+ }
+}
diff --git a/altosui/AltosLaunch.java b/altosui/AltosLaunch.java
new file mode 100644
index 00000000..0e493b91
--- /dev/null
+++ b/altosui/AltosLaunch.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.io.*;
+import java.util.concurrent.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import org.altusmetrum.AltosLib.*;
+
+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 = 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);
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosLaunchUI.java b/altosui/AltosLaunchUI.java
new file mode 100644
index 00000000..44481544
--- /dev/null
+++ b/altosui/AltosLaunchUI.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+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 AltosDialog
+ 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<String> 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<String>();
+
+ device = AltosDeviceDialog.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);
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosLed.java b/altosui/AltosLed.java
new file mode 100644
index 00000000..1358cd48
--- /dev/null
+++ b/altosui/AltosLed.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosLed extends JLabel {
+ ImageIcon on, off;
+
+ ImageIcon create_icon(String path) {
+ java.net.URL imgURL = AltosUI.class.getResource(path);
+ if (imgURL != null)
+ return new ImageIcon(imgURL);
+ System.err.printf("Cannot find icon \"%s\"\n", path);
+ return null;
+ }
+
+ public void set(boolean set) {
+ if (set)
+ setIcon(on);
+ else
+ setIcon(off);
+ }
+
+ public AltosLed(String on_path, String off_path) {
+ on = create_icon(on_path);
+ off = create_icon(off_path);
+ setIcon(off);
+ }
+}
diff --git a/altosui/AltosLights.java b/altosui/AltosLights.java
new file mode 100644
index 00000000..8bd9e7de
--- /dev/null
+++ b/altosui/AltosLights.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosLights extends JComponent {
+
+ GridBagLayout gridbag;
+
+ AltosLed red, green;
+
+ ImageIcon create_icon(String path, String description) {
+ java.net.URL imgURL = AltosUI.class.getResource(path);
+ if (imgURL != null)
+ return new ImageIcon(imgURL, description);
+ System.err.printf("Cannot find icon \"%s\"\n", path);
+ return null;
+ }
+
+ public void set (boolean on) {
+ if (on) {
+ red.set(false);
+ green.set(true);
+ } else {
+ red.set(true);
+ green.set(false);
+ }
+ }
+
+ public AltosLights() {
+ GridBagConstraints c;
+ gridbag = new GridBagLayout();
+ setLayout(gridbag);
+
+ c = new GridBagConstraints();
+ red = new AltosLed("/redled.png", "/grayled.png");
+ c.gridx = 0; c.gridy = 0;
+ c.insets = new Insets (0, 5, 0, 5);
+ gridbag.setConstraints(red, c);
+ add(red);
+ red.set(true);
+ green = new AltosLed("/greenled.png", "/grayled.png");
+ c.gridx = 1; c.gridy = 0;
+ gridbag.setConstraints(green, c);
+ add(green);
+ green.set(false);
+ }
+}
diff --git a/altosui/AltosPad.java b/altosui/AltosPad.java
new file mode 100644
index 00000000..0a3f3d65
--- /dev/null
+++ b/altosui/AltosPad.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosPad extends JComponent implements AltosFlightDisplay {
+ GridBagLayout layout;
+
+ public class LaunchStatus {
+ JLabel label;
+ JTextField value;
+ AltosLights lights;
+
+ void show(AltosState state, int crc_errors) {}
+ void reset() {
+ value.setText("");
+ lights.set(false);
+ }
+
+ public void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ lights.setVisible(true);
+ }
+
+ public void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ lights.setVisible(false);
+ }
+
+ public void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ public LaunchStatus (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.weighty = 1;
+
+ lights = new AltosLights();
+ c.gridx = 0; c.gridy = y;
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(lights, c);
+ add(lights);
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 2; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+
+ }
+ }
+
+ public class LaunchValue {
+ JLabel label;
+ JTextField value;
+ void show(AltosState state, int crc_errors) {}
+
+ void show() {
+ label.setVisible(true);
+ value.setVisible(true);
+ }
+
+ void hide() {
+ label.setVisible(false);
+ value.setVisible(false);
+ }
+
+ public void set_font() {
+ label.setFont(Altos.label_font);
+ value.setFont(Altos.value_font);
+ }
+
+ void reset() {
+ value.setText("");
+ }
+ public LaunchValue (GridBagLayout layout, int y, String text) {
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad);
+ c.weighty = 1;
+
+ label = new JLabel(text);
+ label.setFont(Altos.label_font);
+ label.setHorizontalAlignment(SwingConstants.LEFT);
+ c.gridx = 1; c.gridy = y;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.VERTICAL;
+ c.weightx = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField(Altos.text_width);
+ value.setFont(Altos.value_font);
+ value.setHorizontalAlignment(SwingConstants.RIGHT);
+ c.gridx = 2; c.gridy = y;
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ class Battery extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ value.setText(String.format("%4.2f V", state.battery));
+ lights.set(state.battery > 3.7);
+ }
+ public Battery (GridBagLayout layout, int y) {
+ super(layout, y, "Battery Voltage");
+ }
+ }
+
+ Battery battery;
+
+ class Apogee extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.drogue_sense));
+ lights.set(state.drogue_sense > 3.2);
+ }
+ public Apogee (GridBagLayout layout, int y) {
+ super(layout, y, "Apogee Igniter Voltage");
+ }
+ }
+
+ Apogee apogee;
+
+ class Main extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4.2f V", state.main_sense));
+ lights.set(state.main_sense > 3.2);
+ }
+ public Main (GridBagLayout layout, int y) {
+ super(layout, y, "Main Igniter Voltage");
+ }
+ }
+
+ Main main;
+
+ class LoggingReady extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.data.flight != 0) {
+ if (state.data.state <= Altos.ao_flight_pad)
+ value.setText("Ready to record");
+ else if (state.data.state < Altos.ao_flight_landed)
+ value.setText("Recording data");
+ else
+ value.setText("Recorded data");
+ }
+ else
+ value.setText("Storage full");
+ lights.set(state.data.flight != 0);
+ }
+ public LoggingReady (GridBagLayout layout, int y) {
+ super(layout, y, "On-board Data Logging");
+ }
+ }
+
+ LoggingReady logging_ready;
+
+ class GPSLocked extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(String.format("%4d sats", state.gps.nsat));
+ lights.set(state.gps.locked && state.gps.nsat >= 4);
+ }
+ public GPSLocked (GridBagLayout layout, int y) {
+ super (layout, y, "GPS Locked");
+ }
+ }
+
+ GPSLocked gps_locked;
+
+ class GPSReady extends LaunchStatus {
+ void show (AltosState state, int crc_errors) {
+ show();
+ if (state.gps_ready)
+ value.setText("Ready");
+ else
+ value.setText(String.format("Waiting %d", state.gps_waiting));
+ lights.set(state.gps_ready);
+ }
+ public GPSReady (GridBagLayout layout, int y) {
+ super (layout, y, "GPS Ready");
+ }
+ }
+
+ GPSReady gps_ready;
+
+ String pos(double p, String pos, String neg) {
+ String h = pos;
+ if (p < 0) {
+ h = neg;
+ p = -p;
+ }
+ int deg = (int) Math.floor(p);
+ double min = (p - Math.floor(p)) * 60.0;
+ return String.format("%s %4d° %9.6f", h, deg, min);
+ }
+
+ class PadLat extends LaunchValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(pos(state.pad_lat,"N", "S"));
+ }
+ public PadLat (GridBagLayout layout, int y) {
+ super (layout, y, "Pad Latitude");
+ }
+ }
+
+ PadLat pad_lat;
+
+ class PadLon extends LaunchValue {
+ void show (AltosState state, int crc_errors) {
+ show();
+ value.setText(pos(state.pad_lon,"E", "W"));
+ }
+ public PadLon (GridBagLayout layout, int y) {
+ super (layout, y, "Pad Longitude");
+ }
+ }
+
+ PadLon pad_lon;
+
+ class PadAlt extends LaunchValue {
+ void show (AltosState state, int crc_errors) {
+ value.setText(String.format("%4.0f m", state.pad_alt));
+ }
+ public PadAlt (GridBagLayout layout, int y) {
+ super (layout, y, "Pad Altitude");
+ }
+ }
+
+ PadAlt pad_alt;
+
+ public void reset() {
+ battery.reset();
+ apogee.reset();
+ main.reset();
+ logging_ready.reset();
+ gps_locked.reset();
+ gps_ready.reset();
+ pad_lat.reset();
+ pad_lon.reset();
+ pad_alt.reset();
+ }
+
+ public void set_font() {
+ battery.set_font();
+ apogee.set_font();
+ main.set_font();
+ logging_ready.set_font();
+ gps_locked.set_font();
+ gps_ready.set_font();
+ pad_lat.set_font();
+ pad_lon.set_font();
+ pad_alt.set_font();
+ }
+
+ public void show(AltosState state, int crc_errors) {
+ battery.show(state, crc_errors);
+ if (state.drogue_sense == AltosRecord.MISSING)
+ apogee.hide();
+ else
+ apogee.show(state, crc_errors);
+ if (state.main_sense == AltosRecord.MISSING)
+ main.hide();
+ else
+ main.show(state, crc_errors);
+ logging_ready.show(state, crc_errors);
+ pad_alt.show(state, crc_errors);
+ if (state.gps != null && state.gps.connected) {
+ gps_locked.show(state, crc_errors);
+ gps_ready.show(state, crc_errors);
+ pad_lat.show(state, crc_errors);
+ pad_lon.show(state, crc_errors);
+ } else {
+ gps_locked.hide();
+ gps_ready.hide();
+ pad_lat.hide();
+ pad_lon.hide();
+ }
+ }
+
+ public AltosPad() {
+ layout = new GridBagLayout();
+
+ setLayout(layout);
+
+ /* Elements in pad display:
+ *
+ * Battery voltage
+ * Igniter continuity
+ * GPS lock status
+ * GPS ready status
+ * GPS location
+ * Pad altitude
+ * RSSI
+ */
+ battery = new Battery(layout, 0);
+ apogee = new Apogee(layout, 1);
+ main = new Main(layout, 2);
+ logging_ready = new LoggingReady(layout, 3);
+ gps_locked = new GPSLocked(layout, 4);
+ gps_ready = new GPSReady(layout, 5);
+ pad_lat = new PadLat(layout, 6);
+ pad_lon = new PadLon(layout, 7);
+ pad_alt = new PadAlt(layout, 8);
+ }
+}
diff --git a/altosui/AltosRomconfig.java b/altosui/AltosRomconfig.java
new file mode 100644
index 00000000..0a283e51
--- /dev/null
+++ b/altosui/AltosRomconfig.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.io.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosRomconfig {
+ public boolean valid;
+ public int version;
+ public int check;
+ public int serial_number;
+ public int radio_calibration;
+
+ static int get_int(byte[] bytes, int start, int len) {
+ int v = 0;
+ int o = 0;
+ while (len > 0) {
+ v = v | ((((int) bytes[start]) & 0xff) << o);
+ start++;
+ len--;
+ o += 8;
+ }
+ return v;
+ }
+
+ static void put_int(int value, byte[] bytes, int start, int len) {
+ while (len > 0) {
+ bytes[start] = (byte) (value & 0xff);
+ start++;
+ len--;
+ value >>= 8;
+ }
+ }
+
+ static void put_string(String value, byte[] bytes, int start) {
+ for (int i = 0; i < value.length(); i++)
+ bytes[start + i] = (byte) value.charAt(i);
+ }
+
+ static final int AO_USB_DESC_STRING = 3;
+
+ static void put_usb_serial(int value, byte[] bytes, int start) {
+ int offset = start + 0xa;
+ int string_num = 0;
+
+ while (offset < bytes.length && bytes[offset] != 0) {
+ if (bytes[offset + 1] == AO_USB_DESC_STRING) {
+ ++string_num;
+ if (string_num == 4)
+ break;
+ }
+ offset += ((int) bytes[offset]) & 0xff;
+ }
+ if (offset >= bytes.length || bytes[offset] == 0)
+ return;
+ int len = ((((int) bytes[offset]) & 0xff) - 2) / 2;
+ String fmt = String.format("%%0%dd", len);
+
+ String s = String.format(fmt, value);
+ if (s.length() != len) {
+ System.out.printf("weird usb length issue %s isn't %d\n",
+ s, len);
+ return;
+ }
+ for (int i = 0; i < len; i++) {
+ bytes[offset + 2 + i*2] = (byte) s.charAt(i);
+ bytes[offset + 2 + i*2+1] = 0;
+ }
+ }
+
+ public AltosRomconfig(byte[] bytes, int offset) {
+ version = get_int(bytes, offset + 0, 2);
+ check = get_int(bytes, offset + 2, 2);
+ if (check == (~version & 0xffff)) {
+ switch (version) {
+ case 2:
+ case 1:
+ serial_number = get_int(bytes, offset + 4, 2);
+ radio_calibration = get_int(bytes, offset + 6, 4);
+ valid = true;
+ break;
+ }
+ }
+ }
+
+ public AltosRomconfig(AltosHexfile hexfile) {
+ this(hexfile.data, 0xa0 - hexfile.address);
+ }
+
+ public void write(byte[] bytes, int offset) throws IOException {
+ if (!valid)
+ throw new IOException("rom configuration invalid");
+
+ if (offset < 0 || bytes.length < offset + 10)
+ throw new IOException("image cannot contain rom config");
+
+ AltosRomconfig existing = new AltosRomconfig(bytes, offset);
+ if (!existing.valid)
+ throw new IOException("image does not contain existing rom config");
+
+ switch (existing.version) {
+ case 2:
+ put_usb_serial(serial_number, bytes, offset);
+ case 1:
+ put_int(serial_number, bytes, offset + 4, 2);
+ put_int(radio_calibration, bytes, offset + 6, 4);
+ break;
+ }
+ }
+
+ public void write (AltosHexfile hexfile) throws IOException {
+ write(hexfile.data, 0xa0 - hexfile.address);
+ AltosRomconfig check = new AltosRomconfig(hexfile);
+ if (!check.valid())
+ throw new IOException("writing new rom config failed\n");
+ }
+
+ public AltosRomconfig(int in_serial_number, int in_radio_calibration) {
+ valid = true;
+ version = 1;
+ check = (~version & 0xffff);
+ serial_number = in_serial_number;
+ radio_calibration = in_radio_calibration;
+ }
+
+ public boolean valid() {
+ return valid && serial_number != 0;
+ }
+
+ public AltosRomconfig() {
+ valid = false;
+ }
+}
diff --git a/altosui/AltosRomconfigUI.java b/altosui/AltosRomconfigUI.java
new file mode 100644
index 00000000..306b8623
--- /dev/null
+++ b/altosui/AltosRomconfigUI.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosRomconfigUI
+ extends AltosDialog
+ implements ActionListener
+{
+ Container pane;
+ Box box;
+ JLabel serial_label;
+ JLabel radio_calibration_label;
+
+ JFrame owner;
+ JTextField serial_value;
+ JTextField radio_calibration_value;
+
+ JButton ok;
+ JButton cancel;
+
+ /* Build the UI using a grid bag */
+ public AltosRomconfigUI(JFrame in_owner) {
+ super (in_owner, "Configure TeleMetrum Rom Values", true);
+
+ 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());
+
+ /* Serial */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 0;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ serial_label = new JLabel("Serial:");
+ pane.add(serial_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 3; c.gridy = 0;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ serial_value = new JTextField("0");
+ pane.add(serial_value, c);
+
+ /* Radio calibration value */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 1;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ radio_calibration_label = new JLabel("Radio Calibration:");
+ pane.add(radio_calibration_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 3; c.gridy = 1;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ radio_calibration_value = new JTextField("1186611");
+ pane.add(radio_calibration_value, c);
+
+ /* Buttons */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = 2;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = il;
+ ok = new JButton("OK");
+ pane.add(ok, c);
+ ok.addActionListener(this);
+ ok.setActionCommand("ok");
+
+ c = new GridBagConstraints();
+ c.gridx = 3; c.gridy = 2;
+ c.gridwidth = 3;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = il;
+ cancel = new JButton("Cancel");
+ pane.add(cancel, c);
+ cancel.addActionListener(this);
+ cancel.setActionCommand("cancel");
+
+ pack();
+ setLocationRelativeTo(owner);
+ }
+
+ public AltosRomconfigUI(JFrame frame, AltosRomconfig config) {
+ this(frame);
+ set(config);
+ }
+
+ boolean selected;
+
+ /* Listen for events from our buttons */
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if (cmd.equals("ok")) {
+ AltosRomconfig romconfig = romconfig();
+ if (romconfig == null || !romconfig.valid()) {
+ JOptionPane.showMessageDialog(this,
+ "Invalid serial number or radio calibration value",
+ "Invalid rom configuration",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ selected = true;
+ }
+ setVisible(false);
+ }
+
+ int serial() {
+ return Integer.parseInt(serial_value.getText());
+ }
+
+ void set_serial(int serial) {
+ serial_value.setText(String.format("%d", serial));
+ }
+
+ int radio_calibration() {
+ return Integer.parseInt(radio_calibration_value.getText());
+ }
+
+ void set_radio_calibration(int calibration) {
+ radio_calibration_value.setText(String.format("%d", calibration));
+ }
+
+ public void set(AltosRomconfig config) {
+ if (config != null && config.valid()) {
+ set_serial(config.serial_number);
+ set_radio_calibration(config.radio_calibration);
+ }
+ }
+
+ AltosRomconfig romconfig() {
+ try {
+ return new AltosRomconfig(serial(), radio_calibration());
+ } catch (NumberFormatException ne) {
+ return null;
+ }
+ }
+
+ public AltosRomconfig showDialog() {
+ setVisible(true);
+ if (selected)
+ return romconfig();
+ return null;
+ }
+
+ public static AltosRomconfig show(JFrame frame, AltosRomconfig config) {
+ AltosRomconfigUI ui = new AltosRomconfigUI(frame, config);
+ return ui.showDialog();
+ }
+}
diff --git a/altosui/AltosScanUI.java b/altosui/AltosScanUI.java
new file mode 100644
index 00000000..9da1290f
--- /dev/null
+++ b/altosui/AltosScanUI.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+class AltosScanResult {
+ String callsign;
+ int serial;
+ int flight;
+ AltosFrequency frequency;
+ int telemetry;
+
+ boolean interrupted = false;
+
+ public String toString() {
+ return String.format("%-9.9s serial %-4d flight %-4d (%s %s)",
+ callsign, serial, flight, frequency.toShortString(), Altos.telemetry_name(telemetry));
+ }
+
+ public String toShortString() {
+ return String.format("%s %d %d %7.3f %d",
+ callsign, serial, flight, frequency, telemetry);
+ }
+
+ public AltosScanResult(String in_callsign, int in_serial,
+ int in_flight, AltosFrequency in_frequency, int in_telemetry) {
+ callsign = in_callsign;
+ serial = in_serial;
+ flight = in_flight;
+ frequency = in_frequency;
+ telemetry = in_telemetry;
+ }
+
+ public boolean equals(AltosScanResult other) {
+ return (serial == other.serial &&
+ frequency.frequency == other.frequency.frequency &&
+ telemetry == other.telemetry);
+ }
+
+ public boolean up_to_date(AltosScanResult other) {
+ if (flight == 0 && other.flight != 0) {
+ flight = other.flight;
+ return false;
+ }
+ if (callsign.equals("N0CALL") && !other.callsign.equals("N0CALL")) {
+ callsign = other.callsign;
+ return false;
+ }
+ return true;
+ }
+}
+
+class AltosScanResults extends LinkedList<AltosScanResult> implements ListModel {
+
+ LinkedList<ListDataListener> listeners = new LinkedList<ListDataListener>();
+
+ void changed(ListDataEvent de) {
+ for (ListDataListener l : listeners)
+ l.contentsChanged(de);
+ }
+
+ public boolean add(AltosScanResult r) {
+ int i = 0;
+ for (AltosScanResult old : this) {
+ if (old.equals(r)) {
+ if (!old.up_to_date(r))
+ changed (new ListDataEvent(this,
+ ListDataEvent.CONTENTS_CHANGED,
+ i, i));
+ return true;
+ }
+ i++;
+ }
+
+ super.add(r);
+ changed(new ListDataEvent(this,
+ ListDataEvent.INTERVAL_ADDED,
+ this.size() - 2, this.size() - 1));
+ return true;
+ }
+
+ public void addListDataListener(ListDataListener l) {
+ listeners.add(l);
+ }
+
+ public void removeListDataListener(ListDataListener l) {
+ listeners.remove(l);
+ }
+
+ public AltosScanResult getElementAt(int i) {
+ return this.get(i);
+ }
+
+ public int getSize() {
+ return this.size();
+ }
+}
+
+public class AltosScanUI
+ extends AltosDialog
+ implements ActionListener
+{
+ AltosUI owner;
+ AltosDevice device;
+ AltosConfigData config_data;
+ AltosTelemetryReader reader;
+ private JList list;
+ private JLabel scanning_label;
+ private JLabel frequency_label;
+ private JLabel telemetry_label;
+ private JButton cancel_button;
+ private JButton monitor_button;
+ private JCheckBox[] telemetry_boxes;
+ javax.swing.Timer timer;
+ AltosScanResults results = new AltosScanResults();
+
+ int telemetry;
+
+ final static int timeout = 1200;
+ TelemetryHandler handler;
+ Thread thread;
+ AltosFrequency[] frequencies;
+ int frequency_index;
+
+ void scan_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();
+ }
+
+ class TelemetryHandler implements Runnable {
+
+ public void run() {
+
+ boolean interrupted = false;
+
+ try {
+ for (;;) {
+ try {
+ AltosRecord record = reader.read();
+ if (record == null)
+ continue;
+ if ((record.seen & AltosRecord.seen_flight) != 0) {
+ final AltosScanResult result = new AltosScanResult(record.callsign,
+ record.serial,
+ record.flight,
+ frequencies[frequency_index],
+ telemetry);
+ Runnable r = new Runnable() {
+ public void run() {
+ results.add(result);
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+ } catch (ParseException pp) {
+ } catch (AltosCRCException ce) {
+ }
+ }
+ } catch (InterruptedException ee) {
+ interrupted = true;
+ } catch (IOException ie) {
+ } finally {
+ reader.close(interrupted);
+ }
+ }
+ }
+
+ void set_label() {
+ frequency_label.setText(String.format("Frequency: %s", frequencies[frequency_index].toString()));
+ telemetry_label.setText(String.format("Telemetry: %s", Altos.telemetry_name(telemetry)));
+ }
+
+ void set_telemetry() {
+ reader.set_telemetry(telemetry);
+ }
+
+ void set_frequency() throws InterruptedException, TimeoutException {
+ reader.set_frequency(frequencies[frequency_index].frequency);
+ reader.reset();
+ }
+
+ void next() throws InterruptedException, TimeoutException {
+ reader.set_monitor(false);
+ Thread.sleep(100);
+ ++frequency_index;
+ if (frequency_index >= frequencies.length ||
+ !telemetry_boxes[telemetry - Altos.ao_telemetry_min].isSelected())
+ {
+ frequency_index = 0;
+ do {
+ ++telemetry;
+ if (telemetry > Altos.ao_telemetry_max)
+ telemetry = Altos.ao_telemetry_min;
+ } while (!telemetry_boxes[telemetry - Altos.ao_telemetry_min].isSelected());
+ set_telemetry();
+ }
+ set_frequency();
+ set_label();
+ reader.set_monitor(true);
+ }
+
+
+ void close() {
+ if (thread != null && thread.isAlive()) {
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException ie) {}
+ }
+ thread = null;
+ if (timer != null)
+ timer.stop();
+ setVisible(false);
+ dispose();
+ }
+
+ void tick_timer() throws InterruptedException, TimeoutException {
+ next();
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ try {
+ if (cmd.equals("cancel"))
+ close();
+
+ if (cmd.equals("tick"))
+ tick_timer();
+
+ if (cmd.equals("telemetry")) {
+ int k;
+ int scanning_telemetry = 0;
+ for (k = Altos.ao_telemetry_min; k <= Altos.ao_telemetry_max; k++) {
+ int j = k - Altos.ao_telemetry_min;
+ if (telemetry_boxes[j].isSelected())
+ scanning_telemetry |= (1 << k);
+ }
+ if (scanning_telemetry == 0) {
+ scanning_telemetry |= (1 << Altos.ao_telemetry_standard);
+ telemetry_boxes[Altos.ao_telemetry_standard - Altos.ao_telemetry_min].setSelected(true);
+ }
+ AltosUIPreferences.set_scanning_telemetry(scanning_telemetry);
+ }
+
+ if (cmd.equals("monitor")) {
+ close();
+ AltosScanResult r = (AltosScanResult) (list.getSelectedValue());
+ if (r != null) {
+ if (device != null) {
+ if (reader != null) {
+ reader.set_telemetry(r.telemetry);
+ reader.set_frequency(r.frequency.frequency);
+ reader.save_frequency();
+ owner.telemetry_window(device);
+ }
+ }
+ }
+ }
+ } catch (TimeoutException te) {
+ close();
+ } catch (InterruptedException ie) {
+ close();
+ }
+ }
+
+ /* A window listener to catch closing events and tell the config code */
+ class ConfigListener extends WindowAdapter {
+ AltosScanUI ui;
+
+ public ConfigListener(AltosScanUI this_ui) {
+ ui = this_ui;
+ }
+
+ public void windowClosing(WindowEvent e) {
+ ui.actionPerformed(new ActionEvent(e.getSource(),
+ ActionEvent.ACTION_PERFORMED,
+ "close"));
+ }
+ }
+
+ private boolean open() {
+ device = AltosDeviceDialog.show(owner, Altos.product_basestation);
+ if (device == null)
+ return false;
+ try {
+ reader = new AltosTelemetryReader(new AltosSerial(device));
+ set_frequency();
+ set_telemetry();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ }
+ reader.flush();
+ handler = new TelemetryHandler();
+ thread = new Thread(handler);
+ thread.start();
+ return true;
+ } 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);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(owner,
+ device.toShortString(),
+ "Unkonwn I/O error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (TimeoutException te) {
+ JOptionPane.showMessageDialog(owner,
+ device.toShortString(),
+ "Timeout error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (InterruptedException ie) {
+ JOptionPane.showMessageDialog(owner,
+ device.toShortString(),
+ "Interrupted exception",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ if (reader != null)
+ reader.close(false);
+ return false;
+ }
+
+ public AltosScanUI(AltosUI in_owner) {
+
+ owner = in_owner;
+
+ frequencies = AltosUIPreferences.common_frequencies();
+ frequency_index = 0;
+ telemetry = Altos.ao_telemetry_min;
+
+ if (!open())
+ return;
+
+ 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.restart();
+
+ owner = in_owner;
+
+ pane.setLayout(new GridBagLayout());
+
+ scanning_label = new JLabel("Scanning:");
+ frequency_label = new JLabel("");
+ telemetry_label = new JLabel("");
+
+ set_label();
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.WEST;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 1;
+
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 2;
+
+ pane.add(scanning_label, c);
+ c.gridy = 1;
+ pane.add(frequency_label, c);
+ c.gridy = 2;
+ pane.add(telemetry_label, c);
+
+ int scanning_telemetry = AltosUIPreferences.scanning_telemetry();
+ telemetry_boxes = new JCheckBox[Altos.ao_telemetry_max - Altos.ao_telemetry_min + 1];
+ for (int k = Altos.ao_telemetry_min; k <= Altos.ao_telemetry_max; k++) {
+ int j = k - Altos.ao_telemetry_min;
+ telemetry_boxes[j] = new JCheckBox(AltosLib.telemetry_name(k));
+ c.gridy = 3 + j;
+ pane.add(telemetry_boxes[j], c);
+ telemetry_boxes[j].setActionCommand("telemetry");
+ telemetry_boxes[j].addActionListener(this);
+ telemetry_boxes[j].setSelected((scanning_telemetry & (1 << k)) != 0);
+ }
+
+ int y_offset = 3 + (Altos.ao_telemetry_max - Altos.ao_telemetry_min + 1);
+
+ list = new JList(results) {
+ //Subclass JList to workaround bug 4832765, which can cause the
+ //scroll pane to not let the user easily scroll up to the beginning
+ //of the list. An alternative would be to set the unitIncrement
+ //of the JScrollBar to a fixed value. You wouldn't get the nice
+ //aligned scrolling, but it should work.
+ public int getScrollableUnitIncrement(Rectangle visibleRect,
+ int orientation,
+ int direction) {
+ int row;
+ if (orientation == SwingConstants.VERTICAL &&
+ direction < 0 && (row = getFirstVisibleIndex()) != -1) {
+ Rectangle r = getCellBounds(row, row);
+ if ((r.y == visibleRect.y) && (row != 0)) {
+ Point loc = r.getLocation();
+ loc.y--;
+ int prevIndex = locationToIndex(loc);
+ Rectangle prevR = getCellBounds(prevIndex, prevIndex);
+
+ if (prevR == null || prevR.y >= r.y) {
+ return 0;
+ }
+ return prevR.height;
+ }
+ }
+ return super.getScrollableUnitIncrement(
+ visibleRect, orientation, direction);
+ }
+ };
+
+ list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
+ list.setVisibleRowCount(-1);
+
+ list.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ monitor_button.doClick(); //emulate button click
+ }
+ }
+ });
+ JScrollPane listScroller = new JScrollPane(list);
+ listScroller.setPreferredSize(new Dimension(400, 80));
+ listScroller.setAlignmentX(LEFT_ALIGNMENT);
+
+ //Create a container so that we can add a title around
+ //the scroll pane. Can't add a title directly to the
+ //scroll pane because its background would be white.
+ //Lay out the label and scroll pane from top to bottom.
+ JPanel listPane = new JPanel();
+ listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
+
+ JLabel label = new JLabel("Select Device");
+ label.setLabelFor(list);
+ listPane.add(label);
+ listPane.add(Box.createRigidArea(new Dimension(0,5)));
+ listPane.add(listScroller);
+ listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 1;
+
+ c.gridx = 0;
+ c.gridy = y_offset;
+ c.gridwidth = 2;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(listPane, c);
+
+ cancel_button = new JButton("Cancel");
+ cancel_button.addActionListener(this);
+ cancel_button.setActionCommand("cancel");
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 1;
+
+ c.gridx = 0;
+ c.gridy = y_offset + 1;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(cancel_button, c);
+
+ monitor_button = new JButton("Monitor");
+ monitor_button.addActionListener(this);
+ monitor_button.setActionCommand("monitor");
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 1;
+
+ c.gridx = 1;
+ c.gridy = y_offset + 1;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(monitor_button, c);
+
+ pack();
+ setLocationRelativeTo(owner);
+
+ addWindowListener(new ConfigListener(this));
+
+ setVisible(true);
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosSerial.java b/altosui/AltosSerial.java
new file mode 100644
index 00000000..6cee1609
--- /dev/null
+++ b/altosui/AltosSerial.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/*
+ * Deal with TeleDongle on a serial port
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.util.*;
+import java.text.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+/*
+ * This class reads from the serial port and places each received
+ * line in a queue. Dealing with that queue is left up to other
+ * threads.
+ */
+
+public class AltosSerial extends AltosLink {
+
+ static java.util.List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>());
+
+ AltosDevice device;
+ SWIGTYPE_p_altos_file altos;
+ Thread input_thread;
+ String line;
+ byte[] line_bytes;
+ int line_count;
+ Frame frame;
+
+ public int getchar() {
+ if (altos == null)
+ return ERROR;
+ return libaltos.altos_getchar(altos, 0);
+ }
+
+ public void flush_output() {
+ super.flush_output();
+ if (altos != null) {
+ if (libaltos.altos_flush(altos) != 0) {
+ libaltos.altos_close(altos);
+ altos = null;
+ abort_reply();
+ }
+ }
+ }
+
+ JDialog timeout_dialog;
+
+ private void start_timeout_dialog_internal() {
+
+ Object[] options = { "Cancel" };
+
+ JOptionPane pane = new JOptionPane();
+ pane.setMessage(String.format("Connecting to %s, %7.3f MHz", device.toShortString(), frequency));
+ pane.setOptions(options);
+ pane.setInitialValue(null);
+
+ timeout_dialog = pane.createDialog(frame, "Connecting...");
+
+ timeout_dialog.setVisible(true);
+
+ Object o = pane.getValue();
+ if (o == null)
+ return;
+ if (options[0].equals(o))
+ reply_abort = true;
+ timeout_dialog.dispose();
+ timeout_dialog = null;
+ }
+
+ /*
+ * These are required by the AltosLink implementation
+ */
+
+ public boolean can_cancel_reply() {
+ /*
+ * Can cancel any replies not called from the dispatch thread
+ */
+ return !SwingUtilities.isEventDispatchThread();
+ }
+
+ public boolean show_reply_timeout() {
+ if (!SwingUtilities.isEventDispatchThread() && frame != null) {
+ Runnable r = new Runnable() {
+ public void run() {
+ start_timeout_dialog_internal();
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ return true;
+ }
+ return false;
+ }
+
+ public void hide_reply_timeout() {
+ Runnable r = new Runnable() {
+ public void run() {
+ timeout_dialog.setVisible(false);
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ public void close() {
+ if (remote) {
+ try {
+ stop_remote();
+ } catch (InterruptedException ie) {
+ }
+ }
+ if (in_reply != 0)
+ System.out.printf("Uh-oh. Closing active serial device\n");
+
+ if (altos != null) {
+ libaltos.altos_close(altos);
+ }
+ if (input_thread != null) {
+ try {
+ input_thread.interrupt();
+ input_thread.join();
+ } catch (InterruptedException e) {
+ }
+ input_thread = null;
+ }
+ if (altos != null) {
+ libaltos.altos_free(altos);
+ altos = null;
+ }
+ synchronized (devices_opened) {
+ devices_opened.remove(device.getPath());
+ }
+ if (debug)
+ System.out.printf("Closing %s\n", device.getPath());
+ }
+
+ private void putc(char c) {
+ if (altos != null)
+ if (libaltos.altos_putchar(altos, c) != 0) {
+ libaltos.altos_close(altos);
+ altos = null;
+ abort_reply();
+ }
+ }
+
+ public void print(String data) {
+ for (int i = 0; i < data.length(); i++)
+ putc(data.charAt(i));
+ }
+
+ private void open() throws FileNotFoundException, AltosSerialInUseException {
+ synchronized (devices_opened) {
+ if (devices_opened.contains(device.getPath()))
+ throw new AltosSerialInUseException(device);
+ devices_opened.add(device.getPath());
+ }
+ altos = device.open();
+ if (altos == null) {
+ final String message = device.getErrorString();
+ close();
+ throw new FileNotFoundException(String.format("%s (%s)",
+ device.toShortString(), message));
+ }
+ if (debug)
+ System.out.printf("Open %s\n", device.getPath());
+ input_thread = new Thread(this);
+ input_thread.start();
+ print("~\nE 0\n");
+ set_monitor(false);
+ flush_output();
+ }
+
+ public void set_frame(Frame in_frame) {
+ frame = in_frame;
+ }
+
+ public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
+ device = in_device;
+ frame = null;
+ serial = device.getSerial();
+ name = device.toShortString();
+ open();
+ }
+}
diff --git a/altosui/AltosSerialInUseException.java b/altosui/AltosSerialInUseException.java
new file mode 100644
index 00000000..7380f331
--- /dev/null
+++ b/altosui/AltosSerialInUseException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+public class AltosSerialInUseException extends Exception {
+ public AltosDevice device;
+
+ public AltosSerialInUseException (AltosDevice in_device) {
+ device = in_device;
+ }
+}
diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java
new file mode 100644
index 00000000..b57edcab
--- /dev/null
+++ b/altosui/AltosSiteMap.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.MouseInputAdapter;
+import javax.imageio.ImageIO;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.lang.Math;
+import java.awt.geom.Point2D;
+import java.awt.geom.Line2D;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay {
+ // preferred vertical step in a tile in naut. miles
+ // will actually choose a step size between x and 2x, where this
+ // is 1.5x
+ static final double tile_size_nmi = 0.75;
+
+ static final int px_size = 512;
+
+ static final int MAX_TILE_DELTA = 100;
+
+ private static Point2D.Double translatePoint(Point2D.Double p,
+ Point2D.Double d)
+ {
+ return new Point2D.Double(p.x + d.x, p.y + d.y);
+ }
+
+ static class LatLng {
+ public double lat, lng;
+ public LatLng(double lat, double lng) {
+ this.lat = lat;
+ this.lng = lng;
+ }
+ }
+
+ // based on google js
+ // http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js
+ // search for fromLatLngToPoint and fromPointToLatLng
+ private static Point2D.Double pt(LatLng latlng, int zoom) {
+ double scale_x = 256/360.0 * Math.pow(2, zoom);
+ double scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom);
+ return pt(latlng, scale_x, scale_y);
+ }
+
+ private static Point2D.Double pt(LatLng latlng,
+ double scale_x, double scale_y)
+ {
+ Point2D.Double res = new Point2D.Double();
+ double e;
+
+ res.x = latlng.lng * scale_x;
+
+ e = Math.sin(Math.toRadians(latlng.lat));
+ e = Math.max(e,-(1-1.0E-15));
+ e = Math.min(e, 1-1.0E-15 );
+
+ res.y = 0.5*Math.log((1+e)/(1-e))*-scale_y;
+ return res;
+ }
+
+ static private LatLng latlng(Point2D.Double pt,
+ double scale_x, double scale_y)
+ {
+ double lat, lng;
+ double rads;
+
+ lng = pt.x/scale_x;
+ rads = 2 * Math.atan(Math.exp(-pt.y/scale_y));
+ lat = Math.toDegrees(rads - Math.PI/2);
+
+ return new LatLng(lat,lng);
+ }
+
+ int zoom;
+ double scale_x, scale_y;
+
+ int radius; /* half width/height of tiles to load */
+
+ private Point2D.Double pt(double lat, double lng) {
+ return pt(new LatLng(lat, lng), scale_x, scale_y);
+ }
+
+ private LatLng latlng(double x, double y) {
+ return latlng(new Point2D.Double(x,y), scale_x, scale_y);
+ }
+ private LatLng latlng(Point2D.Double pt) {
+ return latlng(pt, scale_x, scale_y);
+ }
+
+ ConcurrentHashMap<Point,AltosSiteMapTile> mapTiles = new ConcurrentHashMap<Point,AltosSiteMapTile>();
+ Point2D.Double centre;
+
+ private Point2D.Double tileCoordOffset(Point p) {
+ return new Point2D.Double(centre.x - p.x*px_size,
+ centre.y - p.y * px_size);
+ }
+
+ private Point tileOffset(Point2D.Double p) {
+ return new Point((int)Math.floor((centre.x+p.x)/px_size),
+ (int)Math.floor((centre.y+p.y)/px_size));
+ }
+
+ private Point2D.Double getBaseLocation(double lat, double lng) {
+ Point2D.Double locn, north_step;
+
+ zoom = 2;
+ // stupid loop structure to please Java's control flow analysis
+ do {
+ zoom++;
+ scale_x = 256/360.0 * Math.pow(2, zoom);
+ scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom);
+ locn = pt(lat, lng);
+ north_step = pt(lat+tile_size_nmi*4/3/60.0, lng);
+ if (locn.y - north_step.y > px_size)
+ break;
+ } while (zoom < 22);
+ locn.x = -px_size * Math.floor(locn.x/px_size);
+ locn.y = -px_size * Math.floor(locn.y/px_size);
+ return locn;
+ }
+
+ public void reset() {
+ // nothing
+ }
+
+ public void set_font() {
+ // nothing
+ }
+
+ private void loadMap(final AltosSiteMapTile tile,
+ File pngfile, String pngurl)
+ {
+ final ImageIcon res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl);
+ if (res != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ tile.loadMap(res);
+ }
+ });
+ } else {
+ System.out.printf("# Failed to fetch file %s\n", pngfile);
+ System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl);
+ }
+ }
+
+ File pngfile;
+ String pngurl;
+
+ public int prefetchMap(int x, int y) {
+ LatLng map_latlng = latlng(
+ -centre.x + x*px_size + px_size/2,
+ -centre.y + y*px_size + px_size/2);
+ pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom);
+ pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom);
+ if (pngfile.exists()) {
+ return 1;
+ } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ public static void prefetchMaps(double lat, double lng, int w, int h) {
+ AltosSiteMap asm = new AltosSiteMap(true);
+ asm.centre = asm.getBaseLocation(lat, lng);
+
+ Point2D.Double p = new Point2D.Double();
+ Point2D.Double p2;
+ int dx = -w/2, dy = -h/2;
+ for (int y = dy; y < h+dy; y++) {
+ for (int x = dx; x < w+dx; x++) {
+ int r = asm.prefetchMap(x, y);
+ switch (r) {
+ case 1:
+ System.out.printf("Already have %s\n", asm.pngfile);
+ break;
+ case 0:
+ System.out.printf("Fetched map %s\n", asm.pngfile);
+ break;
+ case -1:
+ System.out.printf("# Failed to fetch file %s\n", asm.pngfile);
+ System.out.printf(" wget -O '%s' ''\n", asm.pngfile, asm.pngurl);
+ break;
+ }
+ }
+ }
+ }
+
+ public String initMap(Point offset) {
+ AltosSiteMapTile tile = mapTiles.get(offset);
+ Point2D.Double coord = tileCoordOffset(offset);
+
+ LatLng map_latlng = latlng(px_size/2-coord.x, px_size/2-coord.y);
+
+ File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom);
+ String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom);
+ loadMap(tile, pngfile, pngurl);
+ return pngfile.toString();
+ }
+
+ public void setBaseLocation(double lat, double lng) {
+ for (Point k : mapTiles.keySet()) {
+ AltosSiteMapTile tile = mapTiles.get(k);
+ tile.clearMap();
+ }
+
+ centre = getBaseLocation(lat, lng);
+ scrollRocketToVisible(pt(lat,lng));
+ }
+
+ private void initMaps(double lat, double lng) {
+ setBaseLocation(lat, lng);
+
+ Thread thread = new Thread() {
+ public void run() {
+ for (Point k : mapTiles.keySet())
+ initMap(k);
+ }
+ };
+ thread.start();
+ }
+
+ private static File MapFile(double lat, double lng, int zoom) {
+ char chlat = lat < 0 ? 'S' : 'N';
+ char chlng = lng < 0 ? 'W' : 'E';
+ if (lat < 0) lat = -lat;
+ if (lng < 0) lng = -lng;
+ return new File(AltosUIPreferences.mapdir(),
+ String.format("map-%c%.6f,%c%.6f-%d.png",
+ chlat, lat, chlng, lng, zoom));
+ }
+
+ private static String MapURL(double lat, double lng, int zoom) {
+ return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size);
+ }
+
+ boolean initialised = false;
+ Point2D.Double last_pt = null;
+ int last_state = -1;
+
+ public void show(double lat, double lon) {
+ initMaps(lat, lon);
+ scrollRocketToVisible(pt(lat, lon));
+ }
+ public void show(final AltosState state, final int crc_errors) {
+ // if insufficient gps data, nothing to update
+ if (!state.gps.locked && state.gps.nsat < 4)
+ return;
+
+ if (!initialised) {
+ if (state.pad_lat != 0 || state.pad_lon != 0) {
+ initMaps(state.pad_lat, state.pad_lon);
+ initialised = true;
+ } else if (state.gps.lat != 0 || state.gps.lon != 0) {
+ initMaps(state.gps.lat, state.gps.lon);
+ initialised = true;
+ } else {
+ return;
+ }
+ }
+
+ final Point2D.Double pt = pt(state.gps.lat, state.gps.lon);
+ if (last_pt == pt && last_state == state.state)
+ return;
+
+ if (last_pt == null) {
+ last_pt = pt;
+ }
+ boolean in_any = false;
+ for (Point offset : mapTiles.keySet()) {
+ AltosSiteMapTile tile = mapTiles.get(offset);
+ Point2D.Double ref, lref;
+ ref = translatePoint(pt, tileCoordOffset(offset));
+ lref = translatePoint(last_pt, tileCoordOffset(offset));
+ tile.show(state, crc_errors, lref, ref);
+ if (0 <= ref.x && ref.x < px_size)
+ if (0 <= ref.y && ref.y < px_size)
+ in_any = true;
+ }
+
+ Point offset = tileOffset(pt);
+ if (!in_any) {
+ Point2D.Double ref, lref;
+ ref = translatePoint(pt, tileCoordOffset(offset));
+ lref = translatePoint(last_pt, tileCoordOffset(offset));
+
+ AltosSiteMapTile tile = createTile(offset);
+ tile.show(state, crc_errors, lref, ref);
+ initMap(offset);
+ finishTileLater(tile, offset);
+ }
+
+ scrollRocketToVisible(pt);
+
+ if (offset != tileOffset(last_pt)) {
+ ensureTilesAround(offset);
+ }
+
+ last_pt = pt;
+ last_state = state.state;
+ }
+
+ public void draw_circle(double lat, double lon) {
+ final Point2D.Double pt = pt(lat, lon);
+
+ for (Point offset : mapTiles.keySet()) {
+ AltosSiteMapTile tile = mapTiles.get(offset);
+ Point2D.Double ref = translatePoint(pt, tileCoordOffset(offset));
+ tile.draw_circle(ref);
+ }
+ }
+
+ private AltosSiteMapTile createTile(Point offset) {
+ AltosSiteMapTile tile = new AltosSiteMapTile(px_size);
+ mapTiles.put(offset, tile);
+ return tile;
+ }
+ private void finishTileLater(final AltosSiteMapTile tile,
+ final Point offset)
+ {
+ SwingUtilities.invokeLater( new Runnable() {
+ public void run() {
+ addTileAt(tile, offset);
+ }
+ } );
+ }
+
+ private void ensureTilesAround(Point base_offset) {
+ for (int x = -radius; x <= radius; x++) {
+ for (int y = -radius; y <= radius; y++) {
+ Point offset = new Point(base_offset.x + x, base_offset.y + y);
+ if (mapTiles.containsKey(offset))
+ continue;
+ AltosSiteMapTile tile = createTile(offset);
+ initMap(offset);
+ finishTileLater(tile, offset);
+ }
+ }
+ }
+
+ private Point topleft = new Point(0,0);
+ private void scrollRocketToVisible(Point2D.Double pt) {
+ Rectangle r = comp.getVisibleRect();
+ Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft));
+ int dx = (int)copt.x - r.width/2 - r.x;
+ int dy = (int)copt.y - r.height/2 - r.y;
+ if (Math.abs(dx) > r.width/4 || Math.abs(dy) > r.height/4) {
+ r.x += dx;
+ r.y += dy;
+ comp.scrollRectToVisible(r);
+ }
+ }
+
+ private void addTileAt(AltosSiteMapTile tile, Point offset) {
+ if (Math.abs(offset.x) >= MAX_TILE_DELTA ||
+ Math.abs(offset.y) >= MAX_TILE_DELTA)
+ {
+ System.out.printf("Rocket too far away from pad (tile %d,%d)\n",
+ offset.x, offset.y);
+ return;
+ }
+
+ boolean review = false;
+ Rectangle r = comp.getVisibleRect();
+ if (offset.x < topleft.x) {
+ r.x += (topleft.x - offset.x) * px_size;
+ topleft.x = offset.x;
+ review = true;
+ }
+ if (offset.y < topleft.y) {
+ r.y += (topleft.y - offset.y) * px_size;
+ topleft.y = offset.y;
+ review = true;
+ }
+ GridBagConstraints c = new GridBagConstraints();
+ c.anchor = GridBagConstraints.CENTER;
+ c.fill = GridBagConstraints.BOTH;
+ // put some space between the map tiles, debugging only
+ // c.insets = new Insets(5, 5, 5, 5);
+
+ c.gridx = offset.x + MAX_TILE_DELTA;
+ c.gridy = offset.y + MAX_TILE_DELTA;
+ layout.setConstraints(tile, c);
+
+ comp.add(tile);
+ if (review) {
+ comp.scrollRectToVisible(r);
+ }
+ }
+
+ private AltosSiteMap(boolean knowWhatYouAreDoing) {
+ if (!knowWhatYouAreDoing) {
+ throw new RuntimeException("Arggh.");
+ }
+ }
+
+ JComponent comp = new JComponent() { };
+ private GridBagLayout layout = new GridBagLayout();
+
+ public AltosSiteMap(int in_radius) {
+ radius = in_radius;
+
+ GrabNDrag scroller = new GrabNDrag(comp);
+
+ comp.setLayout(layout);
+
+ for (int x = -radius; x <= radius; x++) {
+ for (int y = -radius; y <= radius; y++) {
+ Point offset = new Point(x, y);
+ AltosSiteMapTile t = createTile(offset);
+ addTileAt(t, offset);
+ }
+ }
+ setViewportView(comp);
+ setPreferredSize(new Dimension(500,500));
+ }
+
+ public AltosSiteMap() {
+ this(1);
+ }
+}
diff --git a/altosui/AltosSiteMapCache.java b/altosui/AltosSiteMapCache.java
new file mode 100644
index 00000000..f729a298
--- /dev/null
+++ b/altosui/AltosSiteMapCache.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.imageio.ImageIO;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.net.URL;
+import java.net.URLConnection;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosSiteMapCache extends JLabel {
+ public static boolean fetchMap(File file, String url) {
+ URL u;
+
+ try {
+ u = new URL(url);
+ } catch (java.net.MalformedURLException e) {
+ return false;
+ }
+
+ byte[] data;
+ try {
+ URLConnection uc = u.openConnection();
+ int contentLength = uc.getContentLength();
+ InputStream in = new BufferedInputStream(uc.getInputStream());
+ int bytesRead = 0;
+ int offset = 0;
+ data = new byte[contentLength];
+ while (offset < contentLength) {
+ bytesRead = in.read(data, offset, data.length - offset);
+ if (bytesRead == -1)
+ break;
+ offset += bytesRead;
+ }
+ in.close();
+
+ if (offset != contentLength) {
+ return false;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+
+ try {
+ FileOutputStream out = new FileOutputStream(file);
+ out.write(data);
+ out.flush();
+ out.close();
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ if (file.exists()) {
+ file.delete();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public static ImageIcon fetchAndLoadMap(File pngfile, String url) {
+ if (!pngfile.exists()) {
+ if (!fetchMap(pngfile, url)) {
+ return null;
+ }
+ }
+ return loadMap(pngfile, url);
+ }
+
+ public static ImageIcon loadMap(File pngfile, String url) {
+ if (!pngfile.exists()) {
+ return null;
+ }
+
+ try {
+ return new ImageIcon(ImageIO.read(pngfile));
+ } catch (IOException e) {
+ System.out.printf("# IO error trying to load %s\n", pngfile);
+ return null;
+ }
+ }
+}
diff --git a/altosui/AltosSiteMapPreload.java b/altosui/AltosSiteMapPreload.java
new file mode 100644
index 00000000..676b0790
--- /dev/null
+++ b/altosui/AltosSiteMapPreload.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.MouseInputAdapter;
+import javax.imageio.ImageIO;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.lang.Math;
+import java.awt.geom.Point2D;
+import java.awt.geom.Line2D;
+import java.net.URL;
+import java.net.URLConnection;
+import org.altusmetrum.AltosLib.*;
+
+class AltosMapPos extends Box {
+ AltosUI owner;
+ JLabel label;
+ JComboBox hemi;
+ JTextField deg;
+ JLabel deg_label;
+ JTextField min;
+ JLabel min_label;
+
+ public void set_value(double new_value) {
+ double d, m;
+ int h;
+
+ h = 0;
+ if (new_value < 0) {
+ h = 1;
+ new_value = -new_value;
+ }
+ d = Math.floor(new_value);
+ deg.setText(String.format("%3.0f", d));
+ m = (new_value - d) * 60.0;
+ min.setText(String.format("%7.4f", m));
+ hemi.setSelectedIndex(h);
+ }
+
+ public double get_value() throws NumberFormatException {
+ int h = hemi.getSelectedIndex();
+ String d_t = deg.getText();
+ String m_t = min.getText();
+ double d, m, v;
+ try {
+ d = Double.parseDouble(d_t);
+ } catch (NumberFormatException ne) {
+ JOptionPane.showMessageDialog(owner,
+ String.format("Invalid degrees \"%s\"",
+ d_t),
+ "Invalid number",
+ JOptionPane.ERROR_MESSAGE);
+ throw ne;
+ }
+ try {
+ if (m_t.equals(""))
+ m = 0;
+ else
+ m = Double.parseDouble(m_t);
+ } catch (NumberFormatException ne) {
+ JOptionPane.showMessageDialog(owner,
+ String.format("Invalid minutes \"%s\"",
+ m_t),
+ "Invalid number",
+ JOptionPane.ERROR_MESSAGE);
+ throw ne;
+ }
+ v = d + m/60.0;
+ if (h == 1)
+ v = -v;
+ return v;
+ }
+
+ public AltosMapPos(AltosUI in_owner,
+ String label_value,
+ String[] hemi_names,
+ double default_value) {
+ super(BoxLayout.X_AXIS);
+ owner = in_owner;
+ label = new JLabel(label_value);
+ hemi = new JComboBox(hemi_names);
+ hemi.setEditable(false);
+ deg = new JTextField(5);
+ deg.setMinimumSize(deg.getPreferredSize());
+ deg.setHorizontalAlignment(JTextField.RIGHT);
+ deg_label = new JLabel("°");
+ min = new JTextField(9);
+ min.setMinimumSize(min.getPreferredSize());
+ min_label = new JLabel("'");
+ set_value(default_value);
+ add(label);
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(hemi);
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(deg);
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(deg_label);
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(min);
+ add(Box.createRigidArea(new Dimension(5, 0)));
+ add(min_label);
+ }
+}
+
+class AltosSite {
+ String name;
+ double latitude;
+ double longitude;
+
+ public String toString() {
+ return name;
+ }
+
+ public AltosSite(String in_name, double in_latitude, double in_longitude) {
+ name = in_name;
+ latitude = in_latitude;
+ longitude = in_longitude;
+ }
+
+ public AltosSite(String line) throws ParseException {
+ String[] elements = line.split(":");
+
+ if (elements.length < 3)
+ throw new ParseException(String.format("Invalid site line %s", line), 0);
+
+ name = elements[0];
+
+ try {
+ latitude = Double.parseDouble(elements[1]);
+ longitude = Double.parseDouble(elements[2]);
+ } catch (NumberFormatException ne) {
+ throw new ParseException(String.format("Invalid site line %s", line), 0);
+ }
+ }
+}
+
+class AltosSites extends Thread {
+ AltosSiteMapPreload preload;
+ URL url;
+ LinkedList<AltosSite> sites;
+
+ void notify_complete() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ preload.set_sites();
+ }
+ });
+ }
+
+ void add(AltosSite site) {
+ sites.add(site);
+ }
+
+ void add(String line) {
+ try {
+ add(new AltosSite(line));
+ } catch (ParseException pe) {
+ }
+ }
+
+ public void run() {
+ try {
+ URLConnection uc = url.openConnection();
+ int length = uc.getContentLength();
+
+ InputStreamReader in_stream = new InputStreamReader(uc.getInputStream(), Altos.unicode_set);
+ BufferedReader in = new BufferedReader(in_stream);
+
+ for (;;) {
+ String line = in.readLine();
+ if (line == null)
+ break;
+ add(line);
+ }
+ } catch (IOException e) {
+ } finally {
+ notify_complete();
+ }
+ }
+
+ public AltosSites(AltosSiteMapPreload in_preload) {
+ sites = new LinkedList<AltosSite>();
+ preload = in_preload;
+ try {
+ url = new URL(Altos.launch_sites_url);
+ } catch (java.net.MalformedURLException e) {
+ notify_complete();
+ }
+ start();
+ }
+}
+
+public class AltosSiteMapPreload extends AltosDialog implements ActionListener, ItemListener {
+ AltosUI owner;
+ AltosSiteMap map;
+
+ AltosMapPos lat;
+ AltosMapPos lon;
+
+ final static int radius = 4;
+ final static int width = (radius * 2 + 1);
+ final static int height = (radius * 2 + 1);
+
+ JProgressBar pbar;
+
+ AltosSites sites;
+ JLabel site_list_label;
+ JComboBox site_list;
+
+ JToggleButton load_button;
+ boolean loading;
+ JButton close_button;
+
+ static final String[] lat_hemi_names = { "N", "S" };
+ static final String[] lon_hemi_names = { "E", "W" };
+
+ class updatePbar implements Runnable {
+ int n;
+ String s;
+
+ public updatePbar(int x, int y, String in_s) {
+ n = (x + radius) + (y + radius) * width + 1;
+ s = in_s;
+ }
+
+ public void run() {
+ pbar.setValue(n);
+ pbar.setString(s);
+ if (n < width * height) {
+ pbar.setValue(n);
+ pbar.setString(s);
+ } else {
+ pbar.setValue(0);
+ pbar.setString("");
+ load_button.setSelected(false);
+ loading = false;
+ }
+ }
+ }
+
+ class bgLoad extends Thread {
+
+ AltosSiteMap map;
+
+ public bgLoad(AltosSiteMap in_map) {
+ map = in_map;
+ }
+
+ public void run() {
+ for (int y = -map.radius; y <= map.radius; y++) {
+ for (int x = -map.radius; x <= map.radius; x++) {
+ String pngfile;
+ pngfile = map.initMap(new Point(x,y));
+ SwingUtilities.invokeLater(new updatePbar(x, y, pngfile));
+ }
+ }
+ }
+ }
+
+ public void set_sites() {
+ int i = 1;
+ for (AltosSite site : sites.sites) {
+ site_list.insertItemAt(site, i);
+ i++;
+ }
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ int state = e.getStateChange();
+
+ if (state == ItemEvent.SELECTED) {
+ Object o = e.getItem();
+ if (o instanceof AltosSite) {
+ AltosSite site = (AltosSite) o;
+ lat.set_value(site.latitude);
+ lon.set_value(site.longitude);
+ }
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if (cmd.equals("close"))
+ setVisible(false);
+
+ if (cmd.equals("load")) {
+ if (!loading) {
+ try {
+ final double latitude = lat.get_value();
+ final double longitude = lon.get_value();
+ map.setBaseLocation(latitude,longitude);
+ map.draw_circle(latitude,longitude);
+ loading = true;
+ bgLoad thread = new bgLoad(map);
+ thread.start();
+ } catch (NumberFormatException ne) {
+ load_button.setSelected(false);
+ }
+ }
+ }
+ }
+
+ public AltosSiteMapPreload(AltosUI in_owner) {
+ owner = in_owner;
+
+ Container pane = getContentPane();
+ GridBagConstraints c = new GridBagConstraints();
+ Insets i = new Insets(4,4,4,4);
+
+ pane.setLayout(new GridBagLayout());
+
+ map = new AltosSiteMap(4);
+
+ c.fill = GridBagConstraints.BOTH;
+ 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;
+
+ pane.add(map, c);
+
+ pbar = new JProgressBar();
+ pbar.setMinimum(0);
+ pbar.setMaximum(width * height);
+ pbar.setValue(0);
+ pbar.setString("");
+ pbar.setStringPainted(true);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ c.gridx = 0;
+ c.gridy = 1;
+ c.gridwidth = 2;
+
+ pane.add(pbar, c);
+
+ site_list_label = new JLabel ("Known Launch Sites:");
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ c.gridx = 0;
+ c.gridy = 2;
+ c.gridwidth = 1;
+
+ pane.add(site_list_label, c);
+
+ site_list = new JComboBox(new String[] { "Site List" });
+ site_list.addItemListener(this);
+
+ sites = new AltosSites(this);
+
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ c.gridx = 1;
+ c.gridy = 2;
+ c.gridwidth = 1;
+
+ pane.add(site_list, c);
+
+ lat = new AltosMapPos(owner,
+ "Latitude:",
+ lat_hemi_names,
+ 37.167833333);
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 0;
+ c.weighty = 0;
+
+ c.gridx = 0;
+ c.gridy = 3;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(lat, c);
+
+ lon = new AltosMapPos(owner,
+ "Longitude:",
+ lon_hemi_names,
+ -97.73975);
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 0;
+ c.weighty = 0;
+
+ c.gridx = 1;
+ c.gridy = 3;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(lon, c);
+
+ load_button = new JToggleButton("Load Map");
+ load_button.addActionListener(this);
+ load_button.setActionCommand("load");
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ c.gridx = 0;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(load_button, c);
+
+ close_button = new JButton("Close");
+ close_button.addActionListener(this);
+ close_button.setActionCommand("close");
+
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = i;
+ c.weightx = 1;
+ c.weighty = 0;
+
+ c.gridx = 1;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.anchor = GridBagConstraints.CENTER;
+
+ pane.add(close_button, c);
+
+ pack();
+ setLocationRelativeTo(owner);
+ setVisible(true);
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java
new file mode 100644
index 00000000..34550219
--- /dev/null
+++ b/altosui/AltosSiteMapTile.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.imageio.ImageIO;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.lang.Math;
+import java.awt.geom.Point2D;
+import java.awt.geom.Line2D;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosSiteMapTile extends JLayeredPane {
+ JLabel mapLabel;
+ JLabel draw;
+ Graphics2D g2d;
+ int px_size;
+
+ public void loadMap(ImageIcon icn) {
+ mapLabel.setIcon(icn);
+ }
+
+ public void clearMap() {
+ fillLabel(mapLabel, Color.GRAY, px_size);
+ g2d = fillLabel(draw, new Color(127,127,127,0), px_size);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ }
+
+ static Color stateColors[] = {
+ Color.WHITE, // startup
+ Color.WHITE, // idle
+ Color.WHITE, // pad
+ Color.RED, // boost
+ Color.PINK, // fast
+ Color.YELLOW, // coast
+ Color.CYAN, // drogue
+ Color.BLUE, // main
+ Color.BLACK // landed
+ };
+
+ private boolean drawn_landed_circle = false;
+ private boolean drawn_boost_circle = false;
+ public synchronized void show(AltosState state, int crc_errors,
+ Point2D.Double last_pt, Point2D.Double pt)
+ {
+ if (0 <= state.state && state.state < stateColors.length) {
+ g2d.setColor(stateColors[state.state]);
+ }
+ g2d.draw(new Line2D.Double(last_pt, pt));
+
+ if (state.state == 3 && !drawn_boost_circle) {
+ drawn_boost_circle = true;
+ g2d.setColor(Color.RED);
+ g2d.drawOval((int)last_pt.x-5, (int)last_pt.y-5, 10, 10);
+ g2d.drawOval((int)last_pt.x-20, (int)last_pt.y-20, 40, 40);
+ g2d.drawOval((int)last_pt.x-35, (int)last_pt.y-35, 70, 70);
+ }
+ if (state.state == 8 && !drawn_landed_circle) {
+ drawn_landed_circle = true;
+ g2d.setColor(Color.BLACK);
+ g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
+ g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
+ g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
+ }
+
+ repaint();
+ }
+
+ public void draw_circle(Point2D.Double pt) {
+ g2d.setColor(Color.RED);
+ g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
+ g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
+ g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
+ }
+
+ public static Graphics2D fillLabel(JLabel l, Color c, int px_size) {
+ BufferedImage img = new BufferedImage(px_size, px_size,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = img.createGraphics();
+ g.setColor(c);
+ g.fillRect(0, 0, px_size, px_size);
+ l.setIcon(new ImageIcon(img));
+ return g;
+ }
+
+ public AltosSiteMapTile(int in_px_size) {
+ px_size = in_px_size;
+ setPreferredSize(new Dimension(px_size, px_size));
+
+ mapLabel = new JLabel();
+ fillLabel(mapLabel, Color.GRAY, px_size);
+ mapLabel.setOpaque(true);
+ mapLabel.setBounds(0, 0, px_size, px_size);
+ add(mapLabel, new Integer(0));
+
+ draw = new JLabel();
+ g2d = fillLabel(draw, new Color(127, 127, 127, 0), px_size);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ draw.setBounds(0, 0, px_size, px_size);
+ draw.setOpaque(false);
+
+ add(draw, new Integer(1));
+ }
+}
diff --git a/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub b/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub
new file mode 100755
index 00000000..c661d3e1
--- /dev/null
+++ b/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub
Binary files differ
diff --git a/altosui/AltosUI.app/Contents/PkgInfo b/altosui/AltosUI.app/Contents/PkgInfo
new file mode 100644
index 00000000..8a43480f
--- /dev/null
+++ b/altosui/AltosUI.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPLAM.O
diff --git a/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns b/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns
new file mode 100644
index 00000000..fe49f362
--- /dev/null
+++ b/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns
Binary files differ
diff --git a/altosui/AltosUI.java b/altosui/AltosUI.java
new file mode 100644
index 00000000..926d66f0
--- /dev/null
+++ b/altosui/AltosUI.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.*;
+import org.altusmetrum.AltosLib.*;
+
+import libaltosJNI.*;
+
+public class AltosUI extends AltosFrame {
+ 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","<undefined>")),
+ "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(AltosUI.this,
+ ee.getMessage(),
+ "Cannot open target device",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (AltosSerialInUseException si) {
+ JOptionPane.showMessageDialog(AltosUI.this,
+ String.format("Device \"%s\" already in use",
+ device.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(AltosUI.this,
+ device.toShortString(),
+ "Unkonwn I/O error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (TimeoutException te) {
+ JOptionPane.showMessageDialog(this,
+ device.toShortString(),
+ "Timeout error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (InterruptedException ie) {
+ JOptionPane.showMessageDialog(this,
+ device.toShortString(),
+ "Interrupted exception",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ 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;
+ }
+
+ public AltosUI() {
+
+ load_library(null);
+
+ java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
+ if (imgURL != null)
+ setIconImage(new ImageIcon(imgURL).getImage());
+
+ 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 AltosUI");
+ b.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ ConfigureAltosUI();
+ }
+ });
+ b.setToolTipText("Global AltosUI 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 AltosUI");
+
+ setTitle("AltOS");
+
+ pane.doLayout();
+ pane.validate();
+
+ doLayout();
+ validate();
+
+ setVisible(true);
+
+ Insets i = getInsets();
+ Dimension ps = rootPane.getPreferredSize();
+ ps.width += i.left + i.right;
+ ps.height += i.top + i.bottom;
+ setPreferredSize(ps);
+ setSize(ps);
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ System.exit(0);
+ }
+ });
+ }
+
+ private void ConnectToDevice() {
+ AltosDevice device = AltosDeviceDialog.show(AltosUI.this,
+ Altos.product_basestation);
+
+ if (device != null)
+ telemetry_window(device);
+ }
+
+ void ConfigureCallsign() {
+ String result;
+ result = JOptionPane.showInputDialog(AltosUI.this,
+ "Configure Callsign",
+ AltosUIPreferences.callsign());
+ if (result != null)
+ AltosUIPreferences.set_callsign(result);
+ }
+
+ void ConfigureTeleMetrum() {
+ new AltosConfig(AltosUI.this);
+ }
+
+ void ConfigureTeleDongle() {
+ new AltosConfigTD(AltosUI.this);
+ }
+
+ void FlashImage() {
+ AltosFlashUI.show(AltosUI.this);
+ }
+
+ void FireIgniter() {
+ new AltosIgniteUI(AltosUI.this);
+ }
+
+ void ScanChannels() {
+ new AltosScanUI(AltosUI.this);
+ }
+
+ void LoadMaps() {
+ new AltosSiteMapPreload(AltosUI.this);
+ }
+
+ void LaunchController() {
+ new AltosLaunchUI(AltosUI.this);
+ }
+
+ /*
+ * Replay a flight from telemetry data
+ */
+ private void Replay() {
+ AltosDataChooser chooser = new AltosDataChooser(
+ AltosUI.this);
+
+ AltosRecordIterable iterable = chooser.runDialog();
+ if (iterable != null) {
+ AltosFlightReader reader = new AltosReplayReader(iterable.iterator(),
+ chooser.file());
+ new AltosFlightUI(voice, reader);
+ }
+ }
+
+ /* Connect to TeleMetrum, either directly or through
+ * a TeleDongle over the packet link
+ */
+ private void SaveFlightData() {
+ new AltosEepromManage(AltosUI.this);
+ }
+
+ /* 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);
+ AltosRecordIterable record_reader = chooser.runDialog();
+ if (record_reader == null)
+ return;
+ new AltosCSVUI(AltosUI.this, record_reader, chooser.file());
+ }
+
+ /* Load a flight log CSV file and display a pretty graph.
+ */
+
+ private void GraphData() {
+ AltosDataChooser chooser;
+ chooser = new AltosDataChooser(this);
+ AltosRecordIterable record_reader = chooser.runDialog();
+ if (record_reader == null)
+ return;
+ try {
+ new AltosGraphUI(record_reader, chooser.filename());
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ }
+
+ private void ConfigureAltosUI() {
+ new AltosConfigureUI(AltosUI.this, voice);
+ }
+
+ private void IdleMonitor() {
+ try {
+ new AltosIdleMonitorUI(this);
+ } catch (Exception e) {
+ }
+ }
+
+ static AltosRecordIterable open_logfile(String filename) {
+ File file = new File (filename);
+ try {
+ FileInputStream in;
+
+ in = new FileInputStream(file);
+ if (filename.endsWith("eeprom"))
+ return new AltosEepromIterable(in);
+ else if (filename.endsWith("mega"))
+ return new AltosEepromMegaIterable(in);
+ else
+ return new AltosTelemetryIterable(in);
+ } catch (FileNotFoundException fe) {
+ System.out.printf("%s\n", fe.getMessage());
+ return null;
+ }
+ }
+
+ static AltosWriter open_csv(String filename) {
+ File file = new File (filename);
+ try {
+ return new AltosCSV(file);
+ } catch (FileNotFoundException fe) {
+ System.out.printf("%s\n", fe.getMessage());
+ return null;
+ }
+ }
+
+ static AltosWriter open_kml(String filename) {
+ File file = new File (filename);
+ try {
+ return new AltosKML(file);
+ } catch (FileNotFoundException fe) {
+ System.out.printf("%s\n", fe.getMessage());
+ 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 void process_csv(String input) {
+ AltosRecordIterable iterable = open_logfile(input);
+ if (iterable == null)
+ return;
+
+ String 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);
+ } else {
+ AltosWriter writer = open_csv(output);
+ if (writer == null)
+ return;
+ writer.write(iterable);
+ writer.close();
+ }
+ }
+
+ static void process_kml(String input) {
+ AltosRecordIterable iterable = open_logfile(input);
+ if (iterable == null)
+ return;
+
+ String 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);
+ } else {
+ AltosWriter writer = open_kml(output);
+ if (writer == null)
+ return;
+ writer.write(iterable);
+ writer.close();
+ }
+ }
+
+ static AltosRecordIterable record_iterable(File file) {
+ FileInputStream in;
+ try {
+ in = new FileInputStream(file);
+ } catch (Exception e) {
+ System.out.printf("Failed to open file '%s'\n", file);
+ return null;
+ }
+ AltosRecordIterable recs;
+ AltosReplayReader reader;
+ if (file.getName().endsWith("eeprom")) {
+ recs = new AltosEepromIterable(in);
+ } else {
+ recs = new AltosTelemetryIterable(in);
+ }
+ return recs;
+ }
+
+ static AltosRecordIterable record_iterable_file(String filename) {
+ return record_iterable (new File(filename));
+ }
+
+ static AltosReplayReader replay_file(String filename) {
+ AltosRecordIterable recs = record_iterable_file(filename);
+ if (recs == null)
+ return null;
+ return new AltosReplayReader(recs.iterator(), new File(filename));
+ }
+
+ static void process_replay(String filename) {
+ AltosReplayReader reader = replay_file(filename);
+ AltosFlightUI flight_ui = new AltosFlightUI(new AltosVoice(), reader);
+ flight_ui.set_exit_on_close();
+ }
+
+ static void process_graph(String filename) {
+ AltosRecordIterable recs = record_iterable_file(filename);
+ if (recs == null)
+ return;
+ try {
+ new AltosGraphUI(recs, filename);
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ }
+
+ static void process_summary(String filename) {
+ AltosRecordIterable iterable = record_iterable_file(filename);
+ try {
+ AltosFlightStats stats = new AltosFlightStats(iterable);
+ if (stats.serial > 0)
+ System.out.printf("Serial: %5d\n", stats.serial);
+ if (stats.flight > 0)
+ System.out.printf("Flight: %5d\n", stats.flight);
+ if (stats.year > 0)
+ System.out.printf("Date: %04d-%02d-%02d\n",
+ stats.year, stats.month, stats.day);
+ if (stats.hour > 0)
+ System.out.printf("Time: %02d:%02d:%02d UTC\n",
+ stats.hour, stats.minute, stats.second);
+ System.out.printf("Max height: %6.0f m %6.0f ft\n",
+ stats.max_height,
+ AltosConvert.meters_to_feet(stats.max_height));
+ 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 != AltosRecord.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));
+ }
+ System.out.printf("Drogue rate: %6.0f m/s %6.0f ft/s\n",
+ stats.state_baro_speed[Altos.ao_flight_drogue],
+ AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_drogue]));
+ System.out.printf("Main rate: %6.0f m/s %6.0f ft/s\n",
+ stats.state_baro_speed[Altos.ao_flight_main],
+ AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_main]));
+ System.out.printf("Flight time: %6.0f s\n",
+ stats.state_end[Altos.ao_flight_main] -
+ stats.state_start[Altos.ao_flight_boost]);
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ }
+
+ public static void help(int code) {
+ System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
+ System.out.printf(" Options:\n");
+ System.out.printf(" --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n");
+ System.out.printf(" --replay <filename>\t\trelive the glory of past flights \n");
+ System.out.printf(" --graph <filename>\t\tgraph 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) {
+ load_library(null);
+ try {
+ UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
+ } catch (Exception e) {
+ }
+ /* Handle batch-mode */
+ if (args.length == 0) {
+ AltosUI altosui = new AltosUI();
+ altosui.setVisible(true);
+
+ java.util.List<AltosDevice> devices = AltosUSBDevice.list(Altos.product_basestation);
+ for (AltosDevice device : devices)
+ altosui.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("--fetchmaps")) {
+ if (args.length < i + 3) {
+ help(1);
+ } else {
+ double lat = Double.parseDouble(args[i+1]);
+ double lon = Double.parseDouble(args[i+2]);
+ AltosSiteMap.prefetchMaps(lat, lon, 5, 5);
+ i += 2;
+ }
+ } 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].startsWith("--"))
+ help(1);
+ else {
+ switch (process) {
+ case process_none:
+ case process_graph:
+ process_graph(args[i]);
+ break;
+ case process_replay:
+ process_replay(args[i]);
+ break;
+ case process_kml:
+ process_kml(args[i]);
+ break;
+ case process_csv:
+ process_csv(args[i]);
+ break;
+ case process_summary:
+ process_summary(args[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/altosui/AltosUIListener.java b/altosui/AltosUIListener.java
new file mode 100644
index 00000000..7ee62afc
--- /dev/null
+++ b/altosui/AltosUIListener.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+public interface AltosUIListener {
+ public void ui_changed(String look_and_feel);
+}
diff --git a/altosui/AltosUIPreferences.java b/altosui/AltosUIPreferences.java
new file mode 100644
index 00000000..10ab26c3
--- /dev/null
+++ b/altosui/AltosUIPreferences.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.awt.Component;
+import javax.swing.*;
+import javax.swing.filechooser.FileSystemView;
+import org.altusmetrum.AltosLib.*;
+
+public class AltosUIPreferences extends AltosPreferences {
+
+ /* font size preferences name */
+ final static String fontSizePreference = "FONT-SIZE";
+
+ /* Look&Feel preference name */
+ final static String lookAndFeelPreference = "LOOK-AND-FEEL";
+
+ /* UI Component to pop dialogs up */
+ static Component component;
+
+ static LinkedList<AltosFontListener> font_listeners;
+
+ static int font_size = Altos.font_size_medium;
+
+ static LinkedList<AltosUIListener> ui_listeners;
+
+ static String look_and_feel = null;
+
+ /* Serial debug */
+ static boolean serial_debug;
+
+ public static void init() {
+ font_listeners = new LinkedList<AltosFontListener>();
+
+ font_size = preferences.getInt(fontSizePreference, Altos.font_size_medium);
+ Altos.set_fonts(font_size);
+ look_and_feel = preferences.get(lookAndFeelPreference, UIManager.getSystemLookAndFeelClassName());
+
+ ui_listeners = new LinkedList<AltosUIListener>();
+ serial_debug = preferences.getBoolean(serialDebugPreference, false);
+ AltosLink.set_debug(serial_debug);
+ }
+
+ static { init(); }
+
+ static void set_component(Component in_component) {
+ component = in_component;
+ }
+
+ private static boolean check_dir(File dir) {
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ JOptionPane.showMessageDialog(component,
+ dir.getName(),
+ "Cannot create directory",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ } else if (!dir.isDirectory()) {
+ JOptionPane.showMessageDialog(component,
+ dir.getName(),
+ "Is not a directory",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ /* Configure the log directory. This is where all telemetry and eeprom files
+ * will be written to, and where replay will look for telemetry files
+ */
+ public static void ConfigureLog() {
+ JFileChooser logdir_chooser = new JFileChooser(logdir.getParentFile());
+
+ logdir_chooser.setDialogTitle("Configure Data Logging Directory");
+ logdir_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+
+ if (logdir_chooser.showDialog(component, "Select Directory") == JFileChooser.APPROVE_OPTION) {
+ File dir = logdir_chooser.getSelectedFile();
+ if (check_dir(dir))
+ set_logdir(dir);
+ }
+ }
+ public static int font_size() {
+ return font_size;
+ }
+
+ static void set_fonts() {
+ }
+
+ public static void set_font_size(int new_font_size) {
+ font_size = new_font_size;
+ synchronized (preferences) {
+ preferences.putInt(fontSizePreference, font_size);
+ flush_preferences();
+ Altos.set_fonts(font_size);
+ for (AltosFontListener l : font_listeners)
+ l.font_size_changed(font_size);
+ }
+ }
+
+ public static void register_font_listener(AltosFontListener l) {
+ synchronized (preferences) {
+ font_listeners.add(l);
+ }
+ }
+
+ public static void unregister_font_listener(AltosFontListener l) {
+ synchronized (preferences) {
+ font_listeners.remove(l);
+ }
+ }
+
+ public static void set_look_and_feel(String new_look_and_feel) {
+ look_and_feel = new_look_and_feel;
+ try {
+ UIManager.setLookAndFeel(look_and_feel);
+ } catch (Exception e) {
+ }
+ synchronized(preferences) {
+ preferences.put(lookAndFeelPreference, look_and_feel);
+ flush_preferences();
+ for (AltosUIListener l : ui_listeners)
+ l.ui_changed(look_and_feel);
+ }
+ }
+
+ public static String look_and_feel() {
+ return look_and_feel;
+ }
+
+ public static void register_ui_listener(AltosUIListener l) {
+ synchronized(preferences) {
+ ui_listeners.add(l);
+ }
+ }
+
+ public static void unregister_ui_listener(AltosUIListener l) {
+ synchronized (preferences) {
+ ui_listeners.remove(l);
+ }
+ }
+ public static void set_serial_debug(boolean new_serial_debug) {
+ serial_debug = new_serial_debug;
+ AltosLink.set_debug(serial_debug);
+ synchronized (preferences) {
+ preferences.putBoolean(serialDebugPreference, serial_debug);
+ flush_preferences();
+ }
+ }
+
+ public static boolean serial_debug() {
+ return serial_debug;
+ }
+
+} \ No newline at end of file
diff --git a/altosui/AltosUSBDevice.java b/altosui/AltosUSBDevice.java
new file mode 100644
index 00000000..ed5f8307
--- /dev/null
+++ b/altosui/AltosUSBDevice.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+import java.lang.*;
+import java.util.*;
+import libaltosJNI.*;
+
+public class AltosUSBDevice extends altos_device implements AltosDevice {
+
+ public String toString() {
+ String name = getName();
+ if (name == null)
+ name = "Altus Metrum";
+ return String.format("%-20.20s %4d %s",
+ name, getSerial(), getPath());
+ }
+
+ public String toShortString() {
+ String name = getName();
+ if (name == null)
+ name = "Altus Metrum";
+ return String.format("%s %d %s",
+ name, getSerial(), getPath());
+
+ }
+
+ public String getErrorString() {
+ altos_error error = new altos_error();
+
+ libaltos.altos_get_last_error(error);
+ return String.format("%s (%d)", error.getString(), error.getCode());
+ }
+
+ public SWIGTYPE_p_altos_file open() {
+ return libaltos.altos_open(this);
+ }
+
+ private boolean isAltusMetrum() {
+ if (getVendor() != Altos.vendor_altusmetrum)
+ return false;
+ if (getProduct() < Altos.product_altusmetrum_min)
+ return false;
+ if (getProduct() > Altos.product_altusmetrum_max)
+ return false;
+ return true;
+ }
+
+ public boolean matchProduct(int want_product) {
+
+ if (!isAltusMetrum())
+ return false;
+
+ if (want_product == Altos.product_any)
+ return true;
+
+ if (want_product == Altos.product_basestation)
+ return matchProduct(Altos.product_teledongle) ||
+ matchProduct(Altos.product_teleterra) ||
+ matchProduct(Altos.product_telebt) ||
+ matchProduct(Altos.product_megadongle);
+
+ if (want_product == Altos.product_altimeter)
+ return matchProduct(Altos.product_telemetrum) ||
+ matchProduct(Altos.product_megametrum);
+
+ int have_product = getProduct();
+
+ if (have_product == Altos.product_altusmetrum) /* old devices match any request */
+ return true;
+
+ if (want_product == have_product)
+ return true;
+
+ return false;
+ }
+
+ static java.util.List<AltosDevice> list(int product) {
+ if (!Altos.load_library())
+ return null;
+
+ SWIGTYPE_p_altos_list list = libaltos.altos_list_start();
+
+ ArrayList<AltosDevice> device_list = new ArrayList<AltosDevice>();
+ if (list != null) {
+ for (;;) {
+ AltosUSBDevice device = new AltosUSBDevice();
+ if (libaltos.altos_list_next(list, device) == 0)
+ break;
+ if (device.matchProduct(product))
+ device_list.add(device);
+ }
+ libaltos.altos_list_finish(list);
+ }
+
+ return device_list;
+ }
+} \ No newline at end of file
diff --git a/altosui/AltosVersion.java.in b/altosui/AltosVersion.java.in
new file mode 100644
index 00000000..b0b3c0cf
--- /dev/null
+++ b/altosui/AltosVersion.java.in
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2011 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+public class AltosVersion {
+ public final static String version = "@VERSION@";
+}
diff --git a/altosui/AltosVoice.java b/altosui/AltosVoice.java
new file mode 100644
index 00000000..ab74e0b3
--- /dev/null
+++ b/altosui/AltosVoice.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import com.sun.speech.freetts.Voice;
+import com.sun.speech.freetts.VoiceManager;
+import com.sun.speech.freetts.audio.JavaClipAudioPlayer;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosVoice implements Runnable {
+ VoiceManager voice_manager;
+ Voice voice;
+ LinkedBlockingQueue<String> phrases;
+ Thread thread;
+ boolean busy;
+
+ final static String voice_name = "kevin16";
+
+ public void run() {
+ try {
+ for (;;) {
+ String s = phrases.take();
+ voice.speak(s);
+ synchronized(this) {
+ if (phrases.isEmpty()) {
+ busy = false;
+ notifyAll();
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public synchronized void drain() throws InterruptedException {
+ while (busy)
+ wait();
+ }
+
+ public void speak_always(String s) {
+ try {
+ if (voice != null) {
+ synchronized(this) {
+ busy = true;
+ phrases.put(s);
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public void speak(String s) {
+ if (AltosUIPreferences.voice())
+ speak_always(s);
+ }
+
+ public void speak(String format, Object... parameters) {
+ speak(String.format(format, parameters));
+ }
+
+ public AltosVoice () {
+ busy = false;
+ voice_manager = VoiceManager.getInstance();
+ voice = voice_manager.getVoice(voice_name);
+ if (voice != null) {
+ voice.allocate();
+ phrases = new LinkedBlockingQueue<String> ();
+ thread = new Thread(this);
+ thread.start();
+ } else {
+ System.out.printf("Voice manager failed to open %s\n", voice_name);
+ Voice[] voices = voice_manager.getVoices();
+ System.out.printf("Available voices:\n");
+ for (int i = 0; i < voices.length; i++) {
+ System.out.println(" " + voices[i].getName()
+ + " (" + voices[i].getDomain() + " domain)");
+ }
+ }
+ }
+}
diff --git a/altosui/AltosWriter.java b/altosui/AltosWriter.java
new file mode 100644
index 00000000..b7375204
--- /dev/null
+++ b/altosui/AltosWriter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import org.altusmetrum.AltosLib.*;
+
+
+public interface AltosWriter {
+
+ public void write(AltosRecord record);
+
+ public void write(AltosRecordIterable iterable);
+
+ public void close();
+}
diff --git a/altosui/GrabNDrag.java b/altosui/GrabNDrag.java
new file mode 100644
index 00000000..c350efec
--- /dev/null
+++ b/altosui/GrabNDrag.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.MouseInputAdapter;
+import javax.imageio.ImageIO;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import org.altusmetrum.AltosLib.*;
+
+class GrabNDrag extends MouseInputAdapter {
+ private JComponent scroll;
+ private Point startPt = new Point();
+
+ public GrabNDrag(JComponent scroll) {
+ this.scroll = scroll;
+ scroll.addMouseMotionListener(this);
+ scroll.addMouseListener(this);
+ scroll.setAutoscrolls(true);
+ }
+
+ public void mousePressed(MouseEvent e) {
+ startPt.setLocation(e.getPoint());
+ }
+ public void mouseDragged(MouseEvent e) {
+ int xd = e.getX() - startPt.x;
+ int yd = e.getY() - startPt.y;
+
+ Rectangle r = scroll.getVisibleRect();
+ r.x -= xd;
+ r.y -= yd;
+ scroll.scrollRectToVisible(r);
+ }
+}
diff --git a/altosui/Info.plist.in b/altosui/Info.plist.in
new file mode 100644
index 00000000..46dea171
--- /dev/null
+++ b/altosui/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleName</key>
+ <string>AltosUI</string>
+ <key>CFBundleVersion</key>
+ <string>@VERSION@</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <string>true</string>
+ <key>CFBundleExecutable</key>
+ <string>JavaApplicationStub</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.altusmetrum.altosui</string>
+ <key>CFBundleSignature</key>
+ <string>Altu</string>
+ <key>CFBundleGetInfoString</key>
+ <string>AltOS UI version @VERSION@</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleIconFile</key>
+ <string>AltosUIIcon.icns</string>
+ <key>Java</key>
+ <dict>
+ <key>MainClass</key>
+ <string>altosui.AltosUI</string>
+ <key>JVMVersion</key>
+ <string>1.5+</string>
+ <key>ClassPath</key>
+ <array>
+ <string>$JAVAROOT/altosui.jar</string>
+ <string>$JAVAROOT/freetts.jar</string>
+ </array>
+ <key>VMOptions</key>
+ <array>
+ <string>-Xms512M</string>
+ <string>-Xmx512M</string>
+ <string>-Dosgi.clean=true</string>
+ </array>
+ </dict>
+</dict>
+</plist>
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi b/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi
new file mode 100644
index 00000000..3ed821eb
--- /dev/null
+++ b/altosui/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/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe
new file mode 100644
index 00000000..615bae15
--- /dev/null
+++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe
Binary files differ
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c
new file mode 100644
index 00000000..efe866e9
--- /dev/null
+++ b/altosui/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 <windows.h>
+#include <setupapi.h>
+#include <newdev.h>
+#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/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp
new file mode 100644
index 00000000..874e66c7
--- /dev/null
+++ b/altosui/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/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw
new file mode 100644
index 00000000..b3d02f0e
--- /dev/null
+++ b/altosui/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/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt b/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt
new file mode 100644
index 00000000..e5877aa6
--- /dev/null
+++ b/altosui/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/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf
new file mode 100644
index 00000000..ccda1d87
--- /dev/null
+++ b/altosui/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/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys
new file mode 100644
index 00000000..7882583b
--- /dev/null
+++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys
Binary files differ
diff --git a/altosui/Instdrv/NSIS/Plugins/InstDrv.dll b/altosui/Instdrv/NSIS/Plugins/InstDrv.dll
new file mode 100644
index 00000000..482e955e
--- /dev/null
+++ b/altosui/Instdrv/NSIS/Plugins/InstDrv.dll
Binary files differ
diff --git a/altosui/Makefile-standalone b/altosui/Makefile-standalone
new file mode 100644
index 00000000..0d9931d5
--- /dev/null
+++ b/altosui/Makefile-standalone
@@ -0,0 +1,184 @@
+.SUFFIXES: .java .class
+
+CLASSPATH=classes:./*:/usr/share/java/*
+CLASSFILES=\
+ Altos.class \
+ AltosChannelMenu.class \
+ AltosConfig.class \
+ AltosConfigUI.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_ICON=../icon/altus-metrum-16x16.jpg
+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_ICON) 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_ICON) 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_ICON) 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 ../telemetrum.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/altosui/Makefile.am b/altosui/Makefile.am
new file mode 100644
index 00000000..9f75d5e3
--- /dev/null
+++ b/altosui/Makefile.am
@@ -0,0 +1,330 @@
+SUBDIRS=libaltos
+JAVAROOT=classes
+AM_JAVACFLAGS=-encoding UTF-8 -Xlint:deprecation
+
+man_MANS=altosui.1
+
+altoslibdir=$(libdir)/altos
+
+CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:../altoslib/*:libaltos::$(JFREECHART)/jfreechart.jar:$(FREETTS)/freetts.jar"
+
+bin_SCRIPTS=altosui
+
+altosui_BT = \
+ AltosBTDevice.java \
+ AltosBTDeviceIterator.java \
+ AltosBTManage.java \
+ AltosBTKnown.java
+
+altosui_JAVA = \
+ GrabNDrag.java \
+ AltosAscent.java \
+ AltosChannelMenu.java \
+ AltosCompanionInfo.java \
+ AltosConfig.java \
+ AltosConfigFreqUI.java \
+ AltosConfigUI.java \
+ AltosConfigureUI.java \
+ AltosConfigTD.java \
+ AltosConfigTDUI.java \
+ AltosCSV.java \
+ AltosCSVUI.java \
+ AltosDebug.java \
+ AltosDescent.java \
+ AltosDeviceDialog.java \
+ AltosDevice.java \
+ AltosUSBDevice.java \
+ AltosDisplayThread.java \
+ AltosEepromDelete.java \
+ AltosEepromDownload.java \
+ AltosEepromList.java \
+ AltosEepromManage.java \
+ AltosEepromMonitor.java \
+ AltosEepromSelect.java \
+ AltosFlash.java \
+ AltosFlashUI.java \
+ AltosFlightDisplay.java \
+ AltosFlightInfoTableModel.java \
+ AltosFlightStats.java \
+ AltosFlightStatsTable.java \
+ AltosFlightStatus.java \
+ AltosFlightStatusUpdate.java \
+ AltosFlightUI.java \
+ AltosFontListener.java \
+ AltosFreqList.java \
+ AltosHexfile.java \
+ Altos.java \
+ AltosIdleMonitorUI.java \
+ AltosIgniteUI.java \
+ AltosLaunch.java \
+ AltosLaunchUI.java \
+ AltosInfoTable.java \
+ AltosKML.java \
+ AltosLanded.java \
+ AltosLed.java \
+ AltosLights.java \
+ AltosPad.java \
+ AltosUIPreferences.java \
+ AltosRomconfig.java \
+ AltosRomconfigUI.java \
+ AltosScanUI.java \
+ AltosSerial.java \
+ AltosSerialInUseException.java \
+ AltosSiteMap.java \
+ AltosSiteMapPreload.java \
+ AltosSiteMapCache.java \
+ AltosSiteMapTile.java \
+ AltosUI.java \
+ AltosUIListener.java \
+ AltosFrame.java \
+ AltosDialog.java \
+ AltosWriter.java \
+ AltosDataPointReader.java \
+ AltosDataPoint.java \
+ AltosGraph.java \
+ AltosGraphTime.java \
+ AltosGraphUI.java \
+ AltosDataChooser.java \
+ AltosVersion.java \
+ AltosVoice.java \
+ $(altosui_BT)
+
+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.jar
+
+LIBALTOS= \
+ libaltos.so \
+ libaltos.dylib \
+ altos.dll
+
+JAR=altosui.jar
+
+FATJAR=altosui-fat.jar
+
+# Icons
+ICONDIR=$(top_srcdir)/icon
+
+JAVA_ICON=$(ICONDIR)/altus-metrum-16x16.jpg
+
+ICONS= $(ICONDIR)/redled.png $(ICONDIR)/redoff.png \
+ $(ICONDIR)/greenled.png $(ICONDIR)/greenoff.png \
+ $(ICONDIR)/grayled.png $(ICONDIR)/grayoff.png
+
+# icon base names for jar
+ICONJAR= -C $(ICONDIR) altus-metrum-16x16.jpg \
+ -C $(ICONDIR) redled.png -C $(ICONDIR) redoff.png \
+ -C $(ICONDIR) greenled.png -C $(ICONDIR) greenoff.png \
+ -C $(ICONDIR) grayon.png -C $(ICONDIR) grayled.png
+
+WINDOWS_ICON=$(ICONDIR)/altus-metrum.ico
+
+# Firmware
+FIRMWARE_TD_0_2=$(top_srcdir)/src/teledongle-v0.2-$(VERSION).ihx
+FIRMWARE_TD=$(FIRMWARE_TD_0_2)
+
+FIRMWARE_TM_1_0=$(top_srcdir)/src/telemetrum-v1.0-$(VERSION).ihx
+FIRMWARE_TM_1_1=$(top_srcdir)/src/telemetrum-v1.1-$(VERSION).ihx
+FIRMWARE_TM_1_2=$(top_srcdir)/src/telemetrum-v1.2-$(VERSION).ihx
+FIRMWARE_TM=$(FIRMWARE_TM_1_0) $(FIRMWARE_TM_1_1) $(FIRMWARE_TM_1_2)
+
+FIRMWARE_TELEMINI_1_0=$(top_srcdir)/src/telemini-v1.0-$(VERSION).ihx
+FIRMWARE_TELEMINI=$(FIRMWARE_TELEMINI_1_0)
+
+FIRMWARE=$(FIRMWARE_TM) $(FIRMWARE_TELEMINI) $(FIRMWARE_TD)
+
+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/megametrum-outline.pdf
+
+DOC=$(ALTUSMETRUM_DOC) $(ALTOS_DOC) $(TELEMETRY_DOC) $(TEMPLATE_DOC)
+
+# Distribution targets
+LINUX_DIST=Altos-Linux-$(VERSION).tar.bz2
+MACOSX_DIST=Altos-Mac-$(VERSION).zip
+WINDOWS_DIST=Altos-Windows-$(VERSION_DASH).exe
+
+FAT_FILES=$(FATJAR) $(ALTOSLIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS)
+
+LINUX_FILES=$(FAT_FILES) libaltos.so $(FIRMWARE) $(DOC)
+LINUX_EXTRA=altosui-fat
+
+MACOSX_INFO_PLIST=Info.plist
+MACOSX_FILES=$(FAT_FILES) libaltos.dylib $(MACOSX_INFO_PLIST)
+MACOSX_EXTRA=$(FIRMWARE)
+
+WINDOWS_FILES=$(FAT_FILES) altos.dll altos64.dll $(top_srcdir)/telemetrum.inf $(WINDOWS_ICON)
+
+all-local: classes/altosui $(JAR) altosui altosui-test altosui-jdb
+
+clean-local:
+ -rm -rf classes $(JAR) $(FATJAR) \
+ $(LINUX_DIST) $(MACOSX_DIST) windows $(WINDOWS_DIST) $(ALTOSLIB_CLASS) $(FREETTS_CLASS) \
+ $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) Manifest.txt Manifest-fat.txt altos-windows.log \
+ altosui altosui-test altosui-jdb macosx linux
+
+if FATINSTALL
+
+FATTARGET=$(FATDIR)/$(VERSION)
+
+LINUX_DIST_TARGET=$(FATTARGET)/$(LINUX_DIST)
+MACOSX_DIST_TARGET=$(FATTARGET)/$(MACOSX_DIST)
+WINDOWS_DIST_TARGET=$(FATTARGET)/$(WINDOWS_DIST)
+
+fat: $(LINUX_DIST_TARGET) $(MACOSX_DIST_TARGET) $(WINDOWS_DIST_TARGET)
+
+$(LINUX_DIST_TARGET): $(LINUX_DIST)
+ 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 $< $@
+
+else
+fat: $(LINUX_DIST) $(MACOSX_DIST) $(WINDOWS_DIST)
+endif
+
+
+altosuidir=$(datadir)/java
+
+install-altosuiJAVA: altosui.jar
+ @$(NORMAL_INSTALL)
+ test -z "$(altosuidir)" || $(MKDIR_P) "$(DESTDIR)$(altosuidir)"
+ echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(altosuidir)/altosui.jar'"; \
+ $(INSTALL_DATA) "$<" "$(DESTDIR)$(altosuidir)"
+
+classes/altosui:
+ mkdir -p classes/altosui
+
+$(JAR): classaltosui.stamp Manifest.txt $(JAVA_ICON) $(ALTOSLIB_CLASS)
+ jar cfm $@ Manifest.txt \
+ $(ICONJAR) \
+ -C classes altosui \
+ -C libaltos libaltosJNI
+
+$(FATJAR): classaltosui.stamp Manifest-fat.txt $(ALTOSLIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) $(JAVA_ICON)
+ jar cfm $@ Manifest-fat.txt \
+ $(ICONJAR) \
+ -C classes altosui \
+ -C libaltos libaltosJNI
+
+Manifest.txt: Makefile
+ echo 'Main-Class: altosui.AltosUI' > $@
+ echo "Class-Path: AltosLib.jar $(FREETTS)/freetts.jar $(JFREECHART)/jfreechart.jar $(JCOMMON)/jcommon.jar" >> $@
+
+Manifest-fat.txt:
+ echo 'Main-Class: altosui.AltosUI' > $@
+ echo "Class-Path: AltosLib.jar freetts.jar jfreechart.jar jcommon.jar" >> $@
+
+altosui: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -cp "$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="$(altoslibdir)" -jar "$(altosuidir)/altosui.jar" "$$@"' >> $@
+ chmod +x $@
+
+altosui-test: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -cp "./*:$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="libaltos/.libs" -jar altosui.jar "$$@"' >> $@
+ chmod +x $@
+
+altosui-jdb: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec jdb -classpath "classes:libaltos:$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="libaltos/.libs" altosui/AltosUI "$$@"' >> $@
+ chmod +x $@
+
+libaltos.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/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/"$@" .
+
+$(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 altosui-fat linux/AltOS/altosui
+ chmod +x linux/AltOS/altosui
+ tar cjf $@ -C linux AltOS
+
+$(MACOSX_DIST): $(MACOSX_FILES) $(MACOSX_EXTRA)
+ -rm -f $@
+ -rm -rf macosx
+ mkdir macosx
+ cp -a AltosUI.app macosx/
+ cp -p Info.plist macosx/AltosUI.app/Contents
+ mkdir -p macosx/AltOS macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(FATJAR) macosx/AltosUI.app/Contents/Resources/Java/altosui.jar
+ cp -p libaltos.dylib macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(ALTOSLIB_CLASS) macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(FREETTS_CLASS) macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(JFREECHART_CLASS) macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(JCOMMON_CLASS) macosx/AltosUI.app/Contents/Resources/Java
+ cp -p $(MACOSX_EXTRA) macosx/AltOS
+ cd macosx && zip -r ../$@ AltosUI.app AltOS
+
+$(WINDOWS_DIST): $(WINDOWS_FILES) altos-windows.nsi
+ -rm -f $@
+ makensis -Oaltos-windows.log "-XOutFile $@" "-DVERSION=$(VERSION)" altos-windows.nsi
+
+publish:
+ scp launch-sites.txt gag.com:public_html \ No newline at end of file
diff --git a/altosui/altos-windows.nsi b/altosui/altos-windows.nsi
new file mode 100644
index 00000000..986919d4
--- /dev/null
+++ b/altosui/altos-windows.nsi
@@ -0,0 +1,168 @@
+!addplugindir Instdrv/NSIS/Plugins
+; Definitions for Java 1.6 Detection
+!define JRE_VERSION "1.6"
+!define JRE_ALTERNATE "1.7"
+!define JRE_URL "http://javadl.sun.com/webapps/download/AutoDL?BundleId=52247&/jre-6u27-windows-i586-p.exe"
+!define PRODUCT_NAME "Altus Metrum Windows Software"
+
+Name "Altus Metrum Installer"
+
+; Default install directory
+InstallDir "$PROGRAMFILES\AltusMetrum"
+
+; Tell the installer where to re-install a new version
+InstallDirRegKey HKLM "Software\AltusMetrum" "Install_Dir"
+
+LicenseText "GNU General Public License Version 2"
+LicenseData "../COPYING"
+
+; Need admin privs for Vista or Win7
+RequestExecutionLevel admin
+
+ShowInstDetails Show
+
+ComponentText "Altus Metrum Software and Driver Installer"
+
+Function GetJRE
+ MessageBox MB_OK "${PRODUCT_NAME} uses Java ${JRE_VERSION} 32-bit, it will now \
+ be downloaded and installed"
+
+ StrCpy $2 "$TEMP\Java Runtime Environment.exe"
+ nsisdl::download /TIMEOUT=30000 ${JRE_URL} $2
+ Pop $R0 ;Get the return value
+ StrCmp $R0 "success" +3
+ MessageBox MB_OK "Download failed: $R0"
+ Quit
+ ExecWait $2
+ Delete $2
+FunctionEnd
+
+
+Function DetectJRE
+ ReadRegStr $2 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment" \
+ "CurrentVersion"
+ StrCmp $2 ${JRE_VERSION} done
+
+ StrCmp $2 ${JRE_ALTERNATE} done
+
+ Call GetJRE
+
+ done:
+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 $TEMP
+ File "../telemetrum.inf"
+ InstDrv::InstallDriver /NOUNLOAD "$TEMP\telemetrum.inf"
+
+ SetOutPath $INSTDIR
+ File "../telemetrum.inf"
+
+ SetOutPath $WINDIR\Inf
+ File "../telemetrum.inf"
+
+SectionEnd
+
+Section "AltosUI Application"
+ Call DetectJRE
+
+ SetOutPath $INSTDIR
+
+ File "altosui-fat.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 "*.dll"
+
+ File "../icon/*.ico"
+
+ CreateShortCut "$SMPROGRAMS\AltusMetrum.lnk" "$SYSDIR\javaw.exe" "-jar altosui-fat.jar" "$INSTDIR\altus-metrum.ico"
+SectionEnd
+
+Section "AltosUI Desktop Shortcut"
+ CreateShortCut "$DESKTOP\AltusMetrum.lnk" "$INSTDIR\altosui-fat.jar" "" "$INSTDIR\altus-metrum.ico"
+SectionEnd
+
+Section "TeleMetrum and TeleDongle 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/telemini-v1.0/telemini-v1.0-${VERSION}.ihx"
+ File "../src/teledongle-v0.2/teledongle-v0.2-${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/megametrum-outline.pdf"
+SectionEnd
+
+Section "Uninstaller"
+
+ ; Deal with the uninstaller
+
+ SetOutPath $INSTDIR
+
+ ; Write the install path to the registry
+ WriteRegStr HKLM SOFTWARE\AltusMetrum "Install_Dir" "$INSTDIR"
+
+ ; Write the uninstall keys for windows
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "DisplayName" "Altus Metrum"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "UninstallString" '"$INSTDIR\uninstall.exe"'
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "NoModify" "1"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "NoRepair" "1"
+
+ WriteUninstaller "uninstall.exe"
+SectionEnd
+
+Section "Uninstall"
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum"
+ DeleteRegKey HKLM "Software\AltusMetrum"
+
+ Delete "$INSTDIR\*.*"
+ RMDir "$INSTDIR"
+
+ ; Remove devices
+ InstDrv::InitDriverSetup /NOUNLOAD {4D36E96D-E325-11CE-BFC1-08002BE10318} AltusMetrumSerial
+ InstDrv::DeleteOemInfFiles /NOUNLOAD
+ InstDrv::RemoveAllDevices
+
+ ; Remove shortcuts, if any
+ Delete "$SMPROGRAMS\AltusMetrum.lnk"
+ Delete "$DESKTOP\AltusMetrum.lnk"
+SectionEnd
diff --git a/altosui/altosui-fat b/altosui/altosui-fat
new file mode 100755
index 00000000..95b1c051
--- /dev/null
+++ b/altosui/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/altosui/altosui.1 b/altosui/altosui.1
new file mode 100644
index 00000000..57fa4489
--- /dev/null
+++ b/altosui/altosui.1
@@ -0,0 +1,46 @@
+.\"
+.\" Copyright © 2010 Bdale Garbee <bdale@gag.com>
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; 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.
+.\"
+.\"
+.TH ALTOSUI 1 "altosui" ""
+.SH NAME
+altosui \- Rocket flight monitor
+.SH SYNOPSIS
+.B "altosui"
+.SH DESCRIPTION
+.I altosui
+connects to a TeleDongle or TeleMetrum device through a USB serial device.
+It provides a menu-oriented
+user interface to monitor, record and review rocket flight data.
+.SH USAGE
+When connected to a TeleDongle device, altosui turns on the radio
+receiver and listens for telemetry packets. It displays the received
+telemetry data, and reports flight status via voice synthesis. All
+received telemetry information is recorded to a file.
+.P
+When connected to a TeleMetrum device, altosui can be used to configure the
+TeleMetrum, and to downloads the eeprom data and store it in a file.
+.P
+A number of other menu options exist, including the ability to export flight
+data in different formats.
+.SH FILES
+All data log files are recorded into a user-specified directory
+(default ~/TeleMetrum). Files are named using the current date, the serial
+number of the reporting device, the flight number recorded in the data
+and either '.telem' for telemetry data or '.eeprom' for eeprom data.
+.SH AUTHOR
+Keith Packard
diff --git a/altosui/altusmetrum.jpg b/altosui/altusmetrum.jpg
new file mode 100644
index 00000000..04027921
--- /dev/null
+++ b/altosui/altusmetrum.jpg
Binary files differ
diff --git a/altosui/launch-sites.txt b/altosui/launch-sites.txt
new file mode 100644
index 00000000..de7955e0
--- /dev/null
+++ b/altosui/launch-sites.txt
@@ -0,0 +1,13 @@
+AHPRA BALLS:40.808333333333333:-119.15
+ARS Rio Rancho:35.33475:-106.75361
+HARA Bragg Farms:34.895875:-86.616211
+KLOUDBusters Rocket Pasture:37.167833333:-97.73975
+METRA Pine Island:41.31939:-74.47077
+NCR Pawnee:40.885955:-104.63793
+OPROC Discovery Bay:47.97808,-122.896383
+OROC Brothers:43.79949786336483:-120.6485810681392
+OROC Sheridan:45.044176:-123.314323
+QRS Cedar Grove:-27.8512283:152.9624000
+SCORE Hudson Ranch:38.155602777777778:-104.809119444444444
+Tripoli Colorado Hartsel:39.009247:-105.702338
+WAC Sportsman Club:47.815327777777778:-119.427186111111111
diff --git a/altosui/libaltos/.gitignore b/altosui/libaltos/.gitignore
new file mode 100644
index 00000000..c490e6f8
--- /dev/null
+++ b/altosui/libaltos/.gitignore
@@ -0,0 +1,12 @@
+*.so
+*.lo
+*.la
+*.java
+*.class
+.libs/
+classlibaltos.stamp
+libaltos_wrap.c
+libaltosJNI
+cjnitest
+libaltos.swig
+swig_bindings/
diff --git a/altosui/libaltos/Makefile-standalone b/altosui/libaltos/Makefile-standalone
new file mode 100644
index 00000000..4e438050
--- /dev/null
+++ b/altosui/libaltos/Makefile-standalone
@@ -0,0 +1,126 @@
+OS:=$(shell uname)
+
+#
+# Linux
+#
+ifeq ($(OS),Linux)
+
+JAVA_CFLAGS=-I/usr/lib/jvm/java-6-openjdk/include
+
+OS_LIB_CFLAGS=-DLINUX -DPOSIX_TTY $(JAVA_CFLAGS)
+
+OS_APP_CFLAGS=$(OS_LIB_CFLAGS)
+
+OS_LDFLAGS=
+
+LIBNAME=libaltos.so
+EXEEXT=
+endif
+
+#
+# Darwin (Mac OS X)
+#
+ifeq ($(OS),Darwin)
+
+OS_LIB_CFLAGS=\
+ -DDARWIN -DPOSIX_TTY -arch i386 -arch x86_64 \
+ --sysroot=/Developer/SDKs/MacOSX10.5.sdk -mmacosx-version-min=10.5 \
+ -iwithsysroot /System/Library/Frameworks/JavaVM.framework/Headers \
+ -iwithsysroot /System/Library/Frameworks/IOKit.framework/Headers \
+ -iwithsysroot /System/Library/Frameworks/CoreFoundation.framework/Headers
+OS_APP_CFLAGS=$(OS_LIB_CFLAGS)
+
+OS_LDFLAGS =\
+ -framework IOKit -framework CoreFoundation
+
+LIBNAME=libaltos.dylib
+EXEEXT=
+
+endif
+
+#
+# Windows
+#
+ifneq (,$(findstring MINGW,$(OS)))
+
+CC=gcc
+
+OS_LIB_CFLAGS = -DWINDOWS -mconsole -DBUILD_DLL
+OS_APP_CFLAGS = -DWINDOWS -mconsole
+
+OS_LDFLAGS = -lgdi32 -luser32 -lcfgmgr32 -lsetupapi -lole32 \
+ -ladvapi32 -lcomctl32 -mconsole -Wl,--add-stdcall-alias
+
+LIBNAME=altos.dll
+
+EXEEXT=.exe
+
+endif
+
+.SUFFIXES: .java .class
+
+CLASSPATH=".:jnitest/*:libaltosJNI:/usr/share/java/*"
+
+SWIG_DIR=swig_bindings/java
+SWIG_FILE=$(SWIG_DIR)/libaltos.swig
+SWIG_WRAP=$(SWIG_DIR)/libaltos_wrap.c
+
+JNI_DIR=libaltosJNI
+JNI_FILE=$(JNI_DIR)/libaltosJNI.java
+JNI_SRCS=$(JNI_FILE) \
+ $(JNI_DIR)/SWIGTYPE_p_altos_file.java \
+ $(JNI_DIR)/SWIGTYPE_p_altos_list.java \
+ $(JNI_DIR)/altos_device.java \
+ $(JNI_DIR)/libaltos.java
+
+JAVAFILES=\
+ $(JNI_SRCS)
+
+CLASSFILES = $(JAVAFILES:%.java=%.class)
+
+JAVAFLAGS=-Xlint:unchecked
+
+CJNITEST=cjnitest$(EXEEXT)
+
+all: $(LIBNAME) $(CJNITEST) $(CLASSFILES)
+
+.java.class:
+ javac -encoding UTF8 -classpath "$(CLASSPATH)" $(JAVAFLAGS) $*.java
+
+CFLAGS=$(OS_LIB_CFLAGS) -O -I.
+
+LDFLAGS=$(OS_LDFLAGS)
+
+HEADERS=libaltos.h
+SRCS = libaltos.c $(SWIG_WRAP)
+OBJS = $(SRCS:%.c=%.o)
+LIBS = $(DARWIN_LIBS)
+
+$(CJNITEST): cjnitest.c $(LIBNAME)
+ $(CC) -o $@ $(OS_APP_CFLAGS) cjnitest.c $(LIBNAME) $(LIBS) $(LDFLAGS)
+
+$(LIBNAME): $(OBJS)
+ $(CC) -shared $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS)
+
+clean:
+ rm -f $(CLASSFILES) $(OBJS) $(LIBNAME) $(CJNITEST) cjnitest.o
+ rm -rf swig_bindings libaltosJNI
+
+distclean: clean
+
+$(JNI_FILE): libaltos.i0 $(HEADERS)
+ mkdir -p $(SWIG_DIR)
+ mkdir -p libaltosJNI
+ sed 's;//%;%;' libaltos.i0 $(HEADERS) > $(SWIG_FILE)
+ swig -java -package libaltosJNI $(SWIG_FILE)
+ cp swig_bindings/java/*.java libaltosJNI
+
+$(SWIG_WRAP): $(JNI_FILE)
+
+ifeq ($(OS),Linux)
+install: $(LIBNAME)
+ install -c $(LIBNAME) $(DESTDIR)/usr/lib/altos/$(LIBNAME)
+
+endif
+
+.NOTPARALLEL:
diff --git a/altosui/libaltos/Makefile.am b/altosui/libaltos/Makefile.am
new file mode 100644
index 00000000..b5ab1ddb
--- /dev/null
+++ b/altosui/libaltos/Makefile.am
@@ -0,0 +1,54 @@
+JAVAC=javac
+AM_CFLAGS=-DLINUX -DPOSIX_TTY -I$(JVM_INCLUDE)
+AM_JAVACFLAGS=-encoding UTF-8
+
+altoslibdir=$(libdir)/altos
+
+altoslib_LTLIBRARIES=libaltos.la
+
+libaltos_la_LDFLAGS = -version-info 1:0:1
+
+libaltos_la_SOURCES=\
+ libaltos.c \
+ libaltos_wrap.c
+
+noinst_PROGRAMS=cjnitest
+
+cjnitest_LDADD=libaltos.la
+
+LIBS=-lbluetooth
+
+HFILES=libaltos.h
+
+SWIG_FILE=libaltos.swig
+
+CLASSDIR=libaltosJNI
+
+$(SWIG_FILE): libaltos.i0 $(HFILES)
+ sed 's;//%;%;' libaltos.i0 $(HFILES) > $(SWIG_FILE)
+
+all-local: classlibaltos.stamp
+
+libaltos_wrap.c: classlibaltos.stamp
+
+classlibaltos.stamp: $(SWIG_FILE)
+ swig -java -package libaltosJNI $(SWIG_FILE)
+ mkdir -p libaltosJNI
+ $(JAVAC) -d . $(AM_JAVACFLAGS) $(JAVACFLAGS) *.java && \
+ touch classlibaltos.stamp
+
+MINGCC32=i686-w64-mingw32-gcc
+MINGCC64=x86_64-w64-mingw32-gcc
+MINGFLAGS=-Wall -DWINDOWS -DBUILD_DLL -I$(JVM_INCLUDE)
+MINGLIBS=-lsetupapi
+
+fat: altos.dll altos64.dll
+
+altos.dll: $(libaltos_la_SOURCES)
+ $(MINGCC32) -o $@ $(MINGFLAGS) -shared $(libaltos_la_SOURCES) $(MINGLIBS)
+
+altos64.dll: $(libaltos_la_SOURCES)
+ $(MINGCC64) -o $@ $(MINGFLAGS) -shared $(libaltos_la_SOURCES) $(MINGLIBS)
+
+clean-local:
+ -rm -rf libaltosJNI *.class *.java classlibaltos.stamp $(SWIG_FILE) libaltos_wrap.c altos.dll altos64.dll
diff --git a/altosui/libaltos/cjnitest.c b/altosui/libaltos/cjnitest.c
new file mode 100644
index 00000000..f0fe78f7
--- /dev/null
+++ b/altosui/libaltos/cjnitest.c
@@ -0,0 +1,71 @@
+#include <stdio.h>
+#include "libaltos.h"
+
+static void
+altos_puts(struct altos_file *file, char *string)
+{
+ char c;
+
+ while ((c = *string++))
+ altos_putchar(file, c);
+}
+
+main ()
+{
+ struct altos_device device;
+ struct altos_list *list;
+ struct altos_bt_device bt_device;
+ struct altos_bt_list *bt_list;
+
+ altos_init();
+ list = altos_list_start();
+ while (altos_list_next(list, &device)) {
+ struct altos_file *file;
+ int c;
+
+ printf ("%04x:%04x %-20s %4d %s\n", device.vendor, device.product,
+ device.name, device.serial, device.path);
+
+ file = altos_open(&device);
+ if (!file) {
+ printf("altos_open failed\n");
+ continue;
+ }
+ altos_puts(file,"v\nc s\n");
+ altos_flush(file);
+ while ((c = altos_getchar(file, 100)) >= 0) {
+ putchar (c);
+ }
+ if (c != LIBALTOS_TIMEOUT)
+ printf ("getchar returns %d\n", c);
+ altos_close(file);
+ }
+ altos_list_finish(list);
+#if HAS_BLUETOOTH
+ bt_list = altos_bt_list_start(8);
+ while (altos_bt_list_next(bt_list, &bt_device)) {
+ printf ("%s %s\n", bt_device.name, bt_device.addr);
+ if (strncmp(bt_device.name, "TeleBT", 6) == 0) {
+ struct altos_file *file;
+
+ int c;
+ file = altos_bt_open(&bt_device);
+ if (!file) {
+ printf("altos_bt_open failed\n");
+ continue;
+ }
+ altos_puts(file,"v\nc s\n");
+ altos_flush(file);
+ while ((c = altos_getchar(file, 100)) >= 0) {
+ putchar(c);
+ }
+ if (c != LIBALTOS_TIMEOUT)
+ printf("getchar returns %d\n", c);
+ altos_close(file);
+ }
+ }
+ altos_bt_list_finish(bt_list);
+#endif
+ altos_fini();
+ return 0;
+}
diff --git a/altosui/libaltos/libaltos.c b/altosui/libaltos/libaltos.c
new file mode 100644
index 00000000..515432f9
--- /dev/null
+++ b/altosui/libaltos/libaltos.c
@@ -0,0 +1,1318 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "libaltos.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define USB_VENDOR_FSF 0xfffe
+#define USB_VENDOR_ALTUSMETRUM USB_VENDOR_FSF
+#define USB_PRODUCT_ALTUSMETRUM 0x000a
+#define USB_PRODUCT_ALTUSMETRUM_MIN 0x000a
+#define USB_PRODUCT_ALTUSMETRUM_MAX 0x00ff
+
+#define USB_IS_ALTUSMETRUM(v,p) ((v) == USB_VENDOR_ALTUSMETRUM && \
+ (USB_PRODUCT_ALTUSMETRUM_MIN <= (p) && \
+ (p) <= USB_PRODUCT_ALTUSMETRUM_MAX))
+
+#define BLUETOOTH_PRODUCT_TELEBT "TeleBT"
+
+#define USE_POLL
+
+PUBLIC int
+altos_init(void)
+{
+ return LIBALTOS_SUCCESS;
+}
+
+PUBLIC void
+altos_fini(void)
+{
+}
+
+static struct altos_error last_error;
+
+static void
+altos_set_last_error(int code, char *string)
+{
+ last_error.code = code;
+ strncpy(last_error.string, string, sizeof (last_error.string) -1);
+ last_error.string[sizeof(last_error.string)-1] = '\0';
+}
+
+PUBLIC void
+altos_get_last_error(struct altos_error *error)
+{
+ *error = last_error;
+}
+
+#ifdef DARWIN
+
+#undef USE_POLL
+
+/* Mac OS X don't have strndup even if _GNU_SOURCE is defined */
+static char *
+altos_strndup (const char *s, size_t n)
+{
+ size_t len = strlen (s);
+ char *ret;
+
+ if (len <= n)
+ return strdup (s);
+ ret = malloc(n + 1);
+ strncpy(ret, s, n);
+ ret[n] = '\0';
+ return ret;
+}
+
+#else
+#define altos_strndup strndup
+#endif
+
+#ifdef POSIX_TTY
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <errno.h>
+
+#define USB_BUF_SIZE 64
+
+struct altos_file {
+ int fd;
+#ifdef USE_POLL
+ int pipe[2];
+#else
+ int out_fd;
+#endif
+ unsigned char out_data[USB_BUF_SIZE];
+ int out_used;
+ unsigned char in_data[USB_BUF_SIZE];
+ int in_used;
+ int in_read;
+};
+
+static void
+altos_set_last_posix_error(void)
+{
+ altos_set_last_error(errno, strerror(errno));
+}
+
+PUBLIC struct altos_file *
+altos_open(struct altos_device *device)
+{
+ struct altos_file *file = calloc (sizeof (struct altos_file), 1);
+ int ret;
+ struct termios term;
+
+ if (!file) {
+ altos_set_last_posix_error();
+ return NULL;
+ }
+
+// altos_set_last_error(12, "yeah yeah, failed again");
+// free(file);
+// return NULL;
+
+ file->fd = open(device->path, O_RDWR | O_NOCTTY);
+ if (file->fd < 0) {
+ altos_set_last_posix_error();
+ free(file);
+ return NULL;
+ }
+#ifdef USE_POLL
+ pipe(file->pipe);
+#else
+ file->out_fd = open(device->path, O_RDWR | O_NOCTTY);
+ if (file->out_fd < 0) {
+ altos_set_last_posix_error();
+ close(file->fd);
+ free(file);
+ return NULL;
+ }
+#endif
+ ret = tcgetattr(file->fd, &term);
+ if (ret < 0) {
+ altos_set_last_posix_error();
+ close(file->fd);
+#ifndef USE_POLL
+ close(file->out_fd);
+#endif
+ free(file);
+ return NULL;
+ }
+ cfmakeraw(&term);
+#ifdef USE_POLL
+ term.c_cc[VMIN] = 1;
+ term.c_cc[VTIME] = 0;
+#else
+ term.c_cc[VMIN] = 0;
+ term.c_cc[VTIME] = 1;
+#endif
+ ret = tcsetattr(file->fd, TCSAFLUSH, &term);
+ if (ret < 0) {
+ altos_set_last_posix_error();
+ close(file->fd);
+#ifndef USE_POLL
+ close(file->out_fd);
+#endif
+ free(file);
+ return NULL;
+ }
+ return file;
+}
+
+PUBLIC void
+altos_close(struct altos_file *file)
+{
+ if (file->fd != -1) {
+ int fd = file->fd;
+ file->fd = -1;
+#ifdef USE_POLL
+ write(file->pipe[1], "\r", 1);
+#else
+ close(file->out_fd);
+ file->out_fd = -1;
+#endif
+ close(fd);
+ }
+}
+
+PUBLIC void
+altos_free(struct altos_file *file)
+{
+ altos_close(file);
+ free(file);
+}
+
+PUBLIC int
+altos_flush(struct altos_file *file)
+{
+ if (file->out_used && 0) {
+ printf ("flush \"");
+ fwrite(file->out_data, 1, file->out_used, stdout);
+ printf ("\"\n");
+ }
+ while (file->out_used) {
+ int ret;
+
+ if (file->fd < 0)
+ return -EBADF;
+#ifdef USE_POLL
+ ret = write (file->fd, file->out_data, file->out_used);
+#else
+ ret = write (file->out_fd, file->out_data, file->out_used);
+#endif
+ if (ret < 0) {
+ altos_set_last_posix_error();
+ return -last_error.code;
+ }
+ if (ret) {
+ memmove(file->out_data, file->out_data + ret,
+ file->out_used - ret);
+ file->out_used -= ret;
+ }
+ }
+ return 0;
+}
+
+PUBLIC int
+altos_putchar(struct altos_file *file, char c)
+{
+ int ret;
+
+ if (file->out_used == USB_BUF_SIZE) {
+ ret = altos_flush(file);
+ if (ret) {
+ return ret;
+ }
+ }
+ file->out_data[file->out_used++] = c;
+ ret = 0;
+ if (file->out_used == USB_BUF_SIZE)
+ ret = altos_flush(file);
+ return ret;
+}
+
+#ifdef USE_POLL
+#include <poll.h>
+#endif
+
+static int
+altos_fill(struct altos_file *file, int timeout)
+{
+ int ret;
+#ifdef USE_POLL
+ struct pollfd fd[2];
+#endif
+
+ if (timeout == 0)
+ timeout = -1;
+ while (file->in_read == file->in_used) {
+ if (file->fd < 0)
+ return LIBALTOS_ERROR;
+#ifdef USE_POLL
+ fd[0].fd = file->fd;
+ fd[0].events = POLLIN|POLLERR|POLLHUP|POLLNVAL;
+ fd[1].fd = file->pipe[0];
+ fd[1].events = POLLIN;
+ ret = poll(fd, 2, timeout);
+ if (ret < 0) {
+ altos_set_last_posix_error();
+ return LIBALTOS_ERROR;
+ }
+ if (ret == 0)
+ return LIBALTOS_TIMEOUT;
+
+ if (fd[0].revents & (POLLHUP|POLLERR|POLLNVAL))
+ return LIBALTOS_ERROR;
+ if (fd[0].revents & POLLIN)
+#endif
+ {
+ ret = read(file->fd, file->in_data, USB_BUF_SIZE);
+ if (ret < 0) {
+ altos_set_last_posix_error();
+ return LIBALTOS_ERROR;
+ }
+ file->in_read = 0;
+ file->in_used = ret;
+#ifndef USE_POLL
+ if (ret == 0 && timeout > 0)
+ return LIBALTOS_TIMEOUT;
+#endif
+ }
+ }
+ if (file->in_used && 0) {
+ printf ("fill \"");
+ fwrite(file->in_data, 1, file->in_used, stdout);
+ printf ("\"\n");
+ }
+ return 0;
+}
+
+PUBLIC int
+altos_getchar(struct altos_file *file, int timeout)
+{
+ int ret;
+ while (file->in_read == file->in_used) {
+ if (file->fd < 0)
+ return LIBALTOS_ERROR;
+ ret = altos_fill(file, timeout);
+ if (ret)
+ return ret;
+ }
+ return file->in_data[file->in_read++];
+}
+
+#endif /* POSIX_TTY */
+
+/*
+ * Scan for Altus Metrum devices by looking through /sys
+ */
+
+#ifdef LINUX
+
+#define _GNU_SOURCE
+#include <ctype.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+
+static char *
+cc_fullname (char *dir, char *file)
+{
+ char *new;
+ int dlen = strlen (dir);
+ int flen = strlen (file);
+ int slen = 0;
+
+ if (dir[dlen-1] != '/')
+ slen = 1;
+ new = malloc (dlen + slen + flen + 1);
+ if (!new)
+ return 0;
+ strcpy(new, dir);
+ if (slen)
+ strcat (new, "/");
+ strcat(new, file);
+ return new;
+}
+
+static char *
+cc_basename(char *file)
+{
+ char *b;
+
+ b = strrchr(file, '/');
+ if (!b)
+ return file;
+ return b + 1;
+}
+
+static char *
+load_string(char *dir, char *file)
+{
+ char *full = cc_fullname(dir, file);
+ char line[4096];
+ char *r;
+ FILE *f;
+ int rlen;
+
+ f = fopen(full, "r");
+ free(full);
+ if (!f)
+ return NULL;
+ r = fgets(line, sizeof (line), f);
+ fclose(f);
+ if (!r)
+ return NULL;
+ rlen = strlen(r);
+ if (r[rlen-1] == '\n')
+ r[rlen-1] = '\0';
+ return strdup(r);
+}
+
+static int
+load_hex(char *dir, char *file)
+{
+ char *line;
+ char *end;
+ long i;
+
+ line = load_string(dir, file);
+ if (!line)
+ return -1;
+ i = strtol(line, &end, 16);
+ free(line);
+ if (end == line)
+ return -1;
+ return i;
+}
+
+static int
+load_dec(char *dir, char *file)
+{
+ char *line;
+ char *end;
+ long i;
+
+ line = load_string(dir, file);
+ if (!line)
+ return -1;
+ i = strtol(line, &end, 10);
+ free(line);
+ if (end == line)
+ return -1;
+ return i;
+}
+
+static int
+dir_filter_tty_colon(const struct dirent *d)
+{
+ return strncmp(d->d_name, "tty:", 4) == 0;
+}
+
+static int
+dir_filter_tty(const struct dirent *d)
+{
+ return strncmp(d->d_name, "tty", 3) == 0;
+}
+
+struct altos_usbdev {
+ char *sys;
+ char *tty;
+ char *manufacturer;
+ char *product_name;
+ int serial; /* AltOS always uses simple integer serial numbers */
+ int idProduct;
+ int idVendor;
+};
+
+static char *
+usb_tty(char *sys)
+{
+ char *base;
+ int num_configs;
+ int config;
+ struct dirent **namelist;
+ int interface;
+ int num_interfaces;
+ char endpoint_base[20];
+ char *endpoint_full;
+ char *tty_dir;
+ int ntty;
+ char *tty;
+
+ base = cc_basename(sys);
+ num_configs = load_hex(sys, "bNumConfigurations");
+ num_interfaces = load_hex(sys, "bNumInterfaces");
+ for (config = 1; config <= num_configs; config++) {
+ for (interface = 0; interface < num_interfaces; interface++) {
+ sprintf(endpoint_base, "%s:%d.%d",
+ base, config, interface);
+ endpoint_full = cc_fullname(sys, endpoint_base);
+
+ /* Check for tty:ttyACMx style names
+ */
+ ntty = scandir(endpoint_full, &namelist,
+ dir_filter_tty_colon,
+ alphasort);
+ if (ntty > 0) {
+ free(endpoint_full);
+ tty = cc_fullname("/dev", namelist[0]->d_name + 4);
+ free(namelist);
+ return tty;
+ }
+
+ /* Check for tty/ttyACMx style names
+ */
+ tty_dir = cc_fullname(endpoint_full, "tty");
+ free(endpoint_full);
+ ntty = scandir(tty_dir, &namelist,
+ dir_filter_tty,
+ alphasort);
+ free (tty_dir);
+ if (ntty > 0) {
+ tty = cc_fullname("/dev", namelist[0]->d_name);
+ free(namelist);
+ return tty;
+ }
+ }
+ }
+ return NULL;
+}
+
+static struct altos_usbdev *
+usb_scan_device(char *sys)
+{
+ struct altos_usbdev *usbdev;
+
+ usbdev = calloc(1, sizeof (struct altos_usbdev));
+ if (!usbdev)
+ return NULL;
+ usbdev->sys = strdup(sys);
+ usbdev->manufacturer = load_string(sys, "manufacturer");
+ usbdev->product_name = load_string(sys, "product");
+ usbdev->serial = load_dec(sys, "serial");
+ usbdev->idProduct = load_hex(sys, "idProduct");
+ usbdev->idVendor = load_hex(sys, "idVendor");
+ usbdev->tty = usb_tty(sys);
+ return usbdev;
+}
+
+static void
+usbdev_free(struct altos_usbdev *usbdev)
+{
+ free(usbdev->sys);
+ free(usbdev->manufacturer);
+ free(usbdev->product_name);
+ /* this can get used as a return value */
+ if (usbdev->tty)
+ free(usbdev->tty);
+ free(usbdev);
+}
+
+#define USB_DEVICES "/sys/bus/usb/devices"
+
+static int
+dir_filter_dev(const struct dirent *d)
+{
+ const char *n = d->d_name;
+ char c;
+
+ while ((c = *n++)) {
+ if (isdigit(c))
+ continue;
+ if (c == '-')
+ continue;
+ if (c == '.' && n != d->d_name + 1)
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+struct altos_list {
+ struct altos_usbdev **dev;
+ int current;
+ int ndev;
+};
+
+struct altos_list *
+altos_list_start(void)
+{
+ int e;
+ struct dirent **ents;
+ char *dir;
+ struct altos_usbdev *dev;
+ struct altos_list *devs;
+ int n;
+
+ devs = calloc(1, sizeof (struct altos_list));
+ if (!devs)
+ return NULL;
+
+ n = scandir (USB_DEVICES, &ents,
+ dir_filter_dev,
+ alphasort);
+ if (!n)
+ return 0;
+ for (e = 0; e < n; e++) {
+ dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
+ dev = usb_scan_device(dir);
+ free(dir);
+ if (USB_IS_ALTUSMETRUM(dev->idVendor, dev->idProduct)) {
+ if (devs->dev)
+ devs->dev = realloc(devs->dev,
+ (devs->ndev + 1) * sizeof (struct usbdev *));
+ else
+ devs->dev = malloc (sizeof (struct usbdev *));
+ devs->dev[devs->ndev++] = dev;
+ }
+ }
+ free(ents);
+ devs->current = 0;
+ return devs;
+}
+
+int
+altos_list_next(struct altos_list *list, struct altos_device *device)
+{
+ struct altos_usbdev *dev;
+ if (list->current >= list->ndev)
+ return 0;
+ dev = list->dev[list->current];
+ strcpy(device->name, dev->product_name);
+ device->vendor = dev->idVendor;
+ device->product = dev->idProduct;
+ strcpy(device->path, dev->tty);
+ device->serial = dev->serial;
+ list->current++;
+ return 1;
+}
+
+void
+altos_list_finish(struct altos_list *usbdevs)
+{
+ int i;
+
+ if (!usbdevs)
+ return;
+ for (i = 0; i < usbdevs->ndev; i++)
+ usbdev_free(usbdevs->dev[i]);
+ free(usbdevs);
+}
+
+struct altos_bt_list {
+ inquiry_info *ii;
+ int sock;
+ int dev_id;
+ int rsp;
+ int num_rsp;
+};
+
+#define INQUIRY_MAX_RSP 255
+
+struct altos_bt_list *
+altos_bt_list_start(int inquiry_time)
+{
+ struct altos_bt_list *bt_list;
+
+ bt_list = calloc(1, sizeof (struct altos_bt_list));
+ if (!bt_list)
+ goto no_bt_list;
+
+ bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info));
+ if (!bt_list->ii)
+ goto no_ii;
+ bt_list->dev_id = hci_get_route(NULL);
+ if (bt_list->dev_id < 0)
+ goto no_dev_id;
+
+ bt_list->sock = hci_open_dev(bt_list->dev_id);
+ if (bt_list->sock < 0)
+ goto no_sock;
+
+ bt_list->num_rsp = hci_inquiry(bt_list->dev_id,
+ inquiry_time,
+ INQUIRY_MAX_RSP,
+ NULL,
+ &bt_list->ii,
+ IREQ_CACHE_FLUSH);
+ if (bt_list->num_rsp < 0)
+ goto no_rsp;
+
+ bt_list->rsp = 0;
+ return bt_list;
+
+no_rsp:
+ close(bt_list->sock);
+no_sock:
+no_dev_id:
+ free(bt_list->ii);
+no_ii:
+ free(bt_list);
+no_bt_list:
+ return NULL;
+}
+
+int
+altos_bt_list_next(struct altos_bt_list *bt_list,
+ struct altos_bt_device *device)
+{
+ inquiry_info *ii;
+
+ if (bt_list->rsp >= bt_list->num_rsp)
+ return 0;
+
+ ii = &bt_list->ii[bt_list->rsp];
+ ba2str(&ii->bdaddr, device->addr);
+ memset(&device->name, '\0', sizeof (device->name));
+ if (hci_read_remote_name(bt_list->sock, &ii->bdaddr,
+ sizeof (device->name),
+ device->name, 0) < 0) {
+ strcpy(device->name, "[unknown]");
+ }
+ bt_list->rsp++;
+ return 1;
+}
+
+void
+altos_bt_list_finish(struct altos_bt_list *bt_list)
+{
+ close(bt_list->sock);
+ free(bt_list->ii);
+ free(bt_list);
+}
+
+void
+altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
+{
+ strncpy(device->name, name, sizeof (device->name));
+ device->name[sizeof(device->name)-1] = '\0';
+ strncpy(device->addr, addr, sizeof (device->addr));
+ device->addr[sizeof(device->addr)-1] = '\0';
+}
+
+struct altos_file *
+altos_bt_open(struct altos_bt_device *device)
+{
+ struct sockaddr_rc addr = { 0 };
+ int s, status;
+ struct altos_file *file;
+
+ file = calloc(1, sizeof (struct altos_file));
+ if (!file)
+ goto no_file;
+ file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (file->fd < 0) {
+ altos_set_last_posix_error();
+ goto no_sock;
+ }
+
+ addr.rc_family = AF_BLUETOOTH;
+ addr.rc_channel = 1;
+ str2ba(device->addr, &addr.rc_bdaddr);
+
+ status = connect(file->fd,
+ (struct sockaddr *)&addr,
+ sizeof(addr));
+ if (status < 0) {
+ altos_set_last_posix_error();
+ goto no_link;
+ }
+ sleep(1);
+
+#ifdef USE_POLL
+ pipe(file->pipe);
+#else
+ file->out_fd = dup(file->fd);
+#endif
+ return file;
+no_link:
+ close(s);
+no_sock:
+ free(file);
+no_file:
+ return NULL;
+}
+
+#endif
+
+#ifdef DARWIN
+
+#include <IOKitLib.h>
+#include <IOKit/usb/USBspec.h>
+#include <sys/param.h>
+#include <paths.h>
+#include <CFNumber.h>
+#include <IOBSD.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+struct altos_list {
+ io_iterator_t iterator;
+};
+
+static int
+get_string(io_object_t object, CFStringRef entry, char *result, int result_len)
+{
+ CFTypeRef entry_as_string;
+ Boolean got_string;
+
+ entry_as_string = IORegistryEntrySearchCFProperty (object,
+ kIOServicePlane,
+ entry,
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively);
+ if (entry_as_string) {
+ got_string = CFStringGetCString(entry_as_string,
+ result, result_len,
+ kCFStringEncodingASCII);
+
+ CFRelease(entry_as_string);
+ if (got_string)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+get_number(io_object_t object, CFStringRef entry, int *result)
+{
+ CFTypeRef entry_as_number;
+ Boolean got_number;
+
+ entry_as_number = IORegistryEntrySearchCFProperty (object,
+ kIOServicePlane,
+ entry,
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively);
+ if (entry_as_number) {
+ got_number = CFNumberGetValue(entry_as_number,
+ kCFNumberIntType,
+ result);
+ if (got_number)
+ return 1;
+ }
+ return 0;
+}
+
+PUBLIC struct altos_list *
+altos_list_start(void)
+{
+ struct altos_list *list = calloc (sizeof (struct altos_list), 1);
+ CFMutableDictionaryRef matching_dictionary = IOServiceMatching("IOUSBDevice");
+ io_iterator_t tdIterator;
+ io_object_t tdObject;
+ kern_return_t ret;
+ int i;
+
+ ret = IOServiceGetMatchingServices(kIOMasterPortDefault, matching_dictionary, &list->iterator);
+ if (ret != kIOReturnSuccess)
+ return NULL;
+ return list;
+}
+
+PUBLIC int
+altos_list_next(struct altos_list *list, struct altos_device *device)
+{
+ io_object_t object;
+ char serial_string[128];
+
+ for (;;) {
+ object = IOIteratorNext(list->iterator);
+ if (!object)
+ return 0;
+
+ if (!get_number (object, CFSTR(kUSBVendorID), &device->vendor) ||
+ !get_number (object, CFSTR(kUSBProductID), &device->product))
+ continue;
+ if (device->vendor != 0xfffe)
+ continue;
+ if (device->product < 0x000a || 0x0013 < device->product)
+ continue;
+ if (get_string (object, CFSTR("IOCalloutDevice"), device->path, sizeof (device->path)) &&
+ get_string (object, CFSTR("USB Product Name"), device->name, sizeof (device->name)) &&
+ get_string (object, CFSTR("USB Serial Number"), serial_string, sizeof (serial_string))) {
+ device->serial = atoi(serial_string);
+ return 1;
+ }
+ }
+}
+
+PUBLIC void
+altos_list_finish(struct altos_list *list)
+{
+ IOObjectRelease (list->iterator);
+ free(list);
+}
+
+struct altos_bt_list {
+ int sock;
+ int dev_id;
+ int rsp;
+ int num_rsp;
+};
+
+#define INQUIRY_MAX_RSP 255
+
+struct altos_bt_list *
+altos_bt_list_start(int inquiry_time)
+{
+ return NULL;
+}
+
+int
+altos_bt_list_next(struct altos_bt_list *bt_list,
+ struct altos_bt_device *device)
+{
+ return 0;
+}
+
+void
+altos_bt_list_finish(struct altos_bt_list *bt_list)
+{
+}
+
+void
+altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
+{
+ strncpy(device->name, name, sizeof (device->name));
+ device->name[sizeof(device->name)-1] = '\0';
+ strncpy(device->addr, addr, sizeof (device->addr));
+ device->addr[sizeof(device->addr)-1] = '\0';
+}
+
+struct altos_file *
+altos_bt_open(struct altos_bt_device *device)
+{
+ return NULL;
+}
+
+#endif
+
+
+#ifdef WINDOWS
+
+#include <stdlib.h>
+#include <windows.h>
+#include <setupapi.h>
+
+struct altos_list {
+ HDEVINFO dev_info;
+ int index;
+};
+
+#define USB_BUF_SIZE 64
+
+struct altos_file {
+ HANDLE handle;
+ unsigned char out_data[USB_BUF_SIZE];
+ int out_used;
+ unsigned char in_data[USB_BUF_SIZE];
+ int in_used;
+ int in_read;
+ OVERLAPPED ov_read;
+ BOOL pend_read;
+ OVERLAPPED ov_write;
+};
+
+static void
+altos_set_last_windows_error(void)
+{
+ DWORD error = GetLastError();
+ TCHAR message[1024];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+ 0,
+ error,
+ 0,
+ message,
+ sizeof (message) / sizeof (TCHAR),
+ NULL);
+ altos_set_last_error(error, message);
+}
+
+PUBLIC struct altos_list *
+altos_list_start(void)
+{
+ struct altos_list *list = calloc(1, sizeof (struct altos_list));
+
+ if (!list)
+ return NULL;
+ list->dev_info = SetupDiGetClassDevs(NULL, "USB", NULL,
+ DIGCF_ALLCLASSES|DIGCF_PRESENT);
+ if (list->dev_info == INVALID_HANDLE_VALUE) {
+ altos_set_last_windows_error();
+ free(list);
+ return NULL;
+ }
+ list->index = 0;
+ return list;
+}
+
+PUBLIC int
+altos_list_next(struct altos_list *list, struct altos_device *device)
+{
+ SP_DEVINFO_DATA dev_info_data;
+ BYTE port[128];
+ DWORD port_len;
+ char friendlyname[256];
+ BYTE symbolic[256];
+ DWORD symbolic_len;
+ HKEY dev_key;
+ unsigned int vid, pid;
+ int serial;
+ HRESULT result;
+ DWORD friendlyname_type;
+ DWORD friendlyname_len;
+
+ dev_info_data.cbSize = sizeof (SP_DEVINFO_DATA);
+ while(SetupDiEnumDeviceInfo(list->dev_info, list->index,
+ &dev_info_data))
+ {
+ list->index++;
+
+ dev_key = SetupDiOpenDevRegKey(list->dev_info, &dev_info_data,
+ DICS_FLAG_GLOBAL, 0, DIREG_DEV,
+ KEY_READ);
+ if (dev_key == INVALID_HANDLE_VALUE) {
+ altos_set_last_windows_error();
+ printf("cannot open device registry key\n");
+ continue;
+ }
+
+ /* Fetch symbolic name for this device and parse out
+ * the vid/pid/serial info */
+ symbolic_len = sizeof(symbolic);
+ result = RegQueryValueEx(dev_key, "SymbolicName", NULL, NULL,
+ symbolic, &symbolic_len);
+ if (result != 0) {
+ altos_set_last_windows_error();
+ printf("cannot find SymbolicName value\n");
+ RegCloseKey(dev_key);
+ continue;
+ }
+ vid = pid = serial = 0;
+ sscanf((char *) symbolic + sizeof("\\??\\USB#VID_") - 1,
+ "%04X", &vid);
+ sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1,
+ "%04X", &pid);
+ sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_XXXX#") - 1,
+ "%d", &serial);
+ if (!USB_IS_ALTUSMETRUM(vid, pid)) {
+ RegCloseKey(dev_key);
+ continue;
+ }
+
+ /* Fetch the com port name */
+ port_len = sizeof (port);
+ result = RegQueryValueEx(dev_key, "PortName", NULL, NULL,
+ port, &port_len);
+ RegCloseKey(dev_key);
+ if (result != 0) {
+ altos_set_last_windows_error();
+ printf("failed to get PortName\n");
+ continue;
+ }
+
+ /* Fetch the device description which is the device name,
+ * with firmware that has unique USB ids */
+ friendlyname_len = sizeof (friendlyname);
+ if(!SetupDiGetDeviceRegistryProperty(list->dev_info,
+ &dev_info_data,
+ SPDRP_FRIENDLYNAME,
+ &friendlyname_type,
+ (BYTE *)friendlyname,
+ sizeof(friendlyname),
+ &friendlyname_len))
+ {
+ altos_set_last_windows_error();
+ printf("Failed to get friendlyname\n");
+ continue;
+ }
+ device->vendor = vid;
+ device->product = pid;
+ device->serial = serial;
+ strcpy(device->name, friendlyname);
+
+ strcpy(device->path, (char *) port);
+ return 1;
+ }
+ result = GetLastError();
+ if (result != ERROR_NO_MORE_ITEMS) {
+ altos_set_last_windows_error();
+ printf ("SetupDiEnumDeviceInfo failed error %d\n", (int) result);
+ }
+ return 0;
+}
+
+PUBLIC void
+altos_list_finish(struct altos_list *list)
+{
+ SetupDiDestroyDeviceInfoList(list->dev_info);
+ free(list);
+}
+
+static int
+altos_queue_read(struct altos_file *file)
+{
+ DWORD got;
+ if (file->pend_read)
+ return LIBALTOS_SUCCESS;
+
+ if (!ReadFile(file->handle, file->in_data, USB_BUF_SIZE, &got, &file->ov_read)) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ altos_set_last_windows_error();
+ return LIBALTOS_ERROR;
+ }
+ file->pend_read = TRUE;
+ } else {
+ file->pend_read = FALSE;
+ file->in_read = 0;
+ file->in_used = got;
+ }
+ return LIBALTOS_SUCCESS;
+}
+
+static int
+altos_wait_read(struct altos_file *file, int timeout)
+{
+ DWORD ret;
+ DWORD got;
+
+ if (!file->pend_read)
+ return LIBALTOS_SUCCESS;
+
+ if (!timeout)
+ timeout = INFINITE;
+
+ ret = WaitForSingleObject(file->ov_read.hEvent, timeout);
+ switch (ret) {
+ case WAIT_OBJECT_0:
+ if (!GetOverlappedResult(file->handle, &file->ov_read, &got, FALSE)) {
+ altos_set_last_windows_error();
+ return LIBALTOS_ERROR;
+ }
+ file->pend_read = FALSE;
+ file->in_read = 0;
+ file->in_used = got;
+ break;
+ case WAIT_TIMEOUT:
+ return LIBALTOS_TIMEOUT;
+ break;
+ default:
+ return LIBALTOS_ERROR;
+ }
+ return LIBALTOS_SUCCESS;
+}
+
+static int
+altos_fill(struct altos_file *file, int timeout)
+{
+ int ret;
+
+ if (file->in_read < file->in_used)
+ return LIBALTOS_SUCCESS;
+
+ file->in_read = file->in_used = 0;
+
+ ret = altos_queue_read(file);
+ if (ret)
+ return ret;
+ ret = altos_wait_read(file, timeout);
+ if (ret)
+ return ret;
+
+ return LIBALTOS_SUCCESS;
+}
+
+PUBLIC int
+altos_flush(struct altos_file *file)
+{
+ DWORD put;
+ unsigned char *data = file->out_data;
+ int used = file->out_used;
+ DWORD ret;
+
+ while (used) {
+ if (!WriteFile(file->handle, data, used, &put, &file->ov_write)) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ altos_set_last_windows_error();
+ return LIBALTOS_ERROR;
+ }
+ ret = WaitForSingleObject(file->ov_write.hEvent, INFINITE);
+ switch (ret) {
+ case WAIT_OBJECT_0:
+ if (!GetOverlappedResult(file->handle, &file->ov_write, &put, FALSE)) {
+ altos_set_last_windows_error();
+ return LIBALTOS_ERROR;
+ }
+ break;
+ default:
+ altos_set_last_windows_error();
+ return LIBALTOS_ERROR;
+ }
+ }
+ data += put;
+ used -= put;
+ }
+ file->out_used = 0;
+ return LIBALTOS_SUCCESS;
+}
+
+PUBLIC struct altos_file *
+altos_open(struct altos_device *device)
+{
+ struct altos_file *file = calloc (1, sizeof (struct altos_file));
+ char full_name[64];
+ DCB dcbSerialParams = {0};
+ COMMTIMEOUTS timeouts;
+
+ if (!file)
+ return NULL;
+
+ strcpy(full_name, "\\\\.\\");
+ strcat(full_name, device->path);
+ file->handle = CreateFile(full_name, GENERIC_READ|GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+ if (file->handle == INVALID_HANDLE_VALUE) {
+ altos_set_last_windows_error();
+ free(file);
+ return NULL;
+ }
+ file->ov_read.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ file->ov_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ timeouts.ReadIntervalTimeout = MAXDWORD;
+ timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
+ timeouts.ReadTotalTimeoutConstant = 1 << 30; /* almost forever */
+ timeouts.WriteTotalTimeoutMultiplier = 0;
+ timeouts.WriteTotalTimeoutConstant = 0;
+ SetCommTimeouts(file->handle, &timeouts);
+
+ dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
+ if (!GetCommState(file->handle, &dcbSerialParams)) {
+ altos_set_last_windows_error();
+ CloseHandle(file->handle);
+ free(file);
+ return NULL;
+ }
+ dcbSerialParams.BaudRate = CBR_9600;
+ dcbSerialParams.ByteSize = 8;
+ dcbSerialParams.StopBits = ONESTOPBIT;
+ dcbSerialParams.Parity = NOPARITY;
+ if (!SetCommState(file->handle, &dcbSerialParams)) {
+ altos_set_last_windows_error();
+ CloseHandle(file->handle);
+ free(file);
+ return NULL;
+ }
+
+ return file;
+}
+
+PUBLIC void
+altos_close(struct altos_file *file)
+{
+ if (file->handle != INVALID_HANDLE_VALUE) {
+ CloseHandle(file->handle);
+ file->handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+PUBLIC void
+altos_free(struct altos_file *file)
+{
+ altos_close(file);
+ free(file);
+}
+
+PUBLIC int
+altos_putchar(struct altos_file *file, char c)
+{
+ int ret;
+
+ if (file->out_used == USB_BUF_SIZE) {
+ ret = altos_flush(file);
+ if (ret)
+ return ret;
+ }
+ file->out_data[file->out_used++] = c;
+ if (file->out_used == USB_BUF_SIZE)
+ return altos_flush(file);
+ return LIBALTOS_SUCCESS;
+}
+
+PUBLIC int
+altos_getchar(struct altos_file *file, int timeout)
+{
+ int ret;
+ while (file->in_read == file->in_used) {
+ if (file->handle == INVALID_HANDLE_VALUE)
+ return LIBALTOS_ERROR;
+ ret = altos_fill(file, timeout);
+ if (ret)
+ return ret;
+ }
+ return file->in_data[file->in_read++];
+}
+
+struct altos_bt_list *
+altos_bt_list_start(int inquiry_time)
+{
+ return NULL;
+}
+
+int
+altos_bt_list_next(struct altos_bt_list *bt_list,
+ struct altos_bt_device *device)
+{
+ return 0;
+}
+
+void
+altos_bt_list_finish(struct altos_bt_list *bt_list)
+{
+ free(bt_list);
+}
+
+void
+altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device)
+{
+ strncpy(device->name, name, sizeof (device->name));
+ device->name[sizeof(device->name)-1] = '\0';
+ strncpy(device->addr, addr, sizeof (device->addr));
+ device->addr[sizeof(device->addr)-1] = '\0';
+}
+
+struct altos_file *
+altos_bt_open(struct altos_bt_device *device)
+{
+ return NULL;
+}
+
+#endif
diff --git a/altosui/libaltos/libaltos.dylib b/altosui/libaltos/libaltos.dylib
new file mode 100755
index 00000000..1038817d
--- /dev/null
+++ b/altosui/libaltos/libaltos.dylib
Binary files differ
diff --git a/altosui/libaltos/libaltos.h b/altosui/libaltos/libaltos.h
new file mode 100644
index 00000000..f90fbb87
--- /dev/null
+++ b/altosui/libaltos/libaltos.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _LIBALTOS_H_
+#define _LIBALTOS_H_
+
+#include <stdlib.h>
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
+# ifndef BUILD_STATIC
+# ifdef BUILD_DLL
+# define PUBLIC __declspec(dllexport)
+# else
+# define PUBLIC __declspec(dllimport)
+# endif
+# endif /* BUILD_STATIC */
+#endif
+
+#ifndef PUBLIC
+# define PUBLIC
+#endif
+
+struct altos_device {
+ //%immutable;
+ int vendor;
+ int product;
+ int serial;
+ char name[256];
+ char path[256];
+ //%mutable;
+};
+
+struct altos_bt_device {
+ //%immutable;
+ char name[256];
+ char addr[20];
+ //%mutable;
+};
+
+struct altos_error {
+ int code;
+ char string[1024];
+};
+
+#define LIBALTOS_SUCCESS 0
+#define LIBALTOS_ERROR -1
+#define LIBALTOS_TIMEOUT -2
+
+/* Returns 0 for success, < 0 on error */
+PUBLIC int
+altos_init(void);
+
+PUBLIC void
+altos_fini(void);
+
+PUBLIC void
+altos_get_last_error(struct altos_error *error);
+
+PUBLIC struct altos_list *
+altos_list_start(void);
+
+/* Returns 1 for success, zero on end of list */
+PUBLIC int
+altos_list_next(struct altos_list *list, struct altos_device *device);
+
+PUBLIC void
+altos_list_finish(struct altos_list *list);
+
+PUBLIC struct altos_file *
+altos_open(struct altos_device *device);
+
+PUBLIC void
+altos_close(struct altos_file *file);
+
+PUBLIC void
+altos_free(struct altos_file *file);
+
+/* Returns < 0 for error */
+PUBLIC int
+altos_putchar(struct altos_file *file, char c);
+
+/* Returns < 0 for error */
+PUBLIC int
+altos_flush(struct altos_file *file);
+
+/* Returns < 0 for error or timeout. timeout of 0 == wait forever */
+PUBLIC int
+altos_getchar(struct altos_file *file, int timeout);
+
+PUBLIC struct altos_bt_list *
+altos_bt_list_start(int inquiry_time);
+
+PUBLIC int
+altos_bt_list_next(struct altos_bt_list *list, struct altos_bt_device *device);
+
+PUBLIC void
+altos_bt_list_finish(struct altos_bt_list *list);
+
+PUBLIC void
+altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device);
+
+PUBLIC struct altos_file *
+altos_bt_open(struct altos_bt_device *device);
+
+#endif /* _LIBALTOS_H_ */
diff --git a/altosui/libaltos/libaltos.i0 b/altosui/libaltos/libaltos.i0
new file mode 100644
index 00000000..d06468f5
--- /dev/null
+++ b/altosui/libaltos/libaltos.i0
@@ -0,0 +1,5 @@
+%module libaltos
+%{
+#include "libaltos.h"
+%}
+